From c05cd760858e64e5ffd66fb28bbd1318357f9f75 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 24 Apr 2026 01:17:20 +0300 Subject: [PATCH] all: fix testsuite `after_each` and `before_each` functions (fixes #19699) --- vlib/v/checker/fn.v | 3 ++- vlib/v/gen/c/cmain.v | 38 +++++++++++++++++++++++--- vlib/v/gen/c/fn.v | 3 +++ vlib/v/gen/js/js.v | 28 +++++++++++++++++++- vlib/v/parser/fn.v | 3 ++- vlib/v/tests/failing_tests_test.v | 44 +++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 6 deletions(-) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 6e4b843ce..f97e8c5a1 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -845,7 +845,8 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } // TODO: c.pref.is_vet if c.file.is_test && (!node.is_method && (node.short_name.starts_with('test_') - || node.short_name.starts_with('testsuite_'))) { + || node.short_name.starts_with('testsuite_') + || node.short_name in ['before_each', 'after_each'])) { if !c.pref.is_test { // simple heuristic for st in node.stmts { diff --git a/vlib/v/gen/c/cmain.v b/vlib/v/gen/c/cmain.v index c3fc4c1ed..80a11a778 100644 --- a/vlib/v/gen/c/cmain.v +++ b/vlib/v/gen/c/cmain.v @@ -316,7 +316,22 @@ pub fn (mut g Gen) gen_c_main_for_tests() { } g.gen_c_main_profile_hook() - mut all_tfuncs := g.get_all_test_function_names() + mut before_each_fn := '' + mut after_each_fn := '' + for tname in g.test_function_names { + short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname } + if short_tname == 'before_each' { + before_each_fn = util.no_dots(tname) + continue + } + if short_tname == 'after_each' { + after_each_fn = util.no_dots(tname) + } + } + mut all_tfuncs := []string{} + for tname in g.get_all_test_function_names() { + all_tfuncs << tname + } all_tfuncs = g.filter_only_matching_fn_names(all_tfuncs) g.writeln('\tstring v_test_file = ${ctoslit(g.pref.path)};') if g.pref.show_asserts { @@ -331,6 +346,8 @@ pub fn (mut g Gen) gen_c_main_for_tests() { for tnumber, tname in all_tfuncs { tcname := util.no_dots(tname) testfn := unsafe { g.table.fns[tname] } + short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname } + is_test_fn := short_tname.starts_with('test_') lnum := testfn.pos.line_nr + 1 g.writeln('\tmain__VTestFnMetaInfo_free(test_runner.fn_test_info);') g.writeln('\tstring tcname_${tnumber} = _S("${tcname}");') @@ -338,19 +355,34 @@ pub fn (mut g Gen) gen_c_main_for_tests() { g.writeln('\tstring tcfile_${tnumber} = ${ctoslit(testfn.file)};') g.writeln('\t*(test_runner.fn_test_info) = main__vtest_new_metainfo(tcname_${tnumber}, tcmod_${tnumber}, tcfile_${tnumber}, ${lnum});') g.writeln('\t_vtrunner._method_fn_start(_vtobj);') + g.writeln('\tbool failed_${tnumber} = false;') g.writeln('\tif (!setjmp(g_jump_buffer)) {') // if g.pref.show_asserts { g.writeln('\t\tmain__BenchedTests_testing_step_start(&bt, tcname_${tnumber});') } + if is_test_fn && before_each_fn != '' { + g.writeln('\t\t${before_each_fn}();') + } g.writeln('\t\t${tcname}();') - g.writeln('\t\t_vtrunner._method_fn_pass(_vtobj);') // g.writeln('\t}else{') // - g.writeln('\t\t_vtrunner._method_fn_fail(_vtobj);') + g.writeln('\t\tfailed_${tnumber} = true;') // g.writeln('\t}') + if is_test_fn && after_each_fn != '' { + g.writeln('\tif (!setjmp(g_jump_buffer)) {') + g.writeln('\t\t${after_each_fn}();') + g.writeln('\t}else{') + g.writeln('\t\tfailed_${tnumber} = true;') + g.writeln('\t}') + } + g.writeln('\tif (failed_${tnumber}) {') + g.writeln('\t\t_vtrunner._method_fn_fail(_vtobj);') + g.writeln('\t}else{') + g.writeln('\t\t_vtrunner._method_fn_pass(_vtobj);') + g.writeln('\t}') if g.pref.show_asserts { g.writeln('\tmain__BenchedTests_testing_step_end(&bt);') } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 5d543e0ee..27c09e677 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -695,6 +695,9 @@ fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool { if node.is_method && node.name in ['[]', '[]='] { return true } + if node.is_test && node.name.all_after_last('.') in ['before_each', 'after_each'] { + return true + } if node.mod == 'builtin' && node.name in ['print', 'println', 'eprint', 'eprintln'] { return true } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 3937286fd..d15dba5e0 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -346,7 +346,25 @@ pub fn (mut g JsGen) gen_js_main_for_tests() { } g.writeln('function js_main() { ') g.inc_indent() - all_tfuncs := g.get_all_test_function_names() + mut before_each_fn := '' + mut after_each_fn := '' + for _, f in g.table.fns { + short_tname := if f.name.contains('.') { f.name.all_after_last('.') } else { f.name } + if !f.is_test { + continue + } + if short_tname == 'before_each' { + before_each_fn = g.js_name(f.name) + continue + } + if short_tname == 'after_each' { + after_each_fn = g.js_name(f.name) + } + } + mut all_tfuncs := []string{} + for tname in g.get_all_test_function_names() { + all_tfuncs << tname + } g.writeln('') g.writeln('globalThis.VTEST=1') @@ -355,12 +373,20 @@ pub fn (mut g JsGen) gen_js_main_for_tests() { } for i, tname in all_tfuncs { tcname := g.js_name(tname) + short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname } + is_test_fn := short_tname.starts_with('test_') if g.pref.is_stats { g.writeln('main__BenchedTests_testing_step_start(bt,new string("${tcname}"))') g.writeln('try {') } + if is_test_fn && before_each_fn != '' { + g.writeln('let before_each_res_${i} = ${before_each_fn}(); if (before_each_res_${i} instanceof Promise) { await before_each_res_${i}; }') + } g.writeln('let res_${i} = ${tcname}(); if (res_${i} instanceof Promise) { await res_${i}; }') + if is_test_fn && after_each_fn != '' { + g.writeln('let after_each_res_${i} = ${after_each_fn}(); if (after_each_res_${i} instanceof Promise) { await after_each_res_${i}; }') + } if g.pref.is_stats { g.writeln('} finally {') g.writeln('main__BenchedTests_testing_step_end(bt);') diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index c3b7e788e..fd17857de 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -912,7 +912,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl { p.main_already_defined = true } is_test := (!is_method && params.len == 0) && p.inside_test_file - && (short_fn_name.starts_with('test_') || short_fn_name.starts_with('testsuite_')) + && (short_fn_name.starts_with('test_') || short_fn_name.starts_with('testsuite_') + || short_fn_name in ['before_each', 'after_each']) file_mode := p.file_backend_mode if is_main { if 'main.main' in p.table.fns { diff --git a/vlib/v/tests/failing_tests_test.v b/vlib/v/tests/failing_tests_test.v index 979119334..6f2836c73 100644 --- a/vlib/v/tests/failing_tests_test.v +++ b/vlib/v/tests/failing_tests_test.v @@ -99,6 +99,50 @@ fn test_run_only_reports_filtered_failures() { assert !res.output.contains('fn test_ok'), res.output } +fn test_before_each_and_after_each_run_around_each_test() { + test_path := os.join_path(os.vtmp_dir(), 'issue_19699_${os.getpid()}_test.v') + log_path := os.join_path(os.vtmp_dir(), 'issue_19699_${os.getpid()}.log') + defer { + os.rm(test_path) or {} + os.rm(log_path) or {} + os.unsetenv('V_ISSUE_19699_LOG_PATH') + } + test_source := [ + 'import os', + '', + "const issue_19699_log_path = os.getenv('V_ISSUE_19699_LOG_PATH')", + '', + 'fn append_log(line string) {', + ' mut entries := os.read_lines(issue_19699_log_path) or { []string{} }', + ' entries << line', + " os.write_file(issue_19699_log_path, entries.join_lines() + '\\n') or { panic(err) }", + '}', + '', + "fn testsuite_begin() { append_log('testsuite_begin') }", + "fn before_each() { append_log('before_each') }", + "fn after_each() { append_log('after_each') }", + "fn test_one() { append_log('test_one') }", + "fn test_two() { append_log('test_two') }", + "fn testsuite_end() { append_log('testsuite_end') }", + ].join_lines() + os.write_file(test_path, test_source)! + os.rm(log_path) or {} + os.setenv('V_ISSUE_19699_LOG_PATH', log_path, true) + res := os.execute('${os.quoted_path(@VEXE)} -test-runner normal ${os.quoted_path(test_path)}') + assert res.exit_code == 0, res.output + log_lines := os.read_lines(log_path)! + assert log_lines == [ + 'testsuite_begin', + 'before_each', + 'test_one', + 'after_each', + 'before_each', + 'test_two', + 'after_each', + 'testsuite_end', + ] +} + fn test_windows_c_system_info_is_undefined_on_non_windows() { $if windows { return -- 2.39.5