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 @@
11pub fn addCases(ctx: *LinkContext) void {
2 if (ctx.includeTest("exports-static")) |prefix| {
3 const lib = ctx.addLibrary(.static, .{
2 if (ctx.includeTest("exports-static")) |case| {
3 const lib = case.addLibrary(.static, .{
44 .name = "lib",
55 .zig_source_file = ctx.sourcePath("exports.zig"),
66 });
7 ctx.verifyObjdump(prefix, lib, &.{
7 case.verifyObjdump(lib, &.{
88 "-s",
99 "--symbols",
1010 "--only-symbol=foo",
1111 }, .{});
1212 }
1313
14 if (ctx.includeTest("exports-dynamic")) |prefix| {
15 const lib = ctx.addLibrary(.dynamic, .{
14 if (ctx.includeTest("exports-dynamic")) |case| {
15 const lib = case.addLibrary(.dynamic, .{
1616 .name = "lib",
1717 .zig_source_file = ctx.sourcePath("exports.zig"),
1818 });
19 ctx.verifyObjdump(prefix, lib, &.{
19 case.verifyObjdump(lib, &.{
2020 "-s",
2121 "--exports",
2222 "--only-symbol=foo",
2323 }, .{});
2424 }
2525
26 if (ctx.includeTest("emit-static-lib")) |prefix| {
27 const obj1 = ctx.addObject(.{
26 if (ctx.includeTest("emit-static-lib")) |case| {
27 const obj1 = case.addObject(.{
2828 .name = "obj1",
29 .name_prefix = false,
30 .name_target = false,
2931 .use_llvm = true,
3032 .use_lld = true,
3133 .c_source_bytes =
3234 \\int foo1 = 1;
3335 \\int foo2 = 2;
34 \\int fooBar() {
36 \\unsigned int fooBar() {
3537 \\ return foo1 + foo2;
3638 \\}
3739 ,
3840 });
39 const obj2 = ctx.addObject(.{
41 const obj2 = case.addObject(.{
4042 .name = "this_is_a_long_name",
43 .name_prefix = false,
44 .name_target = false,
4145 .zig_source_bytes =
4246 \\fn weakFoo() callconv(.c) usize {
43 \\ return 42;
47 \\ return 0xaabbccdd;
4448 \\}
45 \\export var strong_foo: usize = 100;
49 \\export var strong_foo: usize = 0x11223344;
4650 \\comptime {
4751 \\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak });
4852 \\ @export(&strong_foo, .{ .name = "strong_foo_alias", .linkage = .strong });
......@@ -50,17 +54,41 @@ pub fn addCases(ctx: *LinkContext) void {
5054 ,
5155 });
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 });
5462 lib.root_module.addObject(obj1);
5563 lib.root_module.addObject(obj2);
5664
57 ctx.verifyObjdump(prefix, lib, &.{
65 case.verifyObjdump(lib, &.{
5866 "-s",
5967 "--elements=file-type",
6068 "--symbols",
6169 "--only-symbol=foo",
6270 "--only-symbol=Foo",
6371 }, .{});
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 } });
6492 }
6593}
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,
22step: *Step,
33optimize: std.builtin.OptimizeMode,
44target: std.Build.ResolvedTarget,
5target_desc: []const u8,
56use_llvm: bool,
67use_lld: bool,
78link_libc: bool,
......@@ -10,111 +11,168 @@ update_step: ?*Step.UpdateSourceFiles,
1011updated_snapshots: std.StringArrayHashMapUnmanaged(void),
1112max_rss: usize,
1213
13pub fn includeTest(self: *const Link, prefix: []const u8) ?[]const u8 {
14pub fn includeTest(self: *Link, prefix: []const u8) ?Case {
1415 if (for (self.test_filters) |filter| {
1516 if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;
1617 } else self.test_filters.len > 0) return null;
17 return prefix;
18
19 return .{
20 .ctx = self,
21 .prefix = prefix,
22 };
1823}
1924
2025pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath {
2126 return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path }));
2227}
2328
24pub fn addLibrary(
25 self: *const Link,
26 linkage: std.builtin.LinkMode,
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}
29pub const Case = struct {
30 ctx: *Link,
31 prefix: []const u8,
3732
38pub fn addObject(self: *const Link, overlay: OverlayOptions) *Step.Compile {
39 return self.b.addObject(.{
40 .name = 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}
33 fn resolveName(self: *const Case, overlay: *const OverlayOptions) []const u8 {
34 if (!overlay.name_prefix and !overlay.name_target)
35 return overlay.name;
4636
47const SnapshotScope = packed struct {
48 arch: bool = false,
49 os: bool = false,
50 abi: bool = false,
51 optimize: bool = false,
52 use_llvm: bool = false,
53 use_lld: bool = false,
54 link_libc: bool = false,
55};
37 if (overlay.name_prefix == overlay.name_target)
38 return self.ctx.b.fmt("{s}-{s}-{s}", .{ self.prefix, overlay.name, self.ctx.target_desc })
39 else if (overlay.name_prefix)
40 return self.ctx.b.fmt("{s}-{s}", .{ self.prefix, overlay.name })
41 else
42 return self.ctx.b.fmt("{s}-{s}", .{ overlay.name, self.ctx.target_desc });
43 }
5644
57/// Verify the results of a `zig objdump` call against a snapshot, which
58/// contains the expected output. Snapshots alias between all build
59/// configurations by default, but by specifying fields in `scope`,
60/// unique snapshot names are generated for each value of that field.
61pub fn verifyObjdump(
62 self: *Link,
63 prefix: []const u8,
64 compile: *Step.Compile,
65 args: []const []const u8,
66 scope: SnapshotScope,
67) void {
68 const snapshot_name = self.snapshotName(prefix, compile.name, scope) catch @panic("OOM");
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;
45 pub fn addLibrary(
46 self: *const Case,
47 linkage: std.builtin.LinkMode,
48 overlay: OverlayOptions,
49 ) *Step.Compile {
50 return self.ctx.b.addLibrary(.{
51 .linkage = linkage,
52 .name = self.resolveName(&overlay),
53 .root_module = self.ctx.createModule(overlay),
54 .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
55 .use_lld = overlay.use_lld orelse self.ctx.use_lld,
56 });
7657 }
7758
78 const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name}));
79 run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" });
80 run_step.addArtifactArg(compile);
81 run_step.addArgs(args);
82 run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });
59 pub fn addExecutable(
60 self: *const Case,
61 overlay: OverlayOptions,
62 ) *Step.Compile {
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| {
85 // Workaround for the build system not realizing objdump itself has changed
86 run_step.has_side_effects = true;
71 pub fn addRunArtifact(
72 self: *const Case,
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(.{});
89 update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path);
90 } else {
91 run_step.addCheck(.{ .snapshot = .{ .file = self.b.path(snapshot_sub_path) } });
81 pub fn addObject(
82 self: *const Case,
83 overlay: OverlayOptions,
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 });
9291 }
9392
94 self.step.dependOn(&run_step.step);
95}
93 const SnapshotScope = struct {
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(
98 self: *const Link,
99 test_name: []const u8,
100 compile_name: []const u8,
101 scope: SnapshotScope,
102) ![]const u8 {
103 var snapshot_name: std.Io.Writer.Allocating = .init(self.b.allocator);
104 const w = &snapshot_name.writer;
105
106 try w.print("{s}.{s}", .{ test_name, compile_name });
107 if (scope.arch) try w.print("-{t}", .{self.target.result.cpu.arch});
108 if (scope.os) try w.print("-{t}", .{self.target.result.os.tag});
109 if (scope.abi) try w.print("-{t}", .{self.target.result.abi});
110 if (scope.optimize) try w.print("-{t}", .{self.optimize});
111 if (scope.use_llvm) try w.writeAll(if (self.use_llvm) "-llvm" else "-no-llvm");
112 if (scope.use_lld) try w.writeAll(if (self.use_lld) "-lld" else "-no-lld");
113 if (scope.link_libc) try w.writeAll(if (self.link_libc) "-libc" else "-no-libc");
114 try w.writeAll(".dmp");
115
116 return try snapshot_name.toOwnedSlice();
117}
150 fn snapshotName(
151 self: *const Case,
152 scope: SnapshotScope,
153 ) ![]const u8 {
154 const ctx = self.ctx;
155 var snapshot_name: std.Io.Writer.Allocating = .init(ctx.b.allocator);
156 const w = &snapshot_name.writer;
157
158 try w.writeAll(self.prefix);
159 if (scope.sub_name) |sub_name| {
160 try w.writeByte('.');
161 try w.writeAll(sub_name);
162 }
163
164 if (scope.arch) try w.print("-{t}", .{ctx.target.result.cpu.arch});
165 if (scope.os) try w.print("-{t}", .{ctx.target.result.os.tag});
166 if (scope.abi) try w.print("-{t}", .{ctx.target.result.abi});
167 if (scope.optimize) try w.print("-{t}", .{ctx.optimize});
168 if (scope.use_llvm) try w.writeAll(if (ctx.use_llvm) "-llvm" else "-no-llvm");
169 if (scope.use_lld) try w.writeAll(if (ctx.use_lld) "-lld" else "-no-lld");
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
119177fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
120178 const write_files = self.b.addWriteFiles();
......@@ -165,6 +223,13 @@ fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
165223
166224const OverlayOptions = struct {
167225 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,
168233 asm_source_bytes: ?[]const u8 = null,
169234 c_source_bytes: ?[]const u8 = null,
170235 c_source_flags: []const []const u8 = &.{},
test/tests.zig+7-7
......@@ -3184,13 +3184,13 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {
31843184 .step = step,
31853185 .optimize = optimize_mode,
31863186 .target = resolved_target,
3187 // .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{
3188 // target.zigTriple(b.allocator) catch @panic("OOM"),
3189 // optimize_mode,
3190 // if (link_target.use_llvm) "-llvm" else "",
3191 // if (link_target.use_lld) "-lld" else "",
3192 // if (link_target.link_libc) "-libc" else "",
3193 // }) catch @panic("OOM"),
3187 .target_desc = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{
3188 target.zigTriple(b.allocator) catch @panic("OOM"),
3189 optimize_mode,
3190 if (link_target.use_llvm) "-llvm" else "",
3191 if (link_target.use_lld) "-lld" else "",
3192 if (link_target.link_libc) "-libc" else "",
3193 }) catch @panic("OOM"),
31943194 .use_llvm = link_target.use_llvm,
31953195 .use_lld = link_target.use_lld,
31963196 .link_libc = link_target.link_libc,