Commit 9b7f438882

Andrew Kelley <superjoe30@gmail.com>
2017-04-19 20:41:59
convert debug safety tests to zig build system
1 parent d1e01e4
test/debug_safety.zig
@@ -0,0 +1,234 @@
+const tests = @import("tests.zig");
+
+pub fn addCases(cases: &tests.CompareOutputContext) {
+    cases.addDebugSafety("calling panic",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\pub fn main() -> %void {
+        \\    @panic("oh no");
+        \\}
+    );
+
+    cases.addDebugSafety("out of bounds slice access",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\pub fn main() -> %void {
+        \\    const a = []i32{1, 2, 3, 4};
+        \\    baz(bar(a));
+        \\}
+        \\fn bar(a: []const i32) -> i32 {
+        \\    a[4]
+        \\}
+        \\fn baz(a: i32) { }
+    );
+
+    cases.addDebugSafety("integer addition overflow",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = add(65530, 10);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn add(a: u16, b: u16) -> u16 {
+        \\    a + b
+        \\}
+    );
+
+    cases.addDebugSafety("integer subtraction overflow",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = sub(10, 20);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn sub(a: u16, b: u16) -> u16 {
+        \\    a - b
+        \\}
+    );
+
+    cases.addDebugSafety("integer multiplication overflow",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = mul(300, 6000);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn mul(a: u16, b: u16) -> u16 {
+        \\    a * b
+        \\}
+    );
+
+    cases.addDebugSafety("integer negation overflow",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = neg(-32768);
+        \\    if (x == 32767) return error.Whatever;
+        \\}
+        \\fn neg(a: i16) -> i16 {
+        \\    -a
+        \\}
+    );
+
+    cases.addDebugSafety("signed integer division overflow",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = div(-32768, -1);
+        \\    if (x == 32767) return error.Whatever;
+        \\}
+        \\fn div(a: i16, b: i16) -> i16 {
+        \\    a / b
+        \\}
+    );
+
+    cases.addDebugSafety("signed shift left overflow",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = shl(-16385, 1);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn shl(a: i16, b: i16) -> i16 {
+        \\    a << b
+        \\}
+    );
+
+    cases.addDebugSafety("unsigned shift left overflow",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = shl(0b0010111111111111, 3);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn shl(a: u16, b: u16) -> u16 {
+        \\    a << b
+        \\}
+    );
+
+    cases.addDebugSafety("integer division by zero",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = div0(999, 0);
+        \\}
+        \\fn div0(a: i32, b: i32) -> i32 {
+        \\    a / b
+        \\}
+    );
+
+    cases.addDebugSafety("exact division failure",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = divExact(10, 3);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn divExact(a: i32, b: i32) -> i32 {
+        \\    @divExact(a, b)
+        \\}
+    );
+
+    cases.addDebugSafety("cast []u8 to bigger slice of wrong size",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = widenSlice([]u8{1, 2, 3, 4, 5});
+        \\    if (x.len == 0) return error.Whatever;
+        \\}
+        \\fn widenSlice(slice: []const u8) -> []const i32 {
+        \\    ([]const i32)(slice)
+        \\}
+    );
+
+    cases.addDebugSafety("value does not fit in shortening cast",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = shorten_cast(200);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn shorten_cast(x: i32) -> i8 {
+        \\    i8(x)
+        \\}
+    );
+
+    cases.addDebugSafety("signed integer not fitting in cast to unsigned integer",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    const x = unsigned_cast(-10);
+        \\    if (x == 0) return error.Whatever;
+        \\}
+        \\fn unsigned_cast(x: i32) -> u32 {
+        \\    u32(x)
+        \\}
+    );
+
+    cases.addDebugSafety("unwrap error",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\error Whatever;
+        \\pub fn main() -> %void {
+        \\    %%bar();
+        \\}
+        \\fn bar() -> %void {
+        \\    return error.Whatever;
+        \\}
+    );
+
+    cases.addDebugSafety("cast integer to error and no code matches",
+        \\pub fn panic(message: []const u8) -> noreturn {
+        \\    @breakpoint();
+        \\    while (true) {}
+        \\}
+        \\pub fn main() -> %void {
+        \\    _ = bar(9999);
+        \\}
+        \\fn bar(x: u32) -> error {
+        \\    return error(x);
+        \\}
+    );
+}
test/run_tests.cpp
@@ -40,7 +40,6 @@ struct TestCase {
     bool is_parseh;
     TestSpecial special;
     bool is_release_mode;
-    bool is_debug_safety;
     AllowWarnings allow_warnings;
 };
 
