mirror of
https://github.com/ziglang/zig.git
synced 2026-01-21 23:05:24 +00:00
Previously, the `test-stack-traces` step was essentially just testing error traces, and even there we didn't have much coverage. This commit solves that by splitting the "stack trace" tests into two separate harnesses: the "stack trace" tests are for actual stack traces (i.e. involving stack unwinding), while the "error trace" tests are specifically for error return traces. The "stack trace" tests will test different configurations of: * `-lc` * `-fPIE` * `-fomit-frame-pointer` * `-fllvm` * unwind tables (currently disabled) * strip debug info (currently disabled) The main goal there is to test *stack unwinding* under different conditions. Meanwhile, the "error trace" tests will test different configurations of `-O` and `-fllvm`; the main goal here, aside from checking that error traces themselves do not miscompile, is to check whether debug info is still working even in optimized builds. Of course, aggressive optimizations *can* thwart debug info no matter what, so as before, there is a way to disable cases for specific targets / optimize modes. The program which converts stack traces into a more validatable format by removing things like addresses (previously `check-stack-trace.zig`, now `convert-stack-trace.zig`) has been rewritten and simplified. Also, thanks to various fixes in this branch, several workarounds have become unnecessary: for instance, we don't need to ignore the function name printed in stack traces in release modes, because `std.debug.Dwarf` now uses the correct DIE for inlined functions! Neither `test-stack-traces` nor `test-error-traces` does general foreign architecture testing, because it seems that (at least for now) external executors often aren't particularly good at handling stack tracing correctly (looking at you, Wine). Generally, they just test the native target (this matches the old behavior of `test-stack-traces`). However, there is one exception: when on an x86_64 or aarch64 host, we will also test the 32-bit version (x86 or arm) if the OS supports it, because such executables can be trivially tested without an external executor. Oh, also, I wrote a bunch of stack trace tests. Previously there was, erm, *one* test in `test-stack-traces` which wasn't for error traces. Now there are a good few!
431 lines
12 KiB
Zig
431 lines
12 KiB
Zig
pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {
|
|
cases.addCase(.{
|
|
.name = "return",
|
|
.source =
|
|
\\pub fn main() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
,
|
|
.expect_error = "TheSkyIsFalling",
|
|
.expect_trace =
|
|
\\source.zig:2:5: [address] in main
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
,
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try return",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ try foo();
|
|
\\}
|
|
,
|
|
.expect_error = "TheSkyIsFalling",
|
|
.expect_trace =
|
|
\\source.zig:2:5: [address] in foo
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in main
|
|
\\ try foo();
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
cases.addCase(.{
|
|
.name = "non-error return pops error trace",
|
|
.source =
|
|
\\fn bar() !void {
|
|
\\ return error.UhOh;
|
|
\\}
|
|
\\
|
|
\\fn foo() !void {
|
|
\\ bar() catch {
|
|
\\ return; // non-error result: success
|
|
\\ };
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ try foo();
|
|
\\ return error.UnrelatedError;
|
|
\\}
|
|
,
|
|
.expect_error = "UnrelatedError",
|
|
.expect_trace =
|
|
\\source.zig:13:5: [address] in main
|
|
\\ return error.UnrelatedError;
|
|
\\ ^
|
|
,
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "continue in while loop",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.UhOh;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ var i: usize = 0;
|
|
\\ while (i < 3) : (i += 1) {
|
|
\\ foo() catch continue;
|
|
\\ }
|
|
\\ return error.UnrelatedError;
|
|
\\}
|
|
,
|
|
.expect_error = "UnrelatedError",
|
|
.expect_trace =
|
|
\\source.zig:10:5: [address] in main
|
|
\\ return error.UnrelatedError;
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .linux },
|
|
.{ .x86, .linux },
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try return + handled catch/if-else",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ foo() catch {}; // should not affect error trace
|
|
\\ if (foo()) |_| {} else |_| {
|
|
\\ // should also not affect error trace
|
|
\\ }
|
|
\\ try foo();
|
|
\\}
|
|
,
|
|
.expect_error = "TheSkyIsFalling",
|
|
.expect_trace =
|
|
\\source.zig:2:5: [address] in foo
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:10:5: [address] in main
|
|
\\ try foo();
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "break from inline loop pops error return trace",
|
|
.source =
|
|
\\fn foo() !void { return error.FooBar; }
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ comptime var i: usize = 0;
|
|
\\ b: inline while (i < 5) : (i += 1) {
|
|
\\ foo() catch {
|
|
\\ break :b; // non-error break, success
|
|
\\ };
|
|
\\ }
|
|
\\ // foo() was successfully handled, should not appear in trace
|
|
\\
|
|
\\ return error.BadTime;
|
|
\\}
|
|
,
|
|
.expect_error = "BadTime",
|
|
.expect_trace =
|
|
\\source.zig:12:5: [address] in main
|
|
\\ return error.BadTime;
|
|
\\ ^
|
|
,
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "catch and re-throw error",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
|
\\}
|
|
,
|
|
.expect_error = "AndMyCarIsOutOfGas",
|
|
.expect_trace =
|
|
\\source.zig:2:5: [address] in foo
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in main
|
|
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "errors stored in var do not contribute to error trace",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ // Once an error is stored in a variable, it is popped from the trace
|
|
\\ var x = foo();
|
|
\\ x = {};
|
|
\\
|
|
\\ // As a result, this error trace will still be clean
|
|
\\ return error.SomethingUnrelatedWentWrong;
|
|
\\}
|
|
,
|
|
.expect_error = "SomethingUnrelatedWentWrong",
|
|
.expect_trace =
|
|
\\source.zig:11:5: [address] in main
|
|
\\ return error.SomethingUnrelatedWentWrong;
|
|
\\ ^
|
|
,
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "error stored in const has trace preserved for duration of block",
|
|
.source =
|
|
\\fn foo() !void { return error.TheSkyIsFalling; }
|
|
\\fn bar() !void { return error.InternalError; }
|
|
\\fn baz() !void { return error.UnexpectedReality; }
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ const x = foo();
|
|
\\ const y = b: {
|
|
\\ if (true)
|
|
\\ break :b bar();
|
|
\\
|
|
\\ break :b {};
|
|
\\ };
|
|
\\ x catch {};
|
|
\\ y catch {};
|
|
\\ // foo()/bar() error traces not popped until end of block
|
|
\\
|
|
\\ {
|
|
\\ const z = baz();
|
|
\\ z catch {};
|
|
\\ // baz() error trace still alive here
|
|
\\ }
|
|
\\ // baz() error trace popped, foo(), bar() still alive
|
|
\\ return error.StillUnresolved;
|
|
\\}
|
|
,
|
|
.expect_error = "StillUnresolved",
|
|
.expect_trace =
|
|
\\source.zig:1:18: [address] in foo
|
|
\\fn foo() !void { return error.TheSkyIsFalling; }
|
|
\\ ^
|
|
\\source.zig:2:18: [address] in bar
|
|
\\fn bar() !void { return error.InternalError; }
|
|
\\ ^
|
|
\\source.zig:23:5: [address] in main
|
|
\\ return error.StillUnresolved;
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "error passed to function has its trace preserved for duration of the call",
|
|
.source =
|
|
\\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
|
|
\\ actual_error catch |err| {
|
|
\\ if (err == expected_error) return {};
|
|
\\ };
|
|
\\ return error.TestExpectedError;
|
|
\\}
|
|
\\
|
|
\\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
|
|
\\fn foo() !void { return error.Foo; }
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
|
|
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
|
|
\\ try expectError(error.Foo, foo());
|
|
\\
|
|
\\ // Only the error trace for this failing check should appear:
|
|
\\ try expectError(error.Bar, foo());
|
|
\\}
|
|
,
|
|
.expect_error = "TestExpectedError",
|
|
.expect_trace =
|
|
\\source.zig:9:18: [address] in foo
|
|
\\fn foo() !void { return error.Foo; }
|
|
\\ ^
|
|
\\source.zig:5:5: [address] in expectError
|
|
\\ return error.TestExpectedError;
|
|
\\ ^
|
|
\\source.zig:17:5: [address] in main
|
|
\\ try expectError(error.Bar, foo());
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try return from within catch",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\fn bar() !void {
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ foo() catch { // error trace should include foo()
|
|
\\ try bar();
|
|
\\ };
|
|
\\}
|
|
,
|
|
.expect_error = "AndMyCarIsOutOfGas",
|
|
.expect_trace =
|
|
\\source.zig:2:5: [address] in foo
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in bar
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\source.zig:11:9: [address] in main
|
|
\\ try bar();
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try return from within if-else",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\fn bar() !void {
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ if (foo()) |_| {} else |_| { // error trace should include foo()
|
|
\\ try bar();
|
|
\\ }
|
|
\\}
|
|
,
|
|
.expect_error = "AndMyCarIsOutOfGas",
|
|
.expect_trace =
|
|
\\source.zig:2:5: [address] in foo
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in bar
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\source.zig:11:9: [address] in main
|
|
\\ try bar();
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try try return return",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ try bar();
|
|
\\}
|
|
\\
|
|
\\fn bar() !void {
|
|
\\ return make_error();
|
|
\\}
|
|
\\
|
|
\\fn make_error() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ try foo();
|
|
\\}
|
|
,
|
|
.expect_error = "TheSkyIsFalling",
|
|
.expect_trace =
|
|
\\source.zig:10:5: [address] in make_error
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in bar
|
|
\\ return make_error();
|
|
\\ ^
|
|
\\source.zig:2:5: [address] in foo
|
|
\\ try bar();
|
|
\\ ^
|
|
\\source.zig:14:5: [address] in main
|
|
\\ try foo();
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "error union switch with call operand",
|
|
.source =
|
|
\\pub fn main() !void {
|
|
\\ try foo();
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\noinline fn failure() error{ Fatal, NonFatal }!void {
|
|
\\ return error.NonFatal;
|
|
\\}
|
|
\\
|
|
\\fn foo() error{Fatal}!void {
|
|
\\ return failure() catch |err| switch (err) {
|
|
\\ error.Fatal => return error.Fatal,
|
|
\\ error.NonFatal => return,
|
|
\\ };
|
|
\\}
|
|
,
|
|
.expect_error = "TheSkyIsFalling",
|
|
.expect_trace =
|
|
\\source.zig:3:5: [address] in main
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
,
|
|
.disable_trace_optimized = &.{
|
|
.{ .x86_64, .linux },
|
|
.{ .x86, .linux },
|
|
.{ .x86_64, .windows },
|
|
.{ .x86, .windows },
|
|
},
|
|
});
|
|
}
|