1 | module main |
2 | |
3 | import os |
4 | import term |
5 | import time |
6 | |
7 | const vexe_path = os.getenv('VEXE') |
8 | |
9 | const vroot = os.dir(vexe_path) |
10 | |
11 | const vexe = os.quoted_path(vexe_path) |
12 | |
13 | const args_string = os.args[1..].join(' ') |
14 | |
15 | const vargs = args_string.all_before('test-all') |
16 | |
17 | const vtest_nocleanup = os.getenv('VTEST_NOCLEANUP').bool() |
18 | |
19 | fn main() { |
20 | mut commands := get_all_commands() |
21 | // summary |
22 | sw := time.new_stopwatch() |
23 | for mut cmd in commands { |
24 | cmd.run() |
25 | } |
26 | spent := sw.elapsed().milliseconds() |
27 | oks := commands.filter(it.ecode == 0) |
28 | fails := commands.filter(it.ecode != 0) |
29 | flush_stdout() |
30 | println('') |
31 | println(term.header_left(term_highlight('Summary of `v test-all`:'), '-')) |
32 | println(term_highlight('Total runtime: ${spent} ms')) |
33 | for ocmd in oks { |
34 | msg := if ocmd.okmsg != '' { ocmd.okmsg } else { ocmd.line } |
35 | println(term.colorize(term.green, '> OK: ${msg} ')) |
36 | } |
37 | for fcmd in fails { |
38 | msg := if fcmd.errmsg != '' { fcmd.errmsg } else { fcmd.line } |
39 | println(term.failed('> Failed:') + ' ${msg}') |
40 | } |
41 | flush_stdout() |
42 | if fails.len > 0 { |
43 | exit(1) |
44 | } |
45 | } |
46 | |
47 | enum RunCommandKind { |
48 | system |
49 | execute |
50 | } |
51 | |
52 | const expect_nothing = '<nothing>' |
53 | |
54 | const starts_with_nothing = '<nothing>' |
55 | |
56 | const ends_with_nothing = '<nothing>' |
57 | |
58 | const contains_nothing = '<nothing>' |
59 | |
60 | struct Command { |
61 | mut: |
62 | line string |
63 | label string // when set, the label will be printed *before* cmd.line is executed |
64 | ecode int |
65 | okmsg string |
66 | errmsg string |
67 | rmfile string |
68 | runcmd RunCommandKind = .system |
69 | expect string = expect_nothing |
70 | starts_with string = starts_with_nothing |
71 | ends_with string = ends_with_nothing |
72 | contains string = contains_nothing |
73 | output string |
74 | } |
75 | |
76 | fn get_all_commands() []Command { |
77 | mut res := []Command{} |
78 | res << Command{ |
79 | line: '${vexe} examples/hello_world.v' |
80 | okmsg: 'V can compile hello world.' |
81 | rmfile: 'examples/hello_world' |
82 | } |
83 | res << Command{ |
84 | line: '${vexe} -o hhww.c examples/hello_world.v' |
85 | okmsg: 'V can output a .c file, without compiling further.' |
86 | rmfile: 'hhww.c' |
87 | } |
88 | res << Command{ |
89 | line: '${vexe} -skip-unused examples/hello_world.v' |
90 | okmsg: 'V can compile hello world with -skip-unused.' |
91 | rmfile: 'examples/hello_world' |
92 | } |
93 | res << Command{ |
94 | line: '${vexe} -skip-unused -profile - examples/hello_world.v' |
95 | okmsg: 'V can compile hello world with both -skip-unused and -profile .' |
96 | rmfile: 'examples/hello_world' |
97 | } |
98 | res << Command{ |
99 | line: '${vexe} -e "print(84/2)"' |
100 | okmsg: 'V can run code given after `-e`' |
101 | runcmd: .execute |
102 | expect: '42' |
103 | } |
104 | res << Command{ |
105 | line: '${vexe} -e "import os; import math; print(os.args#[1..]) print(math.sin(math.pi/2).str())" arg1 arg2' |
106 | okmsg: 'V can run code with `-e`, that use semicolons and several imports, and that accepts CLI parameters.' |
107 | runcmd: .execute |
108 | expect: "['arg1', 'arg2']1.0" |
109 | } |
110 | $if linux || macos { |
111 | res << Command{ |
112 | line: '${vexe} run examples/hello_world.v' |
113 | okmsg: 'V can run hello world.' |
114 | runcmd: .execute |
115 | expect: 'Hello, World!\n' |
116 | } |
117 | if os.getenv('V_CI_MUSL').len == 0 { |
118 | for compiler_name in ['clang', 'gcc'] { |
119 | if _ := os.find_abs_path_of_executable(compiler_name) { |
120 | res << Command{ |
121 | line: '${vexe} -cc ${compiler_name} -gc boehm run examples/hello_world.v' |
122 | okmsg: '`v -cc ${compiler_name} -gc boehm run examples/hello_world.v` works' |
123 | runcmd: .execute |
124 | expect: 'Hello, World!\n' |
125 | } |
126 | } |
127 | } |
128 | } |
129 | res << Command{ |
130 | line: '${vexe} interpret examples/hello_world.v' |
131 | okmsg: 'V can interpret hello world.' |
132 | runcmd: .execute |
133 | expect: 'Hello, World!\n' |
134 | } |
135 | res << Command{ |
136 | line: '${vexe} interpret examples/hanoi.v' |
137 | okmsg: 'V can interpret hanoi.v' |
138 | runcmd: .execute |
139 | starts_with: 'Disc 1 from A to C...\n' |
140 | ends_with: 'Disc 1 from A to C...\n' |
141 | contains: 'Disc 7 from A to C...\n' |
142 | } |
143 | res << Command{ |
144 | line: '${vexe} -o - examples/hello_world.v | grep "#define V_COMMIT_HASH" > /dev/null' |
145 | okmsg: 'V prints the generated source code to stdout with `-o -` .' |
146 | } |
147 | res << Command{ |
148 | line: '${vexe} run examples/v_script.vsh > /dev/null' |
149 | okmsg: 'V can run the .VSH script file examples/v_script.vsh' |
150 | } |
151 | // Note: -experimental is used here, just to suppress the warningss, |
152 | // that are otherwise printed by the native backend, |
153 | // until globals and hash statements *are implemented*: |
154 | $if linux { |
155 | res << Command{ |
156 | line: '${vexe} -experimental -b native run examples/native/hello_world.v > /dev/null' |
157 | okmsg: 'V compiles and runs examples/native/hello_world.v on the native backend for linux' |
158 | } |
159 | } |
160 | // only compilation: |
161 | res << Command{ |
162 | line: '${vexe} -os linux -experimental -b native -o hw.linux examples/hello_world.v' |
163 | okmsg: 'V compiles hello_world.v on the native backend for linux' |
164 | rmfile: 'hw.linux' |
165 | } |
166 | res << Command{ |
167 | line: '${vexe} -os macos -experimental -b native -o hw.macos examples/hello_world.v' |
168 | okmsg: 'V compiles hello_world.v on the native backend for macos' |
169 | rmfile: 'hw.macos' |
170 | } |
171 | res << Command{ |
172 | line: '${vexe} -os windows -experimental -b native -o hw.exe examples/hello_world.v' |
173 | okmsg: 'V compiles hello_world.v on the native backend for windows' |
174 | rmfile: 'hw.exe' |
175 | } |
176 | // |
177 | res << Command{ |
178 | line: '${vexe} -b js -o hw.js examples/hello_world.v' |
179 | okmsg: 'V compiles hello_world.v on the JS backend' |
180 | rmfile: 'hw.js' |
181 | } |
182 | res << Command{ |
183 | line: '${vexe} -skip-unused -b js -o hw_skip_unused.js examples/hello_world.v' |
184 | okmsg: 'V compiles hello_world.v on the JS backend, with -skip-unused' |
185 | rmfile: 'hw_skip_unused.js' |
186 | } |
187 | res << Command{ |
188 | line: '${vexe} -skip-unused examples/2048' |
189 | okmsg: 'V can compile 2048 with -skip-unused.' |
190 | rmfile: 'examples/2048/2048' |
191 | } |
192 | if _ := os.find_abs_path_of_executable('emcc') { |
193 | res << Command{ |
194 | line: '${vexe} -os wasm32_emscripten examples/2048' |
195 | okmsg: 'V can compile 2048 with -os wasm32_emscripten, using emcc.' |
196 | rmfile: 'examples/2048/2048' |
197 | } |
198 | } else { |
199 | println('> emcc not found, skipping `v -os wasm32_emscripten examples/2048`.') |
200 | } |
201 | res << Command{ |
202 | line: '${vexe} -skip-unused -live examples/hot_reload/bounce.v' |
203 | okmsg: 'V can compile the hot code reloading bounce.v example with both: -skip-unused -live' |
204 | rmfile: 'examples/hot_reload/bounce' |
205 | } |
206 | } |
207 | res << Command{ |
208 | line: '${vexe} -o vtmp cmd/v' |
209 | okmsg: 'V can compile itself.' |
210 | rmfile: 'vtmp' |
211 | } |
212 | res << Command{ |
213 | line: '${vexe} -o vtmp_werror -cstrict cmd/v' |
214 | okmsg: 'V can compile itself with -cstrict.' |
215 | rmfile: 'vtmp_werror' |
216 | } |
217 | res << Command{ |
218 | line: '${vexe} -o vtmp_autofree -autofree cmd/v' |
219 | okmsg: 'V can compile itself with -autofree.' |
220 | rmfile: 'vtmp_autofree' |
221 | } |
222 | res << Command{ |
223 | line: '${vexe} -o vtmp_prealloc -prealloc cmd/v' |
224 | okmsg: 'V can compile itself with -prealloc.' |
225 | rmfile: 'vtmp_prealloc' |
226 | } |
227 | res << Command{ |
228 | line: '${vexe} -o vtmp_unused -skip-unused cmd/v' |
229 | okmsg: 'V can compile itself with -skip-unused.' |
230 | rmfile: 'vtmp_unused' |
231 | } |
232 | $if linux { |
233 | res << Command{ |
234 | line: '${vexe} -cc gcc -keepc -freestanding -o bel vlib/os/bare/bare_example_linux.v' |
235 | okmsg: 'V can compile with -freestanding on Linux with GCC.' |
236 | rmfile: 'bel' |
237 | } |
238 | |
239 | res << Command{ |
240 | line: '${vexe} -cc gcc -keepc -freestanding -o str_array vlib/strconv/bare/str_array_example.v' |
241 | okmsg: 'V can compile & allocate memory with -freestanding on Linux with GCC.' |
242 | rmfile: 'str_array' |
243 | } |
244 | } |
245 | res << Command{ |
246 | line: '${vexe} ${vargs} -progress test-cleancode' |
247 | okmsg: 'All .v files are invariant when processed with `v fmt`' |
248 | } |
249 | res << Command{ |
250 | line: '${vexe} ${vargs} -progress test-fmt' |
251 | okmsg: 'All .v files can be processed with `v fmt`. Note: the result may not always be compilable, but `v fmt` should not crash.' |
252 | } |
253 | res << Command{ |
254 | line: '${vexe} ${vargs} -progress test-self' |
255 | okmsg: 'There are no _test.v file regressions.' |
256 | } |
257 | res << Command{ |
258 | line: '${vexe} ${vargs} -progress -W build-tools' |
259 | okmsg: 'All tools can be compiled.' |
260 | } |
261 | res << Command{ |
262 | line: '${vexe} ${vargs} -progress -W build-examples' |
263 | okmsg: 'All examples can be compiled.' |
264 | } |
265 | res << Command{ |
266 | line: '${vexe} check-md -hide-warnings .' |
267 | label: 'Check ```v ``` code examples and formatting of .MD files...' |
268 | okmsg: 'All .md files look good.' |
269 | } |
270 | res << Command{ |
271 | line: '${vexe} install nedpals.args' |
272 | okmsg: '`v install` works.' |
273 | } |
274 | res << Command{ |
275 | line: '${vexe} -usecache -cg examples/hello_world.v' |
276 | okmsg: '`v -usecache -cg` works.' |
277 | rmfile: 'examples/hello_world' |
278 | } |
279 | // Note: test that a program that depends on thirdparty libraries with its |
280 | // own #flags (tetris depends on gg, which uses sokol) can be compiled |
281 | // with -usecache: |
282 | res << Command{ |
283 | line: '${vexe} -usecache examples/tetris/tetris.v' |
284 | okmsg: '`v -usecache` works.' |
285 | rmfile: 'examples/tetris/tetris' |
286 | } |
287 | $if macos || linux { |
288 | res << Command{ |
289 | line: '${vexe} -o v.c cmd/v && cc -Werror v.c -lpthread -lm && rm -rf a.out' |
290 | label: 'v.c should be buildable with no warnings...' |
291 | okmsg: 'v.c can be compiled without warnings. This is good :)' |
292 | rmfile: 'v.c' |
293 | } |
294 | } |
295 | $if linux { |
296 | res << Command{ |
297 | line: '${vexe} vlib/v/tests/bench/bench_stbi_load.v && prlimit -v10485760 vlib/v/tests/bench/bench_stbi_load' |
298 | okmsg: 'STBI load does not leak with GC on, when loading images multiple times (use < 10MB)' |
299 | runcmd: .execute |
300 | contains: 'logo.png 1000 times.' |
301 | rmfile: 'vlib/v/tests/bench/bench_stbi_load' |
302 | } |
303 | } |
304 | $if !windows { |
305 | res << Command{ |
306 | line: '${vexe} -raw-vsh-tmp-prefix tmp vlib/v/tests/script_with_no_extension' |
307 | okmsg: 'V can crun a script, that lacks a .vsh extension' |
308 | runcmd: .execute |
309 | expect: 'Test\n' |
310 | rmfile: 'vlib/v/tests/tmp.script_with_no_extension' |
311 | } |
312 | |
313 | res << Command{ |
314 | line: '${vexe} -raw-vsh-tmp-prefix tmp run vlib/v/tests/script_with_no_extension' |
315 | okmsg: 'V can run a script, that lacks a .vsh extension' |
316 | runcmd: .execute |
317 | expect: 'Test\n' |
318 | } |
319 | } |
320 | return res |
321 | } |
322 | |
323 | fn (mut cmd Command) run() { |
324 | // Changing the current directory is needed for some of the compiler tests, |
325 | // vlib/v/tests/local_test.v and vlib/v/tests/repl/repl_test.v |
326 | os.chdir(vroot) or {} |
327 | if cmd.label != '' { |
328 | println(term.header_left(cmd.label, '*')) |
329 | } |
330 | sw := time.new_stopwatch() |
331 | if cmd.runcmd == .system { |
332 | cmd.ecode = os.system(cmd.line) |
333 | cmd.output = '' |
334 | } |
335 | if cmd.runcmd == .execute { |
336 | res := os.execute(cmd.line) |
337 | cmd.ecode = res.exit_code |
338 | cmd.output = res.output |
339 | } |
340 | spent := sw.elapsed().milliseconds() |
341 | // |
342 | mut is_failed := false |
343 | mut is_failed_expected := false |
344 | mut is_failed_starts_with := false |
345 | mut is_failed_ends_with := false |
346 | mut is_failed_contains := false |
347 | if cmd.ecode != 0 { |
348 | is_failed = true |
349 | } |
350 | if cmd.expect != expect_nothing { |
351 | if cmd.output != cmd.expect { |
352 | is_failed = true |
353 | is_failed_expected = true |
354 | } |
355 | } |
356 | if cmd.starts_with != starts_with_nothing { |
357 | if !cmd.output.starts_with(cmd.starts_with) { |
358 | is_failed = true |
359 | is_failed_starts_with = true |
360 | } |
361 | } |
362 | if cmd.ends_with != ends_with_nothing { |
363 | if !cmd.output.ends_with(cmd.ends_with) { |
364 | is_failed = true |
365 | is_failed_ends_with = true |
366 | } |
367 | } |
368 | if cmd.contains != contains_nothing { |
369 | if !cmd.output.contains(cmd.contains) { |
370 | is_failed = true |
371 | is_failed_contains = true |
372 | } |
373 | } |
374 | // |
375 | run_label := if is_failed { term.failed('FAILED') } else { term_highlight('OK') } |
376 | println('> Running: "${cmd.line}" took: ${spent} ms ... ${run_label}') |
377 | // |
378 | if is_failed && is_failed_expected { |
379 | eprintln('> expected:\n${cmd.expect}') |
380 | eprintln('> output:\n${cmd.output}') |
381 | } |
382 | if is_failed && is_failed_starts_with { |
383 | eprintln('> expected to start with:\n${cmd.starts_with}') |
384 | eprintln('> output:\n${cmd.output#[..cmd.starts_with.len]}') |
385 | } |
386 | if is_failed && is_failed_ends_with { |
387 | eprintln('> expected to end with:\n${cmd.ends_with}') |
388 | eprintln('> output:\n${cmd.output#[-cmd.starts_with.len..]}') |
389 | } |
390 | if is_failed && is_failed_contains { |
391 | eprintln('> expected to contain:\n${cmd.contains}') |
392 | eprintln('> output:\n${cmd.output}') |
393 | } |
394 | if vtest_nocleanup { |
395 | return |
396 | } |
397 | if cmd.rmfile != '' { |
398 | mut file_existed := rm_existing(cmd.rmfile) |
399 | if os.user_os() == 'windows' { |
400 | file_existed = file_existed || rm_existing(cmd.rmfile + '.exe') |
401 | } |
402 | if !file_existed { |
403 | eprintln('Expected file did not exist: ${cmd.rmfile}') |
404 | cmd.ecode = 999 |
405 | } |
406 | } |
407 | } |
408 | |
409 | // try to remove a file, return if it existed before the removal attempt |
410 | fn rm_existing(path string) bool { |
411 | existed := os.exists(path) |
412 | os.rm(path) or {} |
413 | return existed |
414 | } |
415 | |
416 | fn term_highlight(s string) string { |
417 | return term.colorize(term.yellow, term.colorize(term.bold, s)) |
418 | } |