@@ -58,26 +57,6 @@ static const char *zig_exe = "./zig";
 #define NL "\n"
 #endif
 
-static void add_debug_safety_case(const char *case_name, const char *source) {
-    TestCase *test_case = allocate<TestCase>(1);
-    test_case->is_debug_safety = true;
-    test_case->case_name = buf_ptr(buf_sprintf("%s", case_name));
-    test_case->source_files.resize(1);
-    test_case->source_files.at(0).relative_path = tmp_source_path;
-    test_case->source_files.at(0).source_code = source;
-
-    test_case->compiler_args.append("build_exe");
-    test_case->compiler_args.append(tmp_source_path);
-
-    test_case->compiler_args.append("--name");
-    test_case->compiler_args.append("test");
-
-    test_case->compiler_args.append("--output");
-    test_case->compiler_args.append(tmp_exe_path);
-
-    test_cases.append(test_case);
-}
-
 static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warnings,
     const char *source, size_t count, ...)
 {
@@ -107,240 +86,6 @@ static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warn
     va_end(ap);
     return test_case;
 }
-//////////////////////////////////////////////////////////////////////////////
-
-static void add_debug_safety_test_cases(void) {
-    add_debug_safety_case("calling panic", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-pub fn main() -> %void {
-    @panic("oh no");
-}
-    )SOURCE");
-
-    add_debug_safety_case("out of bounds slice access", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-pub fn main() -> %void {
-    const a = []i32{1, 2, 3, 4};
-    baz(bar(a));
-}
-fn bar(a: []const i32) -> i32 {
-    a[4]
-}
-fn baz(a: i32) { }
-    )SOURCE");
-
-    add_debug_safety_case("integer addition overflow", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = add(65530, 10);
-    if (x == 0) return error.Whatever;
-}
-fn add(a: u16, b: u16) -> u16 {
-    a + b
-}
-    )SOURCE");
-
-    add_debug_safety_case("integer subtraction overflow", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = sub(10, 20);
-    if (x == 0) return error.Whatever;
-}
-fn sub(a: u16, b: u16) -> u16 {
-    a - b
-}
-    )SOURCE");
-
-    add_debug_safety_case("integer multiplication overflow", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = mul(300, 6000);
-    if (x == 0) return error.Whatever;
-}
-fn mul(a: u16, b: u16) -> u16 {
-    a * b
-}
-    )SOURCE");
-
-    add_debug_safety_case("integer negation overflow", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = neg(-32768);
-    if (x == 32767) return error.Whatever;
-}
-fn neg(a: i16) -> i16 {
-    -a
-}
-    )SOURCE");
-
-    add_debug_safety_case("signed integer division overflow", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = div(-32768, -1);
-    if (x == 32767) return error.Whatever;
-}
-fn div(a: i16, b: i16) -> i16 {
-    a / b
-}
-    )SOURCE");
-
-    add_debug_safety_case("signed shift left overflow", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = shl(-16385, 1);
-    if (x == 0) return error.Whatever;
-}
-fn shl(a: i16, b: i16) -> i16 {
-    a << b
-}
-    )SOURCE");
-
-    add_debug_safety_case("unsigned shift left overflow", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = shl(0b0010111111111111, 3);
-    if (x == 0) return error.Whatever;
-}
-fn shl(a: u16, b: u16) -> u16 {
-    a << b
-}
-    )SOURCE");
-
-    add_debug_safety_case("integer division by zero", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = div0(999, 0);
-}
-fn div0(a: i32, b: i32) -> i32 {
-    a / b
-}
-    )SOURCE");
-
-    add_debug_safety_case("exact division failure", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = divExact(10, 3);
-    if (x == 0) return error.Whatever;
-}
-fn divExact(a: i32, b: i32) -> i32 {
-    @divExact(a, b)
-}
-    )SOURCE");
-
-    add_debug_safety_case("cast []u8 to bigger slice of wrong size", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = widenSlice([]u8{1, 2, 3, 4, 5});
-    if (x.len == 0) return error.Whatever;
-}
-fn widenSlice(slice: []const u8) -> []const i32 {
-    ([]const i32)(slice)
-}
-    )SOURCE");
-
-    add_debug_safety_case("value does not fit in shortening cast", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = shorten_cast(200);
-    if (x == 0) return error.Whatever;
-}
-fn shorten_cast(x: i32) -> i8 {
-    i8(x)
-}
-    )SOURCE");
-
-    add_debug_safety_case("signed integer not fitting in cast to unsigned integer", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    const x = unsigned_cast(-10);
-    if (x == 0) return error.Whatever;
-}
-fn unsigned_cast(x: i32) -> u32 {
-    u32(x)
-}
-    )SOURCE");
-
-    add_debug_safety_case("unwrap error", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-error Whatever;
-pub fn main() -> %void {
-    %%bar();
-}
-fn bar() -> %void {
-    return error.Whatever;
-}
-    )SOURCE");
-
-    add_debug_safety_case("cast integer to error and no code matches", R"SOURCE(
-pub fn panic(message: []const u8) -> noreturn {
-    @breakpoint();
-    while (true) {}
-}
-pub fn main() -> %void {
-    _ = bar(9999);
-}
-fn bar(x: u32) -> error {
-    return error(x);
-}
-    )SOURCE");
-}
 
 //////////////////////////////////////////////////////////////////////////////
 
