zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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 }