1b: *Build,
2step: *Step,
3optimize: std.builtin.Optimize,
4target: std.Build.ResolvedTarget,
5target_desc: []const u8,
6use_llvm: bool,
7use_lld: bool,
8link_libc: bool,
9test_filters: []const []const u8,
10update_step: ?*Step.UpdateSourceFiles,
11updated_snapshots: std.array_hash_map.String(void),
12max_rss: usize,
13
14pub fn includeTest(self: *Link, prefix: []const u8) ?Case {
15 if (for (self.test_filters) |filter| {
16 if (std.mem.find(u8, prefix, filter)) |_| break false;
17 } else self.test_filters.len > 0) return null;
18
19 return .{
20 .ctx = self,
21 .prefix = prefix,
22 };
23}
24
25pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath {
26 return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path }));
27}
28
29pub const Case = struct {
30 ctx: *Link,
31 prefix: []const u8,
32
33 fn resolveName(self: *const Case, overlay: *const OverlayOptions) []const u8 {
34 if (!overlay.name_prefix and !overlay.name_target)
35 return overlay.name;
36
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 }
44
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 });
57 }
58
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 }
70
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 }
80
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 });
91 }
92
93 pub fn expectLinkErrors(
94 self: *const Case,
95 comp: *Step.Compile,
96 expected_errors: Step.Compile.ExpectedCompileErrors,
97 ) void {
98 comp.expect_errors = expected_errors;
99 const bin_file = comp.getEmittedBin();
100 bin_file.addStepDependencies(self.ctx.step);
101 }
102
103 const SnapshotScope = struct {
104 /// If a test case has multiple verifyObjdump calls, `opt_sub_name` should
105 /// be used to differentiate them.
106 sub_name: ?[]const u8 = null,
107 arch: bool = false,
108 os: bool = false,
109 abi: bool = false,
110 optimize: bool = false,
111 use_llvm: bool = false,
112 use_lld: bool = false,
113 link_libc: bool = false,
114 };
115
116 /// Verify the results of a `zig objdump` call against a snapshot, which
117 /// contains the expected output. Snapshots alias between all build
118 /// configurations by default, but by specifying fields in `scope`,
119 /// unique snapshot names are generated for each value of that field.
120 pub fn verifyObjdump(
121 self: *const Case,
122 file: Build.LazyPath,
123 args: []const []const u8,
124 scope: SnapshotScope,
125 ) void {
126 const ctx = self.ctx;
127 const snapshot_name = self.snapshotName(scope) catch @panic("OOM");
128 const snapshot_sub_path = ctx.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name });
129
130 // Many tests may read the same snapshot, so only use the first one to update.
131 // If there are differences in output, they will show up on the next test run.
132 if (ctx.update_step != null) {
133 const gop = ctx.updated_snapshots.getOrPut(ctx.b.allocator, snapshot_sub_path) catch @panic("OOM");
134 if (gop.found_existing) return;
135 }
136
137 const run_step = Step.Run.create(ctx.b, ctx.b.fmt(
138 "objdump {s} {s}",
139 .{ snapshot_name, ctx.target_desc },
140 ));
141 run_step.addArgs(&.{ ctx.b.graph.zig_exe, "objdump" });
142 run_step.addFileArg(file);
143 run_step.addArgs(args);
144 run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });
145
146 if (ctx.update_step) |update_step| {
147 // Workaround for the build system not realizing objdump itself has changed
148 run_step.has_side_effects = true;
149
150 const snapshot_update_path = run_step.captureStdOut(.{});
151 update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path);
152 } else {
153 run_step.addCheck(.{ .expect_stdout_snapshot = ctx.b.path(snapshot_sub_path) });
154 }
155
156 ctx.step.dependOn(&run_step.step);
157 }
158
159 fn snapshotName(
160 self: *const Case,
161 scope: SnapshotScope,
162 ) ![]const u8 {
163 const ctx = self.ctx;
164 var snapshot_name: std.Io.Writer.Allocating = .init(ctx.b.allocator);
165 const w = &snapshot_name.writer;
166
167 try w.writeAll(self.prefix);
168 var sep: u8 = '.';
169
170 if (try snapshotNameInner(w, scope.sub_name != null, &sep))
171 try w.writeAll(scope.sub_name.?);
172 if (try snapshotNameInner(w, scope.arch, &sep))
173 try w.print("{t}", .{ctx.target.result.cpu.arch});
174 if (try snapshotNameInner(w, scope.os, &sep))
175 try w.print("{t}", .{ctx.target.result.os.tag});
176 if (try snapshotNameInner(w, scope.abi, &sep))
177 try w.print("{t}", .{ctx.target.result.abi});
178 if (try snapshotNameInner(w, scope.optimize, &sep))
179 try w.print("{t}", .{ctx.optimize});
180 if (try snapshotNameInner(w, scope.use_llvm, &sep))
181 try w.writeAll(if (ctx.use_llvm) "llvm" else "no-llvm");
182 if (try snapshotNameInner(w, scope.use_lld, &sep))
183 try w.writeAll(if (ctx.use_lld) "lld" else "no-lld");
184 if (try snapshotNameInner(w, scope.link_libc, &sep))
185 try w.writeAll(if (ctx.link_libc) "libc" else "no-libc");
186
187 if (sep == '-') sep = '.';
188 try w.writeByte(sep);
189 try w.writeAll("dmp");
190
191 return try snapshot_name.toOwnedSlice();
192 }
193
194 fn snapshotNameInner(w: *std.Io.Writer, cond: bool, sep: *u8) !bool {
195 if (cond) {
196 try w.writeByte(sep.*);
197 sep.* = '-';
198 }
199
200 return cond;
201 }
202};
203
204fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
205 const write_files = self.b.addWriteFiles();
206
207 const mod = self.b.createModule(.{
208 .target = self.target,
209 .optimize = self.optimize,
210 .root_source_file = overlay.zig_source_file orelse rsf: {
211 const bytes = overlay.zig_source_bytes orelse break :rsf null;
212 const name = self.b.fmt("{s}.zig", .{overlay.name});
213 break :rsf write_files.add(name, bytes);
214 },
215 .link_libc = self.link_libc, // TODO: Should this be in overlay instead?
216 .pic = overlay.pic,
217 .strip = overlay.strip,
218 });
219
220 if (overlay.objcpp_source_bytes) |bytes| {
221 mod.addCSourceFile(.{
222 .file = write_files.add("a.mm", bytes),
223 .flags = overlay.objcpp_source_flags,
224 });
225 }
226 if (overlay.objc_source_bytes) |bytes| {
227 mod.addCSourceFile(.{
228 .file = write_files.add("a.m", bytes),
229 .flags = overlay.objc_source_flags,
230 });
231 }
232 if (overlay.cpp_source_bytes) |bytes| {
233 mod.addCSourceFile(.{
234 .file = write_files.add("a.cpp", bytes),
235 .flags = overlay.cpp_source_flags,
236 });
237 }
238 if (overlay.c_source_bytes) |bytes| {
239 mod.addCSourceFile(.{
240 .file = write_files.add("a.c", bytes),
241 .flags = overlay.c_source_flags,
242 });
243 }
244 if (overlay.asm_source_bytes) |bytes| {
245 mod.addAssemblyFile(write_files.add("a.s", bytes));
246 }
247
248 return mod;
249}
250
251const OverlayOptions = struct {
252 name: []const u8,
253 /// Prefix the name with the test case prefix.
254 /// Unset if names with specific lengths are needed.
255 name_prefix: bool = true,
256 /// Prefix the name with `target_desc`.
257 /// Can be unset when the snapshot needs to contain the name,
258 /// so that snapshots can alias between targets.
259 name_target: bool = true,
260 asm_source_bytes: ?[]const u8 = null,
261 c_source_bytes: ?[]const u8 = null,
262 c_source_flags: []const []const u8 = &.{},
263 cpp_source_bytes: ?[]const u8 = null,
264 cpp_source_flags: []const []const u8 = &.{},
265 objc_source_bytes: ?[]const u8 = null,
266 objc_source_flags: []const []const u8 = &.{},
267 objcpp_source_bytes: ?[]const u8 = null,
268 objcpp_source_flags: []const []const u8 = &.{},
269 zig_source_bytes: ?[]const u8 = null,
270 zig_source_file: ?std.Build.LazyPath = null,
271 pic: ?bool = null,
272 strip: ?bool = null,
273 use_llvm: ?bool = null,
274 use_lld: ?bool = null,
275};
276
277const std = @import("std");
278const Build = std.Build;
279const Step = Build.Step;
280
281const Link = @This();