@@ -653,43 +398,24 @@ static void run_test(TestCase *test_case) {
         Buf program_stdout = BUF_INIT;
         os_exec_process(tmp_exe_path, test_case->program_args, &term, &program_stderr, &program_stdout);
 
-        if (test_case->is_debug_safety) {
-            int debug_trap_signal = 5;
-            if (term.how != TerminationIdSignaled || term.code != debug_trap_signal) {
-                if (term.how == TerminationIdClean) {
-                    printf("\nProgram expected to hit debug trap (signal %d) but exited with return code %d\n",
-                            debug_trap_signal, term.code);
-                } else if (term.how == TerminationIdSignaled) {
-                    printf("\nProgram expected to hit debug trap (signal %d) but signaled with code %d\n",
-                            debug_trap_signal, term.code);
-                } else {
-                    printf("\nProgram expected to hit debug trap (signal %d) exited in an unexpected way\n",
-                            debug_trap_signal);
-                }
-                print_compiler_invocation(test_case);
-                print_exe_invocation(test_case);
-                exit(1);
-            }
-        } else {
-            if (term.how != TerminationIdClean || term.code != 0) {
-                printf("\nProgram exited with error\n");
-                print_compiler_invocation(test_case);
-                print_exe_invocation(test_case);
-                printf("%s\n", buf_ptr(&program_stderr));
-                exit(1);
-            }
+        if (term.how != TerminationIdClean || term.code != 0) {
+            printf("\nProgram exited with error\n");
+            print_compiler_invocation(test_case);
+            print_exe_invocation(test_case);
+            printf("%s\n", buf_ptr(&program_stderr));
+            exit(1);
+        }
 
-            if (test_case->output != nullptr && !buf_eql_str(&program_stdout, test_case->output)) {
-                printf("\n");
-                print_compiler_invocation(test_case);
-                print_exe_invocation(test_case);
-                printf("==== Test failed. Expected output: ====\n");
-                printf("%s\n", test_case->output);
-                printf("========= Actual output: ==============\n");
-                printf("%s\n", buf_ptr(&program_stdout));
-                printf("=======================================\n");
-                exit(1);
-            }
+        if (test_case->output != nullptr && !buf_eql_str(&program_stdout, test_case->output)) {
+            printf("\n");
+            print_compiler_invocation(test_case);
+            print_exe_invocation(test_case);
+            printf("==== Test failed. Expected output: ====\n");
+            printf("%s\n", test_case->output);
+            printf("========= Actual output: ==============\n");
+            printf("%s\n", buf_ptr(&program_stdout));
+            printf("=======================================\n");
+            exit(1);
         }
     }
 
@@ -740,7 +466,6 @@ int main(int argc, char **argv) {
             }
         }
     }
-    add_debug_safety_test_cases();
     add_parseh_test_cases();
     run_all_tests(grep_text);
     cleanup();
test/tests.zig
@@ -16,6 +16,7 @@ pub const compare_output = @import("compare_output.zig");
 pub const build_examples = @import("build_examples.zig");
 pub const compile_errors = @import("compile_errors.zig");
 pub const assemble_and_link = @import("assemble_and_link.zig");
+pub const debug_safety = @import("debug_safety.zig");
 
 pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
     const cases = %%b.allocator.create(CompareOutputContext);
@@ -31,6 +32,20 @@ pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &bu
     return cases.step;
 }
 
