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