v / cmd / tools / vtest.v
230 lines · 219 sloc · 6.53 KB · 5ac1641b93ea1bf3bd615384d85469068f241fcb
Raw
1module main
2
3import os
4import os.cmdline
5import testing
6import v.pref
7
8struct Context {
9mut:
10 verbose bool
11 fail_fast bool
12 run_only []string
13}
14
15fn main() {
16 args := os.args.clone()
17 if os.args.last() == 'test' {
18 show_usage()
19 return
20 }
21 args_to_executable := args[1..]
22 mut args_before := cmdline.options_before(args_to_executable, ['test'])
23 mut args_after := cmdline.options_after(args_to_executable, ['test'])
24 mut ctx := Context{}
25 ctx.fail_fast = extract_flag_bool('-fail-fast', mut args_after, testing.fail_fast)
26 ctx.verbose = extract_flag_bool('-v', mut args_after, false)
27 ctx.run_only = extract_flag_string_array('-run-only', mut args_after, testing.test_only_fn)
28 os.setenv('VTEST_ONLY_FN', ctx.run_only.join(','), true)
29 if args_after == ['v'] {
30 eprintln('`v test v` has been deprecated.')
31 eprintln('Use `v test-all` instead.')
32 exit(1)
33 }
34 backend_pos := args_before.index('-b')
35 backend := if backend_pos == -1 { '.c' } else { args_before[backend_pos + 1] }
36
37 mut ts := testing.new_test_session(args_before.join(' '), true)
38 ts.exec_mode = .compile_and_run
39 ts.fail_fast = ctx.fail_fast
40 for targ in args_after {
41 if os.is_dir(targ) {
42 // Fetch all tests from the directory
43 files, skip_files := ctx.should_test_dir(targ.trim_right(os.path_separator), backend)
44 ts.files << files
45 ts.skip_files << skip_files
46 continue
47 } else if os.exists(targ) {
48 match ctx.should_test(targ, backend) {
49 .test {
50 ts.files << targ
51 continue
52 }
53 .skip {
54 if ctx.run_only.len > 0 {
55 continue
56 }
57 ts.files << targ
58 ts.skip_files << os.abs_path(targ)
59 continue
60 }
61 .ignore {}
62 }
63 } else {
64 eprintln('\nUnrecognized test file `${targ}`.\n `v test` can only be used with folders and/or _test.v files.\n')
65 show_usage()
66 exit(1)
67 }
68 }
69 ts.session_start('Testing...')
70 ts.test()
71 ts.session_stop('all V _test.v files')
72 if ts.failed_cmds.len > 0 {
73 exit(1)
74 }
75}
76
77fn show_usage() {
78 println('Usage:')
79 println(' A)')
80 println(' v test folder/ : run all v tests in the given folder.')
81 println(' v -stats test folder/ : the same, but print more stats.')
82 println(' B)')
83 println(' v test file_test.v : run test functions in a given test file.')
84 println(' v -stats test file_test.v : as above, but with more stats.')
85 println(' Note: you can also give many and mixed folder/ file_test.v arguments after `v test` .')
86 println('')
87}
88
89pub fn (mut ctx Context) should_test_dir(path string, backend string) ([]string, []string) { // return is (files, skip_files)
90 mut files := os.ls(path) or { return []string{}, []string{} }
91 mut local_path_separator := os.path_separator
92 if path.ends_with(os.path_separator) {
93 local_path_separator = ''
94 }
95 mut res_files := []string{}
96 mut skip_files := []string{}
97 for file in files {
98 p := path + local_path_separator + file
99 if os.is_dir(p) && !os.is_link(p) {
100 if file == 'testdata' {
101 continue
102 }
103 ret_files, ret_skip_files := ctx.should_test_dir(p, backend)
104 res_files << ret_files
105 skip_files << ret_skip_files
106 } else if os.exists(p) {
107 match ctx.should_test(p, backend) {
108 .test {
109 res_files << p
110 }
111 .skip {
112 if ctx.run_only.len > 0 {
113 continue
114 }
115 res_files << p
116 skip_files << os.abs_path(p)
117 }
118 .ignore {}
119 }
120 }
121 }
122 return res_files, skip_files
123}
124
125enum ShouldTestStatus {
126 test // do test, print OK or FAIL, depending on if it passes
127 skip // print SKIP for the test
128 ignore // just ignore the file, so it will not be printed at all in the list of tests
129}
130
131fn (mut ctx Context) should_test(path string, backend string) ShouldTestStatus {
132 if path.ends_with('_test.v') {
133 return ctx.should_test_when_it_contains_matching_fns(path, backend)
134 }
135 if path.ends_with('_test.c.v') {
136 return ctx.should_test_when_it_contains_matching_fns(path, backend)
137 }
138 if path.ends_with('_test.js.v') {
139 if testing.is_node_present {
140 return ctx.should_test_when_it_contains_matching_fns(path, backend)
141 }
142 return .skip
143 }
144 // `_test.vv2` files are v2-only integration tests. They are full V programs
145 // (with `main()`) that exercise v2-specific syntax; the test runner routes
146 // them through the v2 binary instead of v1. Honor `-run-only` so targeted
147 // runs do not pull in unrelated vv2 tests.
148 if path.ends_with('_test.vv2') {
149 return ctx.should_test_when_it_contains_matching_fns(path, backend)
150 }
151 if path.ends_with('.v') && path.count('.') == 2 {
152 if !path.all_before_last('.v').all_before_last('.').ends_with('_test') {
153 return .ignore
154 }
155 backend_arg := path.all_before_last('.v').all_after_last('.')
156 arch := pref.arch_from_string(backend_arg) or { pref.Arch._auto }
157 if arch == pref.get_host_arch() {
158 return ctx.should_test_when_it_contains_matching_fns(path, backend)
159 } else if arch == ._auto {
160 if backend_arg == 'c' { // .c.v
161 return if backend == 'c' {
162 ctx.should_test_when_it_contains_matching_fns(path, backend)
163 } else {
164 ShouldTestStatus.skip
165 }
166 }
167 if backend_arg == 'js' {
168 return if backend == 'js' {
169 ctx.should_test_when_it_contains_matching_fns(path, backend)
170 } else {
171 ShouldTestStatus.skip
172 }
173 }
174 } else {
175 return .skip
176 }
177 }
178 return .ignore
179}
180
181fn (mut ctx Context) should_test_when_it_contains_matching_fns(path string, _backend string) ShouldTestStatus {
182 if ctx.run_only.len == 0 {
183 // no filters set, so just compile and test
184 return .test
185 }
186 lines := os.read_lines(path) or { return .ignore }
187 for line in lines {
188 if line.match_glob('fn test_*') || line.match_glob('pub fn test_*') {
189 tname := line.replace_each(['pub fn ', '', 'fn ', '']).all_before('(')
190 for pattern in ctx.run_only {
191 mut pat := pattern.clone()
192 if pat.contains('.') {
193 pat = pat.all_after_last('.')
194 }
195 if tname.match_glob(pat) {
196 if ctx.verbose {
197 println('> compiling path: ${path}, since test fn `${tname}` matches glob pattern `${pat}`')
198 }
199 return .test
200 }
201 }
202 }
203 }
204 return .ignore
205}
206
207fn extract_flag_bool(flag_name string, mut after []string, flag_default bool) bool {
208 mut res := flag_default
209 orig_after :=
210 after.clone() // workaround for after.filter() codegen bug, when `mut after []string`
211 matches_after := orig_after.filter(it != flag_name)
212 if matches_after.len < after.len {
213 after = matches_after.clone()
214 res = true
215 }
216 return res
217}
218
219fn extract_flag_string_array(flag_name string, mut after []string, flag_default []string) []string {
220 mut res := flag_default.clone()
221 mut found := after.index(flag_name)
222 if found > -1 {
223 if found + 1 < after.len {
224 res = after[found + 1].split_any(',')
225 after.delete(found)
226 }
227 after.delete(found)
228 }
229 return res
230}
231