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 tests = @import("../tests.zig");
11const error_traces_cases = @import("../error_traces.zig");
12
13b: *std.Build,
14step: *Step,
15options: Options,
16convert_exe: *std.Build.Step.Compile,
17
18pub const Options = struct {
19 test_filters: []const []const u8,
20 test_target_filters: []const []const u8,
21 test_extra_targets: bool,
22 optimize_modes: []const OptimizeMode,
23 skip_non_native: bool,
24 skip_freebsd: bool,
25 skip_netbsd: bool,
26 skip_openbsd: bool,
27 skip_windows: bool,
28 skip_darwin: bool,
29 skip_linux: bool,
30 skip_llvm: bool,
31 skip_libc: bool,
32};
33
34pub const CaseParameters = struct {
35 target: std.Target.Query = .{},
36 optimize: OptimizeMode = .debug,
37 use_llvm: ?bool = null,
38 use_lld: ?bool = null,
39
40 // This is intended for targets that, for any reason, shouldn't be run as part of a normal test
41 // invocation. This could be because of a slow backend, requiring a newer LLVM version, being
42 // too niche, etc.
43 extra_target: bool = false,
44};
45
46/// See the comment in `StackTrace.zig`.
47pub const param_sets = [_]CaseParameters{
48 .{},
49
50 // FreeBSD Targets
51
52 .{
53 .target = .{
54 .cpu_arch = .arm,
55 .os_tag = .freebsd,
56 .abi = .eabihf,
57 },
58 },
59
60 .{
61 .target = .{
62 .cpu_arch = .x86,
63 .os_tag = .freebsd,
64 .abi = .none,
65 },
66 },
67
68 // Linux Targets
69
70 .{
71 .target = .{
72 .cpu_arch = .aarch64,
73 .os_tag = .linux,
74 .abi = .none,
75 },
76 },
77
78 .{
79 .target = .{
80 .cpu_arch = .aarch64_be,
81 .os_tag = .linux,
82 .abi = .none,
83 },
84 },
85
86 .{
87 .target = .{
88 .cpu_arch = .arm,
89 .os_tag = .linux,
90 .abi = .eabihf,
91 },
92 },
93
94 .{
95 .target = .{
96 .cpu_arch = .armeb,
97 .os_tag = .linux,
98 .abi = .eabihf,
99 },
100 },
101
102 .{
103 .target = .{
104 .cpu_arch = .hexagon,
105 .os_tag = .linux,
106 .abi = .none,
107 },
108 },
109
110 .{
111 .target = .{
112 .cpu_arch = .loongarch32,
113 .os_tag = .linux,
114 .abi = .none,
115 },
116 },
117
118 .{
119 .target = .{
120 .cpu_arch = .loongarch64,
121 .os_tag = .linux,
122 .abi = .none,
123 },
124 },
125
126 .{
127 .target = .{
128 .cpu_arch = .mips,
129 .os_tag = .linux,
130 .abi = .eabihf,
131 },
132 },
133
134 .{
135 .target = .{
136 .cpu_arch = .mipsel,
137 .os_tag = .linux,
138 .abi = .eabihf,
139 },
140 },
141
142 .{
143 .target = .{
144 .cpu_arch = .mips64,
145 .os_tag = .linux,
146 .abi = .none,
147 },
148 },
149 .{
150 .target = .{
151 .cpu_arch = .mips64,
152 .os_tag = .linux,
153 .abi = .abin32,
154 },
155 },
156
157 .{
158 .target = .{
159 .cpu_arch = .mips64el,
160 .os_tag = .linux,
161 .abi = .none,
162 },
163 },
164 .{
165 .target = .{
166 .cpu_arch = .mips64el,
167 .os_tag = .linux,
168 .abi = .abin32,
169 },
170 },
171
172 .{
173 .target = .{
174 .cpu_arch = .powerpc,
175 .os_tag = .linux,
176 .abi = .eabihf,
177 },
178 },
179
180 .{
181 .target = .{
182 .cpu_arch = .powerpc64,
183 .os_tag = .linux,
184 .abi = .none,
185 },
186 },
187
188 .{
189 .target = .{
190 .cpu_arch = .powerpc64le,
191 .os_tag = .linux,
192 .abi = .none,
193 },
194 },
195
196 .{
197 .target = .{
198 .cpu_arch = .riscv32,
199 .os_tag = .linux,
200 .abi = .none,
201 },
202 },
203
204 .{
205 .target = .{
206 .cpu_arch = .riscv64,
207 .os_tag = .linux,
208 .abi = .none,
209 },
210 },
211
212 .{
213 .target = .{
214 .cpu_arch = .s390x,
215 .os_tag = .linux,
216 .abi = .none,
217 },
218 },
219
220 .{
221 .target = .{
222 .cpu_arch = .sparc64,
223 .os_tag = .linux,
224 .abi = .none,
225 },
226 },
227
228 .{
229 .target = .{
230 .cpu_arch = .thumb,
231 .os_tag = .linux,
232 .abi = .eabihf,
233 },
234 },
235
236 .{
237 .target = .{
238 .cpu_arch = .thumbeb,
239 .os_tag = .linux,
240 .abi = .eabihf,
241 },
242 },
243
244 .{
245 .target = .{
246 .cpu_arch = .x86,
247 .os_tag = .linux,
248 .abi = .none,
249 },
250 },
251
252 .{
253 .target = .{
254 .cpu_arch = .x86_64,
255 .os_tag = .linux,
256 .abi = .none,
257 },
258 },
259 .{
260 .target = .{
261 .cpu_arch = .x86_64,
262 .os_tag = .linux,
263 .abi = .none,
264 },
265 .use_llvm = true,
266 .use_lld = true,
267 },
268 .{
269 .target = .{
270 .cpu_arch = .x86_64,
271 .os_tag = .linux,
272 .abi = .x32,
273 },
274 },
275
276 // NetBSD Targets
277
278 .{
279 .target = .{
280 .cpu_arch = .riscv32,
281 .os_tag = .netbsd,
282 .abi = .none,
283 },
284 },
285
286 .{
287 .target = .{
288 .cpu_arch = .x86,
289 .os_tag = .netbsd,
290 .abi = .none,
291 },
292 },
293
294 // Windows Targets
295
296 .{
297 .target = .{
298 .cpu_arch = .aarch64,
299 .os_tag = .windows,
300 .abi = .msvc,
301 },
302 },
303 .{
304 .target = .{
305 .cpu_arch = .aarch64,
306 .os_tag = .windows,
307 .abi = .gnu,
308 },
309 },
310
311 .{
312 .target = .{
313 .cpu_arch = .thumb,
314 .os_tag = .windows,
315 .abi = .msvc,
316 },
317 },
318 .{
319 .target = .{
320 .cpu_arch = .thumb,
321 .os_tag = .windows,
322 .abi = .gnu,
323 },
324 },
325
326 .{
327 .target = .{
328 .cpu_arch = .x86,
329 .os_tag = .windows,
330 .abi = .msvc,
331 },
332 },
333 .{
334 .target = .{
335 .cpu_arch = .x86,
336 .os_tag = .windows,
337 .abi = .gnu,
338 },
339 },
340
341 .{
342 .target = .{
343 .cpu_arch = .x86_64,
344 .os_tag = .windows,
345 .abi = .msvc,
346 },
347 },
348 .{
349 .target = .{
350 .cpu_arch = .x86_64,
351 .os_tag = .windows,
352 .abi = .gnu,
353 },
354 },
355};
356
357pub const Case = struct {
358 params: *const CaseParameters,
359 target: *const std.Target,
360 name: []const u8,
361 source: []const u8,
362 expect_error: []const u8,
363 expect_trace: []const u8,
364 /// On these arch/OS pairs we will not test the error trace on optimized LLVM builds because the
365 /// optimizations break the error trace. We will test the binary with error tracing disabled,
366 /// just to ensure that the expected error is still returned from `main`.
367 ///
368 /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it
369 /// seems that LLVM is particularly good at optimizing traces away in those.
370 disable_trace_optimized: []const DisableConfig = &.{},
371
372 pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };
373 pub const Backend = enum { llvm, selfhosted };
374};
375
376pub fn addCases(self: *ErrorTrace) void {
377 const b = self.b;
378
379 for (&param_sets) |*params| {
380 const resolved_target = b.resolveTargetQuery(params.target);
381 const target = &resolved_target.result;
382
383 if (!self.options.test_extra_targets and params.extra_target) continue;
384
385 if (self.options.skip_non_native and !tests.isNative(&resolved_target, &b.graph.host.result)) continue;
386
387 if (self.options.skip_freebsd and target.os.tag == .freebsd) continue;
388 if (self.options.skip_netbsd and target.os.tag == .netbsd) continue;
389 if (self.options.skip_openbsd and target.os.tag == .openbsd) continue;
390 if (self.options.skip_windows and target.os.tag == .windows) continue;
391 if (self.options.skip_darwin and target.os.tag.isDarwin()) continue;
392 if (self.options.skip_linux and target.os.tag == .linux) continue;
393
394 const would_use_llvm = tests.wouldUseLlvm(params.use_llvm, params.target, params.optimize);
395 if (self.options.skip_llvm and would_use_llvm) continue;
396
397 const triple_txt = resolved_target.query.zigTriple(b.allocator) catch @panic("OOM");
398
399 if (self.options.test_target_filters.len > 0) {
400 for (self.options.test_target_filters) |filter| {
401 if (std.mem.find(u8, triple_txt, filter) != null) break;
402 } else continue;
403 }
404
405 if (self.options.skip_libc and std.os.targetRequiresLibC(target))
406 continue;
407
408 for (self.options.optimize_modes) |optimize| {
409 if (optimize == params.optimize) break;
410 } else return;
411
412 error_traces_cases.addCases(self, params, &resolved_target.result);
413 }
414}
415
416/// Called from test/error_traces.zig
417pub fn addCase(self: *ErrorTrace, case: Case) void {
418 const b = self.b;
419 const params = case.params;
420 const target = case.target;
421 const target_query = params.target;
422
423 const triple: ?[]const u8 = if (target_query.isNative()) null else t: {
424 break :t target_query.zigTriple(self.b.graph.arena) catch @panic("OOM");
425 };
426
427 const error_tracing: bool = tracing: {
428 if (params.optimize == .debug) break :tracing true;
429 if (params.use_llvm == false) break :tracing true;
430 if (params.optimize == .small) break :tracing false;
431 for (case.disable_trace_optimized) |disable| {
432 const d_arch, const d_os = disable;
433 if (target.cpu.arch == d_arch and target.os.tag == d_os) {
434 // This particular configuration cannot do error tracing in optimized LLVM builds.
435 break :tracing false;
436 }
437 }
438 break :tracing true;
439 };
440
441 const backend_string = if (params.use_llvm == true)
442 "-llvm"
443 else if (params.use_llvm == false)
444 "-selfhosted"
445 else
446 "";
447
448 const annotated_case_name = b.fmt("check {s} ({s}{s}{t}{s})", .{
449 case.name,
450 triple orelse "",
451 if (triple != null) " " else "",
452 params.optimize,
453 backend_string,
454 });
455 if (self.options.test_filters.len > 0) {
456 for (self.options.test_filters) |test_filter| {
457 if (mem.find(u8, annotated_case_name, test_filter)) |_| break;
458 } else return;
459 }
460
461 const write_files = b.addWriteFiles();
462 const source_zig = write_files.add("source.zig", case.source);
463 const exe = b.addExecutable(.{
464 .name = "test",
465 .root_module = b.createModule(.{
466 .root_source_file = source_zig,
467 .optimize = params.optimize,
468 .target = .{ .result = target.*, .query = target_query },
469 .error_tracing = error_tracing,
470 .strip = false,
471 }),
472 .use_llvm = params.use_llvm,
473 .use_lld = params.use_lld,
474 });
475 exe.bundle_ubsan_rt = false;
476
477 const run = b.addRunArtifact(exe);
478 run.skip_foreign_checks = true;
479 run.removeEnvironmentVariable("CLICOLOR_FORCE");
480 run.setEnvironmentVariable("NO_COLOR", "1");
481 run.expectExitCode(1);
482 run.expectStdOutEqual("");
483
484 const expected_stderr = switch (error_tracing) {
485 true => b.fmt("error: {s}\n{s}\n", .{ case.expect_error, case.expect_trace }),
486 false => b.fmt("error: {s}\n", .{case.expect_error}),
487 };
488
489 const check_run = b.addRunArtifact(self.convert_exe);
490 check_run.skip_foreign_checks = true;
491 check_run.setName(annotated_case_name);
492 check_run.addFileArg(run.captureStdErr(.{}));
493 check_run.expectStdOutEqual(expected_stderr);
494
495 self.step.dependOn(&check_run.step);
496}