authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:37-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:27:16-04:00
loge20860765e926dfab43492c3e0fbbe09f08818f6
tree524211f43cb1eff8ef3d254bc56e7ff23526c3ef
parentb0254a287736922cfb7204650c8e0104aad3ecac

test/link: rework test case names to show the target

Prior to this, it was hard to track down commands to reproduce failing tests in --verbose output

9 files changed, 226 insertions(+), 133 deletions(-)

test/link.zig+42-14
...@@ -1,48 +1,52 @@...@@ -1,48 +1,52 @@
1pub fn addCases(ctx: *LinkContext) void {1pub fn addCases(ctx: *LinkContext) void {
2 if (ctx.includeTest("exports-static")) |prefix| {2 if (ctx.includeTest("exports-static")) |case| {
3 const lib = ctx.addLibrary(.static, .{3 const lib = case.addLibrary(.static, .{
4 .name = "lib",4 .name = "lib",
5 .zig_source_file = ctx.sourcePath("exports.zig"),5 .zig_source_file = ctx.sourcePath("exports.zig"),
6 });6 });
7 ctx.verifyObjdump(prefix, lib, &.{7 case.verifyObjdump(lib, &.{
8 "-s",8 "-s",
9 "--symbols",9 "--symbols",
10 "--only-symbol=foo",10 "--only-symbol=foo",
11 }, .{});11 }, .{});
12 }12 }
1313
14 if (ctx.includeTest("exports-dynamic")) |prefix| {14 if (ctx.includeTest("exports-dynamic")) |case| {
15 const lib = ctx.addLibrary(.dynamic, .{15 const lib = case.addLibrary(.dynamic, .{
16 .name = "lib",16 .name = "lib",
17 .zig_source_file = ctx.sourcePath("exports.zig"),17 .zig_source_file = ctx.sourcePath("exports.zig"),
18 });18 });
19 ctx.verifyObjdump(prefix, lib, &.{19 case.verifyObjdump(lib, &.{
20 "-s",20 "-s",
21 "--exports",21 "--exports",
22 "--only-symbol=foo",22 "--only-symbol=foo",
23 }, .{});23 }, .{});
24 }24 }
2525
26 if (ctx.includeTest("emit-static-lib")) |prefix| {26 if (ctx.includeTest("emit-static-lib")) |case| {
27 const obj1 = ctx.addObject(.{27 const obj1 = case.addObject(.{
28 .name = "obj1",28 .name = "obj1",
29 .name_prefix = false,
30 .name_target = false,
29 .use_llvm = true,31 .use_llvm = true,
30 .use_lld = true,32 .use_lld = true,
31 .c_source_bytes =33 .c_source_bytes =
32 \\int foo1 = 1;34 \\int foo1 = 1;
33 \\int foo2 = 2;35 \\int foo2 = 2;
34 \\int fooBar() {36 \\unsigned int fooBar() {
35 \\ return foo1 + foo2;37 \\ return foo1 + foo2;
36 \\}38 \\}
37 ,39 ,
38 });40 });
39 const obj2 = ctx.addObject(.{41 const obj2 = case.addObject(.{
40 .name = "this_is_a_long_name",42 .name = "this_is_a_long_name",
43 .name_prefix = false,
44 .name_target = false,
41 .zig_source_bytes =45 .zig_source_bytes =
42 \\fn weakFoo() callconv(.c) usize {46 \\fn weakFoo() callconv(.c) usize {
43 \\ return 42;47 \\ return 0xaabbccdd;
44 \\}48 \\}
45 \\export var strong_foo: usize = 100;49 \\export var strong_foo: usize = 0x11223344;
46 \\comptime {50 \\comptime {
47 \\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak });51 \\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak });
48 \\ @export(&strong_foo, .{ .name = "strong_foo_alias", .linkage = .strong });52 \\ @export(&strong_foo, .{ .name = "strong_foo_alias", .linkage = .strong });
...@@ -50,17 +54,41 @@ pub fn addCases(ctx: *LinkContext) void {...@@ -50,17 +54,41 @@ pub fn addCases(ctx: *LinkContext) void {
50 ,54 ,
51 });55 });
5256
53 const lib = ctx.addLibrary(.static, .{ .name = "lib" });57 const lib = case.addLibrary(.static, .{
58 .name = "lib",
59 .name_prefix = false,
60 .name_target = false,
61 });
54 lib.root_module.addObject(obj1);62 lib.root_module.addObject(obj1);
55 lib.root_module.addObject(obj2);63 lib.root_module.addObject(obj2);
5664
57 ctx.verifyObjdump(prefix, lib, &.{65 case.verifyObjdump(lib, &.{
58 "-s",66 "-s",
59 "--elements=file-type",67 "--elements=file-type",
60 "--symbols",68 "--symbols",
61 "--only-symbol=foo",69 "--only-symbol=foo",
62 "--only-symbol=Foo",70 "--only-symbol=Foo",
63 }, .{});71 }, .{});
72
73 const exe = case.addExecutable(.{
74 .name = "test",
75 .zig_source_bytes =
76 \\extern fn fooBar() c_uint;
77 \\extern fn weakFoo() usize;
78 \\extern var strong_foo: usize;
79 \\extern var strong_foo_alias: usize;
80 \\pub fn main() !u8 {
81 \\ return @intFromBool(0xcd003368 != fooBar() +
82 \\ weakFoo() +
83 \\ strong_foo +
84 \\ strong_foo_alias);
85 \\}
86 ,
87 });
88 exe.root_module.linkLibrary(lib);
89
90 const run = case.addRunArtifact(exe);
91 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
64 }92 }
65}93}
6694
test/link/snapshots/emit-static-lib.dmp created+9
...@@ -0,0 +1,9 @@
1lib.lib: COFF archive
2lib.lib(obj1.obj): COFF object
3xxxx 00000000 1 NULL() EXTERNAL | fooBar
4xxxx 00000000 2 NULL EXTERNAL | foo1
5xxxx 00000004 2 NULL EXTERNAL | foo2
6lib.lib(this_is_a_long_name.obj): COFF object
7xxxx 00000000 4 NULL() EXTERNAL | weakFoo
8xxxx 00000000 2 NULL EXTERNAL | strong_foo_alias
9xxxx 00000000 2 NULL EXTERNAL | strong_foo
test/link/snapshots/emit-static-lib.lib.dmp deleted-9
...@@ -1,9 +0,0 @@
1lib.lib: COFF archive
2lib.lib(obj1.obj): COFF object
3xxxx 00000000 1 NULL() EXTERNAL | fooBar
4xxxx 00000000 2 NULL EXTERNAL | foo1
5xxxx 00000004 2 NULL EXTERNAL | foo2
6lib.lib(this_is_a_long_name.obj): COFF object
7xxxx 00000000 4 NULL() EXTERNAL | weakFoo
8xxxx 00000000 2 NULL EXTERNAL | strong_foo_alias
9xxxx 00000000 2 NULL EXTERNAL | strong_foo
test/link/snapshots/exports-dynamic.dmp created+14
...@@ -0,0 +1,14 @@
1Export directory:
2 0 flags
3 0 time_date_stamp
4 0.00 version
5xxxxxxxxxxxxxxxx name_rva
6 1 ordinal_base
7xxxxxxxxxxxxxxxx number_of_entries
8xxxxxxxxxxxxxxxx number_of_names
9xxxxxxxxxxxxxxxx export_address_table_rva
10xxxxxxxxxxxxxxxx name_pointer_table_rva
11xxxxxxxxxxxxxxxx ordinal_table_rva
12xxxx xxxx xxxxxxxx | foo_const
13xxxx xxxx xxxxxxxx | foo_fn
14xxxx xxxx xxxxxxxx | foo_var
test/link/snapshots/exports-dynamic.lib.dmp deleted-14
...@@ -1,14 +0,0 @@
1Export directory:
2 0 flags
3 0 time_date_stamp
4 0.00 version
5xxxxxxxxxxxxxxxx name_rva
6 1 ordinal_base
7xxxxxxxxxxxxxxxx number_of_entries
8xxxxxxxxxxxxxxxx number_of_names
9xxxxxxxxxxxxxxxx export_address_table_rva
10xxxxxxxxxxxxxxxx name_pointer_table_rva
11xxxxxxxxxxxxxxxx ordinal_table_rva
12xxxx xxxx xxxxxxxx | foo_const
13xxxx xxxx xxxxxxxx | foo_fn
14xxxx xxxx xxxxxxxx | foo_var
test/link/snapshots/exports-static.dmp created+3
...@@ -0,0 +1,3 @@
1xxxx 00000000 4 NULL() EXTERNAL | foo_fn
2xxxx 00000000 2 NULL EXTERNAL | foo_var
3xxxx 00000008 3 NULL EXTERNAL | foo_const
test/link/snapshots/exports-static.lib.dmp deleted-3
...@@ -1,3 +0,0 @@
1xxxx 00000000 4 NULL() EXTERNAL | foo_fn
2xxxx 00000000 2 NULL EXTERNAL | foo_var
3xxxx 00000008 3 NULL EXTERNAL | foo_const
test/src/Link.zig+151-86
...@@ -2,6 +2,7 @@ b: *Build,...@@ -2,6 +2,7 @@ b: *Build,
2step: *Step,2step: *Step,
3optimize: std.builtin.OptimizeMode,3optimize: std.builtin.OptimizeMode,
4target: std.Build.ResolvedTarget,4target: std.Build.ResolvedTarget,
5target_desc: []const u8,
5use_llvm: bool,6use_llvm: bool,
6use_lld: bool,7use_lld: bool,
7link_libc: bool,8link_libc: bool,
...@@ -10,111 +11,168 @@ update_step: ?*Step.UpdateSourceFiles,...@@ -10,111 +11,168 @@ update_step: ?*Step.UpdateSourceFiles,
10updated_snapshots: std.StringArrayHashMapUnmanaged(void),11updated_snapshots: std.StringArrayHashMapUnmanaged(void),
11max_rss: usize,12max_rss: usize,
1213
13pub fn includeTest(self: *const Link, prefix: []const u8) ?[]const u8 {14pub fn includeTest(self: *Link, prefix: []const u8) ?Case {
14 if (for (self.test_filters) |filter| {15 if (for (self.test_filters) |filter| {
15 if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;16 if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;
16 } else self.test_filters.len > 0) return null;17 } else self.test_filters.len > 0) return null;
17 return prefix;18
19 return .{
20 .ctx = self,
21 .prefix = prefix,
22 };
18}23}
1924
20pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath {25pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath {
21 return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path }));26 return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path }));
22}27}
2328
24pub fn addLibrary(29pub const Case = struct {
25 self: *const Link,30 ctx: *Link,
26 linkage: std.builtin.LinkMode,31 prefix: []const u8,
27 overlay: OverlayOptions,
28) *Step.Compile {
29 return self.b.addLibrary(.{
30 .linkage = linkage,
31 .name = overlay.name,
32 .root_module = self.createModule(overlay),
33 .use_llvm = overlay.use_llvm orelse self.use_llvm,
34 .use_lld = overlay.use_lld orelse self.use_lld,
35 });
36}
3732
38pub fn addObject(self: *const Link, overlay: OverlayOptions) *Step.Compile {33 fn resolveName(self: *const Case, overlay: *const OverlayOptions) []const u8 {
39 return self.b.addObject(.{34 if (!overlay.name_prefix and !overlay.name_target)
40 .name = overlay.name,35 return overlay.name;
41 .root_module = self.createModule(overlay),
42 .use_llvm = overlay.use_llvm orelse self.use_llvm,
43 .use_lld = overlay.use_lld orelse self.use_lld,
44 });
45}
4636
47const SnapshotScope = packed struct {37 if (overlay.name_prefix == overlay.name_target)
48 arch: bool = false,38 return self.ctx.b.fmt("{s}-{s}-{s}", .{ self.prefix, overlay.name, self.ctx.target_desc })
49 os: bool = false,39 else if (overlay.name_prefix)
50 abi: bool = false,40 return self.ctx.b.fmt("{s}-{s}", .{ self.prefix, overlay.name })
51 optimize: bool = false,41 else
52 use_llvm: bool = false,42 return self.ctx.b.fmt("{s}-{s}", .{ overlay.name, self.ctx.target_desc });
53 use_lld: bool = false,43 }
54 link_libc: bool = false,
55};
5644
57/// Verify the results of a `zig objdump` call against a snapshot, which45 pub fn addLibrary(
58/// contains the expected output. Snapshots alias between all build46 self: *const Case,
59/// configurations by default, but by specifying fields in `scope`,47 linkage: std.builtin.LinkMode,
60/// unique snapshot names are generated for each value of that field.48 overlay: OverlayOptions,
61pub fn verifyObjdump(49 ) *Step.Compile {
62 self: *Link,50 return self.ctx.b.addLibrary(.{
63 prefix: []const u8,51 .linkage = linkage,
64 compile: *Step.Compile,52 .name = self.resolveName(&overlay),
65 args: []const []const u8,53 .root_module = self.ctx.createModule(overlay),
66 scope: SnapshotScope,54 .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
67) void {55 .use_lld = overlay.use_lld orelse self.ctx.use_lld,
68 const snapshot_name = self.snapshotName(prefix, compile.name, scope) catch @panic("OOM");56 });
69 const snapshot_sub_path = self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name });
70
71 // Many tests may read the same snapshot, so only use the first one to update.
72 // If there are differences in output, they will show up on the next test run.
73 if (self.update_step != null) {
74 const gop = self.updated_snapshots.getOrPut(self.b.allocator, snapshot_sub_path) catch @panic("OOM");
75 if (gop.found_existing) return;
76 }57 }
7758
78 const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name}));59 pub fn addExecutable(
79 run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" });60 self: *const Case,
80 run_step.addArtifactArg(compile);61 overlay: OverlayOptions,
81 run_step.addArgs(args);62 ) *Step.Compile {
82 run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });63 return self.ctx.b.addExecutable(.{
64 .name = self.resolveName(&overlay),
65 .root_module = self.ctx.createModule(overlay),
66 .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
67 .use_lld = overlay.use_lld orelse self.ctx.use_lld,
68 });
69 }
8370
84 if (self.update_step) |update_step| {71 pub fn addRunArtifact(
85 // Workaround for the build system not realizing objdump itself has changed72 self: *const Case,
86 run_step.has_side_effects = true;73 exe: *Step.Compile,
74 ) *Step.Run {
75 const run_step = self.ctx.b.addRunArtifact(exe);
76 run_step.skip_foreign_checks = true;
77 self.ctx.step.dependOn(&run_step.step);
78 return run_step;
79 }
8780
88 const snapshot_update_path = run_step.captureStdOut(.{});81 pub fn addObject(
89 update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path);82 self: *const Case,
90 } else {83 overlay: OverlayOptions,
91 run_step.addCheck(.{ .snapshot = .{ .file = self.b.path(snapshot_sub_path) } });84 ) *Step.Compile {
85 return self.ctx.b.addObject(.{
86 .name = self.resolveName(&overlay),
87 .root_module = self.ctx.createModule(overlay),
88 .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
89 .use_lld = overlay.use_lld orelse self.ctx.use_lld,
90 });
92 }91 }
9392
94 self.step.dependOn(&run_step.step);93 const SnapshotScope = struct {
95}94 /// If a test case has multiple verifyObjdump calls, `opt_sub_name` can
95 /// be used to differentiate them.
96 sub_name: ?[]const u8 = null,
97 arch: bool = false,
98 os: bool = false,
99 abi: bool = false,
100 optimize: bool = false,
101 use_llvm: bool = false,
102 use_lld: bool = false,
103 link_libc: bool = false,
104 };
105
106 /// Verify the results of a `zig objdump` call against a snapshot, which
107 /// contains the expected output. Snapshots alias between all build
108 /// configurations by default, but by specifying fields in `scope`,
109 /// unique snapshot names are generated for each value of that field.
110 ///
111 pub fn verifyObjdump(
112 self: *const Case,
113 compile: *Step.Compile,
114 args: []const []const u8,
115 scope: SnapshotScope,
116 ) void {
117 const ctx = self.ctx;
118 const snapshot_name = self.snapshotName(scope) catch @panic("OOM");
119 const snapshot_sub_path = ctx.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name });
120
121 // Many tests may read the same snapshot, so only use the first one to update.
122 // If there are differences in output, they will show up on the next test run.
123 if (ctx.update_step != null) {
124 const gop = ctx.updated_snapshots.getOrPut(ctx.b.allocator, snapshot_sub_path) catch @panic("OOM");
125 if (gop.found_existing) return;
126 }
127
128 const run_step = Step.Run.create(ctx.b, ctx.b.fmt(
129 "objdump {s} {s}",
130 .{ snapshot_name, ctx.target_desc },
131 ));
132 run_step.addArgs(&.{ ctx.b.graph.zig_exe, "objdump" });
133 run_step.addArtifactArg(compile);
134 run_step.addArgs(args);
135 run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });
136
137 if (ctx.update_step) |update_step| {
138 // Workaround for the build system not realizing objdump itself has changed
139 run_step.has_side_effects = true;
140
141 const snapshot_update_path = run_step.captureStdOut(.{});
142 update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path);
143 } else {
144 run_step.addCheck(.{ .snapshot = .{ .file = ctx.b.path(snapshot_sub_path) } });
145 }
146
147 ctx.step.dependOn(&run_step.step);
148 }
96149
97fn snapshotName(150 fn snapshotName(
98 self: *const Link,151 self: *const Case,
99 test_name: []const u8,152 scope: SnapshotScope,
100 compile_name: []const u8,153 ) ![]const u8 {
101 scope: SnapshotScope,154 const ctx = self.ctx;
102) ![]const u8 {155 var snapshot_name: std.Io.Writer.Allocating = .init(ctx.b.allocator);
103 var snapshot_name: std.Io.Writer.Allocating = .init(self.b.allocator);156 const w = &snapshot_name.writer;
104 const w = &snapshot_name.writer;157
105158 try w.writeAll(self.prefix);
106 try w.print("{s}.{s}", .{ test_name, compile_name });159 if (scope.sub_name) |sub_name| {
107 if (scope.arch) try w.print("-{t}", .{self.target.result.cpu.arch});160 try w.writeByte('.');
108 if (scope.os) try w.print("-{t}", .{self.target.result.os.tag});161 try w.writeAll(sub_name);
109 if (scope.abi) try w.print("-{t}", .{self.target.result.abi});162 }
110 if (scope.optimize) try w.print("-{t}", .{self.optimize});163
111 if (scope.use_llvm) try w.writeAll(if (self.use_llvm) "-llvm" else "-no-llvm");164 if (scope.arch) try w.print("-{t}", .{ctx.target.result.cpu.arch});
112 if (scope.use_lld) try w.writeAll(if (self.use_lld) "-lld" else "-no-lld");165 if (scope.os) try w.print("-{t}", .{ctx.target.result.os.tag});
113 if (scope.link_libc) try w.writeAll(if (self.link_libc) "-libc" else "-no-libc");166 if (scope.abi) try w.print("-{t}", .{ctx.target.result.abi});
114 try w.writeAll(".dmp");167 if (scope.optimize) try w.print("-{t}", .{ctx.optimize});
115168 if (scope.use_llvm) try w.writeAll(if (ctx.use_llvm) "-llvm" else "-no-llvm");
116 return try snapshot_name.toOwnedSlice();169 if (scope.use_lld) try w.writeAll(if (ctx.use_lld) "-lld" else "-no-lld");
117}170 if (scope.link_libc) try w.writeAll(if (ctx.link_libc) "-libc" else "-no-libc");
171 try w.writeAll(".dmp");
172
173 return try snapshot_name.toOwnedSlice();
174 }
175};
118176
119fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {177fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
120 const write_files = self.b.addWriteFiles();178 const write_files = self.b.addWriteFiles();
...@@ -165,6 +223,13 @@ fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {...@@ -165,6 +223,13 @@ fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
165223
166const OverlayOptions = struct {224const OverlayOptions = struct {
167 name: []const u8,225 name: []const u8,
226 /// Prefix the name with the test case prefix.
227 /// Unset if names with specific lengths are needed.
228 name_prefix: bool = true,
229 /// Prefix the name with `target_desc`.
230 /// Can be unset when the snapshot needs to contain the name,
231 /// so that snapshots can alias between targets.
232 name_target: bool = true,
168 asm_source_bytes: ?[]const u8 = null,233 asm_source_bytes: ?[]const u8 = null,
169 c_source_bytes: ?[]const u8 = null,234 c_source_bytes: ?[]const u8 = null,
170 c_source_flags: []const []const u8 = &.{},235 c_source_flags: []const []const u8 = &.{},
test/tests.zig+7-7
...@@ -3184,13 +3184,13 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {...@@ -3184,13 +3184,13 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {
3184 .step = step,3184 .step = step,
3185 .optimize = optimize_mode,3185 .optimize = optimize_mode,
3186 .target = resolved_target,3186 .target = resolved_target,
3187 // .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{3187 .target_desc = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{
3188 // target.zigTriple(b.allocator) catch @panic("OOM"),3188 target.zigTriple(b.allocator) catch @panic("OOM"),
3189 // optimize_mode,3189 optimize_mode,
3190 // if (link_target.use_llvm) "-llvm" else "",3190 if (link_target.use_llvm) "-llvm" else "",
3191 // if (link_target.use_lld) "-lld" else "",3191 if (link_target.use_lld) "-lld" else "",
3192 // if (link_target.link_libc) "-libc" else "",3192 if (link_target.link_libc) "-libc" else "",
3193 // }) catch @panic("OOM"),3193 }) catch @panic("OOM"),
3194 .use_llvm = link_target.use_llvm,3194 .use_llvm = link_target.use_llvm,
3195 .use_lld = link_target.use_lld,3195 .use_lld = link_target.use_lld,
3196 .link_libc = link_target.link_libc,3196 .link_libc = link_target.link_libc,