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.
pull/2267/head
Delyan Angelov 2019-10-09 06:01:43 +03:00 committed by Alexander Medvednikov
parent f570cbfca8
commit dbd72ee828
4 changed files with 210 additions and 122 deletions

View File

@ -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. // of the token. Continue scanning for some more lines of context too.
s.goto_scanner_position(ScannerPos{}) s.goto_scanner_position(ScannerPos{})
s.file_lines = []string s.file_lines = []string
mut prevlinepos := 0 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 { for {
prevlinepos = s.pos prevlinepos = s.pos
if s.pos >= s.text.len { break } if s.pos >= s.text.len { break }

View File

@ -8,7 +8,6 @@ import (
os os
strings strings
benchmark benchmark
term
) )
const ( const (
@ -154,12 +153,12 @@ fn main() {
vfmt(args) vfmt(args)
return return
} }
// Construct the V object from command line arguments if 'test' in args {
mut v := new_v(args) test_v()
if args.join(' ').contains(' test v') {
v.test_v()
return return
} }
// Construct the V object from command line arguments
mut v := new_v(args)
if v.pref.is_verbose { if v.pref.is_verbose {
println(args) println(args)
} }
@ -741,10 +740,10 @@ fn (v &V) resolve_deps() &DepGraph {
} }
fn get_arg(joined_args, arg, def string) string { 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 ' key := '$arg '
mut pos := joined_args.index(key) mut pos := joined_args.index(key)
if pos == -1 { if pos == -1 {
@ -756,7 +755,6 @@ fn get_all_after(joined_args, arg, def string) string {
space = joined_args.len space = joined_args.len
} }
res := joined_args.substr(pos, space) res := joined_args.substr(pos, space)
// println('get_arg($arg) = "$res"')
return res return res
} }
@ -777,7 +775,7 @@ fn new_v(args[]string) &V {
mut dir := args.last() mut dir := args.last()
if 'run' in args { if 'run' in args {
dir = get_all_after(joined_args, 'run', '') dir = get_param_after(joined_args, 'run', '')
} }
if dir.ends_with(os.PathSeparator) { if dir.ends_with(os.PathSeparator) {
dir = dir.all_before_last(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() { fn create_symlink() {
vexe := os.executable() vexe := os.executable()
link_path := '/usr/local/bin/v' link_path := '/usr/local/bin/v'

View File

@ -46,6 +46,7 @@ Options/commands:
Example: -cflags `sdl2-config --cflags` Example: -cflags `sdl2-config --cflags`
-debug Keep the generated C file for debugging in program.tmp.c even after compilation. -debug Keep the generated C file for debugging in program.tmp.c even after compilation.
-shared Build a shared library. -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. -g Show v line numbers in backtraces. Implies -debug.
-obf Obfuscate the resulting binary. -obf Obfuscate the resulting binary.
-show_c_cmd Print the full C compilation command and how much time it took. -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. symlink Useful on unix systems. Symlinks the current V executable to /usr/local/bin/v, so that V is globally available.
install <module> Install a user module from https://vpm.vlang.io/. install <module> Install a user module from https://vpm.vlang.io/.
test v Run all V test files, and compile all V examples. 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] fmt Run vfmt to format the source code. [wip]
doc Run vdoc over the source code and produce documentation. [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] translate Translates C to V. [wip, will be available in V 0.3]

182
compiler/vtest.v 100644
View File

@ -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')
*/
}