authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-07 13:28:16-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-07 13:28:16-07:00
log8b2949e372e615886b9087b06187878452421724
tree69109053e156249712255bba6c292f3d7a2eb09c
parent0f1c478db329c4b91c3a34aed2d48e4cde059eff

simplify stack trace and error trace tests

by explicitly listing out the combinations of parameters we would like to check.

5 files changed, 337 insertions(+), 250 deletions(-)

test/error_traces.zig+41-2
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
2const Context = @import("tests.zig").ErrorTracesContext;
23
3pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.Os.Tag) void {4pub fn addCases(cases: *Context, params: *const Context.CaseParameters, target: *const std.Target) void {
4 cases.addCase(.{5 cases.addCase(.{
6 .params = params,
7 .target = target,
5 .name = "return",8 .name = "return",
6 .source =9 .source =
7 \\pub fn main() !void {10 \\pub fn main() !void {
...@@ -17,6 +20,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -17,6 +20,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
17 });20 });
1821
19 cases.addCase(.{22 cases.addCase(.{
23 .params = params,
24 .target = target,
20 .name = "try return",25 .name = "try return",
21 .source =26 .source =
22 \\fn foo() !void {27 \\fn foo() !void {
...@@ -44,6 +49,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -44,6 +49,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
44 },49 },
45 });50 });
46 cases.addCase(.{51 cases.addCase(.{
52 .params = params,
53 .target = target,
47 .name = "non-error return pops error trace",54 .name = "non-error return pops error trace",
48 .source =55 .source =
49 \\fn bar() !void {56 \\fn bar() !void {
...@@ -70,6 +77,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -70,6 +77,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
70 });77 });
7178
72 cases.addCase(.{79 cases.addCase(.{
80 .params = params,
81 .target = target,
73 .name = "continue in while loop",82 .name = "continue in while loop",
74 .source =83 .source =
75 \\fn foo() !void {84 \\fn foo() !void {
...@@ -93,6 +102,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -93,6 +102,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
93 });102 });
94103
95 cases.addCase(.{104 cases.addCase(.{
105 .params = params,
106 .target = target,
96 .name = "for loop pops error return trace",107 .name = "for loop pops error return trace",
97 .source =108 .source =
98 \\fn foo() !void { return error.FooError; }109 \\fn foo() !void { return error.FooError; }
...@@ -123,6 +134,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -123,6 +134,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
123 });134 });
124135
125 cases.addCase(.{136 cases.addCase(.{
137 .params = params,
138 .target = target,
126 .name = "implicit continue in for loop pops stale error return trace",139 .name = "implicit continue in for loop pops stale error return trace",
127 .source =140 .source =
128 \\fn foo() !void { return error.FooError; }141 \\fn foo() !void { return error.FooError; }
...@@ -154,6 +167,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -154,6 +167,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
154 });167 });
155168
156 cases.addCase(.{169 cases.addCase(.{
170 .params = params,
171 .target = target,
157 .name = "while loop pops error return trace",172 .name = "while loop pops error return trace",
158 .source =173 .source =
159 \\fn foo() !void { return error.FooError; }174 \\fn foo() !void { return error.FooError; }
...@@ -186,6 +201,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -186,6 +201,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
186 });201 });
187202
188 cases.addCase(.{203 cases.addCase(.{
204 .params = params,
205 .target = target,
189 .name = "implicit continue in while loop pops stale error return trace",206 .name = "implicit continue in while loop pops stale error return trace",
190 .source =207 .source =
191 \\fn foo() !void { return error.FooError; }208 \\fn foo() !void { return error.FooError; }
...@@ -219,6 +236,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -219,6 +236,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
219 });236 });
220237
221 cases.addCase(.{238 cases.addCase(.{
239 .params = params,
240 .target = target,
222 .name = "try return + handled catch/if-else",241 .name = "try return + handled catch/if-else",
223 .source =242 .source =
224 \\fn foo() !void {243 \\fn foo() !void {
...@@ -251,6 +270,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -251,6 +270,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
251 });270 });
252271
253 cases.addCase(.{272 cases.addCase(.{
273 .params = params,
274 .target = target,
254 .name = "break from inline loop pops error return trace",275 .name = "break from inline loop pops error return trace",
255 .source =276 .source =
256 \\fn foo() !void { return error.FooBar; }277 \\fn foo() !void { return error.FooBar; }
...@@ -276,6 +297,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -276,6 +297,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
276 });297 });
277298
278 cases.addCase(.{299 cases.addCase(.{
300 .params = params,
301 .target = target,
279 .name = "catch and re-throw error",302 .name = "catch and re-throw error",
280 .source =303 .source =
281 \\fn foo() !void {304 \\fn foo() !void {
...@@ -304,6 +327,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -304,6 +327,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
304 });327 });
305328
306 cases.addCase(.{329 cases.addCase(.{
330 .params = params,
331 .target = target,
307 .name = "errors stored in var do not contribute to error trace",332 .name = "errors stored in var do not contribute to error trace",
308 .source =333 .source =
309 \\fn foo() !void {334 \\fn foo() !void {
...@@ -328,6 +353,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -328,6 +353,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
328 });353 });
329354
330 cases.addCase(.{355 cases.addCase(.{
356 .params = params,
357 .target = target,
331 .name = "error stored in const has trace preserved for duration of block",358 .name = "error stored in const has trace preserved for duration of block",
332 .source =359 .source =
333 \\fn foo() !void { return error.TheSkyIsFalling; }360 \\fn foo() !void { return error.TheSkyIsFalling; }
...@@ -376,6 +403,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -376,6 +403,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
376 });403 });
377404
378 cases.addCase(.{405 cases.addCase(.{
406 .params = params,
407 .target = target,
379 .name = "error passed to function has its trace preserved for duration of the call",408 .name = "error passed to function has its trace preserved for duration of the call",
380 .source =409 .source =
381 \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {410 \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
...@@ -418,6 +447,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -418,6 +447,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
418 });447 });
419448
420 cases.addCase(.{449 cases.addCase(.{
450 .params = params,
451 .target = target,
421 .name = "try return from within catch",452 .name = "try return from within catch",
422 .source =453 .source =
423 \\fn foo() !void {454 \\fn foo() !void {
...@@ -455,6 +486,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -455,6 +486,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
455 });486 });
456487
457 cases.addCase(.{488 cases.addCase(.{
489 .params = params,
490 .target = target,
458 .name = "try return from within if-else",491 .name = "try return from within if-else",
459 .source =492 .source =
460 \\fn foo() !void {493 \\fn foo() !void {
...@@ -492,6 +525,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -492,6 +525,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
492 });525 });
493526
494 cases.addCase(.{527 cases.addCase(.{
528 .params = params,
529 .target = target,
495 .name = "try try return return",530 .name = "try try return return",
496 .source =531 .source =
497 \\fn foo() !void {532 \\fn foo() !void {
...@@ -534,6 +569,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -534,6 +569,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
534 });569 });
535570
536 cases.addCase(.{571 cases.addCase(.{
572 .params = params,
573 .target = target,
537 .name = "error union switch with call operand",574 .name = "error union switch with call operand",
538 .source =575 .source =
539 \\pub fn main() !void {576 \\pub fn main() !void {
...@@ -579,6 +616,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -579,6 +616,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
579 });616 });
580617
581 cases.addCase(.{618 cases.addCase(.{
619 .params = params,
620 .target = target,
582 .name = "trace through inline call",621 .name = "trace through inline call",
583 // The main function has two inline calls to ensure622 // The main function has two inline calls to ensure
584 // that inlinees in PDBs are properly deduplicated.623 // that inlinees in PDBs are properly deduplicated.
...@@ -595,7 +634,7 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -595,7 +634,7 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
595 \\}634 \\}
596 ,635 ,
597 .expect_error = "ThisIsSoSad",636 .expect_error = "ThisIsSoSad",
598 .expect_trace = switch (os) {637 .expect_trace = switch (target.os.tag) {
599 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,638 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,
600 // so our expected result is slightly different for Windows than on other operating639 // so our expected result is slightly different for Windows than on other operating
601 // systems.640 // systems.
test/src/ErrorTrace.zig+117-52
...@@ -1,11 +1,81 @@...@@ -1,11 +1,81 @@
1const ErrorTrace = @This();
2
3const builtin = @import("builtin");
4
5const std = @import("std");
6const Step = std.Build.Step;
7const OptimizeMode = std.lang.Optimize;
8const mem = std.mem;
9
10const error_traces_cases = @import("../error_traces.zig");
11
1b: *std.Build,12b: *std.Build,
2step: *Step,13step: *Step,
3test_filters: []const []const u8,14test_filters: []const []const u8,
4targets: []const std.Build.ResolvedTarget,15skip_non_native: bool,
5optimize_modes: []const OptimizeMode,16optimize_modes: []const OptimizeMode,
6convert_exe: *std.Build.Step.Compile,17convert_exe: *std.Build.Step.Compile,
718
19pub const CaseParameters = @import("StackTrace.zig").CaseParameters;
20
21const param_sets = [_]CaseParameters{
22 .{},
23 .{
24 .link_libc = true,
25 },
26 .{
27 .use_llvm = true,
28 .use_lld = true,
29 },
30 .{
31 .pie = true,
32 },
33 .{
34 .target = .{
35 .cpu_arch = .aarch64,
36 .os_tag = .windows,
37 .abi = .msvc,
38 },
39 },
40 .{
41 .target = .{
42 .cpu_arch = .x86_64,
43 .os_tag = .windows,
44 .abi = .gnu,
45 },
46 },
47 .{
48 .target = .{
49 .cpu_arch = .x86,
50 .os_tag = .windows,
51 .abi = .msvc,
52 },
53 },
54 .{
55 .target = .{
56 .cpu_arch = .aarch64,
57 .os_tag = .macos,
58 },
59 },
60 .{
61 .target = .{
62 .cpu_arch = .s390x,
63 .os_tag = .linux,
64 .abi = .none,
65 },
66 },
67 .{
68 .target = .{
69 .cpu_arch = .loongarch32,
70 .os_tag = .linux,
71 .abi = .none,
72 },
73 },
74};
75
8pub const Case = struct {76pub const Case = struct {
77 params: *const CaseParameters,
78 target: *const std.Target,
9 name: []const u8,79 name: []const u8,
10 source: []const u8,80 source: []const u8,
11 expect_error: []const u8,81 expect_error: []const u8,
...@@ -22,50 +92,47 @@ pub const Case = struct {...@@ -22,50 +92,47 @@ pub const Case = struct {
22 pub const Backend = enum { llvm, selfhosted };92 pub const Backend = enum { llvm, selfhosted };
23};93};
2494
25pub fn addCase(self: *ErrorTrace, case: Case) void {95pub fn addCases(self: *ErrorTrace) void {
26 for (self.targets) |*target| {96 const b = self.b;
27 const triple: ?[]const u8 = if (target.query.isNative()) null else t: {97
28 break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM");98 for (&param_sets) |*params| {
29 };99 const resolved_target = b.resolveTargetQuery(params.target);
30 for (self.optimize_modes) |optimize| {100
31 self.addCaseConfig(case, target, triple, optimize, .llvm);101 if (self.skip_non_native and !resolved_target.query.isNative()) continue;
32 }102
33 if (shouldTestNonLlvm(&target.result)) {103 // To avoid redundant testing, skip cross-compilation targets matching the host.
34 for (self.optimize_modes) |optimize| {104 if (resolved_target.result.os.tag == builtin.target.os.tag and
35 self.addCaseConfig(case, target, triple, optimize, .selfhosted);105 resolved_target.result.cpu.arch == builtin.target.cpu.arch)
36 }106 {
107 continue;
37 }108 }
38 }
39}
40109
41fn shouldTestNonLlvm(target: *const std.Target) bool {110 for (self.optimize_modes) |optimize| {
42 if (comptime builtin.cpu.arch.endian() == .big) return false; // https://github.com/ziglang/zig/issues/25961111 if (optimize == params.optimize) break;
43 return switch (target.cpu.arch) {112 } else return;
44 .x86_64 => switch (target.ofmt) {113
45 .elf => !target.os.tag.isBSD() and target.os.tag != .illumos,114 error_traces_cases.addCases(self, params, &resolved_target.result);
46 else => false,115 }
47 },
48 else => false,
49 };
50}116}
51117
52fn addCaseConfig(118/// Called from test/error_traces.zig
53 self: *ErrorTrace,119pub fn addCase(self: *ErrorTrace, case: Case) void {
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;120 const b = self.b;
121 const params = case.params;
122 const target = case.target;
123 const target_query = params.target;
124
125 const triple: ?[]const u8 = if (target_query.isNative()) null else t: {
126 break :t target_query.zigTriple(self.b.graph.arena) catch @panic("OOM");
127 };
61128
62 const error_tracing: bool = tracing: {129 const error_tracing: bool = tracing: {
63 if (optimize == .debug) break :tracing true;130 if (params.optimize == .debug) break :tracing true;
64 if (backend != .llvm) break :tracing true;131 if (params.use_llvm == false) break :tracing true;
65 if (optimize == .small) break :tracing false;132 if (params.optimize == .small) break :tracing false;
66 for (case.disable_trace_optimized) |disable| {133 for (case.disable_trace_optimized) |disable| {
67 const d_arch, const d_os = disable;134 const d_arch, const d_os = disable;
68 if (target.result.cpu.arch == d_arch and target.result.os.tag == d_os) {135 if (target.cpu.arch == d_arch and target.os.tag == d_os) {
69 // This particular configuration cannot do error tracing in optimized LLVM builds.136 // This particular configuration cannot do error tracing in optimized LLVM builds.
70 break :tracing false;137 break :tracing false;
71 }138 }
...@@ -73,12 +140,19 @@ fn addCaseConfig(...@@ -73,12 +140,19 @@ fn addCaseConfig(
73 break :tracing true;140 break :tracing true;
74 };141 };
75142
76 const annotated_case_name = b.fmt("check {s} ({s}{s}{s} {s})", .{143 const backend_string = if (params.use_llvm == true)
144 "-llvm"
145 else if (params.use_llvm == false)
146 "-selfhosted"
147 else
148 "";
149
150 const annotated_case_name = b.fmt("check {s} ({s}{s}{t}{s})", .{
77 case.name,151 case.name,
78 triple orelse "",152 triple orelse "",
79 if (triple != null) " " else "",153 if (triple != null) " " else "",
80 @tagName(optimize),154 params.optimize,
81 @tagName(backend),155 backend_string,
82 });156 });
83 if (self.test_filters.len > 0) {157 if (self.test_filters.len > 0) {
84 for (self.test_filters) |test_filter| {158 for (self.test_filters) |test_filter| {
...@@ -92,15 +166,13 @@ fn addCaseConfig(...@@ -92,15 +166,13 @@ fn addCaseConfig(
92 .name = "test",166 .name = "test",
93 .root_module = b.createModule(.{167 .root_module = b.createModule(.{
94 .root_source_file = source_zig,168 .root_source_file = source_zig,
95 .optimize = optimize,169 .optimize = params.optimize,
96 .target = target.*,170 .target = .{ .result = target.*, .query = target_query },
97 .error_tracing = error_tracing,171 .error_tracing = error_tracing,
98 .strip = false,172 .strip = false,
99 }),173 }),
100 .use_llvm = switch (backend) {174 .use_llvm = params.use_llvm,
101 .llvm => true,175 .use_lld = params.use_lld,
102 .selfhosted => false,
103 },
104 });176 });
105 exe.bundle_ubsan_rt = false;177 exe.bundle_ubsan_rt = false;
106178
...@@ -124,10 +196,3 @@ fn addCaseConfig(...@@ -124,10 +196,3 @@ fn addCaseConfig(
124196
125 self.step.dependOn(&check_run.step);197 self.step.dependOn(&check_run.step);
126}198}
127
128const ErrorTrace = @This();
129const std = @import("std");
130const builtin = @import("builtin");
131const Step = std.Build.Step;
132const OptimizeMode = std.builtin.OptimizeMode;
133const mem = std.mem;
test/src/StackTrace.zig+146-86
...@@ -4,16 +4,88 @@ const builtin = @import("builtin");...@@ -4,16 +4,88 @@ const builtin = @import("builtin");
44
5const std = @import("std");5const std = @import("std");
6const Step = std.Build.Step;6const Step = std.Build.Step;
7const OptimizeMode = std.lang.OptimizeMode;7const OptimizeMode = std.lang.Optimize;
8const mem = std.mem;8const mem = std.mem;
99
10const stack_traces_cases = @import("../stack_traces.zig");
11
10b: *std.Build,12b: *std.Build,
11step: *Step,13step: *Step,
12test_filters: []const []const u8,14test_filters: []const []const u8,
13targets: []const std.Build.ResolvedTarget,15skip_non_native: bool,
14convert_exe: *std.Build.Step.Compile,16convert_exe: *std.Build.Step.Compile,
1517
18pub const CaseParameters = struct {
19 target: std.Target.Query = .{},
20 optimize: std.builtin.OptimizeMode = .debug,
21 link_libc: ?bool = null,
22 use_llvm: ?bool = null,
23 use_lld: ?bool = null,
24 pie: ?bool = null,
25 /// To enable this coverage, one of two things needs to happen:
26 /// * The compiler needs to gain the ability to strip only debug info (not symbols)
27 /// * `std.Build.Step.ObjCopy` needs to be un-regressed
28 strip: ?bool = false,
29};
30
31const param_sets = [_]CaseParameters{
32 .{},
33 .{
34 .link_libc = true,
35 },
36 .{
37 .use_llvm = true,
38 .use_lld = true,
39 },
40 .{
41 .pie = true,
42 },
43 .{
44 .target = .{
45 .cpu_arch = .aarch64,
46 .os_tag = .windows,
47 .abi = .msvc,
48 },
49 },
50 .{
51 .target = .{
52 .cpu_arch = .x86_64,
53 .os_tag = .windows,
54 .abi = .gnu,
55 },
56 },
57 .{
58 .target = .{
59 .cpu_arch = .x86,
60 .os_tag = .windows,
61 .abi = .msvc,
62 },
63 },
64 .{
65 .target = .{
66 .cpu_arch = .aarch64,
67 .os_tag = .macos,
68 },
69 },
70 .{
71 .target = .{
72 .cpu_arch = .s390x,
73 .os_tag = .linux,
74 .abi = .none,
75 },
76 },
77 .{
78 .target = .{
79 .cpu_arch = .loongarch32,
80 .os_tag = .linux,
81 .abi = .none,
82 },
83 },
84};
85
16const Config = struct {86const Config = struct {
87 params: *const CaseParameters,
88 target: *const std.Target,
17 name: []const u8,89 name: []const u8,
18 source: []const u8,90 source: []const u8,
19 /// Whether this test case expects to have unwind tables / frame pointers.91 /// Whether this test case expects to have unwind tables / frame pointers.
...@@ -35,42 +107,37 @@ const Config = struct {...@@ -35,42 +107,37 @@ const Config = struct {
35 expect_strip: []const u8,107 expect_strip: []const u8,
36};108};
37109
38pub fn addCase(self: *StackTrace, config: Config) void {110pub fn addCases(self: *StackTrace) void {
39 for (self.targets) |*target| {111 const b = self.b;
40 addCaseTarget(112
41 self,113 for (&param_sets) |*params| {
42 config,114 const resolved_target = b.resolveTargetQuery(params.target);
43 target,115
44 if (target.query.isNative()) null else t: {116 if (self.skip_non_native and !resolved_target.query.isNative()) continue;
45 break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM");117
46 },118 // To avoid redundant testing, skip cross-compilation targets matching the host.
47 );119 if (resolved_target.result.os.tag == builtin.target.os.tag and
120 resolved_target.result.cpu.arch == builtin.target.cpu.arch)
121 {
122 continue;
123 }
124
125 stack_traces_cases.addCases(self, params, &resolved_target.result);
48 }126 }
49}127}
50fn addCaseTarget(128
51 self: *StackTrace,129/// Called from test/stack_traces.zig
52 config: Config,130pub fn addCase(self: *StackTrace, config: Config) void {
53 target: *const std.Build.ResolvedTarget,131 const params = config.params;
54 triple: ?[]const u8,132 const target = config.target;
55) void {133 const target_query = config.params.target;
56 const both_backends = b: {134
57 if (builtin.cpu.arch.endian() == .big) break :b false; // https://codeberg.org/ziglang/zig/issues/31522135 const triple: ?[]const u8 = if (target_query.isNative()) null else t: {
58 break :b switch (target.result.cpu.arch) {136 break :t target_query.zigTriple(self.b.graph.arena) catch @panic("OOM");
59 .x86_64 => switch (target.result.ofmt) {
60 .elf => !target.result.os.tag.isBSD() and target.result.os.tag != .illumos,
61 else => false,
62 },
63 else => false,
64 };
65 };
66 const both_pie = switch (target.result.os.tag) {
67 .fuchsia => false,
68 else => true,
69 };137 };
70 const both_libc = !std.os.targetRequiresLibC(&target.result);
71138
72 // See `std.debug.StackIterator.fp_usability` logic.139 // See `std.debug.StackIterator.fp_usability` logic.
73 const fp_usability: enum { useless, unsafe, safe, ideal } = switch (target.result.cpu.arch) {140 const fp_usability: enum { useless, unsafe, safe, ideal } = switch (target.cpu.arch) {
74 .alpha,141 .alpha,
75 .csky,142 .csky,
76 .microblaze,143 .microblaze,
...@@ -92,20 +159,15 @@ fn addCaseTarget(...@@ -92,20 +159,15 @@ fn addCaseTarget(
92 .sparc,159 .sparc,
93 .sparc64,160 .sparc64,
94 => .ideal,161 => .ideal,
95 .aarch64 => if (target.result.os.tag.isDarwin()) .safe else .unsafe,162 .aarch64 => if (target.os.tag.isDarwin()) .safe else .unsafe,
96 else => .unsafe,163 else => .unsafe,
97 };164 };
98 const supports_unwind_tables = switch (target.result.os.tag) {165 const supports_unwind_tables = switch (target.os.tag) {
99 // x86-windows just has no way to do stack unwinding other then using frame pointers.166 // x86-windows just has no way to do stack unwinding other then using frame pointers.
100 .windows => target.result.cpu.arch != .x86,167 .windows => target.cpu.arch != .x86,
101 else => true,168 else => true,
102 };169 };
103170
104 const use_llvm_vals: []const bool = if (both_backends) &.{ true, false } else &.{true};
105 const pie_vals: []const ?bool = if (both_pie) &.{ true, false } else &.{null};
106 const link_libc_vals: []const ?bool = if (both_libc) &.{ true, false } else &.{null};
107 const strip_debug_vals: []const bool = &.{ true, false };
108
109 const UnwindInfo = packed struct(u2) {171 const UnwindInfo = packed struct(u2) {
110 tables: bool,172 tables: bool,
111 fp: bool,173 fp: bool,
...@@ -135,43 +197,33 @@ fn addCaseTarget(...@@ -135,43 +197,33 @@ fn addCaseTarget(
135 },197 },
136 };198 };
137199
138 for (use_llvm_vals) |use_llvm| {200 for (unwind_info_vals) |unwind_info| {
139 for (pie_vals) |pie| {201 if (unwind_info.tables and !supports_unwind_tables) continue;
140 for (link_libc_vals) |link_libc| {202 const strip = params.strip orelse switch (params.optimize) {
141 for (strip_debug_vals) |strip_debug| {203 .debug, .fast, .safe => false,
142 for (unwind_info_vals) |unwind_info| {204 .small => true,
143 if (unwind_info.tables and !supports_unwind_tables) continue;205 };
144 self.addCaseInstance(206 self.addCaseInstance(
145 target,207 .{ .result = target.*, .query = target_query },
146 triple,208 triple,
147 config.name,209 config.name,
148 config.source,210 config.source,
149 use_llvm,211 params,
150 pie,212 !unwind_info.tables and supports_unwind_tables,
151 link_libc,213 !unwind_info.fp,
152 strip_debug,214 config.expect_panic,
153 !unwind_info.tables and supports_unwind_tables,215 if (strip) config.expect_strip else config.expect,
154 !unwind_info.fp,216 );
155 config.expect_panic,
156 if (strip_debug) config.expect_strip else config.expect,
157 );
158 }
159 }
160 }
161 }
162 }217 }
163}218}
164219
165fn addCaseInstance(220fn addCaseInstance(
166 self: *StackTrace,221 self: *StackTrace,
167 target: *const std.Build.ResolvedTarget,222 resolved_target: std.Build.ResolvedTarget,
168 triple: ?[]const u8,223 triple: ?[]const u8,
169 name: []const u8,224 name: []const u8,
170 source: []const u8,225 source: []const u8,
171 use_llvm: bool,226 params: *const CaseParameters,
172 pie: ?bool,
173 link_libc: ?bool,
174 strip_debug: bool,
175 strip_unwind: bool,227 strip_unwind: bool,
176 omit_frame_pointer: bool,228 omit_frame_pointer: bool,
177 expect_panic: bool,229 expect_panic: bool,
...@@ -179,13 +231,6 @@ fn addCaseInstance(...@@ -179,13 +231,6 @@ fn addCaseInstance(
179) void {231) void {
180 const b = self.b;232 const b = self.b;
181233
182 if (strip_debug) {
183 // To enable this coverage, one of two things needs to happen:
184 // * The compiler needs to gain the ability to strip only debug info (not symbols)
185 // * `std.Build.Step.ObjCopy` needs to be un-regressed
186 return;
187 }
188
189 if (strip_unwind) {234 if (strip_unwind) {
190 // To enable this coverage, `std.Build.Step.ObjCopy` needs to be un-regressed and gain the235 // To enable this coverage, `std.Build.Step.ObjCopy` needs to be un-regressed and gain the
191 // ability to remove individual sections. `-fno-unwind-tables` is insufficient because it236 // ability to remove individual sections. `-fno-unwind-tables` is insufficient because it
...@@ -196,14 +241,28 @@ fn addCaseInstance(...@@ -196,14 +241,28 @@ fn addCaseInstance(
196 return;241 return;
197 }242 }
198243
244 const backend_string = if (params.use_llvm == true)
245 " llvm"
246 else if (params.use_llvm == false)
247 " selfhosted"
248 else
249 "";
250
251 const strip_string = if (params.strip == true)
252 " strip"
253 else if (params.strip == false)
254 " unstripped"
255 else
256 "";
257
199 const annotated_case_name = b.fmt("check {s} ({s}{s}{s}{s}{s}{s}{s}{s})", .{258 const annotated_case_name = b.fmt("check {s} ({s}{s}{s}{s}{s}{s}{s}{s})", .{
200 name,259 name,
201 triple orelse "",260 triple orelse "",
202 if (triple != null) " " else "",261 if (triple != null) " " else "",
203 if (use_llvm) "llvm" else "selfhosted",262 backend_string,
204 if (pie == true) " pie" else "",263 if (params.pie == true) " pie" else "",
205 if (link_libc == true) " libc" else "",264 if (params.link_libc == true) " libc" else "",
206 if (strip_debug) " strip" else "",265 strip_string,
207 if (strip_unwind) " no_unwind" else "",266 if (strip_unwind) " no_unwind" else "",
208 if (omit_frame_pointer) " no_fp" else "",267 if (omit_frame_pointer) " no_fp" else "",
209 });268 });
...@@ -220,16 +279,17 @@ fn addCaseInstance(...@@ -220,16 +279,17 @@ fn addCaseInstance(
220 .root_module = b.createModule(.{279 .root_module = b.createModule(.{
221 .root_source_file = source_zig,280 .root_source_file = source_zig,
222 .optimize = .Debug,281 .optimize = .Debug,
223 .target = target.*,282 .target = resolved_target,
224 .omit_frame_pointer = omit_frame_pointer,283 .omit_frame_pointer = omit_frame_pointer,
225 .link_libc = link_libc,284 .link_libc = params.link_libc,
226 .unwind_tables = if (strip_unwind) .none else null,285 .unwind_tables = if (strip_unwind) .none else null,
227 // make panics single-threaded so that they don't include a thread ID286 // make panics single-threaded so that they don't include a thread ID
228 .single_threaded = expect_panic,287 .single_threaded = expect_panic,
229 }),288 }),
230 .use_llvm = use_llvm,289 .use_llvm = params.use_llvm,
290 .use_lld = params.use_lld,
231 });291 });
232 exe.pie = pie;292 exe.pie = params.pie;
233 exe.bundle_ubsan_rt = false;293 exe.bundle_ubsan_rt = false;
234294
235 const run = b.addRunArtifact(exe);295 const run = b.addRunArtifact(exe);
...@@ -238,7 +298,7 @@ fn addCaseInstance(...@@ -238,7 +298,7 @@ fn addCaseInstance(
238 run.setEnvironmentVariable("NO_COLOR", "1");298 run.setEnvironmentVariable("NO_COLOR", "1");
239 run.addCheck(.{ .expect_term = term: {299 run.addCheck(.{ .expect_term = term: {
240 if (!expect_panic) break :term .{ .exited = 0 };300 if (!expect_panic) break :term .{ .exited = 0 };
241 if (target.result.os.tag == .windows) break :term .{ .exited = 3 };301 if (resolved_target.result.os.tag == .windows) break :term .{ .exited = 3 };
242 break :term .{ .signal = @fromBackingInt(@intCast(6)) };302 break :term .{ .signal = @fromBackingInt(@intCast(6)) };
243 } });303 } });
244 run.expectStdOutEqual("");304 run.expectStdOutEqual("");
test/stack_traces.zig+24-5
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
2const Context = @import("tests.zig").StackTracesContext;
23
3pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.Os.Tag) void {4pub fn addCases(cases: *Context, params: *const Context.CaseParameters, target: *const std.Target) void {
4 cases.addCase(.{5 cases.addCase(.{
6 .params = params,
7 .target = target,
5 .name = "simple panic",8 .name = "simple panic",
6 .source =9 .source =
7 \\pub fn main() void {10 \\pub fn main() void {
...@@ -33,6 +36,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -33,6 +36,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
33 });36 });
3437
35 cases.addCase(.{38 cases.addCase(.{
39 .params = params,
40 .target = target,
36 .name = "simple panic with no unwind strategy",41 .name = "simple panic with no unwind strategy",
37 .source =42 .source =
38 \\pub fn main() void {43 \\pub fn main() void {
...@@ -50,6 +55,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -50,6 +55,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
50 });55 });
5156
52 cases.addCase(.{57 cases.addCase(.{
58 .params = params,
59 .target = target,
53 .name = "dump current trace",60 .name = "dump current trace",
54 .source =61 .source =
55 \\pub fn main() void {62 \\pub fn main() void {
...@@ -89,6 +96,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -89,6 +96,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
89 });96 });
9097
91 cases.addCase(.{98 cases.addCase(.{
99 .params = params,
100 .target = target,
92 .name = "dump current trace with no unwind strategy",101 .name = "dump current trace with no unwind strategy",
93 .source =102 .source =
94 \\pub fn main() void {103 \\pub fn main() void {
...@@ -114,6 +123,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -114,6 +123,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
114 });123 });
115124
116 cases.addCase(.{125 cases.addCase(.{
126 .params = params,
127 .target = target,
117 .name = "dump captured trace",128 .name = "dump captured trace",
118 .source =129 .source =
119 \\pub fn main() void {130 \\pub fn main() void {
...@@ -155,6 +166,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -155,6 +166,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
155 });166 });
156167
157 cases.addCase(.{168 cases.addCase(.{
169 .params = params,
170 .target = target,
158 .name = "dump captured trace with no unwind strategy",171 .name = "dump captured trace with no unwind strategy",
159 .source =172 .source =
160 \\pub fn main() void {173 \\pub fn main() void {
...@@ -180,6 +193,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -180,6 +193,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
180 });193 });
181194
182 cases.addCase(.{195 cases.addCase(.{
196 .params = params,
197 .target = target,
183 .name = "dump captured trace on thread",198 .name = "dump captured trace on thread",
184 .source =199 .source =
185 \\pub fn main() !void {200 \\pub fn main() !void {
...@@ -225,6 +240,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -225,6 +240,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
225 });240 });
226241
227 cases.addCase(.{242 cases.addCase(.{
243 .params = params,
244 .target = target,
228 .name = "simple inline panic",245 .name = "simple inline panic",
229 // The main function has two inline calls to ensure246 // The main function has two inline calls to ensure
230 // that inlinees in PDBs are properly deduplicated.247 // that inlinees in PDBs are properly deduplicated.
...@@ -240,7 +257,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -240,7 +257,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
240 ,257 ,
241 .unwind = .any,258 .unwind = .any,
242 .expect_panic = true,259 .expect_panic = true,
243 .expect = switch (os) {260 .expect = switch (target.os.tag) {
244 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,261 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,
245 // so the first location has only a row.262 // so the first location has only a row.
246 .windows =>263 .windows =>
...@@ -262,7 +279,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -262,7 +279,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
262 \\ ^279 \\ ^
263 ,280 ,
264 },281 },
265 .expect_strip = switch (os) {282 .expect_strip = switch (target.os.tag) {
266 .windows =>283 .windows =>
267 \\panic: oh no284 \\panic: oh no
268 \\???:?:?: [address] in source.foo285 \\???:?:?: [address] in source.foo
...@@ -279,6 +296,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -279,6 +296,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
279296
280 // Make sure all inline calls are resolved and in the right order!297 // Make sure all inline calls are resolved and in the right order!
281 cases.addCase(.{298 cases.addCase(.{
299 .params = params,
300 .target = target,
282 .name = "nested inline panic",301 .name = "nested inline panic",
283 .source =302 .source =
284 \\pub fn main() void {303 \\pub fn main() void {
...@@ -298,7 +317,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -298,7 +317,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
298 .unwind = .any,317 .unwind = .any,
299 .expect_panic = true,318 .expect_panic = true,
300 // This switch serves a similar purpose as in "inline panic".319 // This switch serves a similar purpose as in "inline panic".
301 .expect = switch (os) {320 .expect = switch (target.os.tag) {
302 .windows =>321 .windows =>
303 \\panic: oh no322 \\panic: oh no
304 \\source.zig:11: [address] in baz323 \\source.zig:11: [address] in baz
...@@ -322,7 +341,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -322,7 +341,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
322 \\ ^341 \\ ^
323 ,342 ,
324 },343 },
325 .expect_strip = switch (os) {344 .expect_strip = switch (target.os.tag) {
326 .windows =>345 .windows =>
327 \\panic: oh no346 \\panic: oh no
328 \\???:?:?: [address] in baz347 \\???:?:?: [address] in baz
test/tests.zig+9-105
...@@ -6,8 +6,6 @@ const OptimizeMode = std.builtin.OptimizeMode;...@@ -6,8 +6,6 @@ const OptimizeMode = std.builtin.OptimizeMode;
6const Step = std.Build.Step;6const Step = std.Build.Step;
77
8// Cases8// Cases
9const error_traces = @import("error_traces.zig");
10const stack_traces = @import("stack_traces.zig");
11const llvm_ir = @import("llvm_ir.zig");9const llvm_ir = @import("llvm_ir.zig");
12const libc = @import("libc.zig");10const libc = @import("libc.zig");
13const link = @import("link.zig");11const link = @import("link.zig");
...@@ -2381,59 +2379,7 @@ pub fn isNative(actual_target: *const std.Build.ResolvedTarget, host: *const std...@@ -2381,59 +2379,7 @@ pub fn isNative(actual_target: *const std.Build.ResolvedTarget, host: *const std
2381 return true;2379 return true;
2382}2380}
23832381
2384/// For stack trace tests, we only test native by default, because external executors are pretty2382pub fn addStackTraceTests(b: *std.Build, test_filters: []const []const u8, skip_non_native: bool) *Step {
2385/// unreliable at stack tracing. However, if there's a 32-bit equivalent target which the host can
2386/// trivially run, we may as well at least test that!
2387fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
2388 const host = b.graph.host.result;
2389 const only_native = (&b.graph.host)[0..1];
2390 if (skip_non_native) return only_native;
2391 const arch32 = compatible32bitArch(&b.graph.host.result) orelse return only_native;
2392 return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{
2393 b.graph.host,
2394 b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }),
2395 }) catch @panic("OOM");
2396}
2397
2398fn wineAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
2399 var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty;
2400
2401 const host = b.graph.host.result;
2402
2403 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2404 .cpu_arch = host.cpu.arch,
2405 .os_tag = .windows,
2406 })) catch @panic("OOM");
2407 if (!skip_non_native) {
2408 if (compatible32bitArch(&b.graph.host.result)) |arch| {
2409 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2410 .cpu_arch = arch,
2411 .os_tag = .windows,
2412 })) catch @panic("OOM");
2413 }
2414 }
2415
2416 return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM");
2417}
2418
2419fn darlingTargets(b: *std.Build) []const std.Build.ResolvedTarget {
2420 var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty;
2421
2422 const host = b.graph.host.result;
2423
2424 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2425 .cpu_arch = host.cpu.arch,
2426 .os_tag = .macos,
2427 })) catch @panic("OOM");
2428
2429 return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM");
2430}
2431
2432pub fn addStackTraceTests(
2433 b: *std.Build,
2434 test_filters: []const []const u8,
2435 skip_non_native: bool,
2436) *Step {
2437 const step = b.step("test-stack-traces", "Run the stack trace tests");2383 const step = b.step("test-stack-traces", "Run the stack trace tests");
24382384
2439 const convert_exe = b.addExecutable(.{2385 const convert_exe = b.addExecutable(.{
...@@ -2445,35 +2391,15 @@ pub fn addStackTraceTests(...@@ -2445,35 +2391,15 @@ pub fn addStackTraceTests(
2445 }),2391 }),
2446 });2392 });
24472393
2448 const host_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");2394 const stack_traces_context = b.allocator.create(StackTracesContext) catch @panic("OOM");
2449 host_cases.* = .{2395 stack_traces_context.* = .{
2450 .b = b,2396 .b = b,
2451 .step = step,2397 .step = step,
2452 .test_filters = test_filters,2398 .test_filters = test_filters,
2453 .targets = nativeAndCompatible32bit(b, skip_non_native),2399 .skip_non_native = skip_non_native,
2454 .convert_exe = convert_exe,2400 .convert_exe = convert_exe,
2455 };2401 };
2456 stack_traces.addCases(host_cases, b.graph.host.result.os.tag);2402 stack_traces_context.addCases();
2457
2458 const wine_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2459 wine_cases.* = .{
2460 .b = b,
2461 .step = step,
2462 .test_filters = test_filters,
2463 .targets = wineAndCompatible32bit(b, skip_non_native),
2464 .convert_exe = convert_exe,
2465 };
2466 stack_traces.addCases(wine_cases, .windows);
2467
2468 const darling_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2469 darling_cases.* = .{
2470 .b = b,
2471 .step = step,
2472 .test_filters = test_filters,
2473 .targets = darlingTargets(b),
2474 .convert_exe = convert_exe,
2475 };
2476 stack_traces.addCases(darling_cases, .macos);
24772403
2478 return step;2404 return step;
2479}2405}
...@@ -2495,38 +2421,16 @@ pub fn addErrorTraceTests(...@@ -2495,38 +2421,16 @@ pub fn addErrorTraceTests(
2495 }),2421 }),
2496 });2422 });
24972423
2498 const host_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");2424 const error_traces_context = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2499 host_cases.* = .{2425 error_traces_context.* = .{
2500 .b = b,
2501 .step = step,
2502 .test_filters = test_filters,
2503 .targets = nativeAndCompatible32bit(b, skip_non_native),
2504 .optimize_modes = optimize_modes,
2505 .convert_exe = convert_exe,
2506 };
2507 error_traces.addCases(host_cases, b.graph.host.result.os.tag);
2508
2509 const wine_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2510 wine_cases.* = .{
2511 .b = b,
2512 .step = step,
2513 .test_filters = test_filters,
2514 .targets = wineAndCompatible32bit(b, skip_non_native),
2515 .optimize_modes = optimize_modes,
2516 .convert_exe = convert_exe,
2517 };
2518 error_traces.addCases(wine_cases, .windows);
2519
2520 const darling_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2521 darling_cases.* = .{
2522 .b = b,2426 .b = b,
2523 .step = step,2427 .step = step,
2524 .test_filters = test_filters,2428 .test_filters = test_filters,
2525 .targets = darlingTargets(b),2429 .skip_non_native = skip_non_native,
2526 .optimize_modes = optimize_modes,2430 .optimize_modes = optimize_modes,
2527 .convert_exe = convert_exe,2431 .convert_exe = convert_exe,
2528 };2432 };
2529 error_traces.addCases(darling_cases, .macos);2433 error_traces_context.addCases();
25302434
2531 return step;2435 return step;
2532}2436}