mirror of
https://github.com/vlang/v.git
synced 2025-09-15 07:22:27 +03:00
tools: support // vtest build: !do_not_test ?
, // vtest build: !windows && tinyc
to skip files during testing on specific platforms, without having to keep centralised skip lists (#23900)
This commit is contained in:
parent
5439ff9cde
commit
35b1cff2d3
9 changed files with 473 additions and 5 deletions
|
@ -12,6 +12,7 @@ import v.util.vtest
|
|||
import runtime
|
||||
import rand
|
||||
import strings
|
||||
import v.build_constraint
|
||||
|
||||
pub const max_header_len = get_max_header_len()
|
||||
|
||||
|
@ -98,6 +99,8 @@ pub mut:
|
|||
hash string // used as part of the name of the temporary directory created for tests, to ease cleanup
|
||||
|
||||
exec_mode ActionMode = .compile // .compile_and_run only for `v test`
|
||||
|
||||
build_environment build_constraint.Environment // see the documentation in v.build_constraint
|
||||
}
|
||||
|
||||
pub fn (mut ts TestSession) add_failed_cmd(cmd string) {
|
||||
|
@ -443,6 +446,9 @@ pub fn (mut ts TestSession) test() {
|
|||
printing_thread := spawn ts.print_messages()
|
||||
pool_of_test_runners.set_shared_context(ts)
|
||||
ts.reporter.worker_threads_start(remaining_files, mut ts)
|
||||
|
||||
ts.build_environment = get_build_environment()
|
||||
|
||||
// all the testing happens here:
|
||||
pool_of_test_runners.work_on_pointers(unsafe { remaining_files.pointers() })
|
||||
|
||||
|
@ -568,9 +574,23 @@ fn worker_trunner(mut p pool.PoolProcessor, idx int, thread_id int) voidptr {
|
|||
} else {
|
||||
os.quoted_path(generated_binary_fpath)
|
||||
}
|
||||
mut details := get_test_details(file)
|
||||
mut should_be_built := true
|
||||
if details.vbuild != '' {
|
||||
should_be_built = ts.build_environment.eval(details.vbuild) or {
|
||||
eprintln('${file}:${details.vbuild_line}:17: error during parsing the `// v test build` expression `${details.vbuild}`: ${err}')
|
||||
false
|
||||
}
|
||||
$if trace_should_be_built ? {
|
||||
eprintln('${file} has specific build constraint: `${details.vbuild}` => should_be_built: `${should_be_built}`')
|
||||
eprintln('> env facts: ${ts.build_environment.facts}')
|
||||
eprintln('> env defines: ${ts.build_environment.defines}')
|
||||
}
|
||||
}
|
||||
|
||||
ts.benchmark.step()
|
||||
tls_bench.step()
|
||||
if !ts.build_tools && abs_path in ts.skip_files {
|
||||
if !ts.build_tools && (!should_be_built || abs_path in ts.skip_files) {
|
||||
ts.benchmark.skip()
|
||||
tls_bench.skip()
|
||||
if !hide_skips {
|
||||
|
@ -599,7 +619,6 @@ fn worker_trunner(mut p pool.PoolProcessor, idx int, thread_id int) voidptr {
|
|||
ts.append_message_with_duration(.cmd_end, '', cmd_duration, mtc)
|
||||
|
||||
if status != 0 {
|
||||
details := get_test_details(file)
|
||||
os.setenv('VTEST_RETRY_MAX', '${details.retry}', true)
|
||||
for retry := 1; retry <= details.retry; retry++ {
|
||||
if !details.hide_retries {
|
||||
|
@ -686,7 +705,6 @@ fn worker_trunner(mut p pool.PoolProcessor, idx int, thread_id int) voidptr {
|
|||
println(r.output.split_into_lines().filter(it.contains(' assert')).join('\n'))
|
||||
}
|
||||
if r.exit_code != 0 {
|
||||
mut details := get_test_details(file)
|
||||
mut trimmed_output := r.output.trim_space()
|
||||
if trimmed_output.len == 0 {
|
||||
// retry running at least 1 more time, to avoid CI false positives as much as possible
|
||||
|
@ -896,18 +914,24 @@ pub mut:
|
|||
flaky bool // when flaky tests fail, the whole run is still considered successful, unless VTEST_FAIL_FLAKY is 1
|
||||
//
|
||||
hide_retries bool // when true, all retry tries are silent; used by `vlib/v/tests/retry_test.v`
|
||||
vbuild string // could be `!(windows && tinyc)`
|
||||
vbuild_line int // for more precise error reporting, if the `vbuild` expression is incorrect
|
||||
}
|
||||
|
||||
pub fn get_test_details(file string) TestDetails {
|
||||
mut res := TestDetails{}
|
||||
lines := os.read_lines(file) or { [] }
|
||||
for line in lines {
|
||||
for idx, line in lines {
|
||||
if line.starts_with('// vtest retry:') {
|
||||
res.retry = line.all_after(':').trim_space().int()
|
||||
}
|
||||
if line.starts_with('// vtest flaky:') {
|
||||
res.flaky = line.all_after(':').trim_space().bool()
|
||||
}
|
||||
if line.starts_with('// vtest build:') {
|
||||
res.vbuild = line.all_after(':').trim_space()
|
||||
res.vbuild_line = idx + 1
|
||||
}
|
||||
if line.starts_with('// vtest hide_retries') {
|
||||
res.hide_retries = true
|
||||
}
|
||||
|
@ -949,3 +973,9 @@ fn get_max_header_len() int {
|
|||
}
|
||||
return cols
|
||||
}
|
||||
|
||||
fn get_build_environment() &build_constraint.Environment {
|
||||
facts := os.getenv('VBUILD_FACTS').split_any(',')
|
||||
defines := os.getenv('VBUILD_DEFINES').split_any(',')
|
||||
return build_constraint.new_environment(facts, defines)
|
||||
}
|
||||
|
|
28
cmd/v/v.v
28
cmd/v/v.v
|
@ -98,6 +98,9 @@ fn main() {
|
|||
exit(1)
|
||||
}
|
||||
timers.show('v parsing CLI args')
|
||||
|
||||
setup_vbuild_env_vars(prefs)
|
||||
|
||||
// Start calling the correct functions/external tools
|
||||
// Note for future contributors: Please add new subcommands in the `match` block below.
|
||||
if command in external_tools {
|
||||
|
@ -206,3 +209,28 @@ fn rebuild(prefs &pref.Preferences) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@[manualfree]
|
||||
fn setup_vbuild_env_vars(prefs &pref.Preferences) {
|
||||
mut facts := []string{cap: 10}
|
||||
facts << prefs.os.lower()
|
||||
facts << prefs.ccompiler_type.str()
|
||||
facts << prefs.arch.str()
|
||||
if prefs.is_prod {
|
||||
facts << 'prod'
|
||||
}
|
||||
github_job := os.getenv('GITHUB_JOB')
|
||||
if github_job != '' {
|
||||
facts << github_job
|
||||
}
|
||||
sfacts := facts.join(',')
|
||||
os.setenv('VBUILD_FACTS', sfacts, true)
|
||||
|
||||
sdefines := prefs.compile_defines_all.join(',')
|
||||
os.setenv('VBUILD_DEFINES', sdefines, true)
|
||||
|
||||
unsafe { sdefines.free() }
|
||||
unsafe { sfacts.free() }
|
||||
unsafe { github_job.free() }
|
||||
unsafe { facts.free() }
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// vtest build: !do_not_test ?
|
||||
module big
|
||||
|
||||
fn test_add_digit_array_01() {
|
||||
|
|
23
vlib/v/build_constraint/ast.v
Normal file
23
vlib/v/build_constraint/ast.v
Normal file
|
@ -0,0 +1,23 @@
|
|||
module build_constraint
|
||||
|
||||
// ast:
|
||||
struct BExpr {
|
||||
expr BOr
|
||||
}
|
||||
|
||||
struct BOr {
|
||||
exprs []BAnd
|
||||
}
|
||||
|
||||
struct BAnd {
|
||||
exprs []BUnary
|
||||
}
|
||||
|
||||
type BUnary = BNot | BExpr | BFact | BDefine
|
||||
|
||||
struct BNot {
|
||||
expr BUnary
|
||||
}
|
||||
|
||||
type BFact = string
|
||||
type BDefine = string
|
102
vlib/v/build_constraint/constraint_test.v
Normal file
102
vlib/v/build_constraint/constraint_test.v
Normal file
|
@ -0,0 +1,102 @@
|
|||
import v.build_constraint
|
||||
|
||||
const benv = build_constraint.new_environment(['linux', 'tinyc'], ['abc', 'def'])
|
||||
|
||||
fn test_eval_fact() {
|
||||
assert benv.is_fact('tinyc')
|
||||
assert benv.is_fact('linux')
|
||||
assert !benv.is_fact('macos')
|
||||
assert !benv.is_fact('windows')
|
||||
}
|
||||
|
||||
fn test_eval_define() {
|
||||
assert benv.is_define('abc')
|
||||
assert benv.is_define('def')
|
||||
assert !benv.is_define('xyz')
|
||||
}
|
||||
|
||||
fn test_eval_platforms_and_compilers() {
|
||||
assert benv.eval('tinyc')!
|
||||
assert benv.eval(' tinyc')!
|
||||
assert benv.eval('tinyc ')!
|
||||
assert benv.eval(' tinyc ')!
|
||||
assert !benv.eval('gcc')!
|
||||
assert !benv.eval('clang')!
|
||||
assert !benv.eval('msvc')!
|
||||
assert benv.eval('linux')!
|
||||
assert benv.eval(' linux')!
|
||||
assert benv.eval('linux ')!
|
||||
assert benv.eval(' linux ')!
|
||||
assert !benv.eval('windows')!
|
||||
assert !benv.eval('macos')!
|
||||
assert !benv.eval('freebsd')!
|
||||
}
|
||||
|
||||
fn test_eval_defines() {
|
||||
assert benv.eval('abc?')!
|
||||
assert benv.eval(' abc?')!
|
||||
assert benv.eval('abc? ')!
|
||||
assert benv.eval(' abc? ')!
|
||||
assert benv.eval('abc ?')!
|
||||
assert benv.eval(' abc ?')!
|
||||
assert benv.eval('abc ? ')!
|
||||
assert benv.eval(' abc ? ')!
|
||||
assert benv.eval('def?')!
|
||||
}
|
||||
|
||||
fn test_eval_not() {
|
||||
assert benv.eval('!gcc')!
|
||||
assert benv.eval('!clang')!
|
||||
assert benv.eval('!msvc')!
|
||||
assert !benv.eval('!tinyc')!
|
||||
assert !benv.eval(' !tinyc')!
|
||||
assert !benv.eval('!tinyc ')!
|
||||
assert !benv.eval(' !tinyc ')!
|
||||
assert benv.eval('!xyz?')!
|
||||
}
|
||||
|
||||
fn test_eval_and() {
|
||||
assert benv.eval('linux && tinyc')!
|
||||
assert !benv.eval('macos && tinyc')!
|
||||
assert !benv.eval('windows && tinyc')!
|
||||
assert !benv.eval('linux && gcc')!
|
||||
//
|
||||
assert benv.eval('linux && tinyc && abc?')!
|
||||
assert benv.eval('linux && tinyc && def?')!
|
||||
assert !benv.eval('linux && tinyc && xyz?')!
|
||||
//
|
||||
assert benv.eval('linux && !gcc')!
|
||||
assert benv.eval('linux && !clang')!
|
||||
assert benv.eval('!gcc && !windows')!
|
||||
assert !benv.eval('!windows && tcc')!
|
||||
assert !benv.eval('windows && gcc')!
|
||||
assert !benv.eval('gcc && !windows')!
|
||||
}
|
||||
|
||||
fn test_eval_or() {
|
||||
assert benv.eval('windows||tinyc')!
|
||||
assert benv.eval('windows || macos || tinyc')!
|
||||
assert benv.eval('windows || macos || tinyc')!
|
||||
assert benv.eval('windows || macos || gcc || abc?')!
|
||||
assert benv.eval('!windows||gcc')!
|
||||
}
|
||||
|
||||
fn test_complex() {
|
||||
assert benv.eval(' (windows || tinyc) && linux ')!
|
||||
assert !benv.eval(' (windows || gcc) && linux ')!
|
||||
assert benv.eval(' (windows || tinyc) && !macos ')!
|
||||
assert !benv.eval(' (windows || tinyc) && macos ')!
|
||||
}
|
||||
|
||||
fn test_precedence() {
|
||||
assert benv.eval(' tinyc && !windows ')! == benv.eval(' tinyc && (!windows)')!
|
||||
assert benv.eval(' tinyc && !windows ')! == benv.eval(' (!windows) && tinyc')!
|
||||
assert benv.eval(' !windows && tinyc')! == benv.eval(' (!windows) && tinyc')!
|
||||
assert benv.eval(' !windows || tinyc')! == benv.eval(' (!windows) || tinyc')!
|
||||
assert benv.eval(' !linux && tinyc')! == benv.eval(' (!linux) && tinyc')!
|
||||
assert benv.eval(' !linux || tinyc')! == benv.eval(' (!linux) || tinyc')!
|
||||
assert benv.eval(' !windows && gcc ')! == benv.eval(' (!windows) && gcc ')!
|
||||
assert benv.eval(' !windows || gcc ')! == benv.eval(' (!windows) || gcc ')!
|
||||
assert benv.eval(' !linux && gcc ')! == benv.eval(' (!linux) && gcc ')!
|
||||
assert benv.eval(' !linux || gcc ')! == benv.eval(' (!linux) || gcc ')!
|
||||
}
|
43
vlib/v/build_constraint/evaluating.v
Normal file
43
vlib/v/build_constraint/evaluating.v
Normal file
|
@ -0,0 +1,43 @@
|
|||
module build_constraint
|
||||
|
||||
// evaluating the AST nodes, in the given environment
|
||||
fn (b BExpr) eval(env &Environment) !bool {
|
||||
return b.expr.eval(env)
|
||||
}
|
||||
|
||||
fn (b BOr) eval(env &Environment) !bool {
|
||||
for e in b.exprs {
|
||||
if e.eval(env)! {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (b BAnd) eval(env &Environment) !bool {
|
||||
for e in b.exprs {
|
||||
if !e.eval(env)! {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn (b BUnary) eval(env &Environment) !bool {
|
||||
match b {
|
||||
BNot, BExpr, BFact, BDefine { return b.eval(env)! }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (b BNot) eval(env &Environment) !bool {
|
||||
return !b.expr.eval(env)!
|
||||
}
|
||||
|
||||
fn (b BFact) eval(env &Environment) !bool {
|
||||
return env.is_fact(b)
|
||||
}
|
||||
|
||||
fn (b BDefine) eval(env &Environment) !bool {
|
||||
return env.is_define(b)
|
||||
}
|
102
vlib/v/build_constraint/lexing.v
Normal file
102
vlib/v/build_constraint/lexing.v
Normal file
|
@ -0,0 +1,102 @@
|
|||
module build_constraint
|
||||
|
||||
// lexing:
|
||||
enum BTokenKind {
|
||||
tfact // linux, tinyc, prod etc
|
||||
tdefine // abc, gcboehm
|
||||
tor // ||
|
||||
tand // &&
|
||||
tnot // !
|
||||
tparen_open
|
||||
tparen_close
|
||||
teof
|
||||
}
|
||||
|
||||
struct Token {
|
||||
kind BTokenKind
|
||||
value string
|
||||
}
|
||||
|
||||
fn unexpected(c u8) IError {
|
||||
return error('unexpected character `${rune(c)}`')
|
||||
}
|
||||
|
||||
fn new_token(kind BTokenKind, value string) Token {
|
||||
return Token{
|
||||
kind: kind
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
||||
fn new_op(kind BTokenKind) Token {
|
||||
return new_token(kind, '')
|
||||
}
|
||||
|
||||
fn new_span(kind BTokenKind, mut span []u8) Token {
|
||||
t := new_token(kind, span.bytestr())
|
||||
span.clear()
|
||||
return t
|
||||
}
|
||||
|
||||
fn lex(s string) ![]Token {
|
||||
mut res := []Token{}
|
||||
mut span := []u8{cap: s.len}
|
||||
mut op := []u8{}
|
||||
for c in s {
|
||||
match c {
|
||||
` `, `\t`, `\n` {}
|
||||
`(` {
|
||||
if span.len > 0 {
|
||||
res << new_span(.tfact, mut span)
|
||||
}
|
||||
res << new_op(.tparen_open)
|
||||
}
|
||||
`)` {
|
||||
if span.len > 0 {
|
||||
res << new_span(.tfact, mut span)
|
||||
}
|
||||
res << new_op(.tparen_close)
|
||||
}
|
||||
`&`, `|` {
|
||||
if span.len > 0 {
|
||||
res << new_span(.tfact, mut span)
|
||||
}
|
||||
op << c
|
||||
if op == [c, c] {
|
||||
op.clear()
|
||||
if c == `&` {
|
||||
res << new_op(.tand)
|
||||
} else if c == `|` {
|
||||
res << new_op(.tor)
|
||||
} else {
|
||||
return unexpected(c)
|
||||
}
|
||||
}
|
||||
if op.len == 2 {
|
||||
return unexpected(c)
|
||||
}
|
||||
}
|
||||
`?` {
|
||||
res << new_span(.tdefine, mut span)
|
||||
}
|
||||
`!` {
|
||||
res << new_op(.tnot)
|
||||
if span.len > 0 {
|
||||
return unexpected(c)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if u8(c).is_alnum() || c in [`_`, `-`] {
|
||||
span << c
|
||||
} else {
|
||||
return unexpected(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if span.len > 0 {
|
||||
res << new_span(.tfact, mut span)
|
||||
}
|
||||
res << new_op(.teof)
|
||||
return res
|
||||
}
|
95
vlib/v/build_constraint/parsing.v
Normal file
95
vlib/v/build_constraint/parsing.v
Normal file
|
@ -0,0 +1,95 @@
|
|||
module build_constraint
|
||||
|
||||
// parsing:
|
||||
struct BParser {
|
||||
tokens []Token
|
||||
mut:
|
||||
pos int
|
||||
}
|
||||
|
||||
fn (mut p BParser) peek(n int) Token {
|
||||
if p.pos + n >= p.tokens.len {
|
||||
return Token{
|
||||
kind: .teof
|
||||
}
|
||||
}
|
||||
t := p.tokens[p.pos + n]
|
||||
return t
|
||||
}
|
||||
|
||||
fn (mut p BParser) next() {
|
||||
p.pos++
|
||||
}
|
||||
|
||||
fn (mut p BParser) parse() !BExpr {
|
||||
return p.expr()
|
||||
}
|
||||
|
||||
fn (mut p BParser) expr() !BExpr {
|
||||
return BExpr{
|
||||
expr: p.or_expr()!
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p BParser) or_expr() !BOr {
|
||||
mut exprs := []BAnd{}
|
||||
exprs << p.and_expr()!
|
||||
for t := p.peek(0); t.kind == .tor; t = p.peek(0) {
|
||||
p.next()
|
||||
exprs << p.and_expr()!
|
||||
}
|
||||
return BOr{
|
||||
exprs: exprs
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p BParser) and_expr() !BAnd {
|
||||
mut exprs := []BUnary{}
|
||||
exprs << p.unary_expr()!
|
||||
for t := p.peek(0); t.kind == .tand; t = p.peek(0) {
|
||||
p.next()
|
||||
exprs << p.unary_expr()!
|
||||
}
|
||||
return BAnd{
|
||||
exprs: exprs
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p BParser) unary_expr() !BUnary {
|
||||
t := p.peek(0)
|
||||
match t.kind {
|
||||
.tfact {
|
||||
p.next()
|
||||
return BUnary(BFact(t.value))
|
||||
}
|
||||
.tdefine {
|
||||
p.next()
|
||||
return BUnary(BDefine(t.value))
|
||||
}
|
||||
.tnot {
|
||||
p.next()
|
||||
nt := p.peek(0)
|
||||
if nt.kind in [.tfact, .tdefine] {
|
||||
ident := p.unary_expr()!
|
||||
return BNot{
|
||||
expr: ident
|
||||
}
|
||||
}
|
||||
expr := p.expr()!
|
||||
return BNot{
|
||||
expr: expr
|
||||
}
|
||||
}
|
||||
.tparen_open {
|
||||
p.next()
|
||||
expr := p.expr()!
|
||||
if p.peek(0).kind != .tparen_close {
|
||||
return error('expected closing )')
|
||||
}
|
||||
p.next()
|
||||
return BUnary(expr)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
return error('unary failed, unexpected ${t}')
|
||||
}
|
44
vlib/v/build_constraint/public.v
Normal file
44
vlib/v/build_constraint/public.v
Normal file
|
@ -0,0 +1,44 @@
|
|||
module build_constraint
|
||||
|
||||
// Environment represents the current build environment.
|
||||
@[heap]
|
||||
pub struct Environment {
|
||||
pub mut:
|
||||
facts map[string]bool
|
||||
defines map[string]bool
|
||||
}
|
||||
|
||||
// new_environment creates a new Environment.
|
||||
// `facts` is a list of predefined platforms, compilers, build options etc, for example: ['linux', 'tinyc', 'prod', 'amd64']
|
||||
// `defines` is a list of the user defines, for example: ['abc', 'gcboehm_opt', 'gg_record', 'show_fps']
|
||||
pub fn new_environment(facts []string, defines []string) &Environment {
|
||||
mut b := &Environment{}
|
||||
for f in facts {
|
||||
b.facts[f] = true
|
||||
}
|
||||
for d in defines {
|
||||
b.defines[d] = true
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// eval evaluates the given build `constraint` against the current environment.
|
||||
// The constraint can be for example something simple like just `linux`,
|
||||
// but it can be also a more complex logic expression like: `(windows && tinyc) || prod`
|
||||
pub fn (b &Environment) eval(constraint string) !bool {
|
||||
mut parser := BParser{
|
||||
tokens: lex(constraint)!
|
||||
}
|
||||
expr := parser.parse()!
|
||||
return expr.eval(b)
|
||||
}
|
||||
|
||||
// is_fact checks whether the given `fact` is present in the environment.
|
||||
pub fn (b &Environment) is_fact(fact string) bool {
|
||||
return fact in b.facts
|
||||
}
|
||||
|
||||
// is_define checks whether the given `define` is present in the environment.
|
||||
pub fn (b &Environment) is_define(define string) bool {
|
||||
return define in b.defines
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue