v4 / vlib / v / gen / c / coutput_test.v
1022 lines · 964 sloc · 38.02 KB · 82e947b1b9e56b6ae3f376c0c2880c4351fff749
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_main_error_propagation_panic_branches_do_not_fall_through() {
240 os.chdir(vroot) or {}
241 test_dir := os.join_path(os.vtmp_dir(), 'main_error_propagation_panic_tail_${os.getpid()}')
242 os.mkdir_all(test_dir)!
243 defer {
244 os.rmdir_all(test_dir) or {}
245 }
246 source_path := os.join_path(test_dir, 'main_error_propagation_panic_tail.v')
247 os.write_file(source_path,
248 ['module main', '', 'fn fail_result() ! {', "\treturn error('new error')", '}', '', 'fn fail_option() ?int {', '\treturn none', '}', '', 'fn defer_result() ! {', '\tdefer {', "\t\tprintln('result deferred')", '\t}', '\tfail_result()!', '}', '', 'fn defer_option() ?int {', '\tdefer {', "\t\tprintln('option deferred')", '\t}', '\treturn fail_option()', '}', '', 'fn main() {', '\tif arguments().len > 1000 {', '\t\tdefer_option()?', '\t}', '\tdefer_result()!', '}'].join('\n') +
249 '\n')!
250 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(source_path)}'
251 compilation := os.execute(cmd)
252 ensure_compilation_succeeded(compilation, cmd)
253 for panic_call in [
254 'builtin__panic_result_not_set(IError_name_table[',
255 'builtin__panic_option_not_set( IError_name_table[',
256 ] {
257 assert compilation.output.contains(panic_call)
258 branch_tail := compilation.output.all_after(panic_call).all_before('}')
259 // The panic helper is `@[noreturn]`, so the branch is closed off with
260 // `VUNREACHABLE();` (matching the other panic sites) instead of dead code
261 // after the noreturn call.
262 assert branch_tail.contains('VUNREACHABLE();')
263 }
264}
265
266fn test_imported_empty_interface_concat_does_not_emit_noop_array_cast_helper() {
267 os.chdir(vroot) or {}
268 path := os.join_path(vroot,
269 'vlib/v/tests/modules/interface_array_concat_from_another_module/main_test.v')
270 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'
271 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(path)}'
272 compilation := os.execute(cmd)
273 ensure_compilation_succeeded(compilation, cmd)
274 assert !compilation.output.contains(symbol)
275}
276
277fn test_comptime_for_empty_attrs_does_not_emit_new_array_calls() {
278 os.chdir(vroot) or {}
279 path := os.join_path(testdata_folder, 'comptime_for_empty_attrs_inline_init.vv')
280 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(path)}'
281 compilation := os.execute(cmd)
282 ensure_compilation_succeeded(compilation, cmd)
283 // regression for https://github.com/vlang/v/issues/27274
284 assert !compilation.output.contains('.attrs = builtin____new_array_with_default(0, 0, sizeof(string), 0)')
285 assert !compilation.output.contains('.attributes = builtin____new_array_with_default(0, 0, sizeof(VAttribute), 0)')
286 assert !compilation.output.contains('.args = builtin____new_array_with_default(0, 0, sizeof(FunctionParam), 0)')
287}
288
289fn test_array_push_no_bounds_checking_keeps_max_len_panics() {
290 os.chdir(vroot) or {}
291 test_source := os.join_path(os.vtmp_dir(), 'coutput_array_push_no_bounds_checking.vv')
292 source_lines := [
293 'module main',
294 '',
295 'fn main() {',
296 '\tmut names := []string{}',
297 "\tnames << 'alpha'",
298 '\tmut scores := []int{}',
299 '\tscores << 1',
300 '\tprintln(names.len + scores.len)',
301 '}',
302 ]
303 os.write_file(test_source, source_lines.join('\n') + '\n')!
304 defer {
305 os.rm(test_source) or {}
306 }
307 cmd := '${os.quoted_path(vexe)} -prod -no-bounds-checking -o - ${os.quoted_path(test_source)}'
308 compilation := os.execute(cmd)
309 ensure_compilation_succeeded(compilation, cmd)
310 assert compilation.output.contains('VV_LOC void builtin__array_push(array* a, voidptr val) {')
311 assert compilation.output.contains('VV_LOC void builtin__array_push_noscan(array* a, voidptr val) {')
312 assert !compilation.output.contains('array.push: negative len')
313 assert compilation.output.contains('array.push: len bigger than max_int')
314 assert !compilation.output.contains('array.push_noscan: negative len')
315 assert compilation.output.contains('array.push_noscan: len bigger than max_int')
316 || compilation.output.contains('builtin__array_push(a, val);')
317}
318
319fn test_windows_sharedlive_string_interpolation_in_ternary_does_not_emit_inline_tmp_decl() {
320 os.chdir(vroot) or {}
321 test_source := os.join_path(os.vtmp_dir(), 'coutput_live_windows_ternary_str_intp.vv')
322 os.write_file(test_source,
323 "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")!
324 defer {
325 os.rm(test_source) or {}
326 }
327 cmd := '${os.quoted_path(vexe)} -o - -os windows -sharedlive ${os.quoted_path(test_source)}'
328 compilation := os.execute(cmd)
329 ensure_compilation_succeeded(compilation, cmd)
330 mut normalized := compilation.output.replace('\t', ' ').replace('\n', ' ')
331 for normalized.contains(' ') {
332 normalized = normalized.replace(' ', ' ')
333 }
334 assert !normalized.contains('? ( string _t')
335 assert compilation.output.contains('builtin__str_intp')
336}
337
338fn test_simple_string_interpolation_does_not_emit_str_intp_runtime() {
339 os.chdir(vroot) or {}
340 test_source := os.join_path(os.vtmp_dir(), 'coutput_simple_interpolation_no_str_intp.vv')
341 os.write_file(test_source,
342 "module main\n\nimport time\n\nfn main() {\n\tt := time.now()\n\tprintln('elapsed \${time.since(t)}')\n}\n")!
343 defer {
344 os.rm(test_source) or {}
345 }
346 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(test_source)}'
347 compilation := os.execute(cmd)
348 ensure_compilation_succeeded(compilation, cmd)
349 assert !compilation.output.contains('builtin__str_intp')
350 assert !compilation.output.contains('StrIntpData')
351}
352
353fn test_auto_str_float_array_still_emits_str_intp_runtime() {
354 os.chdir(vroot) or {}
355 test_source := os.join_path(os.vtmp_dir(), 'coutput_float_array_str_intp.vv')
356 os.write_file(test_source, 'module main\n\nfn main() {\n\tprintln([f32(1.2), f32(3.4)])\n}\n')!
357 defer {
358 os.rm(test_source) or {}
359 }
360 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(test_source)}'
361 compilation := os.execute(cmd)
362 ensure_compilation_succeeded(compilation, cmd)
363 assert compilation.output.contains('builtin__str_intp')
364 assert compilation.output.contains('StrIntpData')
365}
366
367fn test_windows_tcc_atomic_postfix_uses_interlocked_helpers() {
368 os.chdir(vroot) or {}
369 cc := windows_tcc_ccompiler_for_coutput_test()
370 if cc == '' {
371 eprintln('> skipping ${@FN} since tcc is not available on windows')
372 return
373 }
374 test_source := os.join_path(os.vtmp_dir(), 'coutput_windows_tcc_atomic_postfix.vv')
375 os.write_file(test_source, 'module main
376
377struct App {
378mut:
379 idx atomic int
380}
381
382fn (mut app App) bump() {
383 app.idx++
384}
385
386fn main() {
387 mut app := App{}
388 app.bump()
389}
390')!
391 defer {
392 os.rm(test_source) or {}
393 }
394 cmd := '${os.quoted_path(vexe)} -o - -os windows -cc ${cc} ${os.quoted_path(test_source)}'
395 compilation := os.execute(cmd)
396 ensure_compilation_succeeded(compilation, cmd)
397 assert compilation.output.contains('thirdparty/stdatomic/win/atomic.h')
398 assert compilation.output.contains('InterlockedExchangeAdd')
399 assert !compilation.output.contains('__atomic_fetch_add')
400}
401
402fn test_windows_tcc_boehm_prod_does_not_emit_gc_remove_roots() {
403 os.chdir(vroot) or {}
404 cc := windows_tcc_ccompiler_for_coutput_test()
405 if cc == '' {
406 eprintln('> skipping ${@FN} since tcc is not available on windows')
407 return
408 }
409 test_source := os.join_path(os.vtmp_dir(), 'coutput_windows_tcc_boehm_scope_pin.vv')
410 os.write_file(test_source, "module main
411
412fn use_value(value string) {
413 println(value)
414}
415
416fn main() {
417 values := ['alpha', 'beta']
418 use_value(values[0])
419}
420")!
421 defer {
422 os.rm(test_source) or {}
423 }
424 cmd := '${os.quoted_path(vexe)} -o - -os windows -cc ${cc} -prod ${os.quoted_path(test_source)}'
425 compilation := os.execute(cmd)
426 ensure_compilation_succeeded(compilation, cmd)
427 assert !compilation.output.contains('GC_remove_roots(')
428}
429
430fn windows_tcc_ccompiler_for_coutput_test() string {
431 if user_os != 'windows' {
432 return 'x86_64-w64-mingw32-tcc'
433 }
434 bundled_tcc := os.join_path(vroot, 'thirdparty', 'tcc', 'tcc.exe')
435 if os.is_file(bundled_tcc) && os.is_executable(bundled_tcc) {
436 return os.quoted_path(bundled_tcc)
437 }
438 if os.find_abs_path_of_executable('tcc') or { '' } != '' {
439 return 'tcc'
440 }
441 return ''
442}
443
444fn test_no_main_exports_initialize_windows_runtime() {
445 os.chdir(vroot) or {}
446 test_source := os.join_path(os.vtmp_dir(), 'coutput_no_main_export_windows_init.vv')
447 os.write_file(test_source,
448 "module no_main\n\n@[export: 'v_sdl_app_quit']\npub fn app_quit() {}\n")!
449 defer {
450 os.rm(test_source) or {}
451 }
452 cmd := '${os.quoted_path(vexe)} -o - -os windows ${os.quoted_path(test_source)}'
453 compilation := os.execute(cmd)
454 ensure_compilation_succeeded(compilation, cmd)
455 generated_c_lines := compilation.output.split_into_lines()
456 expected_lines := [
457 'static void _vno_main_init_caller(void);',
458 'static void _vno_main_cleanup_caller(void);',
459 'void v_sdl_app_quit(void) {',
460 '_vno_main_init_caller();',
461 'void _vinit(int ___argc, voidptr ___argv) {',
462 'static bool once = false; if (once) {return;} once = true;',
463 'void _vcleanup(void) {',
464 'static void _vno_main_cleanup_caller(void) {',
465 'static void _vno_main_init_caller(void) {',
466 'con_valid = AttachConsole(ATTACH_PARENT_PROCESS);',
467 'err = freopen_s(&res_fp, "NUL", "w", stdout);',
468 '_vinit(0,0);',
469 'atexit(_vno_main_cleanup_caller);',
470 ]
471 for expected_line in expected_lines {
472 assert does_line_match_one_of_generated_lines(expected_line, generated_c_lines)
473 }
474}
475
476fn test_coverage_output_checks_counter_file_open() {
477 os.chdir(vroot) or {}
478 test_source := os.join_path(os.vtmp_dir(), 'coutput_coverage_file_guard.vv')
479 os.write_file(test_source, 'fn main() {\n\tprintln("coverage")\n}\n')!
480 defer {
481 os.rm(test_source) or {}
482 }
483 coverage_dir := os.join_path(os.vtmp_dir(), 'coutput_coverage')
484 cmd := '${os.quoted_path(vexe)} -o - -coverage ${os.quoted_path(coverage_dir)} ${os.quoted_path(test_source)}'
485 compilation := os.execute(cmd)
486 ensure_compilation_succeeded(compilation, cmd)
487 assert compilation.output.contains('FILE *fp = fopen(cov_filename, "wb+");')
488 assert compilation.output.contains('if (fp == NULL) { return; }')
489 assert compilation.output.contains('nsecs = ts.tv_nsec;')
490 assert !compilation.output.contains('\nsecs = ts.tv_nsec;')
491}
492
493fn test_c_fallback_decl_uses_module_wide_c_includes() {
494 os.chdir(vroot) or {}
495 test_source := os.join_path(os.vtmp_dir(), 'coutput_module_c_include')
496 os.rmdir_all(test_source) or {}
497 os.mkdir_all(test_source)!
498 defer {
499 os.rmdir_all(test_source) or {}
500 }
501 header_path := os.join_path(test_source, 'c_header_decl.h')
502 os.write_file(header_path, 'int c_header_decl(const char* input);\n')!
503 header_include_path := header_path.replace('\\', '/')
504 os.write_file(os.join_path(test_source, 'include.v'), 'module main
505
506#include "${header_include_path}"
507')!
508 os.write_file(os.join_path(test_source, 'decl.v'), "module main
509
510fn C.c_header_decl(input &char) int
511
512fn main() {
513 C.c_header_decl(c'text')
514}
515")!
516 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(test_source)}'
517 compilation := os.execute(cmd)
518 ensure_compilation_succeeded(compilation, cmd)
519 assert !compilation.output.contains('extern int c_header_decl(')
520}
521
522fn test_c_fallback_decl_uses_c_helper_submodule_includes() {
523 test_source := os.join_path(os.vtmp_dir(), 'coutput_c_helper_include')
524 module_path := os.join_path(test_source, 'coutput_sdl')
525 c_module_path := os.join_path(module_path, 'c')
526 os.rmdir_all(test_source) or {}
527 os.mkdir_all(c_module_path)!
528 defer {
529 os.rmdir_all(test_source) or {}
530 }
531 header_path := os.join_path(c_module_path, 'c_helper_decl.h')
532 os.write_file(header_path,
533 ['#include <stdbool.h>', 'typedef enum { false_value, true_value } foreign_bool;', 'foreign_bool c_helper_decl(void);'].join('\n') +
534 '\n')!
535 header_include_path := header_path.replace('\\', '/')
536 os.write_file(os.join_path(c_module_path, 'c.c.v'), 'module c
537
538pub const used_import = 1
539
540#include "${header_include_path}"
541')!
542 os.write_file(os.join_path(module_path, 'coutput_sdl.v'), 'module coutput_sdl
543
544import coutput_sdl.c
545
546pub const used_import = c.used_import
547')!
548 os.write_file(os.join_path(module_path, 'atomic.c.v'), 'module coutput_sdl
549
550fn C.c_helper_decl() bool
551
552pub fn call() {
553 _ = C.c_helper_decl()
554}
555')!
556 old_wd := os.getwd()
557 os.chdir(test_source) or {}
558 defer {
559 os.chdir(old_wd) or {}
560 }
561 cmd := '${os.quoted_path(vexe)} -shared -o - coutput_sdl'
562 compilation := os.execute(cmd)
563 ensure_compilation_succeeded(compilation, cmd)
564 assert compilation.output.contains('#include "${header_include_path}"')
565 assert !compilation.output.contains('extern bool c_helper_decl(')
566}
567
568fn test_user_defined_windows_dllmain_disables_generated_entrypoint() {
569 os.chdir(vroot) or {}
570 test_source := os.join_path(os.vtmp_dir(), 'coutput_user_defined_windows_dllmain.vv')
571 os.write_file(test_source,
572 ['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') +
573 '\n')!
574 defer {
575 os.rm(test_source) or {}
576 }
577 cmd := '${os.quoted_path(vexe)} -o - -os windows -shared -gc boehm ${os.quoted_path(test_source)}'
578 compilation := os.execute(cmd)
579 ensure_compilation_succeeded(compilation, cmd)
580 assert compilation.output.contains('void _vinit_caller() {')
581 assert compilation.output.contains('GC_set_pages_executable(0);')
582 // The shared-library GC tuning (issue #27555) must stay guarded, so loading
583 // the library into an already-GC-initialized host does not clobber the host's
584 // process-wide free-space divisor (its local GC_INIT() would be a no-op).
585 assert compilation.output.contains('if (!GC_is_init_called()) {')
586 assert compilation.output.contains('GC_INIT();')
587 assert compilation.output.contains('DllMain(')
588 assert compilation.output.contains('_vinit_caller();')
589 assert compilation.output.contains('_vcleanup_caller();')
590 assert !compilation.output.contains('switch (fdwReason)')
591 assert !compilation.output.contains('case DLL_PROCESS_ATTACH')
592}
593
594fn test_array_sort_with_compare_uses_stable_sort_adapters() {
595 os.chdir(vroot) or {}
596 test_source := os.join_path(os.vtmp_dir(), 'coutput_array_sort_with_compare_stable_sort.vv')
597 source_lines := [
598 'module main',
599 '',
600 'struct Foo {',
601 '\tx int',
602 '}',
603 '',
604 'fn by_x(a &Foo, b &Foo) int {',
605 '\treturn a.x - b.x',
606 '}',
607 '',
608 'fn main() {',
609 '\tmut xs := [Foo{ x: 2 }, Foo{ x: 1 }]',
610 '\txs.sort_with_compare(by_x)',
611 '\tmut ys := [Foo{ x: 2 }, Foo{ x: 1 }]!',
612 '\tys.sort_with_compare(by_x)',
613 '\tmut zs := [Foo{ x: 2 }, Foo{ x: 1 }]',
614 '\tzs.sort(a.x < b.x)',
615 '}',
616 ]
617 os.write_file(test_source, source_lines.join('\n') + '\n')!
618 defer {
619 os.rm(test_source) or {}
620 }
621 cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(test_source)}'
622 compilation := os.execute(cmd)
623 ensure_compilation_succeeded(compilation, cmd)
624 mut normalized := compilation.output.replace('\t', ' ').replace('\n', ' ')
625 for normalized.contains(' ') {
626 normalized = normalized.replace(' ', ' ')
627 }
628 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); }')
629 assert normalized.contains('if (xs.len > 0) { v_stable_sort(xs.data, xs.len, xs.element_size, main__by_x_qsort_adapter); }')
630 assert normalized.contains('v_stable_sort(&ys, 2, sizeof(main__Foo), main__by_x_qsort_adapter);')
631 assert normalized.contains('_qsort_adapter(const void* a, const void* b) { return compare_')
632 assert normalized.contains('v_stable_sort(zs.data, zs.len, zs.element_size, compare_')
633 assert normalized.contains('_qsort_adapter);')
634}
635
636fn test_array_sort_expression_key_avoids_sanitized_name_collisions() {
637 os.chdir(vroot) or {}
638 test_dir := os.join_path(os.vtmp_dir(), 'coutput_array_sort_expr_collision_${os.getpid()}')
639 os.mkdir_all(test_dir)!
640 defer {
641 os.rmdir_all(test_dir) or {}
642 }
643 os.write_file(os.join_path(test_dir, 'main.v'),
644 ['module main', '', 'struct Inner {', '\tbar int', '}', '', 'struct Item {', '\tfoo Inner', '\tfoo_bar int', '\tlabel string', '}', '', 'fn main() {', '\tprintln(sort_by_nested())', '\tprintln(sort_by_flat())', '}'].join('\n') +
645 '\n')!
646 os.write_file(os.join_path(test_dir, 'nested.v'),
647 ['module main', '', 'fn sort_by_nested() string {', "\tmut items := [Item{ foo: Inner{ bar: 2 }, foo_bar: 1, label: 'nested-wrong' }, Item{ foo: Inner{ bar: 1 }, foo_bar: 2, label: 'nested-ok' }]", '\titems.sort(a.foo.bar < b.foo.bar)', '\treturn items[0].label', '}'].join('\n') +
648 '\n')!
649 os.write_file(os.join_path(test_dir, 'flat.v'),
650 ['module main', '', 'fn sort_by_flat() string {', "\tmut items := [Item{ foo: Inner{ bar: 1 }, foo_bar: 2, label: 'flat-wrong' }, Item{ foo: Inner{ bar: 2 }, foo_bar: 1, label: 'flat-ok' }]", '\titems.sort(a.foo_bar < b.foo_bar)', '\treturn items[0].label', '}'].join('\n') +
651 '\n')!
652 pexe := os.join_path(test_dir, 'sort_expr_collision')
653 cmd := '${os.quoted_path(vexe)} -o ${os.quoted_path(pexe)} ${os.quoted_path(test_dir)}'
654 compilation := os.execute(cmd)
655 ensure_compilation_succeeded(compilation, cmd)
656 res := os.execute(os.quoted_path(pexe))
657 assert res.exit_code == 0
658 assert res.output.trim_space().replace('\r\n', '\n') == 'nested-ok\nflat-ok'
659}
660
661// generated_c_symbol_with_prefix extracts one generated C symbol from compiler output.
662fn generated_c_symbol_with_prefix(output string, prefix string) string {
663 idx := output.index(prefix) or { return '' }
664 rest := output[idx..]
665 mut end := 0
666 for end < rest.len && (rest[end].is_letter() || rest[end].is_digit() || rest[end] == `_`) {
667 end++
668 }
669 return rest[..end]
670}
671
672fn test_auxiliary_c_symbols_use_stable_type_hashes() {
673 os.chdir(vroot) or {}
674 test_dir := os.join_path(os.vtmp_dir(), 'coutput_stable_type_symbol_hash_${os.getpid()}')
675 dir_a := os.join_path(test_dir, 'a')
676 dir_b := os.join_path(test_dir, 'b')
677 os.mkdir_all(dir_a)!
678 os.mkdir_all(dir_b)!
679 defer {
680 os.rmdir_all(test_dir) or {}
681 }
682 source_lines := [
683 'module main',
684 '',
685 'struct Item {',
686 '\tname string',
687 '\trank int',
688 '}',
689 '',
690 'fn main() {',
691 "\tmut items := [Item{'b', 2}, Item{'a', 1}, Item{'c', 3}]",
692 '\titems.sort(a.rank < b.rank)',
693 '\tmut nested := [items]',
694 '\tprintln("\${nested[0][0].name}")',
695 '}',
696 ]
697 source := source_lines.join('\n') + '\n'
698 path_a := os.join_path(dir_a, 'main.v')
699 path_b := os.join_path(dir_b, 'main.v')
700 os.write_file(path_a, source)!
701 os.write_file(path_b, source)!
702 cmd_a := '${os.quoted_path(vexe)} -prod -gc boehm_full_opt -o - ${os.quoted_path(path_a)}'
703 cmd_b := '${os.quoted_path(vexe)} -prod -gc boehm_full_opt -o - ${os.quoted_path(path_b)}'
704 compilation_a := os.execute(cmd_a)
705 compilation_b := os.execute(cmd_b)
706 ensure_compilation_succeeded(compilation_a, cmd_a)
707 ensure_compilation_succeeded(compilation_b, cmd_b)
708 compare_a := generated_c_symbol_with_prefix(compilation_a.output, 'compare_')
709 compare_b := generated_c_symbol_with_prefix(compilation_b.output, 'compare_')
710 keepalive_a := generated_c_symbol_with_prefix(compilation_a.output,
711 '__v_boehm_collect_keepalive_')
712 keepalive_b := generated_c_symbol_with_prefix(compilation_b.output,
713 '__v_boehm_collect_keepalive_')
714 assert compare_a != ''
715 assert keepalive_a != ''
716 assert compare_a == compare_b
717 assert keepalive_a == keepalive_b
718}
719
720fn test_veb_implicit_ctx_alias_uses_user_context_name() {
721 os.chdir(vroot) or {}
722 test_source := os.join_path(os.vtmp_dir(), 'coutput_veb_implicit_ctx_alias.vv')
723 os.write_file(test_source,
724 ['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') +
725 '\n')!
726 defer {
727 os.rm(test_source) or {}
728 }
729 cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o - ${os.quoted_path(test_source)}'
730 compilation := os.execute(cmd)
731 ensure_compilation_succeeded(compilation, cmd)
732 mut normalized := compilation.output.replace('\t', ' ').replace('\n', ' ')
733 for normalized.contains(' ') {
734 normalized = normalized.replace(' ', ' ')
735 }
736 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); }')
737}
738
739fn test_veb_implicit_ctx_alias_on_context_receiver_tmpl_not_found() {
740 if github_job.contains('musl') {
741 eprintln('> skipping ${@FN} on ${github_job}, since `-gc boehm_full_opt` links `__pthread_unregister_cancel`, which musl does not provide')
742 return
743 }
744 os.chdir(vroot) or {}
745 test_dir := os.join_path(os.vtmp_dir(), 'coutput_veb_context_receiver_tmpl_not_found')
746 os.rmdir_all(test_dir) or {}
747 os.mkdir_all(os.join_path(test_dir, 'web'))!
748 test_source := os.join_path(test_dir, 'main.v')
749 os.write_file(os.join_path(test_dir, 'web', 'notfound.html'), '<h1>@ctx.req.url</h1>\n')!
750 os.write_file(test_source,
751 ['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') +
752 '\n')!
753 defer {
754 os.rmdir_all(test_dir) or {}
755 }
756 test_exe := os.join_path(test_dir, 'app')
757 compile_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o ${os.quoted_path(test_exe)} ${os.quoted_path(test_source)}'
758 ensure_compilation_succeeded(os.execute(compile_cmd), compile_cmd)
759 c_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o - ${os.quoted_path(test_source)}'
760 compilation := os.execute(c_cmd)
761 ensure_compilation_succeeded(compilation, c_cmd)
762 not_found_start := 'veb__Result main__Context_not_found(main__Context* c) {'
763 assert compilation.output.contains(not_found_start)
764 not_found_body :=
765 compilation.output.all_after(not_found_start).all_before('VV_LOC void main__main')
766 assert !not_found_body.contains('GC_reachable_here(&ctx);')
767 assert not_found_body.contains('GC_reachable_here(&c);')
768 assert not_found_body.contains('return veb__Context_html(&c->Context, _tmpl_res_')
769}
770
771fn test_veb_template_scope_gc_pin_does_not_escape_loop_var() {
772 if github_job.contains('musl') {
773 eprintln('> skipping ${@FN} on ${github_job}, since `-gc boehm_full_opt` links `__pthread_unregister_cancel`, which musl does not provide')
774 return
775 }
776 os.chdir(vroot) or {}
777 test_dir := os.join_path(os.vtmp_dir(), 'coutput_veb_template_scope_gc_pin')
778 os.rmdir_all(test_dir) or {}
779 os.mkdir_all(os.join_path(test_dir, 'templates'))!
780 template_lines := [
781 '<div class="tree-path">',
782 ' @if is_top_directory',
783 ' @for i, p in ctx.parts',
784 ' <a href="/@repo.user_name/@{ctx.make_path(branch_name, i)}">@p</a>',
785 ' @end',
786 ' @end',
787 ' @if is_repo_watcher',
788 ' <span>@watcher_count</span>',
789 ' @end',
790 '</div>',
791 ]
792 os.write_file(os.join_path(test_dir, 'templates', 'tree.html'),
793 template_lines.join('\n') + '\n')!
794 test_source := os.join_path(test_dir, 'main.v')
795 source_lines := [
796 'module main',
797 '',
798 'import veb',
799 '',
800 'pub struct Context {',
801 '\tveb.Context',
802 'pub mut:',
803 '\tparts []string',
804 '}',
805 '',
806 'pub struct App {}',
807 '',
808 'pub struct Repo {',
809 '\tuser_name string',
810 '}',
811 '',
812 'pub fn (ctx &Context) make_path(branch_name string, i int) string {',
813 '\treturn branch_name + i.str()',
814 '}',
815 '',
816 'pub fn (mut app App) index(mut ctx Context) veb.Result {',
817 "\trepo := Repo{ user_name: 'gitly' }",
818 "\tbranch_name := 'master'",
819 '\tis_top_directory := true',
820 '\tis_repo_watcher := false',
821 '\twatcher_count := 0',
822 "\treturn \$veb.html('templates/tree.html')",
823 '}',
824 '',
825 'fn main() {',
826 '\tmut app := App{}',
827 "\tmut ctx := Context{ parts: ['src'] }",
828 '\t_ = app.index(mut ctx)',
829 '}',
830 ]
831 os.write_file(test_source, source_lines.join('\n') + '\n')!
832 defer {
833 os.rmdir_all(test_dir) or {}
834 }
835 test_exe := os.join_path(test_dir, 'app')
836 compile_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o ${os.quoted_path(test_exe)} ${os.quoted_path(test_source)}'
837 ensure_compilation_succeeded(os.execute(compile_cmd), compile_cmd)
838 c_cmd := '${os.quoted_path(vexe)} -gc boehm_full_opt -o - ${os.quoted_path(test_source)}'
839 compilation := os.execute(c_cmd)
840 ensure_compilation_succeeded(compilation, c_cmd)
841 index_start := 'veb__Result main__App_index(main__App* app, main__Context* ctx) {'
842 assert compilation.output.contains(index_start)
843 index_body := compilation.output.all_after(index_start).all_before('VV_LOC void main__main')
844 assert index_body.contains('string p =')
845 assert !index_body.contains('GC_reachable_here(&p);')
846 assert index_body.contains('veb__Context_html(&ctx->Context, _tmpl_res_')
847}
848
849fn does_line_match_one_of_generated_lines(line string, generated_c_lines []string) bool {
850 for cline in generated_c_lines {
851 if line == cline {
852 return true
853 }
854 if cline.contains(line) {
855 return true
856 }
857 }
858 return false
859}
860
861fn normalize_panic_message(message string, vroot string) string {
862 mut msg := message.all_before('=========================================')
863 // change windows to nix path
864 s := vroot.replace(os.path_separator, '/')
865 msg = msg.replace(s + '/', '')
866 msg = msg.trim_space()
867 return msg
868}
869
870fn vroot_relative(opath string) string {
871 nvroot := vroot.replace(os.path_separator, '/') + '/'
872 npath := opath.replace(os.path_separator, '/')
873 return npath.replace(nvroot, '')
874}
875
876fn ensure_compilation_succeeded(compilation os.Result, cmd string) {
877 if compilation.exit_code < 0 {
878 eprintln('> cmd exit_code < 0, cmd: ${cmd}')
879 panic(compilation.output)
880 }
881 if compilation.exit_code != 0 {
882 eprintln('> cmd exit_code != 0, cmd: ${cmd}')
883 panic('compilation failed: ${compilation.output}')
884 }
885}
886
887fn target2paths(target_path string, postfix string) (string, string, string, string) {
888 basename := os.file_name(target_path).replace(postfix, '')
889 target_dir := os.dir(target_path)
890 path := os.join_path(target_dir, '${basename}.vv')
891 relpath := vroot_relative(path)
892 target_relpath := vroot_relative(target_path)
893 return basename, path, relpath, target_relpath
894}
895
896struct FileOptions {
897mut:
898 vflags string
899}
900
901pub fn get_file_options(file string) FileOptions {
902 mut res := FileOptions{}
903 lines := os.read_lines(file) or { [] }
904 for line in lines {
905 if line.starts_with('// vtest vflags:') {
906 res.vflags = line.all_after(':').trim_space()
907 }
908 }
909 return res
910}
911
912const github_job = os.getenv('GITHUB_JOB')
913
914fn should_skip(relpath string) bool {
915 if github_job.contains('musl') && relpath.ends_with('autofree_sql_or_block.vv') {
916 eprintln('> skipping ${relpath} on ${github_job}, since it uses db.sqlite, and its headers are not available to the C compiler in that environment')
917 return true
918 }
919 if github_job.contains('musl') && (relpath.ends_with('print_boehm_leak.vv')
920 || relpath.ends_with('scope_cleanup_boehm_leak.vv')
921 || relpath.ends_with('gc_debugger_linux.vv')
922 || relpath.ends_with('heap_struct_init_order.vv')) {
923 eprintln('> skipping ${relpath} on ${github_job}, since gc related tests are not compatible with `-gc none`')
924 return true
925 }
926 if github_job.contains('musl')
927 && relpath.ends_with('nested_aggregate_boehm_prod_keep_alive_nix.vv') {
928 eprintln('> skipping ${relpath} on ${github_job}, since `-prod -gc boehm_full_opt` pulls in `getcontext`, which musl does not provide')
929 return true
930 }
931 if user_os == 'windows' {
932 if relpath.contains('_nix.vv') {
933 eprintln('> skipping ${relpath} on windows')
934 return true
935 }
936 $if tinyc {
937 if relpath.ends_with('boehm_keepalive_c_union_tag.vv') {
938 eprintln('> skipping ${relpath} on windows-tcc, since deep GC scope pins (and thus their keepalive helpers) are intentionally not emitted there: the bundled Windows tcc libgc archive lacks GC_remove_roots()')
939 return true
940 }
941 }
942 $if !msvc {
943 if relpath.contains('_msvc_windows.vv') {
944 eprintln('> skipping ${relpath} on !msvc')
945 return true
946 }
947 }
948 $if !gcc {
949 if relpath.contains('_gcc_windows.vv') {
950 eprintln('> skipping ${relpath} on !gcc')
951 return true
952 }
953 }
954 $if msvc {
955 if relpath.contains('_not_msvc_windows.vv') {
956 eprintln('> skipping ${relpath} on msvc')
957 return true
958 }
959 if relpath.ends_with('cross_printfn_v_malloc.vv') {
960 eprintln('> skipping ${relpath} on msvc, since -cross -printfn does not emit a runnable executable')
961 return true
962 }
963 if relpath.contains('asm_') {
964 eprintln('> skipping ${relpath} on msvc, since it uses gcc-style inline asm')
965 return true
966 }
967 }
968 } else {
969 if relpath.contains('_windows.vv') {
970 eprintln('> skipping ${relpath} on !windows')
971 return true
972 }
973 }
974 if relpath.contains('freestanding_') {
975 $if !amd64 {
976 // https://github.com/vlang/v/issues/23397
977 eprintln('> skipping ${relpath} on != amd64')
978 return true
979 }
980 if user_os != 'linux' {
981 eprintln('> skipping ${relpath} on != linux')
982 return true
983 }
984 if gcc_path == '' {
985 eprintln('> skipping ${relpath} since it needs gcc, which is not detected')
986 return true
987 }
988 }
989 if user_os == 'macos' {
990 $if arm64 {
991 if relpath.ends_with('spawn_stack_nix.vv') {
992 eprintln('> skipping ${relpath} on macOS arm64, since i386 linking is unavailable')
993 return true
994 }
995 }
996 }
997 if gcc_path == '' {
998 test_path := os.join_path(vroot, relpath)
999 file_options := get_file_options(test_path)
1000 if file_options.vflags.contains('-cc gcc') {
1001 eprintln('> skipping ${relpath} since its vflags require gcc, which is not detected')
1002 return true
1003 }
1004 }
1005 if github_job in ['tcc-windows', 'msvc-windows'] {
1006 test_path := os.join_path(vroot, relpath)
1007 file_options := get_file_options(test_path)
1008 if file_options.vflags.contains('-cc clang') {
1009 // The Windows runner's clang toolchain produces V binaries that
1010 // crash at startup on this CI; the test is still exercised by
1011 // the macOS/Linux jobs.
1012 eprintln('> skipping ${relpath} on ${github_job}, since `-cc clang` produces unstable binaries on this runner')
1013 return true
1014 }
1015 }
1016 return false
1017}
1018
1019@[if !silent ?]
1020fn vprintln(msg string) {
1021 println(msg)
1022}
1023