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