master
  1b: *std.Build,
  2step: *Step,
  3test_filters: []const []const u8,
  4targets: []const std.Build.ResolvedTarget,
  5optimize_modes: []const OptimizeMode,
  6convert_exe: *std.Build.Step.Compile,
  7
  8pub const Case = struct {
  9    name: []const u8,
 10    source: []const u8,
 11    expect_error: []const u8,
 12    expect_trace: []const u8,
 13    /// On these arch/OS pairs we will not test the error trace on optimized LLVM builds because the
 14    /// optimizations break the error trace. We will test the binary with error tracing disabled,
 15    /// just to ensure that the expected error is still returned from `main`.
 16    ///
 17    /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it
 18    /// seems that LLVM is particularly good at optimizing traces away in those.
 19    disable_trace_optimized: []const DisableConfig = &.{},
 20
 21    pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };
 22    pub const Backend = enum { llvm, selfhosted };
 23};
 24
 25pub fn addCase(self: *ErrorTrace, case: Case) void {
 26    for (self.targets) |*target| {
 27        const triple: ?[]const u8 = if (target.query.isNative()) null else t: {
 28            break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM");
 29        };
 30        for (self.optimize_modes) |optimize| {
 31            self.addCaseConfig(case, target, triple, optimize, .llvm);
 32        }
 33        if (shouldTestNonLlvm(&target.result)) {
 34            for (self.optimize_modes) |optimize| {
 35                self.addCaseConfig(case, target, triple, optimize, .selfhosted);
 36            }
 37        }
 38    }
 39}
 40
 41fn shouldTestNonLlvm(target: *const std.Target) bool {
 42    if (comptime builtin.cpu.arch.endian() == .big) return false; // https://github.com/ziglang/zig/issues/25961
 43    return switch (target.cpu.arch) {
 44        .x86_64 => switch (target.ofmt) {
 45            .elf => !target.os.tag.isBSD() and target.os.tag != .illumos,
 46            else => false,
 47        },
 48        else => false,
 49    };
 50}
 51
 52fn addCaseConfig(
 53    self: *ErrorTrace,
 54    case: Case,
 55    target: *const std.Build.ResolvedTarget,
 56    triple: ?[]const u8,
 57    optimize: OptimizeMode,
 58    backend: Case.Backend,
 59) void {
 60    const b = self.b;
 61
 62    const error_tracing: bool = tracing: {
 63        if (optimize == .Debug) break :tracing true;
 64        if (backend != .llvm) break :tracing true;
 65        if (optimize == .ReleaseSmall) break :tracing false;
 66        for (case.disable_trace_optimized) |disable| {
 67            const d_arch, const d_os = disable;
 68            if (target.result.cpu.arch == d_arch and target.result.os.tag == d_os) {
 69                // This particular configuration cannot do error tracing in optimized LLVM builds.
 70                break :tracing false;
 71            }
 72        }
 73        break :tracing true;
 74    };
 75
 76    const annotated_case_name = b.fmt("check {s} ({s}{s}{s} {s})", .{
 77        case.name,
 78        triple orelse "",
 79        if (triple != null) " " else "",
 80        @tagName(optimize),
 81        @tagName(backend),
 82    });
 83    if (self.test_filters.len > 0) {
 84        for (self.test_filters) |test_filter| {
 85            if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
 86        } else return;
 87    }
 88
 89    const write_files = b.addWriteFiles();
 90    const source_zig = write_files.add("source.zig", case.source);
 91    const exe = b.addExecutable(.{
 92        .name = "test",
 93        .root_module = b.createModule(.{
 94            .root_source_file = source_zig,
 95            .optimize = optimize,
 96            .target = target.*,
 97            .error_tracing = error_tracing,
 98            .strip = false,
 99        }),
100        .use_llvm = switch (backend) {
101            .llvm => true,
102            .selfhosted => false,
103        },
104    });
105    exe.bundle_ubsan_rt = false;
106
107    const run = b.addRunArtifact(exe);
108    run.removeEnvironmentVariable("CLICOLOR_FORCE");
109    run.setEnvironmentVariable("NO_COLOR", "1");
110    run.expectExitCode(1);
111    run.expectStdOutEqual("");
112
113    const expected_stderr = switch (error_tracing) {
114        true => b.fmt("error: {s}\n{s}\n", .{ case.expect_error, case.expect_trace }),
115        false => b.fmt("error: {s}\n", .{case.expect_error}),
116    };
117
118    const check_run = b.addRunArtifact(self.convert_exe);
119    check_run.setName(annotated_case_name);
120    check_run.addFileArg(run.captureStdErr(.{}));
121    check_run.expectStdOutEqual(expected_stderr);
122
123    self.step.dependOn(&check_run.step);
124}
125
126const ErrorTrace = @This();
127const std = @import("std");
128const builtin = @import("builtin");
129const Step = std.Build.Step;
130const OptimizeMode = std.builtin.OptimizeMode;
131const mem = std.mem;