| 1 | // vtest build: !self_sandboxed_packaging? && !sanitized_job? |
| 2 | import os |
| 3 | import time |
| 4 | import term |
| 5 | import v.util.diff |
| 6 | import v.util.vtest |
| 7 | |
| 8 | const vexe = @VEXE |
| 9 | |
| 10 | const vroot = os.real_path(@VMODROOT) |
| 11 | |
| 12 | const local_tdata_path = 'vlib/v/gen/c/testdata' |
| 13 | |
| 14 | const testdata_folder = os.real_path(os.join_path(vroot, local_tdata_path)) |
| 15 | |
| 16 | const show_compilation_output = os.getenv('VTEST_SHOW_COMPILATION_OUTPUT').int() == 1 |
| 17 | |
| 18 | const user_os = os.user_os() |
| 19 | |
| 20 | const gcc_path = os.find_abs_path_of_executable('gcc') or { '' } |
| 21 | |
| 22 | fn mm(s string) string { |
| 23 | return term.colorize(term.magenta, s) |
| 24 | } |
| 25 | |
| 26 | fn mj(input ...string) string { |
| 27 | return mm(input.filter(it.len > 0).join(' ')) |
| 28 | } |
| 29 | |
| 30 | fn 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 | |
| 130 | fn 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 | |
| 213 | fn 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 | |
| 239 | fn 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 | |
| 250 | fn 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 | |
| 269 | fn 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 | |
| 279 | struct App { |
| 280 | mut: |
| 281 | idx atomic int |
| 282 | } |
| 283 | |
| 284 | fn (mut app App) bump() { |
| 285 | app.idx++ |
| 286 | } |
| 287 | |
| 288 | fn 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 | |
| 304 | fn 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 | |
| 318 | fn 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 | |
| 350 | fn 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 | |
| 367 | fn C.c_header_decl(input &char) int |
| 368 | |
| 369 | fn 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 | |
| 379 | fn 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 | |
| 395 | pub 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 | |
| 401 | import coutput_sdl.c |
| 402 | |
| 403 | pub const used_import = c.used_import |
| 404 | ')! |
| 405 | os.write_file(os.join_path(module_path, 'atomic.c.v'), 'module coutput_sdl |
| 406 | |
| 407 | fn C.c_helper_decl() bool |
| 408 | |
| 409 | pub 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 | |
| 425 | fn 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 | |
| 447 | fn 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 | |
| 489 | fn 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 | |
| 508 | fn 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 | |
| 536 | fn 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 | |
| 610 | fn 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 | |
| 622 | fn 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 | |
| 631 | fn 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 | |
| 637 | fn 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 | |
| 648 | fn 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 | |
| 657 | struct FileOptions { |
| 658 | mut: |
| 659 | vflags string |
| 660 | } |
| 661 | |
| 662 | pub 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 | |
| 673 | const github_job = os.getenv('GITHUB_JOB') |
| 674 | |
| 675 | fn 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 ?] |
| 769 | fn vprintln(msg string) { |
| 770 | println(msg) |
| 771 | } |
| 772 | |