tests: support `fn test_fn() ? { opt()? }`
parent
f4c03e8ed8
commit
0f042124cb
|
@ -317,6 +317,8 @@ pub:
|
||||||
is_variadic bool
|
is_variadic bool
|
||||||
is_anon bool
|
is_anon bool
|
||||||
is_manualfree bool // true, when [manualfree] is used on a fn
|
is_manualfree bool // true, when [manualfree] is used on a fn
|
||||||
|
is_main bool // true for `fn main()`
|
||||||
|
is_test bool // true for `fn test_abcde`
|
||||||
receiver Field
|
receiver Field
|
||||||
receiver_pos token.Position // `(u User)` in `fn (u User) name()` position
|
receiver_pos token.Position // `(u User)` in `fn (u User) name()` position
|
||||||
is_method bool
|
is_method bool
|
||||||
|
|
|
@ -170,6 +170,7 @@ pub fn (mut c Checker) check_files(ast_files []ast.File) {
|
||||||
the_main_file.stmts << ast.FnDecl{
|
the_main_file.stmts << ast.FnDecl{
|
||||||
name: 'main.main'
|
name: 'main.main'
|
||||||
mod: 'main'
|
mod: 'main'
|
||||||
|
is_main: true
|
||||||
file: the_main_file.path
|
file: the_main_file.path
|
||||||
return_type: table.void_type
|
return_type: table.void_type
|
||||||
scope: &ast.Scope{
|
scope: &ast.Scope{
|
||||||
|
@ -205,7 +206,7 @@ pub fn (mut c Checker) check_files(ast_files []ast.File) {
|
||||||
if c.pref.is_test {
|
if c.pref.is_test {
|
||||||
mut n_test_fns := 0
|
mut n_test_fns := 0
|
||||||
for _, f in c.table.fns {
|
for _, f in c.table.fns {
|
||||||
if f.name.contains('.test_') {
|
if f.is_test {
|
||||||
n_test_fns++
|
n_test_fns++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1213,8 +1214,7 @@ pub fn (mut c Checker) call_expr(mut call_expr ast.CallExpr) table.Type {
|
||||||
c.expected_or_type = table.void_type
|
c.expected_or_type = table.void_type
|
||||||
if call_expr.or_block.kind == .propagate && !c.cur_fn.return_type.has_flag(.optional)
|
if call_expr.or_block.kind == .propagate && !c.cur_fn.return_type.has_flag(.optional)
|
||||||
&& !c.inside_const {
|
&& !c.inside_const {
|
||||||
cur_names := c.cur_fn.name.split('.')
|
if !c.cur_fn.is_main {
|
||||||
if cur_names[cur_names.len - 1] != 'main' {
|
|
||||||
c.error('to propagate the optional call, `$c.cur_fn.name` must return an optional',
|
c.error('to propagate the optional call, `$c.cur_fn.name` must return an optional',
|
||||||
call_expr.or_block.pos)
|
call_expr.or_block.pos)
|
||||||
}
|
}
|
||||||
|
@ -5440,7 +5440,9 @@ pub fn (mut c Checker) enum_val(mut node ast.EnumVal) table.Type {
|
||||||
// rintln('checker: x = $info.x enum val $c.expected_type $typ_sym.name')
|
// rintln('checker: x = $info.x enum val $c.expected_type $typ_sym.name')
|
||||||
// println(info.vals)
|
// println(info.vals)
|
||||||
if node.val !in info.vals {
|
if node.val !in info.vals {
|
||||||
c.error('enum `$typ_sym.name` does not have a value `$node.val`', node.pos)
|
suggestion := util.new_suggestion(node.val, info.vals)
|
||||||
|
c.error(suggestion.say('enum `$typ_sym.name` does not have a value `$node.val`'),
|
||||||
|
node.pos)
|
||||||
}
|
}
|
||||||
node.typ = typ
|
node.typ = typ
|
||||||
return typ
|
return typ
|
||||||
|
@ -5916,8 +5918,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO c.pref.is_vet
|
// TODO c.pref.is_vet
|
||||||
if node.language == .v && !node.is_method && node.params.len == 0
|
if node.language == .v && !node.is_method && node.params.len == 0 && node.is_test {
|
||||||
&& node.name.after('.').starts_with('test_') {
|
|
||||||
if !c.pref.is_test {
|
if !c.pref.is_test {
|
||||||
// simple heuristic
|
// simple heuristic
|
||||||
for st in node.stmts {
|
for st in node.stmts {
|
||||||
|
@ -5928,9 +5929,10 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eprintln('> node.name: $node.name | node.return_type: $node.return_type')
|
if node.return_type != table.void_type_idx
|
||||||
if node.return_type != table.void_type_idx {
|
&& node.return_type.clear_flag(.optional) != table.void_type_idx {
|
||||||
c.error('test functions should not return anything', node.pos)
|
c.error('test functions should either return nothing at all, or be marked to return `?`',
|
||||||
|
node.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.expected_type = table.void_type
|
c.expected_type = table.void_type
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
vlib/v/checker/tests/test_functions_should_not_return_test.vv:9:1: error: test functions should not return anything
|
vlib/v/checker/tests/test_functions_should_not_return_test.vv:9:1: error: test functions should either return nothing at all, or be marked to return `?`
|
||||||
7 |
|
7 |
|
||||||
8 | // should be disallowed:
|
8 | // should be disallowed:
|
||||||
9 | fn test_returning_int() int {
|
9 | fn test_returning_int() int {
|
||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
10 |
|
10 |
|
||||||
11 | }
|
11 | }
|
||||||
vlib/v/checker/tests/test_functions_should_not_return_test.vv:14:1: error: test functions should not return anything
|
|
||||||
12 |
|
|
||||||
13 | // should be disallowed:
|
|
||||||
14 | fn test_returning_opt() ? {
|
|
||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
15 | }
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ fn test_returning_int() int {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// should be disallowed:
|
// NB: this is allowed explicitly now, to allow for shorter tests
|
||||||
|
// of functions returning optionals.
|
||||||
fn test_returning_opt() ? {
|
fn test_returning_opt() ? {
|
||||||
|
assert true
|
||||||
}
|
}
|
||||||
|
|
|
@ -5252,7 +5252,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type table.
|
||||||
g.stmts(stmts)
|
g.stmts(stmts)
|
||||||
}
|
}
|
||||||
} else if or_block.kind == .propagate {
|
} else if or_block.kind == .propagate {
|
||||||
if g.file.mod.name == 'main' && (isnil(g.fn_decl) || g.fn_decl.name == 'main.main') {
|
if g.file.mod.name == 'main' && (isnil(g.fn_decl) || g.fn_decl.is_main) {
|
||||||
// In main(), an `opt()?` call is sugar for `opt() or { panic(err) }`
|
// In main(), an `opt()?` call is sugar for `opt() or { panic(err) }`
|
||||||
if g.pref.is_debug {
|
if g.pref.is_debug {
|
||||||
paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos)
|
paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos)
|
||||||
|
@ -5260,6 +5260,8 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type table.
|
||||||
} else {
|
} else {
|
||||||
g.writeln('\tv_panic(_STR("optional not set (%.*s\\000)", 2, ${cvar_name}.err.msg));')
|
g.writeln('\tv_panic(_STR("optional not set (%.*s\\000)", 2, ${cvar_name}.err.msg));')
|
||||||
}
|
}
|
||||||
|
} else if !isnil(g.fn_decl) && g.fn_decl.is_test {
|
||||||
|
g.gen_failing_error_propagation_for_test_fn(or_block, cvar_name)
|
||||||
} else {
|
} else {
|
||||||
// In ordinary functions, `opt()?` call is sugar for:
|
// In ordinary functions, `opt()?` call is sugar for:
|
||||||
// `opt() or { return err }`
|
// `opt() or { return err }`
|
||||||
|
@ -5455,11 +5457,7 @@ fn (g &Gen) get_all_test_function_names() []string {
|
||||||
if tsuite_end.len > 0 {
|
if tsuite_end.len > 0 {
|
||||||
all_tfuncs << tsuite_end
|
all_tfuncs << tsuite_end
|
||||||
}
|
}
|
||||||
mut all_tfuncs_c := []string{}
|
return all_tfuncs
|
||||||
for f in all_tfuncs {
|
|
||||||
all_tfuncs_c << util.no_dots(f)
|
|
||||||
}
|
|
||||||
return all_tfuncs_c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (g &Gen) is_importing_os() bool {
|
fn (g &Gen) is_importing_os() bool {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module c
|
module c
|
||||||
|
|
||||||
import v.util
|
import v.util
|
||||||
|
import v.ast
|
||||||
|
|
||||||
pub fn (mut g Gen) gen_c_main() {
|
pub fn (mut g Gen) gen_c_main() {
|
||||||
if !g.has_main {
|
if !g.has_main {
|
||||||
|
@ -135,6 +136,16 @@ pub fn (mut g Gen) write_tests_definitions() {
|
||||||
g.definitions.writeln('jmp_buf g_jump_buffer;')
|
g.definitions.writeln('jmp_buf g_jump_buffer;')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn (mut g Gen) gen_failing_error_propagation_for_test_fn(or_block ast.OrExpr, cvar_name string) {
|
||||||
|
// in test_() functions, an `opt()?` call is sugar for
|
||||||
|
// `or { cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg) }`
|
||||||
|
// and the test is considered failed
|
||||||
|
paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos)
|
||||||
|
g.writeln('\tmain__cb_propagate_test_error($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), ${cvar_name}.err.msg );')
|
||||||
|
g.writeln('\tg_test_fails++;')
|
||||||
|
g.writeln('\tlongjmp(g_jump_buffer, 1);')
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (mut g Gen) gen_c_main_for_tests() {
|
pub fn (mut g Gen) gen_c_main_for_tests() {
|
||||||
main_fn_start_pos := g.out.len
|
main_fn_start_pos := g.out.len
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
|
@ -145,11 +156,12 @@ pub fn (mut g Gen) gen_c_main_for_tests() {
|
||||||
g.writeln('\tmain__BenchedTests bt = main__start_testing($all_tfuncs.len, _SLIT("$g.pref.path"));')
|
g.writeln('\tmain__BenchedTests bt = main__start_testing($all_tfuncs.len, _SLIT("$g.pref.path"));')
|
||||||
}
|
}
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
for t in all_tfuncs {
|
for tname in all_tfuncs {
|
||||||
|
tcname := util.no_dots(tname)
|
||||||
if g.pref.is_stats {
|
if g.pref.is_stats {
|
||||||
g.writeln('\tmain__BenchedTests_testing_step_start(&bt, _SLIT("$t"));')
|
g.writeln('\tmain__BenchedTests_testing_step_start(&bt, _SLIT("$tcname"));')
|
||||||
}
|
}
|
||||||
g.writeln('\tif (!setjmp(g_jump_buffer)) ${t}();')
|
g.writeln('\tif (!setjmp(g_jump_buffer)) ${tcname}();')
|
||||||
if g.pref.is_stats {
|
if g.pref.is_stats {
|
||||||
g.writeln('\tmain__BenchedTests_testing_step_end(&bt);')
|
g.writeln('\tmain__BenchedTests_testing_step_end(&bt);')
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,6 +296,9 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
mut type_sym_method_idx := 0
|
mut type_sym_method_idx := 0
|
||||||
no_body := p.tok.kind != .lcbr
|
no_body := p.tok.kind != .lcbr
|
||||||
end_pos := p.prev_tok.position()
|
end_pos := p.prev_tok.position()
|
||||||
|
short_fn_name := name
|
||||||
|
is_main := short_fn_name == 'main' && p.mod == 'main'
|
||||||
|
is_test := short_fn_name.starts_with('test_') || short_fn_name.starts_with('testsuite_')
|
||||||
// Register
|
// Register
|
||||||
if is_method {
|
if is_method {
|
||||||
mut type_sym := p.table.get_type_symbol(rec.typ)
|
mut type_sym := p.table.get_type_symbol(rec.typ)
|
||||||
|
@ -326,6 +329,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
is_pub: is_pub
|
is_pub: is_pub
|
||||||
is_deprecated: is_deprecated
|
is_deprecated: is_deprecated
|
||||||
is_unsafe: is_unsafe
|
is_unsafe: is_unsafe
|
||||||
|
is_main: is_main
|
||||||
|
is_test: is_test
|
||||||
no_body: no_body
|
no_body: no_body
|
||||||
mod: p.mod
|
mod: p.mod
|
||||||
attrs: p.attrs
|
attrs: p.attrs
|
||||||
|
@ -351,6 +356,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
is_pub: is_pub
|
is_pub: is_pub
|
||||||
is_deprecated: is_deprecated
|
is_deprecated: is_deprecated
|
||||||
is_unsafe: is_unsafe
|
is_unsafe: is_unsafe
|
||||||
|
is_main: is_main
|
||||||
|
is_test: is_test
|
||||||
no_body: no_body
|
no_body: no_body
|
||||||
mod: p.mod
|
mod: p.mod
|
||||||
attrs: p.attrs
|
attrs: p.attrs
|
||||||
|
@ -388,6 +395,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
is_direct_arr: is_direct_arr
|
is_direct_arr: is_direct_arr
|
||||||
is_pub: is_pub
|
is_pub: is_pub
|
||||||
is_variadic: is_variadic
|
is_variadic: is_variadic
|
||||||
|
is_main: is_main
|
||||||
|
is_test: is_test
|
||||||
receiver: ast.Field{
|
receiver: ast.Field{
|
||||||
name: rec.name
|
name: rec.name
|
||||||
typ: rec.typ
|
typ: rec.typ
|
||||||
|
|
|
@ -556,6 +556,7 @@ pub fn (mut p Parser) top_stmt() ast.Stmt {
|
||||||
return ast.FnDecl{
|
return ast.FnDecl{
|
||||||
name: 'main.main'
|
name: 'main.main'
|
||||||
mod: 'main'
|
mod: 'main'
|
||||||
|
is_main: true
|
||||||
stmts: stmts
|
stmts: stmts
|
||||||
file: p.file_name
|
file: p.file_name
|
||||||
return_type: table.void_type
|
return_type: table.void_type
|
||||||
|
|
|
@ -350,7 +350,7 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
|
||||||
res.build_options << '$arg $target_os'
|
res.build_options << '$arg $target_os'
|
||||||
}
|
}
|
||||||
'-printfn' {
|
'-printfn' {
|
||||||
res.printfn_list << cmdline.option(current_args, '-printfn', '')
|
res.printfn_list << cmdline.option(current_args, '-printfn', '').split(',')
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
'-cflags' {
|
'-cflags' {
|
||||||
|
|
|
@ -86,3 +86,21 @@ fn cb_assertion_ok(i &VAssertMetaInfo) {
|
||||||
println('$final_funcname ($final_filepath)')
|
println('$final_funcname ($final_filepath)')
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cb_propagate_test_error(line_nr int, file string, mod string, fn_name string, errmsg string) {
|
||||||
|
filepath := if use_relative_paths { file } else { os.real_path(file) }
|
||||||
|
mut final_filepath := filepath + ':$line_nr:'
|
||||||
|
if use_color {
|
||||||
|
final_filepath = term.gray(final_filepath)
|
||||||
|
}
|
||||||
|
mut final_funcname := 'fn ' + fn_name.replace('main.', '').replace('__', '.')
|
||||||
|
if use_color {
|
||||||
|
final_funcname = term.red('✗ ' + final_funcname)
|
||||||
|
}
|
||||||
|
final_msg := if use_color { term.dim(errmsg) } else { errmsg }
|
||||||
|
eprintln('$final_filepath $final_funcname failed propagation with error: $final_msg')
|
||||||
|
if os.is_file(file) {
|
||||||
|
source_lines := os.read_lines(file) or { []string{len: line_nr + 1} }
|
||||||
|
eprintln('${line_nr:5} | ${source_lines[line_nr - 1]}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ pub:
|
||||||
is_deprecated bool
|
is_deprecated bool
|
||||||
is_unsafe bool
|
is_unsafe bool
|
||||||
is_placeholder bool
|
is_placeholder bool
|
||||||
|
is_main bool
|
||||||
|
is_test bool
|
||||||
no_body bool
|
no_body bool
|
||||||
mod string
|
mod string
|
||||||
ctdefine string // compile time define. "myflag", when [if myflag] tag
|
ctdefine string // compile time define. "myflag", when [if myflag] tag
|
||||||
|
|
Loading…
Reference in New Issue