v / cmd / tools / vtest-all.v
517 lines · 491 sloc · 15.39 KB · dd1ad2b6abfa62a9695e4e04d4827bff137ff94e
Raw
1module main
2
3import os
4import term
5import time
6
7type FnCheck = fn () !
8
9type OneOrManyStrings = string | []string
10
11const vexe_path = os.getenv('VEXE')
12
13const vroot = os.dir(vexe_path)
14
15const vexe = os.quoted_path(vexe_path)
16
17const args_string = os.args[1..].join(' ')
18
19const vargs = args_string.all_before('test-all')
20
21const vtest_nocleanup = os.getenv('VTEST_NOCLEANUP').bool()
22
23const clang_path = os.find_abs_path_of_executable('clang') or { '' }
24
25fn main() {
26 unbuffer_stdout()
27 mut commands := get_all_commands()
28 // summary
29 sw := time.new_stopwatch()
30 for mut cmd in commands {
31 cmd.run()?
32 }
33 spent := sw.elapsed().milliseconds()
34 oks := commands.filter(it.ecode == 0)
35 fails := commands.filter(it.ecode != 0)
36 println('')
37 println(term.header_left(term_highlight('Summary of `v test-all`:'), '-'))
38 for ocmd in oks {
39 msg := if ocmd.okmsg != '' { ocmd.okmsg } else { ocmd.line }
40 println(term.colorize(term.green, '> OK: ${msg} '))
41 }
42 for fcmd in fails {
43 msg := if fcmd.errmsg != '' { fcmd.errmsg } else { fcmd.line }
44 println(term.failed('> Failed:') + ' ${msg}')
45 }
46 println(term_highlight('Total runtime: ${spent} ms'))
47 if fails.len > 0 {
48 exit(1)
49 }
50}
51
52enum RunCommandKind {
53 system
54 execute
55}
56
57struct Command {
58mut:
59 line string
60 label string // when set, the label will be printed *before* cmd.line is executed
61 ecode int
62 okmsg string
63 errmsg string
64 rmfile ?OneOrManyStrings
65 runcmd RunCommandKind = .system
66 expect ?string
67 starts_with ?string
68 ends_with ?string
69 contains ?string
70 output string
71 before_cb FnCheck = unsafe { nil }
72 after_cb FnCheck = unsafe { nil }
73}
74
75fn get_all_commands() []Command {
76 mut res := []Command{}
77 res << Command{
78 line: '${vexe} examples/hello_world.v'
79 okmsg: 'V can compile hello world.'
80 rmfile: 'examples/hello_world'
81 }
82 res << Command{
83 line: '${vexe} -W -Wimpure-v examples/hello_world.v'
84 okmsg: 'V can compile hello world with the stricter `-W -Wimpure-v` mode .'
85 rmfile: 'examples/hello_world'
86 }
87 $if linux {
88 l2w_crosscc := os.find_abs_path_of_executable('x86_64-w64-mingw32-gcc-win32') or { '' }
89 if l2w_crosscc != '' {
90 res << Command{
91 line: '${vexe} -os windows examples/hello_world.v'
92 okmsg: 'V cross compiles hello_world.v on linux, to a windows .exe file'
93 rmfile: 'examples/hello_world.exe'
94 }
95 } else {
96 eprintln('Testing cross compilation from linux to windows needs x86_64-w64-mingw32-gcc-win32. Skipping hello_world.exe test.')
97 }
98 }
99 res << Command{
100 line: '${vexe} -o hhww.c examples/hello_world.v'
101 okmsg: 'V can output a .c file, without compiling further.'
102 rmfile: 'hhww.c'
103 }
104 res << Command{
105 line: '${vexe} -silent test vlib/builtin'
106 okmsg: 'V can test vlib/builtin'
107 }
108 res << Command{
109 line: '${vexe} -profile - examples/hello_world.v'
110 okmsg: 'V can compile hello world with -profile .'
111 rmfile: 'examples/hello_world'
112 }
113 res << Command{
114 line: '${vexe} -e "print(84/2)"'
115 okmsg: 'V can run code given after `-e`'
116 runcmd: .execute
117 expect: '42'
118 }
119 res << Command{
120 line: '${vexe} -e "import os; import math; print(os.args#[1..]) print(math.sin(math.pi/2).str())" arg1 arg2'
121 okmsg: 'V can run code with `-e`, that use semicolons and several imports, and that accepts CLI parameters.'
122 runcmd: .execute
123 expect: "['arg1', 'arg2']1.0"
124 }
125 res << Command{
126 line: '${vexe} -o calling_c.exe run examples/call_c_from_v/main.c.v'
127 okmsg: 'V can run main.c.v files'
128 runcmd: .execute
129 contains: 'V can call C functions like `puts` too.'
130 }
131 $if linux || macos {
132 res << Command{
133 line: '${vexe} run examples/hello_world.v'
134 okmsg: 'V can run hello world.'
135 runcmd: .execute
136 expect: 'Hello, World!\n'
137 }
138 if clang_path != '' {
139 res << Command{
140 line: '${vexe} -os freebsd -gc none examples/hello_world.v'
141 okmsg: 'V cross compiles hello_world.v, to a FreeBSD executable'
142 rmfile: 'examples/hello_world'
143 after_cb: fn () ! {
144 for file in ['examples/hello_world',
145 os.join_path(os.vmodules_dir(), 'freebsdroot/usr/include/stdio.h')] {
146 if !os.exists(file) {
147 return error('>> file ${file} does not exist')
148 }
149 }
150 }
151 }
152 } else {
153 eprintln('Testing cross compilation to FreeBSD, needs clang.')
154 }
155 if os.getenv('V_CI_MUSL').len == 0 {
156 for compiler_name in ['clang', 'gcc'] {
157 if _ := os.find_abs_path_of_executable(compiler_name) {
158 res << Command{
159 line: '${vexe} -cc ${compiler_name} -gc boehm run examples/hello_world.v'
160 okmsg: '`v -cc ${compiler_name} -gc boehm run examples/hello_world.v` works'
161 runcmd: .execute
162 expect: 'Hello, World!\n'
163 }
164 }
165 }
166 }
167 res << Command{
168 line: '${vexe} -o - examples/hello_world.v | grep "#define V_COMMIT_HASH" > /dev/null'
169 okmsg: 'V prints the generated source code to stdout with `-o -` .'
170 }
171 res << Command{
172 line: '${vexe} run examples/v_script.vsh > /dev/null'
173 okmsg: 'V can run the .VSH script file examples/v_script.vsh'
174 }
175 res << Command{
176 line: '${vexe} -b js -o hw.js examples/hello_world.v'
177 okmsg: 'V compiles hello_world.v on the JS backend'
178 rmfile: 'hw.js'
179 }
180 res << Command{
181 line: '${vexe} examples/2048'
182 okmsg: 'V can compile 2048.'
183 rmfile: 'examples/2048/2048'
184 }
185 if _ := os.find_abs_path_of_executable('emcc') {
186 res << Command{
187 line: '${vexe} -os wasm32_emscripten examples/2048'
188 okmsg: 'V can compile 2048 with -os wasm32_emscripten, using emcc.'
189 rmfile: 'examples/2048/2048'
190 }
191 } else {
192 println('> emcc not found, skipping `v -os wasm32_emscripten examples/2048`.')
193 }
194 res << Command{
195 line: '${vexe} -live examples/hot_reload/bounce.v'
196 okmsg: 'V can compile the hot code reloading bounce.v example with -live'
197 rmfile: 'examples/hot_reload/bounce'
198 }
199 }
200 res << Command{
201 line: '${vexe} -o vtmp cmd/v'
202 okmsg: 'V can compile itself.'
203 rmfile: 'vtmp'
204 }
205 res << Command{
206 line: '${vexe} -o vtmp_werror -cstrict cmd/v'
207 okmsg: 'V can compile itself with -cstrict.'
208 rmfile: 'vtmp_werror'
209 }
210 res << Command{
211 line: '${vexe} -o vtmp_autofree -autofree cmd/v'
212 okmsg: 'V can compile itself with -autofree.'
213 rmfile: 'vtmp_autofree'
214 }
215 res << Command{
216 line: '${vexe} -o vtmp_prealloc -prealloc cmd/v'
217 okmsg: 'V can compile itself with -prealloc.'
218 rmfile: 'vtmp_prealloc'
219 }
220 res << Command{
221 line: '${vexe} -o vtmp_ntransformer -new-transformer cmd/v'
222 okmsg: 'V can compile itself with -new-transformer.'
223 rmfile: 'vtmp_ntransformer'
224 }
225 $if linux {
226 res << Command{
227 line: '${vexe} -o swait vlib/v/tests/reliability/semaphore_wait.v'
228 okmsg: 'V can compile semaphore_wait.v on Linux with GC on.'
229 rmfile: 'swait'
230 }
231 res << Command{
232 line: '${vexe} -cc gcc -keepc -freestanding -o bel vlib/os/bare/bare_example_linux.v'
233 okmsg: 'V can compile with -freestanding on Linux with GCC.'
234 rmfile: 'bel'
235 }
236
237 res << Command{
238 line: '${vexe} -cc gcc -keepc -freestanding -o str_array vlib/strconv/bare/str_array_example.v'
239 okmsg: 'V can compile & allocate memory with -freestanding on Linux with GCC.'
240 rmfile: 'str_array'
241 }
242 res << Command{
243 line: '${vexe} -cc gcc -keepc -freestanding -o time_now vlib/time/bare/time_now_example.v'
244 okmsg: 'V can compile time.now() with -freestanding on Linux with GCC.'
245 rmfile: 'time_now'
246 }
247 }
248 ////////////////////////////////////////////////////////////////////////
249 // Test compilation of a shared library (.so, .dll. .dylib) with -shared:
250 common_shared_flags := '-shared -skip-unused -d no_backtrace -o library examples/dynamic_library_loader/modules/library/library.v'
251 $if macos {
252 res << Command{
253 line: '${vexe} ${common_shared_flags}'
254 okmsg: 'V compiles library.v with -shared on macos'
255 rmfile: 'library.dylib'
256 }
257 }
258 $if linux {
259 res << Command{
260 line: '${vexe} ${common_shared_flags}'
261 okmsg: 'V compiles library.v with -shared on linux'
262 rmfile: 'library.so'
263 }
264 }
265 // Test cross compilation to windows with -shared:
266 $if linux {
267 l2w_crosscc := os.find_abs_path_of_executable('x86_64-w64-mingw32-gcc-win32') or { '' }
268 if l2w_crosscc != '' {
269 res << Command{
270 line: '${vexe} -os windows ${common_shared_flags}'
271 okmsg: 'V cross compiles library.v with -shared on linux, to a windows library.dll file'
272 rmfile: 'library.dll'
273 }
274 } else {
275 eprintln('Testing cross compilation from linux to windows needs x86_64-w64-mingw32-gcc-win32. Skipping library.dll test.')
276 }
277 }
278 ////////////////////////////////////////////////////////////////////////
279 res << Command{
280 line: '${vexe} ${vargs} -progress test-cleancode'
281 okmsg: 'All .v files are invariant when processed with `v fmt`'
282 }
283 res << Command{
284 line: '${vexe} ${vargs} -progress test-fmt'
285 okmsg: 'All .v files can be processed with `v fmt`. Note: the result may not always be compilable, but `v fmt` should not crash.'
286 }
287 res << Command{
288 line: '${vexe} ${vargs} -progress test-self'
289 okmsg: 'There are no _test.v file regressions.'
290 }
291 res << Command{
292 line: '${vexe} ${vargs} -progress -N -W build-tools'
293 okmsg: 'All tools can be compiled.'
294 }
295 res << Command{
296 line: '${vexe} ${vargs} -progress -N -W build-examples'
297 okmsg: 'All examples can be compiled.'
298 }
299 res << Command{
300 line: '${vexe} check-md -hide-warnings .'
301 label: 'Check ```v ``` code examples and formatting of .MD files...'
302 okmsg: 'All .md files look good.'
303 }
304 res << Command{
305 line: '${vexe} install nedpals.args'
306 okmsg: '`v install` works.'
307 }
308 res << Command{
309 okmsg: 'Running net.http with -d trace_http_request works.'
310 line: '${vexe} -d trace_http_request -e \'import net.http; x := http.fetch(url: "https://vpm.url4e.com/some/unknown/url")!; println(x.status_code)\''
311 runcmd: .execute
312 starts_with: '> GET /some/unknown/url HTTP/1.1'
313 contains: 'User-Agent: v.http'
314 ends_with: '404\n'
315 }
316 res << Command{
317 okmsg: 'Running net.http with -d trace_http_response works.'
318 line: '${vexe} -d trace_http_response -e \'import net.http; x := http.fetch(url: "https://vpm.url4e.com/some/unknown/url")!; println(x.status_code)\''
319 runcmd: .execute
320 starts_with: '< HTTP/1.1 404 Not Found'
321 contains: 'Server: nginx'
322 ends_with: '404\n'
323 }
324 res << Command{
325 line: '${vexe} -usecache -cg examples/hello_world.v'
326 okmsg: '`v -usecache -cg` works.'
327 rmfile: 'examples/hello_world'
328 }
329 // Note: test that a program that depends on thirdparty libraries with its
330 // own #flags (tetris depends on gg, which uses sokol) can be compiled
331 // with -usecache:
332 res << Command{
333 line: '${vexe} -usecache examples/tetris/tetris.v'
334 okmsg: '`v -usecache` works.'
335 rmfile: 'examples/tetris/tetris'
336 }
337 $if macos || linux {
338 res << Command{
339 line: '${vexe} -o v.c cmd/v && cc -Werror -std=c99 v.c -lpthread -lm && rm -rf a.out'
340 label: 'v.c should be buildable with no warnings...'
341 okmsg: 'v.c can be compiled without warnings. This is good :)'
342 rmfile: 'v.c'
343 }
344 }
345 $if linux || macos {
346 res << Command{
347 line: '${vexe} -gc none -no-retry-compilation -cc tcc -d use_openssl examples/veb/todo/main.v'
348 okmsg: 'A simple veb app, compiles with `-gc none -no-retry-compilation -cc tcc -d use_openssl` on macos and linux'
349 rmfile: 'examples/veb/todo/main'
350 }
351 res << Command{
352 line: '${vexe} -d trace_before_request examples/veb/veb_example.v'
353 okmsg: 'examples/veb/veb_example.v compiles with `-d trace_before_request` on macos and linux'
354 rmfile: 'examples/veb/veb_example'
355 }
356 }
357 $if linux {
358 res << Command{
359 line: '${vexe} vlib/v/tests/bench/bench_stbi_load.v && prlimit -v10485760 vlib/v/tests/bench/bench_stbi_load'
360 okmsg: 'STBI load does not leak with GC on, when loading images multiple times (use < 10MB)'
361 runcmd: .execute
362 contains: 'logo.png 1000 times.'
363 rmfile: 'vlib/v/tests/bench/bench_stbi_load'
364 }
365 }
366 $if !windows {
367 res << Command{
368 line: '${vexe} -raw-vsh-tmp-prefix tmp vlib/v/tests/script_with_no_extension'
369 okmsg: 'V can crun a script, that lacks a .vsh extension'
370 runcmd: .execute
371 expect: 'Test\n'
372 rmfile: 'vlib/v/tests/tmp.script_with_no_extension'
373 }
374
375 res << Command{
376 line: '${vexe} -raw-vsh-tmp-prefix tmp run vlib/v/tests/script_with_no_extension'
377 okmsg: 'V can run a script, that lacks a .vsh extension'
378 runcmd: .execute
379 expect: 'Test\n'
380 }
381 }
382 return res
383}
384
385fn (mut cmd Command) run() ? {
386 // Changing the current directory is needed for some of the compiler tests,
387 // vlib/v/tests/local_test.v and vlib/v/tests/repl/repl_test.v
388 os.chdir(vroot) or {}
389 if cmd.label != '' {
390 println(term.header_left(cmd.label, '*'))
391 }
392 if cmd.before_cb != unsafe { nil } {
393 cmd.before_cb() or {
394 cmd.ecode = -1
395 cmd.errmsg = '> before_cb callback for "${cmd.line}" ${term.failed('FAILED')}\n${err}'
396 println(cmd.errmsg)
397 return
398 }
399 }
400 sw := time.new_stopwatch()
401 if cmd.runcmd == .system {
402 cmd.ecode = os.system(cmd.line)
403 cmd.output = ''
404 }
405 if cmd.runcmd == .execute {
406 res := os.execute(cmd.line)
407 cmd.ecode = res.exit_code
408 cmd.output = res.output
409 }
410 spent := sw.elapsed().milliseconds()
411 if cmd.after_cb != unsafe { nil } {
412 cmd.after_cb() or {
413 cmd.ecode = -1
414 cmd.errmsg = '> after_cb callback for "${cmd.line}" ${term.failed('FAILED')}\n${err}'
415 println(cmd.errmsg)
416 return
417 }
418 }
419
420 mut is_failed := false
421 mut is_failed_expected := false
422 mut is_failed_starts_with := false
423 mut is_failed_ends_with := false
424 mut is_failed_contains := false
425 if cmd.ecode != 0 {
426 is_failed = true
427 }
428 if cmd.expect != none {
429 if cmd.output != cmd.expect {
430 is_failed = true
431 is_failed_expected = true
432 }
433 }
434 if cmd.starts_with != none {
435 if !cmd.output.starts_with(cmd.starts_with) {
436 is_failed = true
437 is_failed_starts_with = true
438 }
439 }
440 if cmd.ends_with != none {
441 if !cmd.output.ends_with(cmd.ends_with) {
442 is_failed = true
443 is_failed_ends_with = true
444 }
445 }
446 if cmd.contains != none {
447 if !cmd.output.contains(cmd.contains) {
448 is_failed = true
449 is_failed_contains = true
450 }
451 }
452
453 run_label := if is_failed { term.failed('FAILED') } else { term_highlight('OK') }
454 println('> Running: "${cmd.line}" took: ${spent} ms ... ${run_label}')
455
456 if is_failed && is_failed_expected {
457 eprintln('> expected:\n${cmd.expect}')
458 eprintln('> output:\n${cmd.output}')
459 }
460 if is_failed && is_failed_starts_with {
461 eprintln('> expected to start with:\n${cmd.starts_with?}')
462 eprintln('> output:\n${cmd.output#[..cmd.starts_with?.len]}')
463 }
464 if is_failed && is_failed_ends_with {
465 eprintln('> expected to end with:\n${cmd.ends_with?}')
466 eprintln('> output:\n${cmd.output#[-cmd.starts_with?.len..]}')
467 }
468 if is_failed && is_failed_contains {
469 eprintln('> expected to contain:\n${cmd.contains?}')
470 eprintln('> output:\n${cmd.output}')
471 }
472 if vtest_nocleanup {
473 return
474 }
475 if cmd.rmfile != none {
476 mut file_existed := rm_existing(cmd.rmfile)
477 if !file_existed {
478 eprintln('Expected file did not exist: ${cmd.rmfile}')
479 cmd.ecode = 999
480 }
481 }
482}
483
484fn rm_existing(paths OneOrManyStrings) bool {
485 match paths {
486 string {
487 return rm_existing_file(paths)
488 }
489 []string {
490 mut existing := false
491 for path in paths {
492 existing ||= rm_existing_file(path)
493 }
494 return existing
495 }
496 }
497
498 return false
499}
500
501// try to remove a file, return if it existed before the removal attempt
502fn rm_existing_file(path string) bool {
503 mut existed := os.exists(path)
504 os.rm(path) or {}
505
506 win_path := path + '.exe'
507 if os.exists(win_path) {
508 existed = true
509 os.rm(win_path) or {}
510 }
511
512 return existed
513}
514
515fn term_highlight(s string) string {
516 return term.colorize(term.yellow, term.colorize(term.bold, s))
517}
518