v2 / vlib / v / gen / c / coutput_test.v
771 lines · 725 sloc · 27.19 KB · dca4a2ef662b7b9401b455c0bceaebf46ebdeebc
Raw
1// vtest build: !self_sandboxed_packaging? && !sanitized_job?
2import os
3import time
4import term
5import v.util.diff
6import v.util.vtest
7
8const vexe = @VEXE
9
10const vroot = os.real_path(@VMODROOT)
11
12const local_tdata_path = 'vlib/v/gen/c/testdata'
13
14const testdata_folder = os.real_path(os.join_path(vroot, local_tdata_path))
15
16const show_compilation_output = os.getenv('VTEST_SHOW_COMPILATION_OUTPUT').int() == 1
17
18const user_os = os.user_os()
19
20const gcc_path = os.find_abs_path_of_executable('gcc') or { '' }
21
22fn mm(s string) string {
23 return term.colorize(term.magenta, s)
24}
25
26fn mj(input ...string) string {
27 return mm(input.filter(it.len > 0).join(' '))
28}
29
30fn test_out_files() {
31 os.chdir(vroot) or {}
32 output_path := os.join_path(os.vtmp_dir(), 'coutput_outs')
33 os.mkdir_all(output_path)!
34 defer {
35 os.rmdir_all(output_path) or {}
36 }
37 files := os.ls(testdata_folder) or { [] }
38 tests := files.filter(it.ends_with('.out'))
39 if tests.len == 0 {
40 eprintln('no `.out` tests found in ${testdata_folder}')
41 return
42 }
43 mut total_errors := 0
44 mut total_oks := 0
45 mut total_oks_panic := 0
46 mut total_skips := 0
47 paths := vtest.filter_vtest_only(tests, basepath: testdata_folder).sorted()
48 println(term.colorize(term.green,
49 '> testing whether ${paths.len} .out files in ${local_tdata_path} match:'))
50 for out_path in paths {
51 basename, path, relpath, out_relpath := target2paths(out_path, '.out')
52 if should_skip(relpath) {
53 total_skips++
54 continue
55 }
56 pexe := os.join_path(output_path, '${basename}.exe')
57 //
58 file_options := get_file_options(path)
59 if user_os == 'windows' && file_options.vflags.contains('-cc clang') && gcc_path.len > 0
60 && github_job.contains('gcc') {
61 eprintln('> skipping ${relpath} on gcc-windows, since it requires clang')
62 total_skips++
63 continue
64 }
65 alloptions := '-o ${os.quoted_path(pexe)} ${file_options.vflags}'
66 label := mj('v', file_options.vflags, 'run', relpath) + ' == ${mm(out_relpath)} '
67 //
68 compile_cmd := '${os.quoted_path(vexe)} ${alloptions} ${os.quoted_path(path)}'
69 sw_compile := time.new_stopwatch()
70 compilation := os.execute(compile_cmd)
71 compile_ms := sw_compile.elapsed().milliseconds()
72 ensure_compilation_succeeded(compilation, compile_cmd)
73 //
74 sw_run := time.new_stopwatch()
75 res := os.execute(os.quoted_path(pexe))
76 run_ms := sw_run.elapsed().milliseconds()
77 //
78 if res.exit_code < 0 {
79 println('${term.red('FAIL')} C:${compile_ms:6}ms, R:${run_ms:2}ms ${label}')
80 println(' run crashed with exit code: ${res.exit_code}')
81 if res.output.len > 0 {
82 println(res.output)
83 }
84 total_errors++
85 continue
86 }
87 mut found := res.output.trim_right('\r\n').replace('\r\n', '\n')
88 mut expected := os.read_file(out_path)!
89 expected = expected.trim_right('\r\n').replace('\r\n', '\n')
90 if expected.contains('================ V panic ================') {
91 // panic include backtraces and absolute file paths, so can't do char by char comparison
92 n_found := normalize_panic_message(found, vroot)
93 n_expected := normalize_panic_message(expected, vroot)
94 if found.contains('================ V panic ================') {
95 if n_found.starts_with(n_expected) {
96 vprintln('${term.green('OK (panic)')} C:${compile_ms:6}ms, R:${run_ms:2}ms ${label}')
97 total_oks_panic++
98 continue
99 } else {
100 // Both have panics, but there was a difference...
101 // Pass the normalized strings for further reporting.
102 // There is no point in comparing the backtraces too.
103 found = n_found
104 expected = n_expected
105 }
106 }
107 }
108 if expected != found {
109 println('${term.red('FAIL')} C:${compile_ms:6}ms, R:${run_ms:2}ms ${label}')
110 if diff_ := diff.compare_text(expected, found) {
111 println(term.header('difference:', '-'))
112 println(diff_)
113 } else {
114 println(term.header('expected:', '-'))
115 println(expected)
116 println(term.header('found:', '-'))
117 println(found)
118 }
119 println(term.h_divider('-'))
120 total_errors++
121 } else {
122 vprintln('${term.green('OK ')} C:${compile_ms:6}ms, R:${run_ms:2}ms ${label}')
123 total_oks++
124 }
125 }
126 println('>>> Summary for test_out_files: files: ${paths.len}, oks: ${total_oks}, ok panics: ${total_oks_panic}, skipped: ${total_skips}, error: ${total_errors} .')
127 assert total_errors == 0
128}
129
130fn test_c_must_have_files() {
131 os.chdir(vroot) or {}
132 output_path := os.join_path(os.vtmp_dir(), 'coutput_c_must_haves')
133 os.mkdir_all(output_path)!
134 defer {
135 os.rmdir_all(output_path) or {}
136 }
137 files := os.ls(testdata_folder) or { [] }
138 tests := files.filter(it.ends_with('.c.must_have'))
139 if tests.len == 0 {
140 eprintln('no `.c.must_have` files found in ${testdata_folder}')
141 return
142 }
143 paths := vtest.filter_vtest_only(tests, basepath: testdata_folder).sorted()
144 mut total_errors := 0
145 mut total_oks := 0
146 mut total_oks_panic := 0
147 mut total_skips := 0
148 mut failed_descriptions := []string{cap: paths.len}
149 println(term.colorize(term.green,
150 '> testing whether all line patterns in ${paths.len} `.c.must_have` files in ${local_tdata_path} match:'))
151 for must_have_path in paths {
152 basename, path, relpath, must_have_relpath := target2paths(must_have_path, '.c.must_have')
153 if should_skip(relpath) {
154 total_skips++
155 continue
156 }
157 file_options := get_file_options(path)
158 alloptions := '-o - ${file_options.vflags}'
159 mut description := mj('v', alloptions, relpath) + ' matches ${mm(must_have_relpath)} '
160 cmd := '${os.quoted_path(vexe)} ${alloptions} ${os.quoted_path(path)}'
161 sw_compile := time.new_stopwatch()
162 compilation := os.execute(cmd)
163 compile_ms := sw_compile.elapsed().milliseconds()
164 ensure_compilation_succeeded(compilation, cmd)
165 expected_lines := os.read_lines(must_have_path) or { [] }
166 generated_c_lines := compilation.output.split_into_lines()
167 mut nmatches := 0
168 mut failed_patterns := []string{}
169 for idx_expected_line, eline in expected_lines {
170 if does_line_match_one_of_generated_lines(eline, generated_c_lines) {
171 nmatches++
172 // eprintln('> testing: ${must_have_path} has line: ${eline}')
173 } else {
174 failed_patterns << eline
175 description += '\n failed pattern: `${eline}`'
176 println('${term.red('FAIL')} C:${compile_ms:5}ms ${description}')
177 eprintln('${must_have_path}:${idx_expected_line + 1}: expected match error:')
178 eprintln('`${cmd}` did NOT produce expected line:')
179 eprintln(term.colorize(term.red, eline))
180 if description !in failed_descriptions {
181 failed_descriptions << description
182 }
183 total_errors++
184 continue
185 }
186 }
187 if nmatches == expected_lines.len {
188 vprintln('${term.green('OK ')} C:${compile_ms:5}ms ${description}')
189 total_oks++
190 } else {
191 if show_compilation_output {
192 eprintln('> ALL lines:')
193 eprintln(compilation.output)
194 }
195 eprintln('--------- failed patterns: -------------------------------------------')
196 for fpattern in failed_patterns {
197 eprintln(fpattern)
198 }
199 eprintln('----------------------------------------------------------------------')
200 }
201 }
202 if failed_descriptions.len > 0 {
203 eprintln('--------- failed commands: -------------------------------------------')
204 for fd in failed_descriptions {
205 eprintln(' > ${fd}')
206 }
207 eprintln('----------------------------------------------------------------------')
208 }
209 println('>>> Summary for test_c_must_have_files: files: ${paths.len}, oks: ${total_oks}, ok panics: ${total_oks_panic}, skipped: ${total_skips}, error: ${total_errors} .')
210 assert total_errors == 0
211}
212
213fn test_or_block_err_var_collision_does_not_emit_self_referential_err() {
214 os.chdir(vroot) or {}
215 path := os.join_path(testdata_folder, 'or_block_err_var_collision.vv')
216 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(path)}'
217 compilation := os.execute(cmd)
218 ensure_compilation_succeeded(compilation, cmd)
219 assert !compilation.output.contains('IError err = err.err;')
220 mut source_err_tmp := ''
221 mut has_visible_or_block_err := false
222 for line in compilation.output.split_into_lines() {
223 trimmed := line.trim_space()
224 if trimmed.starts_with('IError _t') && trimmed.ends_with(' = err.err;') {
225 source_err_tmp = trimmed.all_after('IError ').all_before(' = err.err;')
226 }
227 if trimmed.starts_with('IError _t') && trimmed.contains('.err;') {
228 err_tmp := trimmed.all_after('IError ').all_before(' = ')
229 if compilation.output.contains('IError err = ${err_tmp};') {
230 has_visible_or_block_err = true
231 }
232 }
233 }
234 assert source_err_tmp != ''
235 assert !compilation.output.contains('IError err = ${source_err_tmp};')
236 assert has_visible_or_block_err
237}
238
239fn test_imported_empty_interface_concat_does_not_emit_noop_array_cast_helper() {
240 os.chdir(vroot) or {}
241 path := os.join_path(vroot,
242 'vlib/v/tests/modules/interface_array_concat_from_another_module/main_test.v')
243 symbol := '__v_array_to_interface_array__Array_interface_array_concat_from_another_module__mod__Value__to__Array_interface_array_concat_from_another_module__mod__Value'
244 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(path)}'
245 compilation := os.execute(cmd)
246 ensure_compilation_succeeded(compilation, cmd)
247 assert !compilation.output.contains(symbol)
248}
249
250fn test_windows_sharedlive_string_interpolation_in_ternary_does_not_emit_inline_tmp_decl() {
251 os.chdir(vroot) or {}
252 test_source := os.join_path(os.vtmp_dir(), 'coutput_live_windows_ternary_str_intp.vv')
253 os.write_file(test_source,
254 "module main\n\n@[live]\nfn foo(ok bool, name string) string {\n\treturn if ok { 'Hello, \${name}!' } else { '\${u32(7)}' }\n}\n\nfn main() {\n\tprintln(foo(true, 'V'))\n}\n")!
255 defer {
256 os.rm(test_source) or {}
257 }
258 cmd := '${os.quoted_path(vexe)} -o - -os windows -sharedlive ${os.quoted_path(test_source)}'
259 compilation := os.execute(cmd)
260 ensure_compilation_succeeded(compilation, cmd)
261 mut normalized := compilation.output.replace('\t', ' ').replace('\n', ' ')
262 for normalized.contains(' ') {
263 normalized = normalized.replace(' ', ' ')
264 }
265 assert !normalized.contains('? ( string _t')
266 assert compilation.output.contains('builtin__str_intp')
267}
268
269fn test_windows_tcc_atomic_postfix_uses_interlocked_helpers() {
270 os.chdir(vroot) or {}
271 cc := windows_tcc_ccompiler_for_coutput_test()
272 if cc == '' {
273 eprintln('> skipping ${@FN} since tcc is not available on windows')
274 return
275 }
276 test_source := os.join_path(os.vtmp_dir(), 'coutput_windows_tcc_atomic_postfix.vv')
277 os.write_file(test_source, 'module main
278
279struct App {
280mut:
281 idx atomic int
282}
283
284fn (mut app App) bump() {
285 app.idx++
286}
287
288fn main() {
289 mut app := App{}
290 app.bump()
291}
292')!
293 defer {
294 os.rm(test_source) or {}
295 }
296 cmd := '${os.quoted_path(vexe)} -o - -os windows -cc ${cc} ${os.quoted_path(test_source)}'
297 compilation := os.execute(cmd)
298 ensure_compilation_succeeded(compilation, cmd)
299 assert compilation.output.contains('thirdparty/stdatomic/win/atomic.h')
300 assert compilation.output.contains('InterlockedExchangeAdd')
301 assert !compilation.output.contains('__atomic_fetch_add')
302}
303
304fn windows_tcc_ccompiler_for_coutput_test() string {
305 if user_os != 'windows' {
306 return 'x86_64-w64-mingw32-tcc'
307 }
308 bundled_tcc := os.join_path(vroot, 'thirdparty', 'tcc', 'tcc.exe')
309 if os.is_file(bundled_tcc) && os.is_executable(bundled_tcc) {
310 return os.quoted_path(bundled_tcc)
311 }
312 if os.find_abs_path_of_executable('tcc') or { '' } != '' {
313 return 'tcc'
314 }
315 return ''
316}
317
318fn test_no_main_exports_initialize_windows_runtime() {
319 os.chdir(vroot) or {}
320 test_source := os.join_path(os.vtmp_dir(), 'coutput_no_main_export_windows_init.vv')
321 os.write_file(test_source,
322 "module no_main\n\n@[export: 'v_sdl_app_quit']\npub fn app_quit() {}\n")!
323 defer {
324 os.rm(test_source) or {}
325 }
326 cmd := '${os.quoted_path(vexe)} -o - -os windows ${os.quoted_path(test_source)}'
327 compilation := os.execute(cmd)
328 ensure_compilation_succeeded(compilation, cmd)
329 generated_c_lines := compilation.output.split_into_lines()
330 expected_lines := [
331 'static void _vno_main_init_caller(void);',
332 'static void _vno_main_cleanup_caller(void);',
333 'void v_sdl_app_quit(void) {',
334 '_vno_main_init_caller();',
335 'void _vinit(int ___argc, voidptr ___argv) {',
336 'static bool once = false; if (once) {return;} once = true;',
337 'void _vcleanup(void) {',
338 'static void _vno_main_cleanup_caller(void) {',
339 'static void _vno_main_init_caller(void) {',
340 'con_valid = AttachConsole(ATTACH_PARENT_PROCESS);',
341 'err = freopen_s(&res_fp, "NUL", "w", stdout);',
342 '_vinit(0,0);',
343 'atexit(_vno_main_cleanup_caller);',
344 ]
345 for expected_line in expected_lines {
346 assert does_line_match_one_of_generated_lines(expected_line, generated_c_lines)
347 }
348}
349
350fn test_c_fallback_decl_uses_module_wide_c_includes() {
351 os.chdir(vroot) or {}
352 test_source := os.join_path(os.vtmp_dir(), 'coutput_module_c_include')
353 os.rmdir_all(test_source) or {}
354 os.mkdir_all(test_source)!
355 defer {
356 os.rmdir_all(test_source) or {}
357 }
358 header_path := os.join_path(test_source, 'c_header_decl.h')
359 os.write_file(header_path, 'int c_header_decl(const char* input);\n')!
360 header_include_path := header_path.replace('\\', '/')
361 os.write_file(os.join_path(test_source, 'include.v'), 'module main
362
363#include "${header_include_path}"
364')!
365 os.write_file(os.join_path(test_source, 'decl.v'), "module main
366
367fn C.c_header_decl(input &char) int
368
369fn main() {
370 C.c_header_decl(c'text')
371}
372")!
373 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(test_source)}'
374 compilation := os.execute(cmd)
375 ensure_compilation_succeeded(compilation, cmd)
376 assert !compilation.output.contains('extern int c_header_decl(')
377}
378
379fn test_c_fallback_decl_uses_c_helper_submodule_includes() {
380 test_source := os.join_path(os.vtmp_dir(), 'coutput_c_helper_include')
381 module_path := os.join_path(test_source, 'coutput_sdl')
382 c_module_path := os.join_path(module_path, 'c')
383 os.rmdir_all(test_source) or {}
384 os.mkdir_all(c_module_path)!
385 defer {
386 os.rmdir_all(test_source) or {}
387 }
388 header_path := os.join_path(c_module_path, 'c_helper_decl.h')
389 os.write_file(header_path,
390 ['#include <stdbool.h>', 'typedef enum { false_value, true_value } foreign_bool;', 'foreign_bool c_helper_decl(void);'].join('\n') +
391 '\n')!
392 header_include_path := header_path.replace('\\', '/')
393 os.write_file(os.join_path(c_module_path, 'c.c.v'), 'module c
394
395pub const used_import = 1
396
397#include "${header_include_path}"
398')!
399 os.write_file(os.join_path(module_path, 'coutput_sdl.v'), 'module coutput_sdl
400
401import coutput_sdl.c
402
403pub const used_import = c.used_import
404')!
405 os.write_file(os.join_path(module_path, 'atomic.c.v'), 'module coutput_sdl
406
407fn C.c_helper_decl() bool
408
409pub fn call() {
410 _ = C.c_helper_decl()
411}
412')!
413 old_wd := os.getwd()
414 os.chdir(test_source) or {}
415 defer {
416 os.chdir(old_wd) or {}
417 }
418 cmd := '${os.quoted_path(vexe)} -shared -o - coutput_sdl'
419 compilation := os.execute(cmd)
420 ensure_compilation_succeeded(compilation, cmd)
421 assert compilation.output.contains('#include "${header_include_path}"')
422 assert !compilation.output.contains('extern bool c_helper_decl(')
423}
424
425fn test_user_defined_windows_dllmain_disables_generated_entrypoint() {
426 os.chdir(vroot) or {}
427 test_source := os.join_path(os.vtmp_dir(), 'coutput_user_defined_windows_dllmain.vv')
428 os.write_file(test_source,
429 ['module test', '', 'pub type C.DWORD = u32', 'pub type C.LPVOID = voidptr', '', 'fn C._vinit_caller()', 'fn C._vcleanup_caller()', '', "@[export: 'library_answer']", 'pub fn library_answer() int {', '\treturn 42', '}', '', "@[export: 'DllMain']", 'pub fn dll_main(hinst C.HINSTANCE, reason C.DWORD, reserved C.LPVOID) C.BOOL {', '\t_ = hinst', '\t_ = reserved', '\tif reason == C.DWORD(1) {', '\t\tC._vinit_caller()', '\t} else if reason == C.DWORD(0) {', '\t\tC._vcleanup_caller()', '\t}', '\treturn 1', '}'].join('\n') +
430 '\n')!
431 defer {
432 os.rm(test_source) or {}
433 }
434 cmd := '${os.quoted_path(vexe)} -o - -os windows -shared -gc boehm ${os.quoted_path(test_source)}'
435 compilation := os.execute(cmd)
436 ensure_compilation_succeeded(compilation, cmd)
437 assert compilation.output.contains('void _vinit_caller() {')
438 assert compilation.output.contains('GC_set_pages_executable(0);')
439 assert compilation.output.contains('GC_INIT();')
440 assert compilation.output.contains('DllMain(')
441 assert compilation.output.contains('_vinit_caller();')
442 assert compilation.output.contains('_vcleanup_caller();')
443 assert !compilation.output.contains('switch (fdwReason)')
444 assert !compilation.output.contains('case DLL_PROCESS_ATTACH')
445}
446
447fn test_array_sort_with_compare_uses_stable_sort_adapters() {
448 os.chdir(vroot) or {}
449 test_source := os.join_path(os.vtmp_dir(), 'coutput_array_sort_with_compare_stable_sort.vv')
450 source_lines := [
451 'module main',
452 '',
453 'struct Foo {',
454 '\tx int',
455 '}',
456 '',
457 'fn by_x(a &Foo, b &Foo) int {',
458 '\treturn a.x - b.x',
459 '}',
460 '',
461 'fn main() {',
462 '\tmut xs := [Foo{ x: 2 }, Foo{ x: 1 }]',
463 '\txs.sort_with_compare(by_x)',
464 '\tmut ys := [Foo{ x: 2 }, Foo{ x: 1 }]!',
465 '\tys.sort_with_compare(by_x)',
466 '\tmut zs := [Foo{ x: 2 }, Foo{ x: 1 }]',
467 '\tzs.sort(a.x < b.x)',
468 '}',
469 ]
470 os.write_file(test_source, source_lines.join('\n') + '\n')!
471 defer {
472 os.rm(test_source) or {}
473 }
474 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(test_source)}'
475 compilation := os.execute(cmd)
476 ensure_compilation_succeeded(compilation, cmd)
477 mut normalized := compilation.output.replace('\t', ' ').replace('\n', ' ')
478 for normalized.contains(' ') {
479 normalized = normalized.replace(' ', ' ')
480 }
481 assert normalized.contains('int main__by_x_qsort_adapter(const void* a, const void* b) { return main__by_x((main__Foo*)a, (main__Foo*)b); }')
482 assert normalized.contains('if (xs.len > 0) { v_stable_sort(xs.data, xs.len, xs.element_size, main__by_x_qsort_adapter); }')
483 assert normalized.contains('v_stable_sort(&ys, 2, sizeof(main__Foo), main__by_x_qsort_adapter);')
484 assert normalized.contains('_qsort_adapter(const void* a, const void* b) { return compare_')
485 assert normalized.contains('v_stable_sort(zs.data, zs.len, zs.element_size, compare_')
486 assert normalized.contains('_qsort_adapter);')
487}
488
489fn test_veb_implicit_ctx_alias_uses_user_context_name() {
490 os.chdir(vroot) or {}
491 test_source := os.join_path(os.vtmp_dir(), 'coutput_veb_implicit_ctx_alias.vv')
492 os.write_file(test_source,
493 ['module main', '', 'import veb', '', 'struct App {}', '', 'struct Context {', '\tveb.Context', '}', '', 'fn (app App) nested(mut ctx Context) veb.Result {', "\treturn ctx.text('nested')", '}', '', 'fn (app App) log(_ Context) {', "\tprintln('hi')", '}', '', 'fn (app App) index(mut c Context) veb.Result {', '\tapp.log(c)', '\treturn app.nested()', '}', '', 'fn main() {', '\tmut app := App{}', '\tmut ctx := Context{}', '\t_ = app.index(mut ctx)', '}'].join('\n') +
494 '\n')!
495 defer {
496 os.rm(test_source) or {}
497 }
498 cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o - ${os.quoted_path(test_source)}'
499 compilation := os.execute(cmd)
500 ensure_compilation_succeeded(compilation, cmd)
501 mut normalized := compilation.output.replace('\t', ' ').replace('\n', ' ')
502 for normalized.contains(' ') {
503 normalized = normalized.replace(' ', ' ')
504 }
505 assert normalized.contains('veb__Result main__App_index(main__App app, main__Context* c) { main__App_log(app, *c); GC_reachable_here(&c); return main__App_nested(app, c); }')
506}
507
508fn test_veb_implicit_ctx_alias_on_context_receiver_tmpl_not_found() {
509 os.chdir(vroot) or {}
510 test_dir := os.join_path(os.vtmp_dir(), 'coutput_veb_context_receiver_tmpl_not_found')
511 os.rmdir_all(test_dir) or {}
512 os.mkdir_all(os.join_path(test_dir, 'web'))!
513 test_source := os.join_path(test_dir, 'main.v')
514 os.write_file(os.join_path(test_dir, 'web', 'notfound.html'), '<h1>@ctx.req.url</h1>\n')!
515 os.write_file(test_source,
516 ['module main', '', 'import veb', '', 'pub struct Context {', '\tveb.Context', '}', '', 'pub struct App {}', '', 'pub fn (mut c Context) not_found() veb.Result {', '\tc.res.set_status(.not_found)', "\treturn c.html(\$tmpl('web/notfound.html'))", '}', '', 'fn main() {', '\tmut app := App{}', '\tveb.run[App, Context](mut app, 8080)', '}'].join('\n') +
517 '\n')!
518 defer {
519 os.rmdir_all(test_dir) or {}
520 }
521 test_exe := os.join_path(test_dir, 'app')
522 compile_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o ${os.quoted_path(test_exe)} ${os.quoted_path(test_source)}'
523 ensure_compilation_succeeded(os.execute(compile_cmd), compile_cmd)
524 c_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o - ${os.quoted_path(test_source)}'
525 compilation := os.execute(c_cmd)
526 ensure_compilation_succeeded(compilation, c_cmd)
527 not_found_start := 'veb__Result main__Context_not_found(main__Context* c) {'
528 assert compilation.output.contains(not_found_start)
529 not_found_body :=
530 compilation.output.all_after(not_found_start).all_before('VV_LOC void main__main')
531 assert !not_found_body.contains('GC_reachable_here(&ctx);')
532 assert not_found_body.contains('GC_reachable_here(&c);')
533 assert not_found_body.contains('return veb__Context_html(&c->Context, _tmpl_res_')
534}
535
536fn test_veb_template_scope_gc_pin_does_not_escape_loop_var() {
537 os.chdir(vroot) or {}
538 test_dir := os.join_path(os.vtmp_dir(), 'coutput_veb_template_scope_gc_pin')
539 os.rmdir_all(test_dir) or {}
540 os.mkdir_all(os.join_path(test_dir, 'templates'))!
541 template_lines := [
542 '<div class="tree-path">',
543 ' @if is_top_directory',
544 ' @for i, p in ctx.parts',
545 ' <a href="/@repo.user_name/@{ctx.make_path(branch_name, i)}">@p</a>',
546 ' @end',
547 ' @end',
548 ' @if is_repo_watcher',
549 ' <span>@watcher_count</span>',
550 ' @end',
551 '</div>',
552 ]
553 os.write_file(os.join_path(test_dir, 'templates', 'tree.html'),
554 template_lines.join('\n') + '\n')!
555 test_source := os.join_path(test_dir, 'main.v')
556 source_lines := [
557 'module main',
558 '',
559 'import veb',
560 '',
561 'pub struct Context {',
562 '\tveb.Context',
563 'pub mut:',
564 '\tparts []string',
565 '}',
566 '',
567 'pub struct App {}',
568 '',
569 'pub struct Repo {',
570 '\tuser_name string',
571 '}',
572 '',
573 'pub fn (ctx &Context) make_path(branch_name string, i int) string {',
574 '\treturn branch_name + i.str()',
575 '}',
576 '',
577 'pub fn (mut app App) index(mut ctx Context) veb.Result {',
578 "\trepo := Repo{ user_name: 'gitly' }",
579 "\tbranch_name := 'master'",
580 '\tis_top_directory := true',
581 '\tis_repo_watcher := false',
582 '\twatcher_count := 0',
583 "\treturn \$veb.html('templates/tree.html')",
584 '}',
585 '',
586 'fn main() {',
587 '\tmut app := App{}',
588 "\tmut ctx := Context{ parts: ['src'] }",
589 '\t_ = app.index(mut ctx)',
590 '}',
591 ]
592 os.write_file(test_source, source_lines.join('\n') + '\n')!
593 defer {
594 os.rmdir_all(test_dir) or {}
595 }
596 test_exe := os.join_path(test_dir, 'app')
597 compile_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o ${os.quoted_path(test_exe)} ${os.quoted_path(test_source)}'
598 ensure_compilation_succeeded(os.execute(compile_cmd), compile_cmd)
599 c_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o - ${os.quoted_path(test_source)}'
600 compilation := os.execute(c_cmd)
601 ensure_compilation_succeeded(compilation, c_cmd)
602 index_start := 'veb__Result main__App_index(main__App* app, main__Context* ctx) {'
603 assert compilation.output.contains(index_start)
604 index_body := compilation.output.all_after(index_start).all_before('VV_LOC void main__main')
605 assert index_body.contains('string p =')
606 assert !index_body.contains('GC_reachable_here(&p);')
607 assert index_body.contains('veb__Context_html(&ctx->Context, _tmpl_res_')
608}
609
610fn does_line_match_one_of_generated_lines(line string, generated_c_lines []string) bool {
611 for cline in generated_c_lines {
612 if line == cline {
613 return true
614 }
615 if cline.contains(line) {
616 return true
617 }
618 }
619 return false
620}
621
622fn normalize_panic_message(message string, vroot string) string {
623 mut msg := message.all_before('=========================================')
624 // change windows to nix path
625 s := vroot.replace(os.path_separator, '/')
626 msg = msg.replace(s + '/', '')
627 msg = msg.trim_space()
628 return msg
629}
630
631fn vroot_relative(opath string) string {
632 nvroot := vroot.replace(os.path_separator, '/') + '/'
633 npath := opath.replace(os.path_separator, '/')
634 return npath.replace(nvroot, '')
635}
636
637fn ensure_compilation_succeeded(compilation os.Result, cmd string) {
638 if compilation.exit_code < 0 {
639 eprintln('> cmd exit_code < 0, cmd: ${cmd}')
640 panic(compilation.output)
641 }
642 if compilation.exit_code != 0 {
643 eprintln('> cmd exit_code != 0, cmd: ${cmd}')
644 panic('compilation failed: ${compilation.output}')
645 }
646}
647
648fn target2paths(target_path string, postfix string) (string, string, string, string) {
649 basename := os.file_name(target_path).replace(postfix, '')
650 target_dir := os.dir(target_path)
651 path := os.join_path(target_dir, '${basename}.vv')
652 relpath := vroot_relative(path)
653 target_relpath := vroot_relative(target_path)
654 return basename, path, relpath, target_relpath
655}
656
657struct FileOptions {
658mut:
659 vflags string
660}
661
662pub fn get_file_options(file string) FileOptions {
663 mut res := FileOptions{}
664 lines := os.read_lines(file) or { [] }
665 for line in lines {
666 if line.starts_with('// vtest vflags:') {
667 res.vflags = line.all_after(':').trim_space()
668 }
669 }
670 return res
671}
672
673const github_job = os.getenv('GITHUB_JOB')
674
675fn should_skip(relpath string) bool {
676 if github_job.contains('musl') && relpath.ends_with('autofree_sql_or_block.vv') {
677 eprintln('> skipping ${relpath} on ${github_job}, since it uses db.sqlite, and its headers are not available to the C compiler in that environment')
678 return true
679 }
680 if github_job.contains('musl') && (relpath.ends_with('print_boehm_leak.vv')
681 || relpath.ends_with('scope_cleanup_boehm_leak.vv')
682 || relpath.ends_with('gc_debugger_linux.vv')) {
683 eprintln('> skipping ${relpath} on ${github_job}, since gc related tests are not compatible with `-gc none`')
684 return true
685 }
686 if user_os == 'windows' {
687 if relpath.contains('_nix.vv') {
688 eprintln('> skipping ${relpath} on windows')
689 return true
690 }
691 $if !msvc {
692 if relpath.contains('_msvc_windows.vv') {
693 eprintln('> skipping ${relpath} on !msvc')
694 return true
695 }
696 }
697 $if !gcc {
698 if relpath.contains('_gcc_windows.vv') {
699 eprintln('> skipping ${relpath} on !gcc')
700 return true
701 }
702 }
703 $if msvc {
704 if relpath.contains('_not_msvc_windows.vv') {
705 eprintln('> skipping ${relpath} on msvc')
706 return true
707 }
708 if relpath.ends_with('cross_printfn_v_malloc.vv') {
709 eprintln('> skipping ${relpath} on msvc, since -cross -printfn does not emit a runnable executable')
710 return true
711 }
712 if relpath.contains('asm_') {
713 eprintln('> skipping ${relpath} on msvc, since it uses gcc-style inline asm')
714 return true
715 }
716 }
717 } else {
718 if relpath.contains('_windows.vv') {
719 eprintln('> skipping ${relpath} on !windows')
720 return true
721 }
722 }
723 if relpath.contains('freestanding_module_import_') {
724 $if !amd64 {
725 // https://github.com/vlang/v/issues/23397
726 eprintln('> skipping ${relpath} on != amd64')
727 return true
728 }
729 if user_os != 'linux' {
730 eprintln('> skipping ${relpath} on != linux')
731 return true
732 }
733 if gcc_path == '' {
734 eprintln('> skipping ${relpath} since it needs gcc, which is not detected')
735 return true
736 }
737 }
738 if user_os == 'macos' {
739 $if arm64 {
740 if relpath.ends_with('spawn_stack_nix.vv') {
741 eprintln('> skipping ${relpath} on macOS arm64, since i386 linking is unavailable')
742 return true
743 }
744 }
745 }
746 if gcc_path == '' {
747 test_path := os.join_path(vroot, relpath)
748 file_options := get_file_options(test_path)
749 if file_options.vflags.contains('-cc gcc') {
750 eprintln('> skipping ${relpath} since its vflags require gcc, which is not detected')
751 return true
752 }
753 }
754 if github_job in ['tcc-windows', 'msvc-windows'] {
755 test_path := os.join_path(vroot, relpath)
756 file_options := get_file_options(test_path)
757 if file_options.vflags.contains('-cc clang') {
758 // The Windows runner's clang toolchain produces V binaries that
759 // crash at startup on this CI; the test is still exercised by
760 // the macOS/Linux jobs.
761 eprintln('> skipping ${relpath} on ${github_job}, since `-cc clang` produces unstable binaries on this runner')
762 return true
763 }
764 }
765 return false
766}
767
768@[if !silent ?]
769fn vprintln(msg string) {
770 println(msg)
771}
772