blob 9cf4e7bd (39260B) - Raw
1 const std = @import("std"); 2 const builtin = std.builtin; 3 const debug = std.debug; 4 const warn = debug.warn; 5 const build = std.build; 6 const CrossTarget = std.zig.CrossTarget; 7 const Buffer = std.Buffer; 8 const io = std.io; 9 const fs = std.fs; 10 const mem = std.mem; 11 const fmt = std.fmt; 12 const ArrayList = std.ArrayList; 13 const Mode = builtin.Mode; 14 const LibExeObjStep = build.LibExeObjStep; 15 16 // Cases 17 const compare_output = @import("compare_output.zig"); 18 const standalone = @import("standalone.zig"); 19 const stack_traces = @import("stack_traces.zig"); 20 const compile_errors = @import("compile_errors.zig"); 21 const assemble_and_link = @import("assemble_and_link.zig"); 22 const runtime_safety = @import("runtime_safety.zig"); 23 const translate_c = @import("translate_c.zig"); 24 const run_translated_c = @import("run_translated_c.zig"); 25 const gen_h = @import("gen_h.zig"); 26 27 // Implementations 28 pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; 29 pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext; 30 pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext; 31 32 const TestTarget = struct { 33 target: CrossTarget = @as(CrossTarget, .{}), 34 mode: builtin.Mode = .Debug, 35 link_libc: bool = false, 36 single_threaded: bool = false, 37 disable_native: bool = false, 38 }; 39 40 const test_targets = blk: { 41 // getBaselineCpuFeatures calls populateDependencies which has a O(N ^ 2) algorithm 42 // (where N is roughly 160, which technically makes it O(1), but it adds up to a 43 // lot of branches) 44 @setEvalBranchQuota(50000); 45 break :blk [_]TestTarget{ 46 TestTarget{}, 47 TestTarget{ 48 .link_libc = true, 49 }, 50 TestTarget{ 51 .single_threaded = true, 52 }, 53 54 TestTarget{ 55 .target = .{ 56 .cpu_arch = .x86_64, 57 .os_tag = .linux, 58 .abi = .none, 59 }, 60 }, 61 TestTarget{ 62 .target = .{ 63 .cpu_arch = .x86_64, 64 .os_tag = .linux, 65 .abi = .gnu, 66 }, 67 .link_libc = true, 68 }, 69 TestTarget{ 70 .target = .{ 71 .cpu_arch = .x86_64, 72 .os_tag = .linux, 73 .abi = .musl, 74 }, 75 .link_libc = true, 76 }, 77 78 TestTarget{ 79 .target = .{ 80 .cpu_arch = .i386, 81 .os_tag = .linux, 82 .abi = .none, 83 }, 84 }, 85 TestTarget{ 86 .target = .{ 87 .cpu_arch = .i386, 88 .os_tag = .linux, 89 .abi = .musl, 90 }, 91 .link_libc = true, 92 }, 93 94 TestTarget{ 95 .target = .{ 96 .cpu_arch = .aarch64, 97 .os_tag = .linux, 98 .abi = .none, 99 }, 100 }, 101 TestTarget{ 102 .target = .{ 103 .cpu_arch = .aarch64, 104 .os_tag = .linux, 105 .abi = .musl, 106 }, 107 .link_libc = true, 108 }, 109 TestTarget{ 110 .target = .{ 111 .cpu_arch = .aarch64, 112 .os_tag = .linux, 113 .abi = .gnu, 114 }, 115 .link_libc = true, 116 }, 117 118 TestTarget{ 119 .target = CrossTarget.parse(.{ 120 .arch_os_abi = "arm-linux-none", 121 .cpu_features = "generic+v8a", 122 }) catch unreachable, 123 }, 124 TestTarget{ 125 .target = CrossTarget.parse(.{ 126 .arch_os_abi = "arm-linux-musleabihf", 127 .cpu_features = "generic+v8a", 128 }) catch unreachable, 129 .link_libc = true, 130 }, 131 // TODO https://github.com/ziglang/zig/issues/3287 132 //TestTarget{ 133 // .target = CrossTarget.parse(.{ 134 // .arch_os_abi = "arm-linux-gnueabihf", 135 // .cpu_features = "generic+v8a", 136 // }) catch unreachable, 137 // .link_libc = true, 138 //}, 139 140 TestTarget{ 141 .target = .{ 142 .cpu_arch = .mipsel, 143 .os_tag = .linux, 144 .abi = .none, 145 }, 146 }, 147 TestTarget{ 148 .target = .{ 149 .cpu_arch = .mipsel, 150 .os_tag = .linux, 151 .abi = .musl, 152 }, 153 .link_libc = true, 154 }, 155 156 TestTarget{ 157 .target = .{ 158 .cpu_arch = .x86_64, 159 .os_tag = .macosx, 160 .abi = .gnu, 161 }, 162 // TODO https://github.com/ziglang/zig/issues/3295 163 .disable_native = true, 164 }, 165 166 TestTarget{ 167 .target = .{ 168 .cpu_arch = .i386, 169 .os_tag = .windows, 170 .abi = .msvc, 171 }, 172 }, 173 174 TestTarget{ 175 .target = .{ 176 .cpu_arch = .x86_64, 177 .os_tag = .windows, 178 .abi = .msvc, 179 }, 180 }, 181 182 TestTarget{ 183 .target = .{ 184 .cpu_arch = .i386, 185 .os_tag = .windows, 186 .abi = .gnu, 187 }, 188 .link_libc = true, 189 }, 190 191 TestTarget{ 192 .target = .{ 193 .cpu_arch = .x86_64, 194 .os_tag = .windows, 195 .abi = .gnu, 196 }, 197 .link_libc = true, 198 }, 199 200 // Do the release tests last because they take a long time 201 TestTarget{ 202 .mode = .ReleaseFast, 203 }, 204 TestTarget{ 205 .link_libc = true, 206 .mode = .ReleaseFast, 207 }, 208 TestTarget{ 209 .mode = .ReleaseFast, 210 .single_threaded = true, 211 }, 212 213 TestTarget{ 214 .mode = .ReleaseSafe, 215 }, 216 TestTarget{ 217 .link_libc = true, 218 .mode = .ReleaseSafe, 219 }, 220 TestTarget{ 221 .mode = .ReleaseSafe, 222 .single_threaded = true, 223 }, 224 225 TestTarget{ 226 .mode = .ReleaseSmall, 227 }, 228 TestTarget{ 229 .link_libc = true, 230 .mode = .ReleaseSmall, 231 }, 232 TestTarget{ 233 .mode = .ReleaseSmall, 234 .single_threaded = true, 235 }, 236 }; 237 }; 238 239 const max_stdout_size = 1 * 1024 * 1024; // 1 MB 240 241 pub fn addCompareOutputTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { 242 const cases = b.allocator.create(CompareOutputContext) catch unreachable; 243 cases.* = CompareOutputContext{ 244 .b = b, 245 .step = b.step("test-compare-output", "Run the compare output tests"), 246 .test_index = 0, 247 .test_filter = test_filter, 248 .modes = modes, 249 }; 250 251 compare_output.addCases(cases); 252 253 return cases.step; 254 } 255 256 pub fn addStackTraceTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { 257 const cases = b.allocator.create(StackTracesContext) catch unreachable; 258 cases.* = StackTracesContext{ 259 .b = b, 260 .step = b.step("test-stack-traces", "Run the stack trace tests"), 261 .test_index = 0, 262 .test_filter = test_filter, 263 .modes = modes, 264 }; 265 266 stack_traces.addCases(cases); 267 268 return cases.step; 269 } 270 271 pub fn addRuntimeSafetyTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { 272 const cases = b.allocator.create(CompareOutputContext) catch unreachable; 273 cases.* = CompareOutputContext{ 274 .b = b, 275 .step = b.step("test-runtime-safety", "Run the runtime safety tests"), 276 .test_index = 0, 277 .test_filter = test_filter, 278 .modes = modes, 279 }; 280 281 runtime_safety.addCases(cases); 282 283 return cases.step; 284 } 285 286 pub fn addCompileErrorTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { 287 const cases = b.allocator.create(CompileErrorContext) catch unreachable; 288 cases.* = CompileErrorContext{ 289 .b = b, 290 .step = b.step("test-compile-errors", "Run the compile error tests"), 291 .test_index = 0, 292 .test_filter = test_filter, 293 .modes = modes, 294 }; 295 296 compile_errors.addCases(cases); 297 298 return cases.step; 299 } 300 301 pub fn addStandaloneTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { 302 const cases = b.allocator.create(StandaloneContext) catch unreachable; 303 cases.* = StandaloneContext{ 304 .b = b, 305 .step = b.step("test-standalone", "Run the standalone tests"), 306 .test_index = 0, 307 .test_filter = test_filter, 308 .modes = modes, 309 }; 310 311 standalone.addCases(cases); 312 313 return cases.step; 314 } 315 316 pub fn addCliTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { 317 const step = b.step("test-cli", "Test the command line interface"); 318 319 const exe = b.addExecutable("test-cli", "test/cli.zig"); 320 const run_cmd = exe.run(); 321 run_cmd.addArgs(&[_][]const u8{ 322 fs.realpathAlloc(b.allocator, b.zig_exe) catch unreachable, 323 b.pathFromRoot(b.cache_root), 324 }); 325 326 step.dependOn(&run_cmd.step); 327 return step; 328 } 329 330 pub fn addAssembleAndLinkTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { 331 const cases = b.allocator.create(CompareOutputContext) catch unreachable; 332 cases.* = CompareOutputContext{ 333 .b = b, 334 .step = b.step("test-asm-link", "Run the assemble and link tests"), 335 .test_index = 0, 336 .test_filter = test_filter, 337 .modes = modes, 338 }; 339 340 assemble_and_link.addCases(cases); 341 342 return cases.step; 343 } 344 345 pub fn addTranslateCTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { 346 const cases = b.allocator.create(TranslateCContext) catch unreachable; 347 cases.* = TranslateCContext{ 348 .b = b, 349 .step = b.step("test-translate-c", "Run the C transation tests"), 350 .test_index = 0, 351 .test_filter = test_filter, 352 }; 353 354 translate_c.addCases(cases); 355 356 return cases.step; 357 } 358 359 pub fn addRunTranslatedCTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { 360 const cases = b.allocator.create(RunTranslatedCContext) catch unreachable; 361 cases.* = .{ 362 .b = b, 363 .step = b.step("test-run-translated-c", "Run the Run-Translated-C tests"), 364 .test_index = 0, 365 .test_filter = test_filter, 366 }; 367 368 run_translated_c.addCases(cases); 369 370 return cases.step; 371 } 372 373 pub fn addGenHTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { 374 const cases = b.allocator.create(GenHContext) catch unreachable; 375 cases.* = GenHContext{ 376 .b = b, 377 .step = b.step("test-gen-h", "Run the C header file generation tests"), 378 .test_index = 0, 379 .test_filter = test_filter, 380 }; 381 382 gen_h.addCases(cases); 383 384 return cases.step; 385 } 386 387 pub fn addPkgTests( 388 b: *build.Builder, 389 test_filter: ?[]const u8, 390 root_src: []const u8, 391 name: []const u8, 392 desc: []const u8, 393 modes: []const Mode, 394 skip_single_threaded: bool, 395 skip_non_native: bool, 396 skip_libc: bool, 397 is_wine_enabled: bool, 398 is_qemu_enabled: bool, 399 glibc_dir: ?[]const u8, 400 ) *build.Step { 401 const step = b.step(b.fmt("test-{}", .{name}), desc); 402 403 for (test_targets) |test_target| { 404 if (skip_non_native and !test_target.target.isNative()) 405 continue; 406 407 if (skip_libc and test_target.link_libc) 408 continue; 409 410 if (test_target.link_libc and test_target.target.getOs().requiresLibC()) { 411 // This would be a redundant test. 412 continue; 413 } 414 415 if (skip_single_threaded and test_target.single_threaded) 416 continue; 417 418 const ArchTag = @TagType(builtin.Arch); 419 if (test_target.disable_native and 420 test_target.target.getOsTag() == std.Target.current.os.tag and 421 test_target.target.getCpuArch() == std.Target.current.cpu.arch) 422 { 423 continue; 424 } 425 426 const want_this_mode = for (modes) |m| { 427 if (m == test_target.mode) break true; 428 } else false; 429 if (!want_this_mode) continue; 430 431 const libc_prefix = if (test_target.target.getOs().requiresLibC()) 432 "" 433 else if (test_target.link_libc) 434 "c" 435 else 436 "bare"; 437 438 const triple_prefix = test_target.target.zigTriple(b.allocator) catch unreachable; 439 440 const these_tests = b.addTest(root_src); 441 const single_threaded_txt = if (test_target.single_threaded) "single" else "multi"; 442 these_tests.setNamePrefix(b.fmt("{}-{}-{}-{}-{} ", .{ 443 name, 444 triple_prefix, 445 @tagName(test_target.mode), 446 libc_prefix, 447 single_threaded_txt, 448 })); 449 these_tests.single_threaded = test_target.single_threaded; 450 these_tests.setFilter(test_filter); 451 these_tests.setBuildMode(test_target.mode); 452 these_tests.setTarget(test_target.target); 453 if (test_target.link_libc) { 454 these_tests.linkSystemLibrary("c"); 455 } 456 these_tests.overrideZigLibDir("lib"); 457 these_tests.enable_wine = is_wine_enabled; 458 these_tests.enable_qemu = is_qemu_enabled; 459 these_tests.glibc_multi_install_dir = glibc_dir; 460 461 step.dependOn(&these_tests.step); 462 } 463 return step; 464 } 465 466 pub const StackTracesContext = struct { 467 b: *build.Builder, 468 step: *build.Step, 469 test_index: usize, 470 test_filter: ?[]const u8, 471 modes: []const Mode, 472 473 const Expect = [@typeInfo(Mode).Enum.fields.len][]const u8; 474 475 pub fn addCase( 476 self: *StackTracesContext, 477 name: []const u8, 478 source: []const u8, 479 expect: Expect, 480 ) void { 481 const b = self.b; 482 483 for (self.modes) |mode| { 484 const expect_for_mode = expect[@enumToInt(mode)]; 485 if (expect_for_mode.len == 0) continue; 486 487 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", .{ 488 "stack-trace", 489 name, 490 @tagName(mode), 491 }) catch unreachable; 492 if (self.test_filter) |filter| { 493 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; 494 } 495 496 const src_basename = "source.zig"; 497 const write_src = b.addWriteFile(src_basename, source); 498 const exe = b.addExecutableFromWriteFileStep("test", write_src, src_basename); 499 exe.setBuildMode(mode); 500 501 const run_and_compare = RunAndCompareStep.create( 502 self, 503 exe, 504 annotated_case_name, 505 mode, 506 expect_for_mode, 507 ); 508 509 self.step.dependOn(&run_and_compare.step); 510 } 511 } 512 513 const RunAndCompareStep = struct { 514 step: build.Step, 515 context: *StackTracesContext, 516 exe: *LibExeObjStep, 517 name: []const u8, 518 mode: Mode, 519 expect_output: []const u8, 520 test_index: usize, 521 522 pub fn create( 523 context: *StackTracesContext, 524 exe: *LibExeObjStep, 525 name: []const u8, 526 mode: Mode, 527 expect_output: []const u8, 528 ) *RunAndCompareStep { 529 const allocator = context.b.allocator; 530 const ptr = allocator.create(RunAndCompareStep) catch unreachable; 531 ptr.* = RunAndCompareStep{ 532 .step = build.Step.init("StackTraceCompareOutputStep", allocator, make), 533 .context = context, 534 .exe = exe, 535 .name = name, 536 .mode = mode, 537 .expect_output = expect_output, 538 .test_index = context.test_index, 539 }; 540 ptr.step.dependOn(&exe.step); 541 context.test_index += 1; 542 return ptr; 543 } 544 545 fn make(step: *build.Step) !void { 546 const self = @fieldParentPtr(RunAndCompareStep, "step", step); 547 const b = self.context.b; 548 549 const full_exe_path = self.exe.getOutputPath(); 550 var args = ArrayList([]const u8).init(b.allocator); 551 defer args.deinit(); 552 args.append(full_exe_path) catch unreachable; 553 554 warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name }); 555 556 const child = std.ChildProcess.init(args.toSliceConst(), b.allocator) catch unreachable; 557 defer child.deinit(); 558 559 child.stdin_behavior = .Ignore; 560 child.stdout_behavior = .Pipe; 561 child.stderr_behavior = .Pipe; 562 child.env_map = b.env_map; 563 564 if (b.verbose) { 565 printInvocation(args.toSliceConst()); 566 } 567 child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", .{ full_exe_path, @errorName(err) }); 568 569 var stdout = Buffer.initNull(b.allocator); 570 var stderr = Buffer.initNull(b.allocator); 571 572 var stdout_file_in_stream = child.stdout.?.inStream(); 573 var stderr_file_in_stream = child.stderr.?.inStream(); 574 575 stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable; 576 stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable; 577 578 const term = child.wait() catch |err| { 579 debug.panic("Unable to spawn {}: {}\n", .{ full_exe_path, @errorName(err) }); 580 }; 581 582 switch (term) { 583 .Exited => |code| { 584 const expect_code: u32 = 1; 585 if (code != expect_code) { 586 warn("Process {} exited with error code {} but expected code {}\n", .{ 587 full_exe_path, 588 code, 589 expect_code, 590 }); 591 printInvocation(args.toSliceConst()); 592 return error.TestFailed; 593 } 594 }, 595 .Signal => |signum| { 596 warn("Process {} terminated on signal {}\n", .{ full_exe_path, signum }); 597 printInvocation(args.toSliceConst()); 598 return error.TestFailed; 599 }, 600 .Stopped => |signum| { 601 warn("Process {} stopped on signal {}\n", .{ full_exe_path, signum }); 602 printInvocation(args.toSliceConst()); 603 return error.TestFailed; 604 }, 605 .Unknown => |code| { 606 warn("Process {} terminated unexpectedly with error code {}\n", .{ full_exe_path, code }); 607 printInvocation(args.toSliceConst()); 608 return error.TestFailed; 609 }, 610 } 611 612 // process result 613 // - keep only basename of source file path 614 // - replace address with symbolic string 615 // - skip empty lines 616 const got: []const u8 = got_result: { 617 var buf = try Buffer.initSize(b.allocator, 0); 618 defer buf.deinit(); 619 const bytes = if (stderr.endsWith("\n")) 620 stderr.toSliceConst()[0 .. stderr.len() - 1] 621 else 622 stderr.toSliceConst()[0..stderr.len()]; 623 var it = mem.separate(bytes, "\n"); 624 process_lines: while (it.next()) |line| { 625 if (line.len == 0) continue; 626 const delims = [_][]const u8{ ":", ":", ":", " in " }; 627 var marks = [_]usize{0} ** 4; 628 // offset search past `[drive]:` on windows 629 var pos: usize = if (std.Target.current.os.tag == .windows) 2 else 0; 630 for (delims) |delim, i| { 631 marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { 632 try buf.append(line); 633 try buf.append("\n"); 634 continue :process_lines; 635 }; 636 pos = marks[i] + delim.len; 637 } 638 pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse { 639 try buf.append(line); 640 try buf.append("\n"); 641 continue :process_lines; 642 }; 643 try buf.append(line[pos + 1 .. marks[2] + delims[2].len]); 644 try buf.append(" [address]"); 645 try buf.append(line[marks[3]..]); 646 try buf.append("\n"); 647 } 648 break :got_result buf.toOwnedSlice(); 649 }; 650 651 if (!mem.eql(u8, self.expect_output, got)) { 652 warn( 653 \\ 654 \\========= Expected this output: ========= 655 \\{} 656 \\================================================ 657 \\{} 658 \\ 659 , .{ self.expect_output, got }); 660 return error.TestFailed; 661 } 662 warn("OK\n", .{}); 663 } 664 }; 665 }; 666 667 pub const CompileErrorContext = struct { 668 b: *build.Builder, 669 step: *build.Step, 670 test_index: usize, 671 test_filter: ?[]const u8, 672 modes: []const Mode, 673 674 const TestCase = struct { 675 name: []const u8, 676 sources: ArrayList(SourceFile), 677 expected_errors: ArrayList([]const u8), 678 expect_exact: bool, 679 link_libc: bool, 680 is_exe: bool, 681 is_test: bool, 682 target: CrossTarget = CrossTarget{}, 683 684 const SourceFile = struct { 685 filename: []const u8, 686 source: []const u8, 687 }; 688 689 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { 690 self.sources.append(SourceFile{ 691 .filename = filename, 692 .source = source, 693 }) catch unreachable; 694 } 695 696 pub fn addExpectedError(self: *TestCase, text: []const u8) void { 697 self.expected_errors.append(text) catch unreachable; 698 } 699 }; 700 701 const CompileCmpOutputStep = struct { 702 step: build.Step, 703 context: *CompileErrorContext, 704 name: []const u8, 705 test_index: usize, 706 case: *const TestCase, 707 build_mode: Mode, 708 write_src: *build.WriteFileStep, 709 710 const ErrLineIter = struct { 711 lines: mem.SplitIterator, 712 713 const source_file = "tmp.zig"; 714 715 fn init(input: []const u8) ErrLineIter { 716 return ErrLineIter{ .lines = mem.separate(input, "\n") }; 717 } 718 719 fn next(self: *ErrLineIter) ?[]const u8 { 720 while (self.lines.next()) |line| { 721 if (mem.indexOf(u8, line, source_file) != null) 722 return line; 723 } 724 return null; 725 } 726 }; 727 728 pub fn create( 729 context: *CompileErrorContext, 730 name: []const u8, 731 case: *const TestCase, 732 build_mode: Mode, 733 write_src: *build.WriteFileStep, 734 ) *CompileCmpOutputStep { 735 const allocator = context.b.allocator; 736 const ptr = allocator.create(CompileCmpOutputStep) catch unreachable; 737 ptr.* = CompileCmpOutputStep{ 738 .step = build.Step.init("CompileCmpOutput", allocator, make), 739 .context = context, 740 .name = name, 741 .test_index = context.test_index, 742 .case = case, 743 .build_mode = build_mode, 744 .write_src = write_src, 745 }; 746 747 context.test_index += 1; 748 return ptr; 749 } 750 751 fn make(step: *build.Step) !void { 752 const self = @fieldParentPtr(CompileCmpOutputStep, "step", step); 753 const b = self.context.b; 754 755 var zig_args = ArrayList([]const u8).init(b.allocator); 756 zig_args.append(b.zig_exe) catch unreachable; 757 758 if (self.case.is_exe) { 759 try zig_args.append("build-exe"); 760 } else if (self.case.is_test) { 761 try zig_args.append("test"); 762 } else { 763 try zig_args.append("build-obj"); 764 } 765 const root_src_basename = self.case.sources.toSliceConst()[0].filename; 766 try zig_args.append(self.write_src.getOutputPath(root_src_basename)); 767 768 zig_args.append("--name") catch unreachable; 769 zig_args.append("test") catch unreachable; 770 771 zig_args.append("--output-dir") catch unreachable; 772 zig_args.append(b.pathFromRoot(b.cache_root)) catch unreachable; 773 774 if (!self.case.target.isNative()) { 775 try zig_args.append("-target"); 776 try zig_args.append(try self.case.target.zigTriple(b.allocator)); 777 } 778 779 switch (self.build_mode) { 780 Mode.Debug => {}, 781 Mode.ReleaseSafe => zig_args.append("--release-safe") catch unreachable, 782 Mode.ReleaseFast => zig_args.append("--release-fast") catch unreachable, 783 Mode.ReleaseSmall => zig_args.append("--release-small") catch unreachable, 784 } 785 786 warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name }); 787 788 if (b.verbose) { 789 printInvocation(zig_args.toSliceConst()); 790 } 791 792 const child = std.ChildProcess.init(zig_args.toSliceConst(), b.allocator) catch unreachable; 793 defer child.deinit(); 794 795 child.env_map = b.env_map; 796 child.stdin_behavior = .Ignore; 797 child.stdout_behavior = .Pipe; 798 child.stderr_behavior = .Pipe; 799 800 child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", .{ zig_args.items[0], @errorName(err) }); 801 802 var stdout_buf = Buffer.initNull(b.allocator); 803 var stderr_buf = Buffer.initNull(b.allocator); 804 805 var stdout_file_in_stream = child.stdout.?.inStream(); 806 var stderr_file_in_stream = child.stderr.?.inStream(); 807 808 stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable; 809 stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable; 810 811 const term = child.wait() catch |err| { 812 debug.panic("Unable to spawn {}: {}\n", .{ zig_args.items[0], @errorName(err) }); 813 }; 814 switch (term) { 815 .Exited => |code| { 816 if (code == 0) { 817 printInvocation(zig_args.toSliceConst()); 818 return error.CompilationIncorrectlySucceeded; 819 } 820 }, 821 else => { 822 warn("Process {} terminated unexpectedly\n", .{b.zig_exe}); 823 printInvocation(zig_args.toSliceConst()); 824 return error.TestFailed; 825 }, 826 } 827 828 const stdout = stdout_buf.toSliceConst(); 829 const stderr = stderr_buf.toSliceConst(); 830 831 if (stdout.len != 0) { 832 warn( 833 \\ 834 \\Expected empty stdout, instead found: 835 \\================================================ 836 \\{} 837 \\================================================ 838 \\ 839 , .{stdout}); 840 return error.TestFailed; 841 } 842 843 var ok = true; 844 if (self.case.expect_exact) { 845 var err_iter = ErrLineIter.init(stderr); 846 var i: usize = 0; 847 ok = while (err_iter.next()) |line| : (i += 1) { 848 if (i >= self.case.expected_errors.len) break false; 849 const expected = self.case.expected_errors.at(i); 850 if (mem.indexOf(u8, line, expected) == null) break false; 851 continue; 852 } else true; 853 854 ok = ok and i == self.case.expected_errors.len; 855 856 if (!ok) { 857 warn("\n======== Expected these compile errors: ========\n", .{}); 858 for (self.case.expected_errors.toSliceConst()) |expected| { 859 warn("{}\n", .{expected}); 860 } 861 } 862 } else { 863 for (self.case.expected_errors.toSliceConst()) |expected| { 864 if (mem.indexOf(u8, stderr, expected) == null) { 865 warn( 866 \\ 867 \\=========== Expected compile error: ============ 868 \\{} 869 \\ 870 , .{expected}); 871 ok = false; 872 break; 873 } 874 } 875 } 876 877 if (!ok) { 878 warn( 879 \\================= Full output: ================= 880 \\{} 881 \\ 882 , .{stderr}); 883 return error.TestFailed; 884 } 885 886 warn("OK\n", .{}); 887 } 888 }; 889 890 pub fn create( 891 self: *CompileErrorContext, 892 name: []const u8, 893 source: []const u8, 894 expected_lines: []const []const u8, 895 ) *TestCase { 896 const tc = self.b.allocator.create(TestCase) catch unreachable; 897 tc.* = TestCase{ 898 .name = name, 899 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), 900 .expected_errors = ArrayList([]const u8).init(self.b.allocator), 901 .expect_exact = false, 902 .link_libc = false, 903 .is_exe = false, 904 .is_test = false, 905 }; 906 907 tc.addSourceFile("tmp.zig", source); 908 var arg_i: usize = 0; 909 while (arg_i < expected_lines.len) : (arg_i += 1) { 910 tc.addExpectedError(expected_lines[arg_i]); 911 } 912 return tc; 913 } 914 915 pub fn addC(self: *CompileErrorContext, name: []const u8, source: []const u8, expected_lines: []const []const u8) void { 916 var tc = self.create(name, source, expected_lines); 917 tc.link_libc = true; 918 self.addCase(tc); 919 } 920 921 pub fn addExe( 922 self: *CompileErrorContext, 923 name: []const u8, 924 source: []const u8, 925 expected_lines: []const []const u8, 926 ) void { 927 var tc = self.create(name, source, expected_lines); 928 tc.is_exe = true; 929 self.addCase(tc); 930 } 931 932 pub fn add( 933 self: *CompileErrorContext, 934 name: []const u8, 935 source: []const u8, 936 expected_lines: []const []const u8, 937 ) void { 938 const tc = self.create(name, source, expected_lines); 939 self.addCase(tc); 940 } 941 942 pub fn addTest( 943 self: *CompileErrorContext, 944 name: []const u8, 945 source: []const u8, 946 expected_lines: []const []const u8, 947 ) void { 948 const tc = self.create(name, source, expected_lines); 949 tc.is_test = true; 950 self.addCase(tc); 951 } 952 953 pub fn addCase(self: *CompileErrorContext, case: *const TestCase) void { 954 const b = self.b; 955 956 const annotated_case_name = fmt.allocPrint(self.b.allocator, "compile-error {}", .{ 957 case.name, 958 }) catch unreachable; 959 if (self.test_filter) |filter| { 960 if (mem.indexOf(u8, annotated_case_name, filter) == null) return; 961 } 962 const write_src = b.addWriteFiles(); 963 for (case.sources.toSliceConst()) |src_file| { 964 write_src.add(src_file.filename, src_file.source); 965 } 966 967 const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, .Debug, write_src); 968 compile_and_cmp_errors.step.dependOn(&write_src.step); 969 self.step.dependOn(&compile_and_cmp_errors.step); 970 } 971 }; 972 973 pub const StandaloneContext = struct { 974 b: *build.Builder, 975 step: *build.Step, 976 test_index: usize, 977 test_filter: ?[]const u8, 978 modes: []const Mode, 979 980 pub fn addC(self: *StandaloneContext, root_src: []const u8) void { 981 self.addAllArgs(root_src, true); 982 } 983 984 pub fn add(self: *StandaloneContext, root_src: []const u8) void { 985 self.addAllArgs(root_src, false); 986 } 987 988 pub fn addBuildFile(self: *StandaloneContext, build_file: []const u8) void { 989 const b = self.b; 990 991 const annotated_case_name = b.fmt("build {} (Debug)", .{build_file}); 992 if (self.test_filter) |filter| { 993 if (mem.indexOf(u8, annotated_case_name, filter) == null) return; 994 } 995 996 var zig_args = ArrayList([]const u8).init(b.allocator); 997 const rel_zig_exe = fs.path.relative(b.allocator, b.build_root, b.zig_exe) catch unreachable; 998 zig_args.append(rel_zig_exe) catch unreachable; 999 zig_args.append("build") catch unreachable; 1000 1001 zig_args.append("--build-file") catch unreachable; 1002 zig_args.append(b.pathFromRoot(build_file)) catch unreachable; 1003 1004 zig_args.append("test") catch unreachable; 1005 1006 if (b.verbose) { 1007 zig_args.append("--verbose") catch unreachable; 1008 } 1009 1010 const run_cmd = b.addSystemCommand(zig_args.toSliceConst()); 1011 1012 const log_step = b.addLog("PASS {}\n", .{annotated_case_name}); 1013 log_step.step.dependOn(&run_cmd.step); 1014 1015 self.step.dependOn(&log_step.step); 1016 } 1017 1018 pub fn addAllArgs(self: *StandaloneContext, root_src: []const u8, link_libc: bool) void { 1019 const b = self.b; 1020 1021 for (self.modes) |mode| { 1022 const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {} ({})", .{ 1023 root_src, 1024 @tagName(mode), 1025 }) catch unreachable; 1026 if (self.test_filter) |filter| { 1027 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; 1028 } 1029 1030 const exe = b.addExecutable("test", root_src); 1031 exe.setBuildMode(mode); 1032 if (link_libc) { 1033 exe.linkSystemLibrary("c"); 1034 } 1035 1036 const log_step = b.addLog("PASS {}\n", .{annotated_case_name}); 1037 log_step.step.dependOn(&exe.step); 1038 1039 self.step.dependOn(&log_step.step); 1040 } 1041 } 1042 }; 1043 1044 pub const GenHContext = struct { 1045 b: *build.Builder, 1046 step: *build.Step, 1047 test_index: usize, 1048 test_filter: ?[]const u8, 1049 1050 const TestCase = struct { 1051 name: []const u8, 1052 sources: ArrayList(SourceFile), 1053 expected_lines: ArrayList([]const u8), 1054 1055 const SourceFile = struct { 1056 filename: []const u8, 1057 source: []const u8, 1058 }; 1059 1060 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { 1061 self.sources.append(SourceFile{ 1062 .filename = filename, 1063 .source = source, 1064 }) catch unreachable; 1065 } 1066 1067 pub fn addExpectedLine(self: *TestCase, text: []const u8) void { 1068 self.expected_lines.append(text) catch unreachable; 1069 } 1070 }; 1071 1072 const GenHCmpOutputStep = struct { 1073 step: build.Step, 1074 context: *GenHContext, 1075 obj: *LibExeObjStep, 1076 name: []const u8, 1077 test_index: usize, 1078 case: *const TestCase, 1079 1080 pub fn create( 1081 context: *GenHContext, 1082 obj: *LibExeObjStep, 1083 name: []const u8, 1084 case: *const TestCase, 1085 ) *GenHCmpOutputStep { 1086 const allocator = context.b.allocator; 1087 const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; 1088 ptr.* = GenHCmpOutputStep{ 1089 .step = build.Step.init("ParseCCmpOutput", allocator, make), 1090 .context = context, 1091 .obj = obj, 1092 .name = name, 1093 .test_index = context.test_index, 1094 .case = case, 1095 }; 1096 ptr.step.dependOn(&obj.step); 1097 context.test_index += 1; 1098 return ptr; 1099 } 1100 1101 fn make(step: *build.Step) !void { 1102 const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); 1103 const b = self.context.b; 1104 1105 warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name }); 1106 1107 const full_h_path = self.obj.getOutputHPath(); 1108 const actual_h = try io.readFileAlloc(b.allocator, full_h_path); 1109 1110 for (self.case.expected_lines.toSliceConst()) |expected_line| { 1111 if (mem.indexOf(u8, actual_h, expected_line) == null) { 1112 warn( 1113 \\ 1114 \\========= Expected this output: ================ 1115 \\{} 1116 \\========= But found: =========================== 1117 \\{} 1118 \\ 1119 , .{ expected_line, actual_h }); 1120 return error.TestFailed; 1121 } 1122 } 1123 warn("OK\n", .{}); 1124 } 1125 }; 1126 1127 fn printInvocation(args: []const []const u8) void { 1128 for (args) |arg| { 1129 warn("{} ", .{arg}); 1130 } 1131 warn("\n", .{}); 1132 } 1133 1134 pub fn create( 1135 self: *GenHContext, 1136 filename: []const u8, 1137 name: []const u8, 1138 source: []const u8, 1139 expected_lines: []const []const u8, 1140 ) *TestCase { 1141 const tc = self.b.allocator.create(TestCase) catch unreachable; 1142 tc.* = TestCase{ 1143 .name = name, 1144 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), 1145 .expected_lines = ArrayList([]const u8).init(self.b.allocator), 1146 }; 1147 1148 tc.addSourceFile(filename, source); 1149 var arg_i: usize = 0; 1150 while (arg_i < expected_lines.len) : (arg_i += 1) { 1151 tc.addExpectedLine(expected_lines[arg_i]); 1152 } 1153 return tc; 1154 } 1155 1156 pub fn add(self: *GenHContext, name: []const u8, source: []const u8, expected_lines: []const []const u8) void { 1157 const tc = self.create("test.zig", name, source, expected_lines); 1158 self.addCase(tc); 1159 } 1160 1161 pub fn addCase(self: *GenHContext, case: *const TestCase) void { 1162 const b = self.b; 1163 1164 const mode = builtin.Mode.Debug; 1165 const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {} ({})", .{ case.name, @tagName(mode) }) catch unreachable; 1166 if (self.test_filter) |filter| { 1167 if (mem.indexOf(u8, annotated_case_name, filter) == null) return; 1168 } 1169 1170 const write_src = b.addWriteFiles(); 1171 for (case.sources.toSliceConst()) |src_file| { 1172 write_src.add(src_file.filename, src_file.source); 1173 } 1174 1175 const obj = b.addObjectFromWriteFileStep("test", write_src, case.sources.items[0].filename); 1176 obj.setBuildMode(mode); 1177 1178 const cmp_h = GenHCmpOutputStep.create(self, obj, annotated_case_name, case); 1179 1180 self.step.dependOn(&cmp_h.step); 1181 } 1182 }; 1183 1184 fn printInvocation(args: []const []const u8) void { 1185 for (args) |arg| { 1186 warn("{} ", .{arg}); 1187 } 1188 warn("\n", .{}); 1189 }