+pub fn addDebugSafetyTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
+    const cases = %%b.allocator.create(CompareOutputContext);
+    *cases = CompareOutputContext {
+        .b = b,
+        .step = b.step("test-debug-safety", "Run the debug safety tests"),
+        .test_index = 0,
+        .test_filter = test_filter,
+    };
+
+    debug_safety.addCases(cases);
+
+    return cases.step;
+}
+
 pub fn addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
     const cases = %%b.allocator.create(CompileErrorContext);
     *cases = CompileErrorContext {
@@ -79,12 +94,18 @@ pub const CompareOutputContext = struct {
     test_index: usize,
     test_filter: ?[]const u8,
 
+    const Special = enum {
+        None,
+        Asm,
+        DebugSafety,
+    };
+
     const TestCase = struct {
         name: []const u8,
         sources: List(SourceFile),
         expected_output: []const u8,
         link_libc: bool,
-        is_asm: bool,
+        special: Special,
 
         const SourceFile = struct {
             filename: []const u8,
@@ -175,17 +196,83 @@ pub const CompareOutputContext = struct {
         }
     };
 
+    const DebugSafetyRunStep = struct {
+        step: build.Step,
+        context: &CompareOutputContext,
+        exe_path: []const u8,
+        name: []const u8,
+        test_index: usize,
+
+        pub fn create(context: &CompareOutputContext, exe_path: []const u8,
+            name: []const u8) -> &DebugSafetyRunStep
+        {
+            const allocator = context.b.allocator;
+            const ptr = %%allocator.create(DebugSafetyRunStep);
+            *ptr = DebugSafetyRunStep {
+                .context = context,
+                .exe_path = exe_path,
+                .name = name,
+                .test_index = context.test_index,
+                .step = build.Step.init("DebugSafetyRun", allocator, make),
+            };
+            context.test_index += 1;
+            return ptr;
+        }
+
+        fn make(step: &build.Step) -> %void {
+            const self = @fieldParentPtr(DebugSafetyRunStep, "step", step);
+            const b = self.context.b;
+
+            const full_exe_path = b.pathFromRoot(self.exe_path);
+
+            %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
+
+            var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, &b.env_map,
+                StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
+            {
+                debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
+            };
+
+            const term = child.wait() %% |err| {
+                debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
+            };
+
+            const debug_trap_signal: i32 = 5;
+            switch (term) {
+                Term.Clean => |code| {
+                    %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
+                        "but exited with return code {}\n", debug_trap_signal, code);
+                    return error.TestFailed;
+                },
+                Term.Signal => |sig| {
+                    if (sig != debug_trap_signal) {
+                        %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
+                            "but instead signaled {}\n", debug_trap_signal, sig);
+                        return error.TestFailed;
+                    }
+                },
+                else => {
+                    %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
+                        " but exited in an unexpected way\n", debug_trap_signal);
+                    return error.TestFailed;
+                },
+            }
+
+            %%io.stderr.printf("OK\n");
+        }
+    };
+
     pub fn createExtra(self: &CompareOutputContext, name: []const u8, source: []const u8,
-        expected_output: []const u8, is_asm: bool) -> TestCase
+        expected_output: []const u8, special: Special) -> TestCase
     {
         var tc = TestCase {
             .name = name,
             .sources = List(TestCase.SourceFile).init(self.b.allocator),
             .expected_output = expected_output,
             .link_libc = false,
-            .is_asm = is_asm,
+            .special = special,
         };
-        const root_src_name = if (is_asm) "source.s" else "source.zig";
+        const root_src_name = if (special == Special.Asm) "source.s" else "source.zig";
         tc.addSourceFile(root_src_name, source);
         return tc;
     }
