zig

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

blob 349cd3bf (34488B) - Raw


      1 const std = @import("std");
      2 const debug = std.debug;
      3 const build = std.build;
      4 const os = std.os;
      5 const StdIo = os.ChildProcess.StdIo;
      6 const Term = os.ChildProcess.Term;
      7 const Buffer = std.Buffer;
      8 const io = std.io;
      9 const mem = std.mem;
     10 const fmt = std.fmt;
     11 const ArrayList = std.ArrayList;
     12 const builtin = @import("builtin");
     13 const Mode = builtin.Mode;
     14 
     15 const compare_output = @import("compare_output.zig");
     16 const build_examples = @import("build_examples.zig");
     17 const compile_errors = @import("compile_errors.zig");
     18 const assemble_and_link = @import("assemble_and_link.zig");
     19 const debug_safety = @import("debug_safety.zig");
     20 const parsec = @import("parsec.zig");
     21 
     22 const TestTarget = struct {
     23     os: builtin.Os,
     24     arch: builtin.Arch,
     25     environ: builtin.Environ,
     26 };
     27 
     28 const test_targets = []TestTarget {
     29     TestTarget {
     30         .os = builtin.Os.linux,
     31         .arch = builtin.Arch.x86_64,
     32         .environ = builtin.Environ.gnu,
     33     },
     34     TestTarget {
     35         .os = builtin.Os.darwin,
     36         .arch = builtin.Arch.x86_64,
     37         .environ = builtin.Environ.unknown,
     38     },
     39     TestTarget {
     40         .os = builtin.Os.windows,
     41         .arch = builtin.Arch.x86_64,
     42         .environ = builtin.Environ.msvc,
     43     },
     44 };
     45 
     46 error TestFailed;
     47 
     48 pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
     49     const cases = %%b.allocator.create(CompareOutputContext);
     50     *cases = CompareOutputContext {
     51         .b = b,
     52         .step = b.step("test-compare-output", "Run the compare output tests"),
     53         .test_index = 0,
     54         .test_filter = test_filter,
     55     };
     56 
     57     compare_output.addCases(cases);
     58 
     59     return cases.step;
     60 }
     61 
     62 pub fn addDebugSafetyTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
     63     const cases = %%b.allocator.create(CompareOutputContext);
     64     *cases = CompareOutputContext {
     65         .b = b,
     66         .step = b.step("test-debug-safety", "Run the debug safety tests"),
     67         .test_index = 0,
     68         .test_filter = test_filter,
     69     };
     70 
     71     debug_safety.addCases(cases);
     72 
     73     return cases.step;
     74 }
     75 
     76 pub fn addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
     77     const cases = %%b.allocator.create(CompileErrorContext);
     78     *cases = CompileErrorContext {
     79         .b = b,
     80         .step = b.step("test-compile-errors", "Run the compile error tests"),
     81         .test_index = 0,
     82         .test_filter = test_filter,
     83     };
     84 
     85     compile_errors.addCases(cases);
     86 
     87     return cases.step;
     88 }
     89 
     90 pub fn addBuildExampleTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
     91     const cases = %%b.allocator.create(BuildExamplesContext);
     92     *cases = BuildExamplesContext {
     93         .b = b,
     94         .step = b.step("test-build-examples", "Build the examples"),
     95         .test_index = 0,
     96         .test_filter = test_filter,
     97     };
     98 
     99     build_examples.addCases(cases);
    100 
    101     return cases.step;
    102 }
    103 
    104 pub fn addAssembleAndLinkTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
    105     const cases = %%b.allocator.create(CompareOutputContext);
    106     *cases = CompareOutputContext {
    107         .b = b,
    108         .step = b.step("test-asm-link", "Run the assemble and link tests"),
    109         .test_index = 0,
    110         .test_filter = test_filter,
    111     };
    112 
    113     assemble_and_link.addCases(cases);
    114 
    115     return cases.step;
    116 }
    117 
    118 pub fn addParseCTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
    119     const cases = %%b.allocator.create(ParseCContext);
    120     *cases = ParseCContext {
    121         .b = b,
    122         .step = b.step("test-parsec", "Run the C header file parsing tests"),
    123         .test_index = 0,
    124         .test_filter = test_filter,
    125     };
    126 
    127     parsec.addCases(cases);
    128 
    129     return cases.step;
    130 }
    131 
    132 pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
    133     name:[] const u8, desc: []const u8, with_lldb: bool) -> &build.Step
    134 {
    135     return addPkgTestsRaw(b, test_filter, root_src, name, desc, false, with_lldb);
    136 }
    137 
    138 pub fn addPkgTestsAlwaysLibc(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
    139     name:[] const u8, desc: []const u8, with_lldb: bool) -> &build.Step
    140 {
    141     return addPkgTestsRaw(b, test_filter, root_src, name, desc, true, with_lldb);
    142 }
    143 
    144 pub fn addPkgTestsRaw(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
    145     name:[] const u8, desc: []const u8, always_link_libc: bool, with_lldb: bool) -> &build.Step
    146 {
    147     const libc_bools = if (always_link_libc) []bool{true} else []bool{false, true};
    148     const step = b.step(b.fmt("test-{}", name), desc);
    149     for (test_targets) |test_target| {
    150         const is_native = (test_target.os == builtin.os and test_target.arch == builtin.arch);
    151         for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
    152             for (libc_bools) |link_libc| {
    153                 if (link_libc and !is_native) {
    154                     // don't assume we have a cross-compiling libc set up
    155                     continue;
    156                 }
    157                 const these_tests = b.addTest(root_src);
    158                 these_tests.setNamePrefix(b.fmt("{}-{}-{}-{}-{} ", name, @enumTagName(test_target.os),
    159                     @enumTagName(test_target.arch), @enumTagName(mode), if (link_libc) "c" else "bare"));
    160                 these_tests.setFilter(test_filter);
    161                 these_tests.setBuildMode(mode);
    162                 if (!is_native) {
    163                     these_tests.setTarget(test_target.arch, test_target.os, test_target.environ);
    164                 }
    165                 if (link_libc) {
    166                     these_tests.linkSystemLibrary("c");
    167                 }
    168                 if (with_lldb) {
    169                     these_tests.setExecCmd([]?[]const u8{
    170                         "lldb", null, "-o", "run", "-o", "bt", "-o", "exit"});
    171                 }
    172                 step.dependOn(&these_tests.step);
    173             }
    174         }
    175     }
    176     return step;
    177 }
    178 
    179 pub const CompareOutputContext = struct {
    180     b: &build.Builder,
    181     step: &build.Step,
    182     test_index: usize,
    183     test_filter: ?[]const u8,
    184 
    185     const Special = enum {
    186         None,
    187         Asm,
    188         DebugSafety,
    189     };
    190 
    191     const TestCase = struct {
    192         name: []const u8,
    193         sources: ArrayList(SourceFile),
    194         expected_output: []const u8,
    195         link_libc: bool,
    196         special: Special,
    197 
    198         const SourceFile = struct {
    199             filename: []const u8,
    200             source: []const u8,
    201         };
    202 
    203         pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
    204             %%self.sources.append(SourceFile {
    205                 .filename = filename,
    206                 .source = source,
    207             });
    208         }
    209     };
    210 
    211     const RunCompareOutputStep = struct {
    212         step: build.Step,
    213         context: &CompareOutputContext,
    214         exe_path: []const u8,
    215         name: []const u8,
    216         expected_output: []const u8,
    217         test_index: usize,
    218 
    219         pub fn create(context: &CompareOutputContext, exe_path: []const u8,
    220             name: []const u8, expected_output: []const u8) -> &RunCompareOutputStep
    221         {
    222             const allocator = context.b.allocator;
    223             const ptr = %%allocator.create(RunCompareOutputStep);
    224             *ptr = RunCompareOutputStep {
    225                 .context = context,
    226                 .exe_path = exe_path,
    227                 .name = name,
    228                 .expected_output = expected_output,
    229                 .test_index = context.test_index,
    230                 .step = build.Step.init("RunCompareOutput", allocator, make),
    231             };
    232             context.test_index += 1;
    233             return ptr;
    234         }
    235 
    236         fn make(step: &build.Step) -> %void {
    237             const self = @fieldParentPtr(RunCompareOutputStep, "step", step);
    238             const b = self.context.b;
    239 
    240             const full_exe_path = b.pathFromRoot(self.exe_path);
    241 
    242             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
    243 
    244             var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
    245                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
    246             {
    247                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
    248             };
    249 
    250             var stdout = Buffer.initNull(b.allocator);
    251             var stderr = Buffer.initNull(b.allocator);
    252 
    253             %%(??child.stdout).readAll(&stdout);
    254             %%(??child.stderr).readAll(&stderr);
    255 
    256             const term = child.wait() %% |err| {
    257                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
    258             };
    259             switch (term) {
    260                 Term.Exited => |code| {
    261                     if (code != 0) {
    262                         %%io.stderr.printf("Process {} exited with error code {}\n", full_exe_path, code);
    263                         return error.TestFailed;
    264                     }
    265                 },
    266                 else => {
    267                     %%io.stderr.printf("Process {} terminated unexpectedly\n", full_exe_path);
    268                     return error.TestFailed;
    269                 },
    270             };
    271 
    272 
    273             if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
    274                 %%io.stderr.printf(
    275                     \\
    276                     \\========= Expected this output: =========
    277                     \\{}
    278                     \\================================================
    279                     \\{}
    280                     \\
    281                 , self.expected_output, stdout.toSliceConst());
    282                 return error.TestFailed;
    283             }
    284             %%io.stderr.printf("OK\n");
    285         }
    286     };
    287 
    288     const DebugSafetyRunStep = struct {
    289         step: build.Step,
    290         context: &CompareOutputContext,
    291         exe_path: []const u8,
    292         name: []const u8,
    293         test_index: usize,
    294 
    295         pub fn create(context: &CompareOutputContext, exe_path: []const u8,
    296             name: []const u8) -> &DebugSafetyRunStep
    297         {
    298             const allocator = context.b.allocator;
    299             const ptr = %%allocator.create(DebugSafetyRunStep);
    300             *ptr = DebugSafetyRunStep {
    301                 .context = context,
    302                 .exe_path = exe_path,
    303                 .name = name,
    304                 .test_index = context.test_index,
    305                 .step = build.Step.init("DebugSafetyRun", allocator, make),
    306             };
    307             context.test_index += 1;
    308             return ptr;
    309         }
    310 
    311         fn make(step: &build.Step) -> %void {
    312             const self = @fieldParentPtr(DebugSafetyRunStep, "step", step);
    313             const b = self.context.b;
    314 
    315             const full_exe_path = b.pathFromRoot(self.exe_path);
    316 
    317             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
    318 
    319             var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
    320                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
    321             {
    322                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
    323             };
    324 
    325             const term = child.wait() %% |err| {
    326                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
    327             };
    328 
    329             const debug_trap_signal: i32 = 5;
    330             switch (term) {
    331                 Term.Exited => |code| {
    332                     %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
    333                         "but exited with return code {}\n", debug_trap_signal, code);
    334                     return error.TestFailed;
    335                 },
    336                 Term.Signal => |sig| {
    337                     if (sig != debug_trap_signal) {
    338                         %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
    339                             "but instead signaled {}\n", debug_trap_signal, sig);
    340                         return error.TestFailed;
    341                     }
    342                 },
    343                 else => {
    344                     %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
    345                         " but exited in an unexpected way\n", debug_trap_signal);
    346                     return error.TestFailed;
    347                 },
    348             }
    349 
    350             %%io.stderr.printf("OK\n");
    351         }
    352     };
    353 
    354     pub fn createExtra(self: &CompareOutputContext, name: []const u8, source: []const u8,
    355         expected_output: []const u8, special: Special) -> TestCase
    356     {
    357         var tc = TestCase {
    358             .name = name,
    359             .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
    360             .expected_output = expected_output,
    361             .link_libc = false,
    362             .special = special,
    363         };
    364         const root_src_name = if (special == Special.Asm) "source.s" else "source.zig";
    365         tc.addSourceFile(root_src_name, source);
    366         return tc;
    367     }
    368 
    369     pub fn create(self: &CompareOutputContext, name: []const u8, source: []const u8,
    370         expected_output: []const u8) -> TestCase
    371     {
    372         return createExtra(self, name, source, expected_output, Special.None);
    373     }
    374 
    375     pub fn addC(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
    376         var tc = self.create(name, source, expected_output);
    377         tc.link_libc = true;
    378         self.addCase(tc);
    379     }
    380 
    381     pub fn add(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
    382         const tc = self.create(name, source, expected_output);
    383         self.addCase(tc);
    384     }
    385 
    386     pub fn addAsm(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
    387         const tc = self.createExtra(name, source, expected_output, Special.Asm);
    388         self.addCase(tc);
    389     }
    390 
    391     pub fn addDebugSafety(self: &CompareOutputContext, name: []const u8, source: []const u8) {
    392         const tc = self.createExtra(name, source, undefined, Special.DebugSafety);
    393         self.addCase(tc);
    394     }
    395 
    396     pub fn addCase(self: &CompareOutputContext, case: &const TestCase) {
    397         const b = self.b;
    398 
    399         const root_src = %%os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename);
    400 
    401         switch (case.special) {
    402             Special.Asm => {
    403                 const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name);
    404                 if (self.test_filter) |filter| {
    405                     if (mem.indexOf(u8, annotated_case_name, filter) == null)
    406                         return;
    407                 }
    408 
    409                 const exe = b.addExecutable("test", null);
    410                 exe.addAssemblyFile(root_src);
    411 
    412                 for (case.sources.toSliceConst()) |src_file| {
    413                     const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
    414                     const write_src = b.addWriteFile(expanded_src_path, src_file.source);
    415                     exe.step.dependOn(&write_src.step);
    416                 }
    417 
    418                 const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(), annotated_case_name,
    419                     case.expected_output);
    420                 run_and_cmp_output.step.dependOn(&exe.step);
    421 
    422                 self.step.dependOn(&run_and_cmp_output.step);
    423             },
    424             Special.None => {
    425                 for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
    426                     const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} {} ({})",
    427                         "compare-output", case.name, @enumTagName(mode));
    428                     if (self.test_filter) |filter| {
    429                         if (mem.indexOf(u8, annotated_case_name, filter) == null)
    430                             continue;
    431                     }
    432 
    433                     const exe = b.addExecutable("test", root_src);
    434                     exe.setBuildMode(mode);
    435                     if (case.link_libc) {
    436                         exe.linkSystemLibrary("c");
    437                     }
    438 
    439                     for (case.sources.toSliceConst()) |src_file| {
    440                         const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
    441                         const write_src = b.addWriteFile(expanded_src_path, src_file.source);
    442                         exe.step.dependOn(&write_src.step);
    443                     }
    444 
    445                     const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(),
    446                         annotated_case_name, case.expected_output);
    447                     run_and_cmp_output.step.dependOn(&exe.step);
    448 
    449                     self.step.dependOn(&run_and_cmp_output.step);
    450                 }
    451             },
    452             Special.DebugSafety => {
    453                 const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "safety {}", case.name);
    454                 if (self.test_filter) |filter| {
    455                     if (mem.indexOf(u8, annotated_case_name, filter) == null)
    456                         return;
    457                 }
    458 
    459                 const exe = b.addExecutable("test", root_src);
    460                 if (case.link_libc) {
    461                     exe.linkSystemLibrary("c");
    462                 }
    463 
    464                 for (case.sources.toSliceConst()) |src_file| {
    465                     const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
    466                     const write_src = b.addWriteFile(expanded_src_path, src_file.source);
    467                     exe.step.dependOn(&write_src.step);
    468                 }
    469 
    470                 const run_and_cmp_output = DebugSafetyRunStep.create(self, exe.getOutputPath(), annotated_case_name);
    471                 run_and_cmp_output.step.dependOn(&exe.step);
    472 
    473                 self.step.dependOn(&run_and_cmp_output.step);
    474             },
    475         }
    476     }
    477 };
    478 
    479 pub const CompileErrorContext = struct {
    480     b: &build.Builder,
    481     step: &build.Step,
    482     test_index: usize,
    483     test_filter: ?[]const u8,
    484 
    485     const TestCase = struct {
    486         name: []const u8,
    487         sources: ArrayList(SourceFile),
    488         expected_errors: ArrayList([]const u8),
    489         link_libc: bool,
    490         is_exe: bool,
    491 
    492         const SourceFile = struct {
    493             filename: []const u8,
    494             source: []const u8,
    495         };
    496 
    497         pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
    498             %%self.sources.append(SourceFile {
    499                 .filename = filename,
    500                 .source = source,
    501             });
    502         }
    503 
    504         pub fn addExpectedError(self: &TestCase, text: []const u8) {
    505             %%self.expected_errors.append(text);
    506         }
    507     };
    508 
    509     const CompileCmpOutputStep = struct {
    510         step: build.Step,
    511         context: &CompileErrorContext,
    512         name: []const u8,
    513         test_index: usize,
    514         case: &const TestCase,
    515         build_mode: Mode,
    516 
    517         pub fn create(context: &CompileErrorContext, name: []const u8,
    518             case: &const TestCase, build_mode: Mode) -> &CompileCmpOutputStep
    519         {
    520             const allocator = context.b.allocator;
    521             const ptr = %%allocator.create(CompileCmpOutputStep);
    522             *ptr = CompileCmpOutputStep {
    523                 .step = build.Step.init("CompileCmpOutput", allocator, make),
    524                 .context = context,
    525                 .name = name,
    526                 .test_index = context.test_index,
    527                 .case = case,
    528                 .build_mode = build_mode,
    529             };
    530             context.test_index += 1;
    531             return ptr;
    532         }
    533 
    534         fn make(step: &build.Step) -> %void {
    535             const self = @fieldParentPtr(CompileCmpOutputStep, "step", step);
    536             const b = self.context.b;
    537 
    538             const root_src = %%os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename);
    539             const obj_path = %%os.path.join(b.allocator, b.cache_root, "test.o");
    540 
    541             var zig_args = ArrayList([]const u8).init(b.allocator);
    542             %%zig_args.append(if (self.case.is_exe) "build-exe" else "build-obj");
    543             %%zig_args.append(b.pathFromRoot(root_src));
    544 
    545             %%zig_args.append("--name");
    546             %%zig_args.append("test");
    547 
    548             %%zig_args.append("--output");
    549             %%zig_args.append(b.pathFromRoot(obj_path));
    550 
    551             switch (self.build_mode) {
    552                 Mode.Debug => {},
    553                 Mode.ReleaseSafe => %%zig_args.append("--release-safe"),
    554                 Mode.ReleaseFast => %%zig_args.append("--release-fast"),
    555             }
    556 
    557             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
    558 
    559             if (b.verbose) {
    560                 printInvocation(b.zig_exe, zig_args.toSliceConst());
    561             }
    562 
    563             var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
    564                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
    565             {
    566                 debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
    567             };
    568 
    569             var stdout_buf = Buffer.initNull(b.allocator);
    570             var stderr_buf = Buffer.initNull(b.allocator);
    571 
    572             %%(??child.stdout).readAll(&stdout_buf);
    573             %%(??child.stderr).readAll(&stderr_buf);
    574 
    575             const term = child.wait() %% |err| {
    576                 debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
    577             };
    578             switch (term) {
    579                 Term.Exited => |code| {
    580                     if (code == 0) {
    581                         %%io.stderr.printf("Compilation incorrectly succeeded\n");
    582                         return error.TestFailed;
    583                     }
    584                 },
    585                 else => {
    586                     %%io.stderr.printf("Process {} terminated unexpectedly\n", b.zig_exe);
    587                     return error.TestFailed;
    588                 },
    589             };
    590 
    591 
    592             const stdout = stdout_buf.toSliceConst();
    593             const stderr = stderr_buf.toSliceConst();
    594 
    595             if (stdout.len != 0) {
    596                 %%io.stderr.printf(
    597                     \\
    598                     \\Expected empty stdout, instead found:
    599                     \\================================================
    600                     \\{}
    601                     \\================================================
    602                     \\
    603                 , stdout);
    604                 return error.TestFailed;
    605             }
    606 
    607             for (self.case.expected_errors.toSliceConst()) |expected_error| {
    608                 if (mem.indexOf(u8, stderr, expected_error) == null) {
    609                     %%io.stderr.printf(
    610                         \\
    611                         \\========= Expected this compile error: =========
    612                         \\{}
    613                         \\================================================
    614                         \\{}
    615                         \\
    616                     , expected_error, stderr);
    617                     return error.TestFailed;
    618                 }
    619             }
    620             %%io.stderr.printf("OK\n");
    621         }
    622     };
    623 
    624     fn printInvocation(exe_path: []const u8, args: []const []const u8) {
    625         %%io.stderr.printf("{}", exe_path);
    626         for (args) |arg| {
    627             %%io.stderr.printf(" {}", arg);
    628         }
    629         %%io.stderr.printf("\n");
    630     }
    631 
    632     pub fn create(self: &CompileErrorContext, name: []const u8, source: []const u8,
    633         expected_lines: ...) -> &TestCase
    634     {
    635         const tc = %%self.b.allocator.create(TestCase);
    636         *tc = TestCase {
    637             .name = name,
    638             .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
    639             .expected_errors = ArrayList([]const u8).init(self.b.allocator),
    640             .link_libc = false,
    641             .is_exe = false,
    642         };
    643         tc.addSourceFile(".tmp_source.zig", source);
    644         comptime var arg_i = 0;
    645         inline while (arg_i < expected_lines.len) : (arg_i += 1) {
    646             tc.addExpectedError(expected_lines[arg_i]);
    647         }
    648         return tc;
    649     }
    650 
    651     pub fn addC(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) {
    652         var tc = self.create(name, source, expected_lines);
    653         tc.link_libc = true;
    654         self.addCase(tc);
    655     }
    656 
    657     pub fn addExe(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) {
    658         var tc = self.create(name, source, expected_lines);
    659         tc.is_exe = true;
    660         self.addCase(tc);
    661     }
    662 
    663     pub fn add(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) {
    664         const tc = self.create(name, source, expected_lines);
    665         self.addCase(tc);
    666     }
    667 
    668     pub fn addCase(self: &CompileErrorContext, case: &const TestCase) {
    669         const b = self.b;
    670 
    671         for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
    672             const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "compile-error {} ({})",
    673                 case.name, @enumTagName(mode));
    674             if (self.test_filter) |filter| {
    675                 if (mem.indexOf(u8, annotated_case_name, filter) == null)
    676                     continue;
    677             }
    678 
    679             const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, mode);
    680             self.step.dependOn(&compile_and_cmp_errors.step);
    681 
    682             for (case.sources.toSliceConst()) |src_file| {
    683                 const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
    684                 const write_src = b.addWriteFile(expanded_src_path, src_file.source);
    685                 compile_and_cmp_errors.step.dependOn(&write_src.step);
    686             }
    687         }
    688     }
    689 };
    690 
    691 pub const BuildExamplesContext = struct {
    692     b: &build.Builder,
    693     step: &build.Step,
    694     test_index: usize,
    695     test_filter: ?[]const u8,
    696 
    697     pub fn addC(self: &BuildExamplesContext, root_src: []const u8) {
    698         self.addAllArgs(root_src, true);
    699     }
    700 
    701     pub fn add(self: &BuildExamplesContext, root_src: []const u8) {
    702         self.addAllArgs(root_src, false);
    703     }
    704 
    705     pub fn addBuildFile(self: &BuildExamplesContext, build_file: []const u8) {
    706         const b = self.b;
    707 
    708         const annotated_case_name = b.fmt("build {} (Debug)", build_file);
    709         if (self.test_filter) |filter| {
    710             if (mem.indexOf(u8, annotated_case_name, filter) == null)
    711                 return;
    712         }
    713 
    714         var zig_args = ArrayList([]const u8).init(b.allocator);
    715         %%zig_args.append("build");
    716 
    717         %%zig_args.append("--build-file");
    718         %%zig_args.append(b.pathFromRoot(build_file));
    719 
    720         %%zig_args.append("test");
    721 
    722         if (b.verbose) {
    723             %%zig_args.append("--verbose");
    724         }
    725 
    726         const run_cmd = b.addCommand(null, b.env_map, b.zig_exe, zig_args.toSliceConst());
    727 
    728         const log_step = b.addLog("PASS {}\n", annotated_case_name);
    729         log_step.step.dependOn(&run_cmd.step);
    730 
    731         self.step.dependOn(&log_step.step);
    732     }
    733 
    734     pub fn addAllArgs(self: &BuildExamplesContext, root_src: []const u8, link_libc: bool) {
    735         const b = self.b;
    736 
    737         for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
    738             const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "build {} ({})",
    739                 root_src, @enumTagName(mode));
    740             if (self.test_filter) |filter| {
    741                 if (mem.indexOf(u8, annotated_case_name, filter) == null)
    742                     continue;
    743             }
    744 
    745             const exe = b.addExecutable("test", root_src);
    746             exe.setBuildMode(mode);
    747             if (link_libc) {
    748                 exe.linkSystemLibrary("c");
    749             }
    750 
    751             const log_step = b.addLog("PASS {}\n", annotated_case_name);
    752             log_step.step.dependOn(&exe.step);
    753 
    754             self.step.dependOn(&log_step.step);
    755         }
    756     }
    757 };
    758 
    759 pub const ParseCContext = struct {
    760     b: &build.Builder,
    761     step: &build.Step,
    762     test_index: usize,
    763     test_filter: ?[]const u8,
    764 
    765     const TestCase = struct {
    766         name: []const u8,
    767         sources: ArrayList(SourceFile),
    768         expected_lines: ArrayList([]const u8),
    769         allow_warnings: bool,
    770 
    771         const SourceFile = struct {
    772             filename: []const u8,
    773             source: []const u8,
    774         };
    775 
    776         pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
    777             %%self.sources.append(SourceFile {
    778                 .filename = filename,
    779                 .source = source,
    780             });
    781         }
    782 
    783         pub fn addExpectedLine(self: &TestCase, text: []const u8) {
    784             %%self.expected_lines.append(text);
    785         }
    786     };
    787 
    788     const ParseCCmpOutputStep = struct {
    789         step: build.Step,
    790         context: &ParseCContext,
    791         name: []const u8,
    792         test_index: usize,
    793         case: &const TestCase,
    794 
    795         pub fn create(context: &ParseCContext, name: []const u8, case: &const TestCase) -> &ParseCCmpOutputStep {
    796             const allocator = context.b.allocator;
    797             const ptr = %%allocator.create(ParseCCmpOutputStep);
    798             *ptr = ParseCCmpOutputStep {
    799                 .step = build.Step.init("ParseCCmpOutput", allocator, make),
    800                 .context = context,
    801                 .name = name,
    802                 .test_index = context.test_index,
    803                 .case = case,
    804             };
    805             context.test_index += 1;
    806             return ptr;
    807         }
    808 
    809         fn make(step: &build.Step) -> %void {
    810             const self = @fieldParentPtr(ParseCCmpOutputStep, "step", step);
    811             const b = self.context.b;
    812 
    813             const root_src = %%os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename);
    814 
    815             var zig_args = ArrayList([]const u8).init(b.allocator);
    816             %%zig_args.append("parsec");
    817             %%zig_args.append(b.pathFromRoot(root_src));
    818 
    819             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
    820 
    821             if (b.verbose) {
    822                 printInvocation(b.zig_exe, zig_args.toSliceConst());
    823             }
    824 
    825             var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
    826                 StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
    827             {
    828                 debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
    829             };
    830 
    831             var stdout_buf = Buffer.initNull(b.allocator);
    832             var stderr_buf = Buffer.initNull(b.allocator);
    833 
    834             %%(??child.stdout).readAll(&stdout_buf);
    835             %%(??child.stderr).readAll(&stderr_buf);
    836 
    837             const term = child.wait() %% |err| {
    838                 debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
    839             };
    840             switch (term) {
    841                 Term.Exited => |code| {
    842                     if (code != 0) {
    843                         %%io.stderr.printf("Compilation failed with exit code {}\n", code);
    844                         return error.TestFailed;
    845                     }
    846                 },
    847                 Term.Signal => |code| {
    848                     %%io.stderr.printf("Compilation failed with signal {}\n", code);
    849                     return error.TestFailed;
    850                 },
    851                 else => {
    852                     %%io.stderr.printf("Compilation terminated unexpectedly\n");
    853                     return error.TestFailed;
    854                 },
    855             };
    856 
    857             const stdout = stdout_buf.toSliceConst();
    858             const stderr = stderr_buf.toSliceConst();
    859 
    860             if (stderr.len != 0 and !self.case.allow_warnings) {
    861                 %%io.stderr.printf(
    862                     \\====== parsec emitted warnings: ============
    863                     \\{}
    864                     \\============================================
    865                     \\
    866                 , stderr);
    867                 return error.TestFailed;
    868             }
    869 
    870             for (self.case.expected_lines.toSliceConst()) |expected_line| {
    871                 if (mem.indexOf(u8, stdout, expected_line) == null) {
    872                     %%io.stderr.printf(
    873                         \\
    874                         \\========= Expected this output: ================
    875                         \\{}
    876                         \\================================================
    877                         \\{}
    878                         \\
    879                     , expected_line, stdout);
    880                     return error.TestFailed;
    881                 }
    882             }
    883             %%io.stderr.printf("OK\n");
    884         }
    885     };
    886 
    887     fn printInvocation(exe_path: []const u8, args: []const []const u8) {
    888         %%io.stderr.printf("{}", exe_path);
    889         for (args) |arg| {
    890             %%io.stderr.printf(" {}", arg);
    891         }
    892         %%io.stderr.printf("\n");
    893     }
    894 
    895     pub fn create(self: &ParseCContext, allow_warnings: bool, name: []const u8,
    896         source: []const u8, expected_lines: ...) -> &TestCase
    897     {
    898         const tc = %%self.b.allocator.create(TestCase);
    899         *tc = TestCase {
    900             .name = name,
    901             .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
    902             .expected_lines = ArrayList([]const u8).init(self.b.allocator),
    903             .allow_warnings = allow_warnings,
    904         };
    905         tc.addSourceFile("source.h", source);
    906         comptime var arg_i = 0;
    907         inline while (arg_i < expected_lines.len) : (arg_i += 1) {
    908             tc.addExpectedLine(expected_lines[arg_i]);
    909         }
    910         return tc;
    911     }
    912 
    913     pub fn add(self: &ParseCContext, name: []const u8, source: []const u8, expected_lines: ...) {
    914         const tc = self.create(false, name, source, expected_lines);
    915         self.addCase(tc);
    916     }
    917 
    918     pub fn addAllowWarnings(self: &ParseCContext, name: []const u8, source: []const u8, expected_lines: ...) {
    919         const tc = self.create(true, name, source, expected_lines);
    920         self.addCase(tc);
    921     }
    922 
    923     pub fn addCase(self: &ParseCContext, case: &const TestCase) {
    924         const b = self.b;
    925 
    926         const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "parsec {}", case.name);
    927         if (self.test_filter) |filter| {
    928             if (mem.indexOf(u8, annotated_case_name, filter) == null)
    929                 return;
    930         }
    931 
    932         const parsec_and_cmp = ParseCCmpOutputStep.create(self, annotated_case_name, case);
    933         self.step.dependOn(&parsec_and_cmp.step);
    934 
    935         for (case.sources.toSliceConst()) |src_file| {
    936             const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
    937             const write_src = b.addWriteFile(expanded_src_path, src_file.source);
    938             parsec_and_cmp.step.dependOn(&write_src.step);
    939         }
    940     }
    941 };