v4 / vlib / v3 / tests / test_file_harness_codegen_test.v
554 lines · 475 sloc · 15.62 KB · 288feee4702b35beadb4037d02b96b5c8674156b
Raw
1import os
2
3const vexe = @VEXE
4const tests_dir = os.dir(@FILE)
5const v3_dir = os.dir(tests_dir)
6const vlib_dir = os.dir(v3_dir)
7const v3_src = os.join_path(v3_dir, 'v3.v')
8
9fn build_v3_with(name string, flags string) string {
10 v3_bin := os.join_path(os.temp_dir(), name)
11 build :=
12 os.execute('${vexe} -gc none ${flags} -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13 assert build.exit_code == 0, build.output
14 return v3_bin
15}
16
17fn build_v3() string {
18 return build_v3_with('v3_test_file_harness_test', '')
19}
20
21fn write_source(name string, suffix string, src string) string {
22 src_path := os.join_path(os.temp_dir(), 'v3_${name}${suffix}')
23 os.write_file(src_path, src) or { panic(err) }
24 return src_path
25}
26
27fn write_project(name string, files map[string]string) string {
28 root := os.join_path(os.temp_dir(), 'v3_${name}')
29 os.rmdir_all(root) or {}
30 for rel, src in files {
31 path := os.join_path(root, rel)
32 os.mkdir_all(os.dir(path)) or { panic(err) }
33 os.write_file(path, src) or { panic(err) }
34 }
35 return root
36}
37
38fn gen_c(v3_bin string, name string, suffix string, src string) string {
39 return gen_c_flags(v3_bin, name, suffix, src, '')
40}
41
42fn gen_c_flags(v3_bin string, name string, suffix string, src string, flags string) string {
43 src_path := write_source(name, suffix, src)
44 c_path := os.join_path(os.temp_dir(), 'v3_${name}.c')
45 os.rm(c_path) or {}
46 compile := os.execute('${v3_bin} ${flags} ${src_path} -b c -o ${c_path}')
47 assert compile.exit_code == 0, '${name}: C output failed: ${compile.output}'
48 assert os.exists(c_path), '${name}: missing generated C file ${c_path}'
49 return os.read_file(c_path) or { panic(err) }
50}
51
52fn compile_and_run(v3_bin string, name string, suffix string, src string) os.Result {
53 return compile_and_run_flags(v3_bin, name, suffix, src, '')
54}
55
56fn compile_and_run_flags(v3_bin string, name string, suffix string, src string, flags string) os.Result {
57 src_path := write_source(name, suffix, src)
58 bin_path := os.join_path(os.temp_dir(), 'v3_${name}')
59 compile := os.execute('${v3_bin} ${flags} ${src_path} -b c -o ${bin_path}')
60 assert compile.exit_code == 0, '${name}: compile failed: ${compile.output}'
61 assert !compile.output.contains("undefined reference to `main'"), compile.output
62 return os.execute(bin_path)
63}
64
65fn compile_project_and_run(v3_bin string, name string, files map[string]string) (os.Result, string) {
66 root := write_project(name, files)
67 return compile_project_root_and_run(v3_bin, name, root)
68}
69
70fn compile_project_root_and_run(v3_bin string, name string, root string) (os.Result, string) {
71 bin_path := os.join_path(root, 'out')
72 compile := os.execute('${v3_bin} ${root} -b c -o ${bin_path}')
73 assert compile.exit_code == 0, '${name}: compile failed: ${compile.output}'
74 run := os.execute(bin_path)
75 c_code := os.read_file(bin_path + '.c') or { panic(err) }
76 return run, c_code
77}
78
79fn compile_expect_failure(v3_bin string, name string, suffix string, src string) os.Result {
80 return compile_expect_failure_flags(v3_bin, name, suffix, src, '')
81}
82
83fn compile_expect_failure_flags(v3_bin string, name string, suffix string, src string, flags string) os.Result {
84 src_path := write_source(name, suffix, src)
85 bin_path := os.join_path(os.temp_dir(), 'v3_${name}')
86 c_path := bin_path + '.c'
87 os.rm(c_path) or {}
88 compile := os.execute('${v3_bin} ${flags} ${src_path} -b c -o ${bin_path}')
89 assert compile.exit_code != 0, '${name}: compile unexpectedly succeeded: ${compile.output}'
90 assert !os.exists(c_path), '${name}: generated C despite harness input failure'
91 return compile
92}
93
94fn test_v3_generates_minimal_test_file_harness() {
95 v3_bin := build_v3()
96 order_src := "fn test_one() {
97 println('one')
98}
99
100fn test_two() {
101 println('two')
102}
103"
104 order_c := gen_c(v3_bin, 'harness_order', '_test.c.v', order_src)
105 assert order_c.contains('int main('), order_c
106 one_idx := order_c.index('test_one();') or { -1 }
107 two_idx := order_c.index('test_two();') or { -1 }
108 assert one_idx >= 0, order_c
109 assert two_idx > one_idx, order_c
110 order_run := compile_and_run(v3_bin, 'harness_order_run', '_test.c.v', order_src)
111 assert order_run.exit_code == 0, order_run.output
112 assert order_run.output.trim_space() == 'one\ntwo'
113
114 parallel_v3_bin := build_v3_with('v3_test_file_harness_parallel_test', '-d parallel')
115 parallel_c := gen_c(parallel_v3_bin, 'harness_parallel_order', '_test.c.v', order_src)
116 assert parallel_c.contains('int main('), parallel_c
117 assert parallel_c.contains('test_one();'), parallel_c
118 assert parallel_c.contains('test_two();'), parallel_c
119 parallel_run := compile_and_run(parallel_v3_bin, 'harness_parallel_order_run', '_test.c.v',
120 order_src)
121 assert parallel_run.exit_code == 0, parallel_run.output
122 assert parallel_run.output.trim_space() == 'one\ntwo'
123
124 hooks_src := "fn testsuite_begin() {
125 println('begin')
126}
127
128fn testsuite_end() {
129 println('end')
130}
131
132fn before_each() {
133 println('before')
134}
135
136fn after_each() {
137 println('after')
138}
139
140fn test_option() ? {
141 println('option')
142 return
143}
144
145fn test_result() ! {
146 println('result')
147 return
148}
149"
150 hooks_run := compile_and_run(v3_bin, 'harness_hooks', '_test.v', hooks_src)
151 assert hooks_run.exit_code == 0, hooks_run.output
152 assert hooks_run.output.trim_space() == 'begin\nbefore\noption\nafter\nbefore\nresult\nafter\nend'
153
154 result_fail := compile_and_run(v3_bin, 'harness_result_fail', '_test.v', "fn test_fail() ! {
155 return error('bad')
156}
157")
158 assert result_fail.exit_code != 0
159 assert result_fail.output.contains('test failed: test_fail')
160
161 result_fail_cleanup := compile_and_run(v3_bin, 'harness_result_fail_cleanup', '_test.v', "fn testsuite_end() {
162 println('end')
163}
164
165fn after_each() {
166 println('after')
167}
168
169fn test_fail() ! {
170 return error('bad')
171}
172")
173 assert result_fail_cleanup.exit_code != 0
174 assert result_fail_cleanup.output.contains('test failed: test_fail')
175 assert result_fail_cleanup.output.contains('after')
176 assert result_fail_cleanup.output.contains('end')
177
178 option_fail := compile_and_run(v3_bin, 'harness_option_fail', '_test.v', 'fn test_fail() ? {
179 return none
180}
181')
182 assert option_fail.exit_code != 0
183 assert option_fail.output.contains('test failed: test_fail')
184
185 option_fail_cleanup := compile_and_run(v3_bin, 'harness_option_fail_cleanup', '_test.v', "fn testsuite_end() {
186 println('end')
187}
188
189fn after_each() {
190 println('after')
191}
192
193fn test_fail() ? {
194 return none
195}
196")
197 assert option_fail_cleanup.exit_code != 0
198 assert option_fail_cleanup.output.contains('test failed: test_fail')
199 assert option_fail_cleanup.output.contains('after')
200 assert option_fail_cleanup.output.contains('end')
201
202 assert_fail := compile_and_run(v3_bin, 'harness_assert_fail', '_test.v', 'fn test_fail() {
203 assert false
204}
205')
206 assert assert_fail.exit_code != 0
207 assert assert_fail.output.contains('assert failed')
208
209 invalid_param := compile_expect_failure(v3_bin, 'harness_invalid_param', '_test.v', 'fn test_bad(i int) {
210}
211')
212 assert invalid_param.output.contains('invalid test signature'), invalid_param.output
213
214 generic_test := compile_expect_failure(v3_bin, 'harness_generic_test', '_test.v', 'fn test_generic[T]() {
215}
216')
217 assert generic_test.output.contains('invalid test signature'), generic_test.output
218
219 invalid_return := compile_expect_failure(v3_bin, 'harness_invalid_return', '_test.v', 'fn test_bad() int {
220 return 1
221}
222')
223 assert invalid_return.output.contains('invalid test signature'), invalid_return.output
224
225 non_main := compile_expect_failure(v3_bin, 'harness_non_main_module', '_test.c.v', 'module sample
226
227fn test_one() {
228}
229')
230 assert non_main.output.contains('no runnable tests'), non_main.output
231
232 result_hook := compile_expect_failure(v3_bin, 'harness_result_hook', '_test.v', 'fn before_each() ! {
233 return
234}
235
236fn test_one() {
237}
238')
239 assert result_hook.output.contains('invalid test hook signature'), result_hook.output
240
241 generic_hook := compile_expect_failure(v3_bin, 'harness_generic_hook', '_test.v', 'fn before_each[T]() {
242}
243
244fn test_one() {
245}
246')
247 assert generic_hook.output.contains('invalid test hook signature'), generic_hook.output
248
249 option_hook := compile_expect_failure(v3_bin, 'harness_option_hook', '_test.v', 'fn after_each() ? {
250 return
251}
252
253fn test_one() {
254}
255')
256 assert option_hook.output.contains('invalid test hook signature'), option_hook.output
257
258 ordinary_c := gen_c(v3_bin, 'harness_plain_file', '.v', "fn test_lonely() {
259 println('lonely')
260}
261")
262 assert !ordinary_c.contains('int main('), ordinary_c
263 assert !ordinary_c.contains('test_lonely();'), ordinary_c
264}
265
266fn test_v3_test_file_harness_rejects_top_level_stmt() {
267 v3_bin := build_v3()
268 src := "println('top')
269
270fn test_one() {
271 println('test')
272}
273"
274 invalid := compile_expect_failure(v3_bin, 'harness_top_level_and_test', '_test.v', src)
275 assert invalid.output.contains('executable top-level statements are not supported in test files'), invalid.output
276}
277
278fn test_v3_test_file_harness_owns_entrypoint_over_user_main() {
279 v3_bin := build_v3()
280 src := "fn main() {
281 println('user-main')
282}
283
284fn test_one() {
285 println('test')
286}
287"
288 c_code := gen_c(v3_bin, 'harness_user_main', '_test.v', src)
289 assert c_code.count('int main(') == 1, c_code
290 assert c_code.contains('test_one();'), c_code
291 run := compile_and_run(v3_bin, 'harness_user_main_run', '_test.v', src)
292 assert run.exit_code == 0, run.output
293 assert run.output.trim_space() == 'test'
294}
295
296fn test_v3_test_file_harness_keeps_user_main_callable() {
297 v3_bin := build_v3()
298 src := "fn main() {
299 println('user-main')
300}
301
302fn test_call_main() {
303 main()
304}
305"
306 c_code := gen_c(v3_bin, 'harness_user_main_callable', '_test.v', src)
307 assert c_code.count('int main(') == 1, c_code
308 assert c_code.contains('main__user_main'), c_code
309 assert c_code.contains('main__user_main();'), c_code
310 assert c_code.contains('test_call_main();'), c_code
311 run := compile_and_run(v3_bin, 'harness_user_main_callable_run', '_test.v', src)
312 assert run.exit_code == 0, run.output
313 assert run.output.trim_space() == 'user-main'
314}
315
316fn test_v3_test_file_harness_forward_declares_user_main() {
317 v3_bin := build_v3()
318 src := "fn test_call_main() {
319 main()
320}
321
322fn main() {
323 println('late-main')
324}
325"
326 c_code := gen_c(v3_bin, 'harness_user_main_forward', '_test.v', src)
327 assert c_code.count('int main(') == 1, c_code
328 proto_idx := c_code.index('void main__user_main(void);') or { -1 }
329 test_idx := c_code.index('void test_call_main(void) {') or { -1 }
330 assert proto_idx >= 0, c_code
331 assert test_idx > proto_idx, c_code
332 assert c_code.contains('main__user_main();'), c_code
333 run := compile_and_run(v3_bin, 'harness_user_main_forward_run', '_test.v', src)
334 assert run.exit_code == 0, run.output
335 assert run.output.trim_space() == 'late-main'
336}
337
338fn test_v3_test_file_harness_renames_user_main_without_collision() {
339 v3_bin := build_v3()
340 src := "fn main__user_main() {
341 println('collision')
342}
343
344fn main() {
345 println('user-main')
346}
347
348fn test_call_both() {
349 main__user_main()
350 main()
351}
352"
353 c_code := gen_c(v3_bin, 'harness_user_main_collision', '_test.v', src)
354 assert c_code.count('int main(') == 1, c_code
355 assert c_code.contains('void main__user_main('), c_code
356 assert c_code.contains('void main__user_main_1('), c_code
357 assert c_code.contains('main__user_main();'), c_code
358 assert c_code.contains('main__user_main_1();'), c_code
359 run := compile_and_run(v3_bin, 'harness_user_main_collision_run', '_test.v', src)
360 assert run.exit_code == 0, run.output
361 assert run.output.trim_space() == 'collision\nuser-main'
362}
363
364fn test_v3_test_file_harness_renames_user_main_fn_value() {
365 v3_bin := build_v3()
366 src := "fn main() {
367 println('user-main')
368}
369
370fn test_fn_value_main() {
371 f := main
372 f()
373}
374"
375 c_code := gen_c(v3_bin, 'harness_user_main_fn_value', '_test.v', src)
376 assert c_code.count('int main(') == 1, c_code
377 assert c_code.contains('main__user_main'), c_code
378 assert c_code.contains('test_fn_value_main();'), c_code
379 assert !c_code.contains('= main;'), c_code
380 run := compile_and_run(v3_bin, 'harness_user_main_fn_value_run', '_test.v', src)
381 assert run.exit_code == 0, run.output
382 assert run.output.trim_space() == 'user-main'
383}
384
385fn test_v3_test_file_harness_renames_user_main_fn_arg() {
386 v3_bin := build_v3()
387 src := "fn takes(cb fn ()) {
388 cb()
389}
390
391fn main() {
392 println('user-main')
393}
394
395fn test_fn_arg_main() {
396 takes(main)
397}
398"
399 c_code := gen_c(v3_bin, 'harness_user_main_fn_arg', '_test.v', src)
400 assert c_code.count('int main(') == 1, c_code
401 assert c_code.contains('takes(main__user_main);'), c_code
402 assert !c_code.contains('takes(main);'), c_code
403 run := compile_and_run(v3_bin, 'harness_user_main_fn_arg_run', '_test.v', src)
404 assert run.exit_code == 0, run.output
405 assert run.output.trim_space() == 'user-main'
406}
407
408fn test_v3_test_file_harness_does_not_rename_shadowed_main_fn_value_call() {
409 v3_bin := build_v3()
410 src := "fn call_shadowed(main fn ()) {
411 main()
412}
413
414fn local_cb() {
415 println('local-cb')
416}
417
418fn main() {
419 println('user-main')
420}
421
422fn test_shadowed_main() {
423 call_shadowed(local_cb)
424}
425"
426 c_code := gen_c(v3_bin, 'harness_shadowed_main_fn_value_call', '_test.v', src)
427 assert c_code.count('int main(') == 1, c_code
428 assert c_code.contains('main();'), c_code
429 assert !c_code.contains('main__user_main();'), c_code
430 run := compile_and_run(v3_bin, 'harness_shadowed_main_fn_value_call_run', '_test.v', src)
431 assert run.exit_code == 0, run.output
432 assert run.output.trim_space() == 'local-cb'
433}
434
435fn test_v3_test_file_harness_finds_top_level_comptime_block_tests() {
436 v3_bin := build_v3()
437 src := "$if v3_nested_harness ? {
438 fn before_each() {
439 println('before')
440 }
441
442 fn test_nested() {
443 println('nested')
444 }
445}
446"
447 c_code := gen_c_flags(v3_bin, 'harness_nested_block', '_test.v', src, '-d v3_nested_harness')
448 assert c_code.contains('before_each();'), c_code
449 assert c_code.contains('test_nested();'), c_code
450 run := compile_and_run_flags(v3_bin, 'harness_nested_block_run', '_test.v', src,
451 '-d v3_nested_harness')
452 assert run.exit_code == 0, run.output
453 assert run.output.trim_space() == 'before\nnested'
454
455 invalid := compile_expect_failure_flags(v3_bin, 'harness_nested_invalid', '_test.v', '$if v3_nested_harness ? {
456 fn test_bad(i int) {
457 }
458}
459',
460 '-d v3_nested_harness')
461 assert invalid.output.contains('invalid test signature'), invalid.output
462
463 invalid_generic := compile_expect_failure_flags(v3_bin, 'harness_nested_generic_invalid',
464 '_test.v', '$if v3_nested_harness ? {
465 fn before_each[T]() {
466 }
467
468 fn test_nested[T]() {
469 }
470}
471',
472 '-d v3_nested_harness')
473 assert invalid_generic.output.contains('invalid test hook signature'), invalid_generic.output
474 assert invalid_generic.output.contains('invalid test signature'), invalid_generic.output
475}
476
477fn test_v3_top_level_main_preserves_file_import_alias_context() {
478 v3_bin := build_v3()
479 run, c_code := compile_project_and_run(v3_bin, 'harness_file_alias_context', {
480 'v.mod': 'Module { name: "alias_context" }
481'
482 'a/a.v': 'module a
483
484pub fn value() int {
485 return 11
486}
487'
488 'b/b.v': 'module b
489
490pub fn value() int {
491 return 22
492}
493'
494 'aa_alias.v': 'module main
495
496import a as m
497
498a_value := m.value()
499println(int_str(a_value))
500'
501 'zz_alias.v': 'module main
502
503import b as m
504
505b_value := m.value()
506println(int_str(b_value))
507'
508 })
509 assert run.exit_code == 0, run.output
510 lines := run.output.trim_space().split_into_lines()
511 assert lines.len == 2, run.output
512 assert '11' in lines, run.output
513 assert '22' in lines, run.output
514 assert c_code.contains('a__value()'), c_code
515 assert c_code.contains('b__value()'), c_code
516}
517
518fn test_v3_top_level_match_lowers_before_cgen_main() {
519 v3_bin := build_v3()
520 run := compile_and_run(v3_bin, 'harness_top_level_match', '.v', "x := match 2 {
521 2 {
522 7
523 }
524 else {
525 3
526 }
527}
528println(int_str(x))
529match 2 {
530 2 {
531 println('two')
532 }
533 else {}
534}
535")
536 assert run.exit_code == 0, run.output
537 assert run.output.trim_space() == '7\ntwo'
538}
539
540fn test_v3_top_level_defer_runs_after_synthetic_main_body() {
541 v3_bin := build_v3()
542 run := compile_and_run(v3_bin, 'harness_top_level_defer', '.v', "fn deferred_msg() string {
543 return 'deferred'
544}
545
546defer {
547 println(deferred_msg())
548}
549
550println('body')
551")
552 assert run.exit_code == 0, run.output
553 assert run.output.trim_space() == 'body\ndeferred'
554}
555