@@ -193,7 +280,7 @@ pub const CompareOutputContext = struct {
     pub fn create(self: &CompareOutputContext, name: []const u8, source: []const u8,
         expected_output: []const u8) -> TestCase
     {
-        return createExtra(self, name, source, expected_output, false);
+        return createExtra(self, name, source, expected_output, Special.None);
     }
 
     pub fn addC(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
@@ -208,7 +295,12 @@ pub const CompareOutputContext = struct {
     }
 
     pub fn addAsm(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
-        const tc = self.createExtra(name, source, expected_output, true);
+        const tc = self.createExtra(name, source, expected_output, Special.Asm);
+        self.addCase(tc);
+    }
+
+    pub fn addDebugSafety(self: &CompareOutputContext, name: []const u8, source: []const u8) {
+        const tc = self.createExtra(name, source, undefined, Special.DebugSafety);
         self.addCase(tc);
     }
 
@@ -218,45 +310,74 @@ pub const CompareOutputContext = struct {
         const root_src = %%os.path.join(b.allocator, "test_artifacts", case.sources.items[0].filename);
         const exe_path = %%os.path.join(b.allocator, "test_artifacts", "test");
 
-        if (case.is_asm) {
-            const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
-            const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name);
-            if (const filter ?= self.test_filter) {
-                if (mem.indexOf(u8, annotated_case_name, filter) == null)
-                    return;
-            }
+        switch (case.special) {
+            Special.Asm => {
+                const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
+                const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name);
+                if (const filter ?= self.test_filter) {
+                    if (mem.indexOf(u8, annotated_case_name, filter) == null)
+                        return;
+                }
 
-            const obj = b.addAssemble("test", root_src);
-            obj.setOutputPath(obj_path);
+                const obj = b.addAssemble("test", root_src);
+                obj.setOutputPath(obj_path);
 
-            for (case.sources.toSliceConst()) |src_file| {
-                const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
-                const write_src = b.addWriteFile(expanded_src_path, src_file.source);
-                obj.step.dependOn(&write_src.step);
-            }
+                for (case.sources.toSliceConst()) |src_file| {
+                    const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
+                    const write_src = b.addWriteFile(expanded_src_path, src_file.source);
+                    obj.step.dependOn(&write_src.step);
+                }
 
-            const exe = b.addLinkExecutable("test");
-            exe.step.dependOn(&obj.step);
-            exe.addObjectFile(obj_path);
-            exe.setOutputPath(exe_path);
+                const exe = b.addLinkExecutable("test");
+                exe.step.dependOn(&obj.step);
+                exe.addObjectFile(obj_path);
+                exe.setOutputPath(exe_path);
 
-            const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name,
-                case.expected_output);
-            run_and_cmp_output.step.dependOn(&exe.step);
+                const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name,
+                    case.expected_output);
+                run_and_cmp_output.step.dependOn(&exe.step);
+
+                self.step.dependOn(&run_and_cmp_output.step);
+            },
+            Special.None => {
+                for ([]bool{false, true}) |release| {
+                    const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} ({})",
+                        case.name, if (release) "release" else "debug");
+                    if (const filter ?= self.test_filter) {
+                        if (mem.indexOf(u8, annotated_case_name, filter) == null)
+                            continue;
+                    }
 
-            self.step.dependOn(&run_and_cmp_output.step);
-        } else {
-            for ([]bool{false, true}) |release| {
-                const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} ({})",
-                    case.name, if (release) "release" else "debug");
+                    const exe = b.addExecutable("test", root_src);
+                    exe.setOutputPath(exe_path);
+                    exe.setRelease(release);
+                    if (case.link_libc) {
+                        exe.linkLibrary("c");
+                    }
+
+                    for (case.sources.toSliceConst()) |src_file| {
+                        const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
+                        const write_src = b.addWriteFile(expanded_src_path, src_file.source);
+                        exe.step.dependOn(&write_src.step);
+                    }
+
+                    const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name,
+                        case.expected_output);
+                    run_and_cmp_output.step.dependOn(&exe.step);
+
+                    self.step.dependOn(&run_and_cmp_output.step);
+                }
+            },
+            Special.DebugSafety => {
+                const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
+                const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "debug-safety {}", case.name);
                 if (const filter ?= self.test_filter) {
                     if (mem.indexOf(u8, annotated_case_name, filter) == null)
-                        continue;
+                        return;
                 }
 
                 const exe = b.addExecutable("test", root_src);
                 exe.setOutputPath(exe_path);
-                exe.setRelease(release);
                 if (case.link_libc) {
                     exe.linkLibrary("c");
                 }
@@ -267,14 +388,12 @@ pub const CompareOutputContext = struct {
                     exe.step.dependOn(&write_src.step);
                 }
 
-                const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name,
-                    case.expected_output);
+                const run_and_cmp_output = DebugSafetyRunStep.create(self, exe_path, annotated_case_name);
                 run_and_cmp_output.step.dependOn(&exe.step);
 
                 self.step.dependOn(&run_and_cmp_output.step);
-            }
-        };
-
+            },
+        }
     }
 };
 
build.zig
@@ -39,4 +39,5 @@ pub fn build(b: &Builder) {
     test_step.dependOn(tests.addBuildExampleTests(b, test_filter));
     test_step.dependOn(tests.addCompileErrorTests(b, test_filter));
     test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
+    test_step.dependOn(tests.addDebugSafetyTests(b, test_filter));
 }