From dbd72ee8283a21df0b20ab3ab421613f67fdc0cb Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 9 Oct 2019 06:01:43 +0300 Subject: [PATCH] v test: add ability to test a folder or a set of _test.v files * v test: support for running 'v test folder/' . * Support passing multiple folders and also single _test.v files to 'v test' . * Update vhelp too, with descriptions of v test folder/ and v -stats . * Fix running `v test v` from outside the root of the v tree. --- compiler/compile_errors.v | 19 ++++ compiler/main.v | 129 ++------------------------- compiler/vhelp.v | 2 + compiler/vtest.v | 182 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 122 deletions(-) create mode 100644 compiler/vtest.v diff --git a/compiler/compile_errors.v b/compiler/compile_errors.v index 550c61b648..cdbd258127 100644 --- a/compiler/compile_errors.v +++ b/compiler/compile_errors.v @@ -224,7 +224,26 @@ fn (s mut Scanner) get_scanner_pos_of_token(t &Token) ScannerPos { // of the token. Continue scanning for some more lines of context too. s.goto_scanner_position(ScannerPos{}) s.file_lines = []string + mut prevlinepos := 0 + // NB: TCC BUG workaround: removing the `mut ate:=0 ate++` line + // below causes a bug in v, when v is compiled with tcc, and v + // wants to report the error: 'the following imports were never used:' + // + // This can be reproduced, if you follow the steps: + // a) ./v -cc tcc -o v compiler ; + // b) ./v vlib/builtin/hashmap_test.v' + // + // In this case, prevlinepos gets a random value on each run. + // Any kind of operation may be used seemingly, as long as + // there is a new stack allocation that will 'protect' prevlinepos. + ////////////////////////////////////////////////////////////////// + mut ate:=0 ate++ // This var will be smashed by TCC, instead of + /////////////////// prevlinepos. The cause is the call to + /////////////////// s.get_scanner_pos() + /////////////////// which just returns a struct, and that works + /////////////////// in gcc and clang, but causes the TCC problem. + for { prevlinepos = s.pos if s.pos >= s.text.len { break } diff --git a/compiler/main.v b/compiler/main.v index af17e109ca..57a9cb74cd 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -8,7 +8,6 @@ import ( os strings benchmark - term ) const ( @@ -154,12 +153,12 @@ fn main() { vfmt(args) return } - // Construct the V object from command line arguments - mut v := new_v(args) - if args.join(' ').contains(' test v') { - v.test_v() + if 'test' in args { + test_v() return } + // Construct the V object from command line arguments + mut v := new_v(args) if v.pref.is_verbose { println(args) } @@ -741,10 +740,10 @@ fn (v &V) resolve_deps() &DepGraph { } fn get_arg(joined_args, arg, def string) string { - return get_all_after(joined_args, '-$arg', def) + return get_param_after(joined_args, '-$arg', def) } -fn get_all_after(joined_args, arg, def string) string { +fn get_param_after(joined_args, arg, def string) string { key := '$arg ' mut pos := joined_args.index(key) if pos == -1 { @@ -756,7 +755,6 @@ fn get_all_after(joined_args, arg, def string) string { space = joined_args.len } res := joined_args.substr(pos, space) - // println('get_arg($arg) = "$res"') return res } @@ -777,7 +775,7 @@ fn new_v(args[]string) &V { mut dir := args.last() if 'run' in args { - dir = get_all_after(joined_args, 'run', '') + dir = get_param_after(joined_args, 'run', '') } if dir.ends_with(os.PathSeparator) { dir = dir.all_before_last(os.PathSeparator) @@ -1038,119 +1036,6 @@ fn install_v(args[]string) { } } -fn (v &V) test_vget() { - /* - vexe := os.executable() - ret := os.system('$vexe install nedpals.args') - if ret != 0 { - println('failed to run v install') - exit(1) - } - if !os.file_exists(v_modules_path + '/nedpals/args') { - println('v failed to install a test module') - exit(1) - } - println('vget is OK') - */ -} - -fn (v &V) test_v() { - args := env_vflags_and_os_args() - vexe := os.executable() - parent_dir := os.dir(vexe) - if !os.dir_exists(parent_dir + '/vlib') { - println('vlib/ is missing, it must be next to the V executable') - exit(1) - } - if !os.dir_exists(parent_dir + '/compiler') { - println('compiler/ is missing, it must be next to the V executable') - exit(1) - } - // Make sure v.c can be compiled without warnings - $if mac { - os.system('$vexe -o v.c compiler') - if os.system('cc -Werror v.c') != 0 { - println('cc failed to build v.c without warnings') - exit(1) - } - println('v.c can be compiled without warnings. This is good :)') - } - // Emily: pass args from the invocation to the test - // e.g. `v -g -os msvc test v` -> `$vexe -g -os msvc $file` - mut joined_args := args.right(1).join(' ') - joined_args = joined_args.left(joined_args.last_index('test')) - // println('$joined_args') - mut failed := false - test_files := os.walk_ext(parent_dir, '_test.v') - - ok := term.ok_message('OK') - fail := term.fail_message('FAIL') - println('Testing...') - mut tmark := benchmark.new_benchmark() - for dot_relative_file in test_files { - relative_file := dot_relative_file.replace('./', '') - file := os.realpath( relative_file ) - tmpc_filepath := file.replace('_test.v', '_test.tmp.c') - - mut cmd := '"$vexe" $joined_args -debug "$file"' - if os.user_os() == 'windows' { cmd = '"$cmd"' } - - tmark.step() - r := os.exec(cmd) or { - tmark.fail() - failed = true - println(tmark.step_message('$relative_file $fail')) - continue - } - if r.exit_code != 0 { - failed = true - tmark.fail() - println(tmark.step_message('$relative_file $fail\n`$file`\n (\n$r.output\n)')) - } else { - tmark.ok() - println(tmark.step_message('$relative_file $ok')) - } - os.rm( tmpc_filepath ) - } - tmark.stop() - println( tmark.total_message('running V tests') ) - - println('\nBuilding examples...') - examples := os.walk_ext(parent_dir + '/examples', '.v') - mut bmark := benchmark.new_benchmark() - for relative_file in examples { - if relative_file.contains('vweb') { - continue - } - file := os.realpath( relative_file ) - tmpc_filepath := file.replace('.v', '.tmp.c') - mut cmd := '"$vexe" $joined_args -debug "$file"' - if os.user_os() == 'windows' { cmd = '"$cmd"' } - bmark.step() - r := os.exec(cmd) or { - failed = true - bmark.fail() - println(bmark.step_message('$relative_file $fail')) - continue - } - if r.exit_code != 0 { - failed = true - bmark.fail() - println(bmark.step_message('$relative_file $fail \n`$file`\n (\n$r.output\n)')) - } else { - bmark.ok() - println(bmark.step_message('$relative_file $ok')) - } - os.rm(tmpc_filepath) - } - bmark.stop() - println( bmark.total_message('building examples') ) - v.test_vget() - if failed { - exit(1) - } -} - fn create_symlink() { vexe := os.executable() link_path := '/usr/local/bin/v' diff --git a/compiler/vhelp.v b/compiler/vhelp.v index cfe694dbba..83197e46a5 100644 --- a/compiler/vhelp.v +++ b/compiler/vhelp.v @@ -46,6 +46,7 @@ Options/commands: Example: -cflags `sdl2-config --cflags` -debug Keep the generated C file for debugging in program.tmp.c even after compilation. -shared Build a shared library. + -stats Show additional stats when compiling/running tests. Try `v -stats test .` -g Show v line numbers in backtraces. Implies -debug. -obf Obfuscate the resulting binary. -show_c_cmd Print the full C compilation command and how much time it took. @@ -58,6 +59,7 @@ Options/commands: symlink Useful on unix systems. Symlinks the current V executable to /usr/local/bin/v, so that V is globally available. install Install a user module from https://vpm.vlang.io/. test v Run all V test files, and compile all V examples. + test folder/ Run all V test files located in the folder and its subfolders. You can also pass individual _test.v files too. fmt Run vfmt to format the source code. [wip] doc Run vdoc over the source code and produce documentation. [wip] translate Translates C to V. [wip, will be available in V 0.3] diff --git a/compiler/vtest.v b/compiler/vtest.v new file mode 100644 index 0000000000..ddd0fadd5a --- /dev/null +++ b/compiler/vtest.v @@ -0,0 +1,182 @@ +module main + +import ( + os + term + benchmark +) + +struct TestSession { +mut: + files []string + vexe string + vargs string + failed bool + benchmark benchmark.Benchmark +} + +fn new_test_sesion(vargs string) TestSession { + return TestSession{ + vexe: os.executable() + vargs: vargs + } +} + +fn test_v() { + args := os.args + if args.last() == 'test' { + println('Usage:') + println(' A)') + println(' v test v : run all v tests and build all the examples') + println(' B)') + println(' v test folder/ : run all v tests in the given folder.') + println(' v -stats test folder/ : the same, but print more stats.') + println(' C)') + println(' v test file_test.v : run test functions in a given test file.') + println(' v -stats test file_test.v : as above, but with more stats.') + println(' NB: you can also give many and mixed folder/ file_test.v arguments after test.') + println('') + return + } + + args_string := args.right(1).join(' ') + args_before := args_string.all_before('test ') + args_after := args_string.all_after('test ') + + if args_after == 'v' { + v_test_v(args_before) + return + } + + mut ts := new_test_sesion(args_before) + for targ in args_after.split(' ') { + if os.file_exists(targ) && targ.ends_with('_test.v') { + ts.files << targ + continue + } + if os.dir_exists(targ) { + + ts.files << os.walk_ext( targ.trim_right(os.PathSeparator), '_test.v') + continue + } + println('Unrecognized test file $targ .') + } + + println('Testing...') + ts.test() + println('----------------------------------------------------------------------------') + println( ts.benchmark.total_message('running V _test.v files') ) + if ts.failed { + exit(1) + } +} + +fn (ts mut TestSession) test() { + ok := term.ok_message('OK') + fail := term.fail_message('FAIL') + cmd_needs_quoting := (os.user_os() == 'windows') + show_stats := '-stats' in ts.vargs.split(' ') + ts.benchmark = benchmark.new_benchmark() + for dot_relative_file in ts.files { + relative_file := dot_relative_file.replace('./', '') + file := os.realpath( relative_file ) + tmpc_filepath := file.replace('.v', '.tmp.c') + + mut cmd := '"$ts.vexe" $ts.vargs "$file"' + if cmd_needs_quoting { cmd = '"$cmd"' } + + ts.benchmark.step() + if show_stats { + println('-------------------------------------------------') + status := os.system(cmd) + if status == 0 { + ts.benchmark.ok() + }else{ + ts.benchmark.fail() + ts.failed = true + continue + } + }else{ + r := os.exec(cmd) or { + ts.benchmark.fail() + ts.failed = true + println(ts.benchmark.step_message('$relative_file $fail')) + continue + } + if r.exit_code != 0 { + ts.benchmark.fail() + ts.failed = true + println(ts.benchmark.step_message('$relative_file $fail\n`$file`\n (\n$r.output\n)')) + } else { + ts.benchmark.ok() + println(ts.benchmark.step_message('$relative_file $ok')) + } + } + os.rm( tmpc_filepath ) + } + ts.benchmark.stop() +} + +fn stable_example(example string, index int, arr []string) bool { + return !example.contains('vweb') +} + +fn v_test_v(args_before_test string){ + vexe := os.executable() + parent_dir := os.dir(vexe) + // Changing the current directory is needed for some of the compiler tests, + // compiler/tests/local_test.v and compiler/tests/repl/repl_test.v + os.chdir( parent_dir ) + if !os.dir_exists(parent_dir + '/vlib') { + println('vlib/ is missing, it must be next to the V executable') + exit(1) + } + if !os.dir_exists(parent_dir + '/compiler') { + println('compiler/ is missing, it must be next to the V executable') + exit(1) + } + // Make sure v.c can be compiled without warnings + $if mac { + os.system('$vexe -o v.c compiler') + if os.system('cc -Werror v.c') != 0 { + println('cc failed to build v.c without warnings') + exit(1) + } + println('v.c can be compiled without warnings. This is good :)') + } + ////////////////////////////////////////////////////////////// + println('Testing...') + mut ts := new_test_sesion( args_before_test ) + ts.files << os.walk_ext(parent_dir, '_test.v') + ts.test() + println( ts.benchmark.total_message('running V tests') ) + ////////////////////////////////////////////////////////////// + println('\nBuilding examples...') + mut es := new_test_sesion( args_before_test ) + es.files << os.walk_ext(parent_dir+'/examples','.v').filter(stable_example) + es.test() + println( es.benchmark.total_message('building examples') ) + ////////////////////////////////////////////////////////////// + + test_vget() + + if ts.failed || es.failed { + exit(1) + } +} + +fn test_vget() { + /* + vexe := os.executable() + ret := os.system('$vexe install nedpals.args') + if ret != 0 { + println('failed to run v install') + exit(1) + } + if !os.file_exists(v_modules_path + '/nedpals/args') { + println('v failed to install a test module') + exit(1) + } + println('vget is OK') + */ +}