zig

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

blob 5fbb56b7 (42063B) - Raw


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