authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-09 18:22:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:14-07:00
log29cfd47d6509fc6ee1a165b3bc03180f6cf351a5
treeb5c91bf3715f488713e24fc61dff030d7ccbad28
parent7cc4a6965c28e427cbfba57a985f837734d6257e

re-enable test-cases and get them all passing

Instead of using `zig test` to build a special version of the compiler that runs all the test-cases, the zig build system is now used as much as possible - all with the basic steps found in the standard library. For incremental compilation tests (the ones that look like foo.0.zig, foo.1.zig, foo.2.zig, etc.), a special version of the compiler is compiled into a utility executable called "check-case" which checks exactly one sequence of incremental updates in an independent subprocess. Previously, all incremental and non-incremental test cases were done in the same test runner process. The compile error checking code is now simpler, but also a bit rudimentary, and so it additionally makes sure that the actual compile errors do not include *extra* messages, and it makes sure that the actual compile errors output in the same order as expected. It is also based on the "ends-with" property of each line rather than the previous logic, which frankly I didn't want to touch with a ten-meter pole. The compile error test cases have been updated to pass in light of these differences. Previously, 'error' mode with 0 compile errors was used to shoehorn in a different kind of test-case - one that only checks if a piece of code compiles without errors. Now there is a 'compile' mode of test-cases, and 'error' must be only used when there are greater than 0 errors. link test cases are updated to omit the target object format argument when calling checkObject since that is no longer needed. The test/stage2 directory is removed; the 2 files within are moved to be directly in the test/ directory.

81 files changed, 2840 insertions(+), 3378 deletions(-)

build.zig+24-26
......@@ -53,13 +53,14 @@ pub fn build(b: *std.Build) !void {
5353 const docs_step = b.step("docs", "Build documentation");
5454 docs_step.dependOn(&docgen_cmd.step);
5555
56 const test_cases = b.addTest(.{
57 .root_source_file = .{ .path = "src/test.zig" },
56 const check_case_exe = b.addExecutable(.{
57 .name = "check-case",
58 .root_source_file = .{ .path = "test/src/Cases.zig" },
5859 .optimize = optimize,
5960 });
60 test_cases.main_pkg_path = ".";
61 test_cases.stack_size = stack_size;
62 test_cases.single_threaded = single_threaded;
61 check_case_exe.main_pkg_path = ".";
62 check_case_exe.stack_size = stack_size;
63 check_case_exe.single_threaded = single_threaded;
6364
6465 const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;
6566 const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
......@@ -178,7 +179,7 @@ pub fn build(b: *std.Build) !void {
178179 if (target.isWindows() and target.getAbi() == .gnu) {
179180 // LTO is currently broken on mingw, this can be removed when it's fixed.
180181 exe.want_lto = false;
181 test_cases.want_lto = false;
182 check_case_exe.want_lto = false;
182183 }
183184
184185 const exe_options = b.addOptions();
......@@ -196,7 +197,7 @@ pub fn build(b: *std.Build) !void {
196197
197198 if (link_libc) {
198199 exe.linkLibC();
199 test_cases.linkLibC();
200 check_case_exe.linkLibC();
200201 }
201202
202203 const is_debug = optimize == .Debug;
......@@ -282,14 +283,14 @@ pub fn build(b: *std.Build) !void {
282283 }
283284
284285 try addCmakeCfgOptionsToExe(b, cfg, exe, use_zig_libcxx);
285 try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx);
286 try addCmakeCfgOptionsToExe(b, cfg, check_case_exe, use_zig_libcxx);
286287 } else {
287288 // Here we are -Denable-llvm but no cmake integration.
288289 try addStaticLlvmOptionsToExe(exe);
289 try addStaticLlvmOptionsToExe(test_cases);
290 try addStaticLlvmOptionsToExe(check_case_exe);
290291 }
291292 if (target.isWindows()) {
292 inline for (.{ exe, test_cases }) |artifact| {
293 inline for (.{ exe, check_case_exe }) |artifact| {
293294 artifact.linkSystemLibrary("version");
294295 artifact.linkSystemLibrary("uuid");
295296 artifact.linkSystemLibrary("ole32");
......@@ -334,8 +335,9 @@ pub fn build(b: *std.Build) !void {
334335 const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
335336
336337 const test_cases_options = b.addOptions();
337 test_cases.addOptions("build_options", test_cases_options);
338 check_case_exe.addOptions("build_options", test_cases_options);
338339
340 test_cases_options.addOption(bool, "enable_tracy", false);
339341 test_cases_options.addOption(bool, "enable_logging", enable_logging);
340342 test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
341343 test_cases_options.addOption(bool, "skip_non_native", skip_non_native);
......@@ -358,12 +360,6 @@ pub fn build(b: *std.Build) !void {
358360 test_cases_options.addOption(std.SemanticVersion, "semver", semver);
359361 test_cases_options.addOption(?[]const u8, "test_filter", test_filter);
360362
361 const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
362 test_cases_step.dependOn(&test_cases.step);
363 if (!skip_stage2_tests) {
364 test_step.dependOn(test_cases_step);
365 }
366
367363 var chosen_opt_modes_buf: [4]builtin.Mode = undefined;
368364 var chosen_mode_index: usize = 0;
369365 if (!skip_debug) {
......@@ -386,21 +382,20 @@ pub fn build(b: *std.Build) !void {
386382
387383 const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };
388384 const fmt_exclude_paths = &.{"test/cases"};
389 const check_fmt = b.addFmt(.{
390 .paths = fmt_include_paths,
391 .exclude_paths = fmt_exclude_paths,
392 .check = true,
393 });
394385 const do_fmt = b.addFmt(.{
395386 .paths = fmt_include_paths,
396387 .exclude_paths = fmt_exclude_paths,
397388 });
398389
399 const test_fmt_step = b.step("test-fmt", "Check whether source files have conforming formatting");
400 test_fmt_step.dependOn(&check_fmt.step);
390 b.step("test-fmt", "Check source files having conforming formatting").dependOn(&b.addFmt(.{
391 .paths = fmt_include_paths,
392 .exclude_paths = fmt_exclude_paths,
393 .check = true,
394 }).step);
401395
402 const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting");
403 do_fmt_step.dependOn(&do_fmt.step);
396 const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
397 try tests.addCases(b, test_cases_step, test_filter, check_case_exe);
398 if (!skip_stage2_tests) test_step.dependOn(test_cases_step);
404399
405400 test_step.dependOn(tests.addModuleTests(b, .{
406401 .test_filter = test_filter,
......@@ -475,6 +470,9 @@ pub fn build(b: *std.Build) !void {
475470 }));
476471
477472 try addWasiUpdateStep(b, version);
473
474 b.step("fmt", "Modify source files in place to have conforming formatting")
475 .dependOn(&do_fmt.step);
478476}
479477
480478fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
src/test.zig deleted-1968
......@@ -1,1968 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const Allocator = std.mem.Allocator;
4const CrossTarget = std.zig.CrossTarget;
5const print = std.debug.print;
6const assert = std.debug.assert;
7const ThreadPool = std.Thread.Pool;
8const WaitGroup = std.Thread.WaitGroup;
9
10const link = @import("link.zig");
11const Compilation = @import("Compilation.zig");
12const Package = @import("Package.zig");
13const introspect = @import("introspect.zig");
14const build_options = @import("build_options");
15const zig_h = link.File.C.zig_h;
16
17const enable_qemu: bool = build_options.enable_qemu;
18const enable_wine: bool = build_options.enable_wine;
19const enable_wasmtime: bool = build_options.enable_wasmtime;
20const enable_darling: bool = build_options.enable_darling;
21const enable_rosetta: bool = build_options.enable_rosetta;
22const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir;
23const skip_stage1 = true;
24
25const hr = "=" ** 80;
26
27test {
28 const use_gpa = build_options.force_gpa or !builtin.link_libc;
29 const gpa = gpa: {
30 if (use_gpa) {
31 break :gpa std.testing.allocator;
32 }
33 // We would prefer to use raw libc allocator here, but cannot
34 // use it if it won't support the alignment we need.
35 if (@alignOf(std.c.max_align_t) < @alignOf(i128)) {
36 break :gpa std.heap.c_allocator;
37 }
38 break :gpa std.heap.raw_c_allocator;
39 };
40
41 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
42 defer arena_allocator.deinit();
43 const arena = arena_allocator.allocator();
44
45 var ctx = TestContext.init(gpa, arena);
46 defer ctx.deinit();
47
48 {
49 const dir_path = try std.fs.path.join(arena, &.{
50 std.fs.path.dirname(@src().file).?, "..", "test", "cases",
51 });
52
53 var dir = try std.fs.cwd().openIterableDir(dir_path, .{});
54 defer dir.close();
55
56 ctx.addTestCasesFromDir(dir);
57 }
58
59 try @import("../test/cases.zig").addCases(&ctx);
60
61 try ctx.run();
62}
63
64const ErrorMsg = union(enum) {
65 src: struct {
66 src_path: []const u8,
67 msg: []const u8,
68 // maxint means match anything
69 // this is a workaround for stage1 compiler bug I ran into when making it ?u32
70 line: u32,
71 // maxint means match anything
72 // this is a workaround for stage1 compiler bug I ran into when making it ?u32
73 column: u32,
74 kind: Kind,
75 count: u32,
76 },
77 plain: struct {
78 msg: []const u8,
79 kind: Kind,
80 count: u32,
81 },
82
83 const Kind = enum {
84 @"error",
85 note,
86 };
87
88 fn init(other: Compilation.AllErrors.Message, kind: Kind) ErrorMsg {
89 switch (other) {
90 .src => |src| return .{
91 .src = .{
92 .src_path = src.src_path,
93 .msg = src.msg,
94 .line = @intCast(u32, src.line),
95 .column = @intCast(u32, src.column),
96 .kind = kind,
97 .count = src.count,
98 },
99 },
100 .plain => |plain| return .{
101 .plain = .{
102 .msg = plain.msg,
103 .kind = kind,
104 .count = plain.count,
105 },
106 },
107 }
108 }
109
110 pub fn format(
111 self: ErrorMsg,
112 comptime fmt: []const u8,
113 options: std.fmt.FormatOptions,
114 writer: anytype,
115 ) !void {
116 _ = fmt;
117 _ = options;
118 switch (self) {
119 .src => |src| {
120 if (!std.mem.eql(u8, src.src_path, "?") or
121 src.line != std.math.maxInt(u32) or
122 src.column != std.math.maxInt(u32))
123 {
124 try writer.print("{s}:", .{src.src_path});
125 if (src.line != std.math.maxInt(u32)) {
126 try writer.print("{d}:", .{src.line + 1});
127 } else {
128 try writer.writeAll("?:");
129 }
130 if (src.column != std.math.maxInt(u32)) {
131 try writer.print("{d}: ", .{src.column + 1});
132 } else {
133 try writer.writeAll("?: ");
134 }
135 }
136 try writer.print("{s}: {s}", .{ @tagName(src.kind), src.msg });
137 if (src.count != 1) {
138 try writer.print(" ({d} times)", .{src.count});
139 }
140 },
141 .plain => |plain| {
142 try writer.print("{s}: {s}", .{ @tagName(plain.kind), plain.msg });
143 if (plain.count != 1) {
144 try writer.print(" ({d} times)", .{plain.count});
145 }
146 },
147 }
148 }
149};
150
151/// Default config values for known test manifest key-value pairings.
152/// Currently handled defaults are:
153/// * backend
154/// * target
155/// * output_mode
156/// * is_test
157const TestManifestConfigDefaults = struct {
158 /// Asserts if the key doesn't exist - yep, it's an oversight alright.
159 fn get(@"type": TestManifest.Type, key: []const u8) []const u8 {
160 if (std.mem.eql(u8, key, "backend")) {
161 return "stage2";
162 } else if (std.mem.eql(u8, key, "target")) {
163 comptime {
164 var defaults: []const u8 = "";
165 // TODO should we only return "mainstream" targets by default here?
166 // TODO we should also specify ABIs explicitly as the backends are
167 // getting more and more complete
168 // Linux
169 inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| {
170 defaults = defaults ++ arch ++ "-linux" ++ ",";
171 }
172 // macOS
173 inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| {
174 defaults = defaults ++ arch ++ "-macos" ++ ",";
175 }
176 // Windows
177 defaults = defaults ++ "x86_64-windows" ++ ",";
178 // Wasm
179 defaults = defaults ++ "wasm32-wasi";
180 return defaults;
181 }
182 } else if (std.mem.eql(u8, key, "output_mode")) {
183 return switch (@"type") {
184 .@"error" => "Obj",
185 .run => "Exe",
186 .cli => @panic("TODO test harness for CLI tests"),
187 };
188 } else if (std.mem.eql(u8, key, "is_test")) {
189 return "0";
190 } else unreachable;
191 }
192};
193
194/// Manifest syntax example:
195/// (see https://github.com/ziglang/zig/issues/11288)
196///
197/// error
198/// backend=stage1,stage2
199/// output_mode=exe
200///
201/// :3:19: error: foo
202///
203/// run
204/// target=x86_64-linux,aarch64-macos
205///
206/// I am expected stdout! Hello!
207///
208/// cli
209///
210/// build test
211const TestManifest = struct {
212 type: Type,
213 config_map: std.StringHashMap([]const u8),
214 trailing_bytes: []const u8 = "",
215
216 const Type = enum {
217 @"error",
218 run,
219 cli,
220 };
221
222 const TrailingIterator = struct {
223 inner: std.mem.TokenIterator(u8),
224
225 fn next(self: *TrailingIterator) ?[]const u8 {
226 const next_inner = self.inner.next() orelse return null;
227 return std.mem.trim(u8, next_inner[2..], " \t");
228 }
229 };
230
231 fn ConfigValueIterator(comptime T: type) type {
232 return struct {
233 inner: std.mem.SplitIterator(u8),
234
235 fn next(self: *@This()) !?T {
236 const next_raw = self.inner.next() orelse return null;
237 const parseFn = getDefaultParser(T);
238 return try parseFn(next_raw);
239 }
240 };
241 }
242
243 fn parse(arena: Allocator, bytes: []const u8) !TestManifest {
244 // The manifest is the last contiguous block of comments in the file
245 // We scan for the beginning by searching backward for the first non-empty line that does not start with "//"
246 var start: ?usize = null;
247 var end: usize = bytes.len;
248 if (bytes.len > 0) {
249 var cursor: usize = bytes.len - 1;
250 while (true) {
251 // Move to beginning of line
252 while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1;
253
254 if (std.mem.startsWith(u8, bytes[cursor..], "//")) {
255 start = cursor; // Contiguous comment line, include in manifest
256 } else {
257 if (start != null) break; // Encountered non-comment line, end of manifest
258
259 // We ignore all-whitespace lines following the comment block, but anything else
260 // means that there is no manifest present.
261 if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) {
262 end = cursor;
263 } else break; // If it's not whitespace, there is no manifest
264 }
265
266 // Move to previous line
267 if (cursor != 0) cursor -= 1 else break;
268 }
269 }
270
271 const actual_start = start orelse return error.MissingTestManifest;
272 const manifest_bytes = bytes[actual_start..end];
273
274 var it = std.mem.tokenize(u8, manifest_bytes, "\r\n");
275
276 // First line is the test type
277 const tt: Type = blk: {
278 const line = it.next() orelse return error.MissingTestCaseType;
279 const raw = std.mem.trim(u8, line[2..], " \t");
280 if (std.mem.eql(u8, raw, "error")) {
281 break :blk .@"error";
282 } else if (std.mem.eql(u8, raw, "run")) {
283 break :blk .run;
284 } else if (std.mem.eql(u8, raw, "cli")) {
285 break :blk .cli;
286 } else {
287 std.log.warn("unknown test case type requested: {s}", .{raw});
288 return error.UnknownTestCaseType;
289 }
290 };
291
292 var manifest: TestManifest = .{
293 .type = tt,
294 .config_map = std.StringHashMap([]const u8).init(arena),
295 };
296
297 // Any subsequent line until a blank comment line is key=value(s) pair
298 while (it.next()) |line| {
299 const trimmed = std.mem.trim(u8, line[2..], " \t");
300 if (trimmed.len == 0) break;
301
302 // Parse key=value(s)
303 var kv_it = std.mem.split(u8, trimmed, "=");
304 const key = kv_it.first();
305 try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig);
306 }
307
308 // Finally, trailing is expected output
309 manifest.trailing_bytes = manifest_bytes[it.index..];
310
311 return manifest;
312 }
313
314 fn getConfigForKey(
315 self: TestManifest,
316 key: []const u8,
317 comptime T: type,
318 ) ConfigValueIterator(T) {
319 const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key);
320 return ConfigValueIterator(T){
321 .inner = std.mem.split(u8, bytes, ","),
322 };
323 }
324
325 fn getConfigForKeyAlloc(
326 self: TestManifest,
327 allocator: Allocator,
328 key: []const u8,
329 comptime T: type,
330 ) ![]const T {
331 var out = std.ArrayList(T).init(allocator);
332 defer out.deinit();
333 var it = self.getConfigForKey(key, T);
334 while (try it.next()) |item| {
335 try out.append(item);
336 }
337 return try out.toOwnedSlice();
338 }
339
340 fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T {
341 var it = self.getConfigForKey(key, T);
342 const res = (try it.next()) orelse unreachable;
343 assert((try it.next()) == null);
344 return res;
345 }
346
347 fn trailing(self: TestManifest) TrailingIterator {
348 return .{
349 .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"),
350 };
351 }
352
353 fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 {
354 var out = std.ArrayList([]const u8).init(allocator);
355 defer out.deinit();
356 var it = self.trailing();
357 while (it.next()) |line| {
358 try out.append(line);
359 }
360 return try out.toOwnedSlice();
361 }
362
363 fn ParseFn(comptime T: type) type {
364 return fn ([]const u8) anyerror!T;
365 }
366
367 fn getDefaultParser(comptime T: type) ParseFn(T) {
368 if (T == CrossTarget) return struct {
369 fn parse(str: []const u8) anyerror!T {
370 var opts = CrossTarget.ParseOptions{
371 .arch_os_abi = str,
372 };
373 return try CrossTarget.parse(opts);
374 }
375 }.parse;
376
377 switch (@typeInfo(T)) {
378 .Int => return struct {
379 fn parse(str: []const u8) anyerror!T {
380 return try std.fmt.parseInt(T, str, 0);
381 }
382 }.parse,
383 .Bool => return struct {
384 fn parse(str: []const u8) anyerror!T {
385 const as_int = try std.fmt.parseInt(u1, str, 0);
386 return as_int > 0;
387 }
388 }.parse,
389 .Enum => return struct {
390 fn parse(str: []const u8) anyerror!T {
391 return std.meta.stringToEnum(T, str) orelse {
392 std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str });
393 return error.UnknownEnumVariant;
394 };
395 }
396 }.parse,
397 .Struct => @compileError("no default parser for " ++ @typeName(T)),
398 else => @compileError("no default parser for " ++ @typeName(T)),
399 }
400 }
401};
402
403const TestStrategy = enum {
404 /// Execute tests as independent compilations, unless they are explicitly
405 /// incremental ("foo.0.zig", "foo.1.zig", etc.)
406 independent,
407 /// Execute all tests as incremental updates to a single compilation. Explicitly
408 /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order
409 incremental,
410};
411
412/// Iterates a set of filenames extracting batches that are either incremental
413/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.).
414/// Assumes filenames are sorted.
415const TestIterator = struct {
416 start: usize = 0,
417 end: usize = 0,
418 filenames: []const []const u8,
419 /// reset on each call to `next`
420 index: usize = 0,
421
422 const Error = error{InvalidIncrementalTestIndex};
423
424 fn next(it: *TestIterator) Error!?[]const []const u8 {
425 try it.nextInner();
426 if (it.start == it.end) return null;
427 return it.filenames[it.start..it.end];
428 }
429
430 fn nextInner(it: *TestIterator) Error!void {
431 it.start = it.end;
432 if (it.end == it.filenames.len) return;
433 if (it.end + 1 == it.filenames.len) {
434 it.end += 1;
435 return;
436 }
437
438 const remaining = it.filenames[it.end..];
439 it.index = 0;
440 while (it.index < remaining.len - 1) : (it.index += 1) {
441 // First, check if this file is part of an incremental update sequence
442 // Split filename into "<base_name>.<index>.<file_ext>"
443 const prev_parts = getTestFileNameParts(remaining[it.index]);
444 const new_parts = getTestFileNameParts(remaining[it.index + 1]);
445
446 // If base_name and file_ext match, these files are in the same test sequence
447 // and the new one should be the incremented version of the previous test
448 if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and
449 std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext))
450 {
451 // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1
452 if (prev_parts.test_index == null)
453 return error.InvalidIncrementalTestIndex;
454 if (new_parts.test_index == null)
455 return error.InvalidIncrementalTestIndex;
456 if (new_parts.test_index.? != prev_parts.test_index.? + 1)
457 return error.InvalidIncrementalTestIndex;
458 } else {
459 // This is not the same test sequence, so the new file must be the first file
460 // in a new sequence ("*.0.zig") or an independent test file ("*.zig")
461 if (new_parts.test_index != null and new_parts.test_index.? != 0)
462 return error.InvalidIncrementalTestIndex;
463
464 it.end += it.index + 1;
465 break;
466 }
467 } else {
468 it.end += remaining.len;
469 }
470 }
471
472 /// In the event of an `error.InvalidIncrementalTestIndex`, this function can
473 /// be used to find the current filename that was being processed.
474 /// Asserts the iterator hasn't reached the end.
475 fn currentFilename(it: TestIterator) []const u8 {
476 assert(it.end != it.filenames.len);
477 const remaining = it.filenames[it.end..];
478 return remaining[it.index + 1];
479 }
480};
481
482/// For a filename in the format "<filename>.X.<ext>" or "<filename>.<ext>", returns
483/// "<filename>", "<ext>" and X parsed as a decimal number. If X is not present, or
484/// cannot be parsed as a decimal number, it is treated as part of <filename>
485fn getTestFileNameParts(name: []const u8) struct {
486 base_name: []const u8,
487 file_ext: []const u8,
488 test_index: ?usize,
489} {
490 const file_ext = std.fs.path.extension(name);
491 const trimmed = name[0 .. name.len - file_ext.len]; // Trim off ".<ext>"
492 const maybe_index = std.fs.path.extension(trimmed); // Extract ".X"
493
494 // Attempt to parse index
495 const index: ?usize = if (maybe_index.len > 0)
496 std.fmt.parseInt(usize, maybe_index[1..], 10) catch null
497 else
498 null;
499
500 // Adjust "<filename>" extent based on parsing success
501 const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0;
502 return .{
503 .base_name = name[0..base_name_end],
504 .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext,
505 .test_index = index,
506 };
507}
508
509/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig",
510/// "foo.1.zig", etc.) are contiguous and appear in numerical order.
511fn sortTestFilenames(filenames: [][]const u8) void {
512 const Context = struct {
513 pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool {
514 const a_parts = getTestFileNameParts(a);
515 const b_parts = getTestFileNameParts(b);
516
517 // Sort "<base_name>.X.<file_ext>" based on "<base_name>" and "<file_ext>" first
518 return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) {
519 .lt => true,
520 .gt => false,
521 .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) {
522 .lt => true,
523 .gt => false,
524 .eq => {
525 // a and b differ only in their ".X" part
526
527 // Sort "<base_name>.<file_ext>" before any "<base_name>.X.<file_ext>"
528 if (a_parts.test_index) |a_index| {
529 if (b_parts.test_index) |b_index| {
530 // Make sure that incremental tests appear in linear order
531 return a_index < b_index;
532 } else {
533 return false;
534 }
535 } else {
536 return b_parts.test_index != null;
537 }
538 },
539 },
540 };
541 }
542 };
543 std.sort.sort([]const u8, filenames, Context{}, Context.lessThan);
544}
545
546pub const TestContext = struct {
547 gpa: Allocator,
548 arena: Allocator,
549 cases: std.ArrayList(Case),
550
551 pub const Update = struct {
552 /// The input to the current update. We simulate an incremental update
553 /// with the file's contents changed to this value each update.
554 ///
555 /// This value can change entirely between updates, which would be akin
556 /// to deleting the source file and creating a new one from scratch; or
557 /// you can keep it mostly consistent, with small changes, testing the
558 /// effects of the incremental compilation.
559 src: [:0]const u8,
560 name: []const u8,
561 case: union(enum) {
562 /// Check the main binary output file against an expected set of bytes.
563 /// This is most useful with, for example, `-ofmt=c`.
564 CompareObjectFile: []const u8,
565 /// An error update attempts to compile bad code, and ensures that it
566 /// fails to compile, and for the expected reasons.
567 /// A slice containing the expected errors *in sequential order*.
568 Error: []const ErrorMsg,
569 /// An execution update compiles and runs the input, testing the
570 /// stdout against the expected results
571 /// This is a slice containing the expected message.
572 Execution: []const u8,
573 /// A header update compiles the input with the equivalent of
574 /// `-femit-h` and tests the produced header against the
575 /// expected result
576 Header: []const u8,
577 },
578 };
579
580 pub const File = struct {
581 /// Contents of the importable file. Doesn't yet support incremental updates.
582 src: [:0]const u8,
583 path: []const u8,
584 };
585
586 pub const DepModule = struct {
587 name: []const u8,
588 path: []const u8,
589 };
590
591 pub const Backend = enum {
592 stage1,
593 stage2,
594 llvm,
595 };
596
597 /// A `Case` consists of a list of `Update`. The same `Compilation` is used for each
598 /// update, so each update's source is treated as a single file being
599 /// updated by the test harness and incrementally compiled.
600 pub const Case = struct {
601 /// The name of the test case. This is shown if a test fails, and
602 /// otherwise ignored.
603 name: []const u8,
604 /// The platform the test targets. For non-native platforms, an emulator
605 /// such as QEMU is required for tests to complete.
606 target: CrossTarget,
607 /// In order to be able to run e.g. Execution updates, this must be set
608 /// to Executable.
609 output_mode: std.builtin.OutputMode,
610 optimize_mode: std.builtin.Mode = .Debug,
611 updates: std.ArrayList(Update),
612 emit_h: bool = false,
613 is_test: bool = false,
614 expect_exact: bool = false,
615 backend: Backend = .stage2,
616 link_libc: bool = false,
617
618 files: std.ArrayList(File),
619 deps: std.ArrayList(DepModule),
620
621 result: anyerror!void = {},
622
623 pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void {
624 case.files.append(.{ .path = name, .src = src }) catch @panic("out of memory");
625 }
626
627 pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void {
628 case.deps.append(.{
629 .name = name,
630 .path = path,
631 }) catch @panic("out of memory");
632 }
633
634 /// Adds a subcase in which the module is updated with `src`, and a C
635 /// header is generated.
636 pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
637 self.emit_h = true;
638 self.updates.append(.{
639 .src = src,
640 .name = "update",
641 .case = .{ .Header = result },
642 }) catch @panic("out of memory");
643 }
644
645 /// Adds a subcase in which the module is updated with `src`, compiled,
646 /// run, and the output is tested against `result`.
647 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
648 self.updates.append(.{
649 .src = src,
650 .name = "update",
651 .case = .{ .Execution = result },
652 }) catch @panic("out of memory");
653 }
654
655 /// Adds a subcase in which the module is updated with `src`, compiled,
656 /// and the object file data is compared against `result`.
657 pub fn addCompareObjectFile(self: *Case, src: [:0]const u8, result: []const u8) void {
658 self.updates.append(.{
659 .src = src,
660 .name = "update",
661 .case = .{ .CompareObjectFile = result },
662 }) catch @panic("out of memory");
663 }
664
665 pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {
666 return self.addErrorNamed("update", src, errors);
667 }
668
669 /// Adds a subcase in which the module is updated with `src`, which
670 /// should contain invalid input, and ensures that compilation fails
671 /// for the expected reasons, given in sequential order in `errors` in
672 /// the form `:line:column: error: message`.
673 pub fn addErrorNamed(
674 self: *Case,
675 name: []const u8,
676 src: [:0]const u8,
677 errors: []const []const u8,
678 ) void {
679 var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch @panic("out of memory");
680 for (errors, 0..) |err_msg_line, i| {
681 if (std.mem.startsWith(u8, err_msg_line, "error: ")) {
682 array[i] = .{
683 .plain = .{
684 .msg = err_msg_line["error: ".len..],
685 .kind = .@"error",
686 .count = 1,
687 },
688 };
689 continue;
690 } else if (std.mem.startsWith(u8, err_msg_line, "note: ")) {
691 array[i] = .{
692 .plain = .{
693 .msg = err_msg_line["note: ".len..],
694 .kind = .note,
695 .count = 1,
696 },
697 };
698 continue;
699 }
700 // example: "file.zig:1:2: error: bad thing happened"
701 var it = std.mem.split(u8, err_msg_line, ":");
702 const src_path = it.first();
703 const line_text = it.next() orelse @panic("missing line");
704 const col_text = it.next() orelse @panic("missing column");
705 const kind_text = it.next() orelse @panic("missing 'error'/'note'");
706 var msg = it.rest()[1..]; // skip over the space at end of "error: "
707
708 const line: ?u32 = if (std.mem.eql(u8, line_text, "?"))
709 null
710 else
711 std.fmt.parseInt(u32, line_text, 10) catch @panic("bad line number");
712 const column: ?u32 = if (std.mem.eql(u8, line_text, "?"))
713 null
714 else
715 std.fmt.parseInt(u32, col_text, 10) catch @panic("bad column number");
716 const kind: ErrorMsg.Kind = if (std.mem.eql(u8, kind_text, " error"))
717 .@"error"
718 else if (std.mem.eql(u8, kind_text, " note"))
719 .note
720 else
721 @panic("expected 'error'/'note'");
722
723 const line_0based: u32 = if (line) |n| blk: {
724 if (n == 0) {
725 print("{s}: line must be specified starting at one\n", .{self.name});
726 return;
727 }
728 break :blk n - 1;
729 } else std.math.maxInt(u32);
730
731 const column_0based: u32 = if (column) |n| blk: {
732 if (n == 0) {
733 print("{s}: line must be specified starting at one\n", .{self.name});
734 return;
735 }
736 break :blk n - 1;
737 } else std.math.maxInt(u32);
738
739 const suffix = " times)";
740 const count = if (std.mem.endsWith(u8, msg, suffix)) count: {
741 const lparen = std.mem.lastIndexOfScalar(u8, msg, '(').?;
742 const count = std.fmt.parseInt(u32, msg[lparen + 1 .. msg.len - suffix.len], 10) catch @panic("bad error note count number");
743 msg = msg[0 .. lparen - 1];
744 break :count count;
745 } else 1;
746
747 array[i] = .{
748 .src = .{
749 .src_path = src_path,
750 .msg = msg,
751 .line = line_0based,
752 .column = column_0based,
753 .kind = kind,
754 .count = count,
755 },
756 };
757 }
758 self.updates.append(.{
759 .src = src,
760 .name = name,
761 .case = .{ .Error = array },
762 }) catch @panic("out of memory");
763 }
764
765 /// Adds a subcase in which the module is updated with `src`, and
766 /// asserts that it compiles without issue
767 pub fn compiles(self: *Case, src: [:0]const u8) void {
768 self.addError(src, &[_][]const u8{});
769 }
770 };
771
772 pub fn addExe(
773 ctx: *TestContext,
774 name: []const u8,
775 target: CrossTarget,
776 ) *Case {
777 ctx.cases.append(Case{
778 .name = name,
779 .target = target,
780 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
781 .output_mode = .Exe,
782 .files = std.ArrayList(File).init(ctx.arena),
783 .deps = std.ArrayList(DepModule).init(ctx.arena),
784 }) catch @panic("out of memory");
785 return &ctx.cases.items[ctx.cases.items.len - 1];
786 }
787
788 /// Adds a test case for Zig input, producing an executable
789 pub fn exe(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
790 return ctx.addExe(name, target);
791 }
792
793 pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
794 const prefixed_name = std.fmt.allocPrint(ctx.arena, "CBE: {s}", .{name}) catch
795 @panic("out of memory");
796 var target_adjusted = target;
797 target_adjusted.ofmt = std.Target.ObjectFormat.c;
798 ctx.cases.append(Case{
799 .name = prefixed_name,
800 .target = target_adjusted,
801 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
802 .output_mode = .Exe,
803 .files = std.ArrayList(File).init(ctx.arena),
804 .deps = std.ArrayList(DepModule).init(ctx.arena),
805 .link_libc = true,
806 }) catch @panic("out of memory");
807 return &ctx.cases.items[ctx.cases.items.len - 1];
808 }
809
810 /// Adds a test case that uses the LLVM backend to emit an executable.
811 /// Currently this implies linking libc, because only then we can generate a testable executable.
812 pub fn exeUsingLlvmBackend(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
813 ctx.cases.append(Case{
814 .name = name,
815 .target = target,
816 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
817 .output_mode = .Exe,
818 .files = std.ArrayList(File).init(ctx.arena),
819 .deps = std.ArrayList(DepModule).init(ctx.arena),
820 .backend = .llvm,
821 .link_libc = true,
822 }) catch @panic("out of memory");
823 return &ctx.cases.items[ctx.cases.items.len - 1];
824 }
825
826 pub fn addObj(
827 ctx: *TestContext,
828 name: []const u8,
829 target: CrossTarget,
830 ) *Case {
831 ctx.cases.append(Case{
832 .name = name,
833 .target = target,
834 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
835 .output_mode = .Obj,
836 .files = std.ArrayList(File).init(ctx.arena),
837 .deps = std.ArrayList(DepModule).init(ctx.arena),
838 }) catch @panic("out of memory");
839 return &ctx.cases.items[ctx.cases.items.len - 1];
840 }
841
842 pub fn addTest(
843 ctx: *TestContext,
844 name: []const u8,
845 target: CrossTarget,
846 ) *Case {
847 ctx.cases.append(Case{
848 .name = name,
849 .target = target,
850 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
851 .output_mode = .Exe,
852 .is_test = true,
853 .files = std.ArrayList(File).init(ctx.arena),
854 .deps = std.ArrayList(DepModule).init(ctx.arena),
855 }) catch @panic("out of memory");
856 return &ctx.cases.items[ctx.cases.items.len - 1];
857 }
858
859 /// Adds a test case for Zig input, producing an object file.
860 pub fn obj(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
861 return ctx.addObj(name, target);
862 }
863
864 /// Adds a test case for ZIR input, producing an object file.
865 pub fn objZIR(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
866 return ctx.addObj(name, target, .ZIR);
867 }
868
869 /// Adds a test case for Zig or ZIR input, producing C code.
870 pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
871 var target_adjusted = target;
872 target_adjusted.ofmt = std.Target.ObjectFormat.c;
873 ctx.cases.append(Case{
874 .name = name,
875 .target = target_adjusted,
876 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
877 .output_mode = .Obj,
878 .files = std.ArrayList(File).init(ctx.arena),
879 .deps = std.ArrayList(DepModule).init(ctx.arena),
880 }) catch @panic("out of memory");
881 return &ctx.cases.items[ctx.cases.items.len - 1];
882 }
883
884 pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
885 ctx.addC(name, target).addCompareObjectFile(src, zig_h ++ out);
886 }
887
888 pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
889 ctx.addC(name, target).addHeader(src, zig_h ++ out);
890 }
891
892 pub fn objErrStage1(
893 ctx: *TestContext,
894 name: []const u8,
895 src: [:0]const u8,
896 expected_errors: []const []const u8,
897 ) void {
898 const case = ctx.addObj(name, .{});
899 case.backend = .stage1;
900 case.addError(src, expected_errors);
901 }
902
903 pub fn testErrStage1(
904 ctx: *TestContext,
905 name: []const u8,
906 src: [:0]const u8,
907 expected_errors: []const []const u8,
908 ) void {
909 const case = ctx.addTest(name, .{});
910 case.backend = .stage1;
911 case.addError(src, expected_errors);
912 }
913
914 pub fn exeErrStage1(
915 ctx: *TestContext,
916 name: []const u8,
917 src: [:0]const u8,
918 expected_errors: []const []const u8,
919 ) void {
920 const case = ctx.addExe(name, .{});
921 case.backend = .stage1;
922 case.addError(src, expected_errors);
923 }
924
925 pub fn addCompareOutput(
926 ctx: *TestContext,
927 name: []const u8,
928 src: [:0]const u8,
929 expected_stdout: []const u8,
930 ) void {
931 ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout);
932 }
933
934 /// Adds a test case that compiles the Zig source given in `src`, executes
935 /// it, runs it, and tests the output against `expected_stdout`
936 pub fn compareOutput(
937 ctx: *TestContext,
938 name: []const u8,
939 src: [:0]const u8,
940 expected_stdout: []const u8,
941 ) void {
942 return ctx.addCompareOutput(name, src, expected_stdout);
943 }
944
945 /// Adds a test case that compiles the ZIR source given in `src`, executes
946 /// it, runs it, and tests the output against `expected_stdout`
947 pub fn compareOutputZIR(
948 ctx: *TestContext,
949 name: []const u8,
950 src: [:0]const u8,
951 expected_stdout: []const u8,
952 ) void {
953 ctx.addCompareOutput(name, .ZIR, src, expected_stdout);
954 }
955
956 pub fn addTransform(
957 ctx: *TestContext,
958 name: []const u8,
959 target: CrossTarget,
960 src: [:0]const u8,
961 result: [:0]const u8,
962 ) void {
963 ctx.addObj(name, target).addTransform(src, result);
964 }
965
966 /// Adds a test case that compiles the Zig given in `src` to ZIR and tests
967 /// the ZIR against `result`
968 pub fn transform(
969 ctx: *TestContext,
970 name: []const u8,
971 target: CrossTarget,
972 src: [:0]const u8,
973 result: [:0]const u8,
974 ) void {
975 ctx.addTransform(name, target, src, result);
976 }
977
978 pub fn addError(
979 ctx: *TestContext,
980 name: []const u8,
981 target: CrossTarget,
982 src: [:0]const u8,
983 expected_errors: []const []const u8,
984 ) void {
985 ctx.addObj(name, target).addError(src, expected_errors);
986 }
987
988 /// Adds a test case that ensures that the Zig given in `src` fails to
989 /// compile for the expected reasons, given in sequential order in
990 /// `expected_errors` in the form `:line:column: error: message`.
991 pub fn compileError(
992 ctx: *TestContext,
993 name: []const u8,
994 target: CrossTarget,
995 src: [:0]const u8,
996 expected_errors: []const []const u8,
997 ) void {
998 ctx.addError(name, target, src, expected_errors);
999 }
1000
1001 /// Adds a test case that ensures that the ZIR given in `src` fails to
1002 /// compile for the expected reasons, given in sequential order in
1003 /// `expected_errors` in the form `:line:column: error: message`.
1004 pub fn compileErrorZIR(
1005 ctx: *TestContext,
1006 name: []const u8,
1007 target: CrossTarget,
1008 src: [:0]const u8,
1009 expected_errors: []const []const u8,
1010 ) void {
1011 ctx.addError(name, target, .ZIR, src, expected_errors);
1012 }
1013
1014 pub fn addCompiles(
1015 ctx: *TestContext,
1016 name: []const u8,
1017 target: CrossTarget,
1018 src: [:0]const u8,
1019 ) void {
1020 ctx.addObj(name, target).compiles(src);
1021 }
1022
1023 /// Adds a test case that asserts that the Zig given in `src` compiles
1024 /// without any errors.
1025 pub fn compiles(
1026 ctx: *TestContext,
1027 name: []const u8,
1028 target: CrossTarget,
1029 src: [:0]const u8,
1030 ) void {
1031 ctx.addCompiles(name, target, src);
1032 }
1033
1034 /// Adds a test case that asserts that the ZIR given in `src` compiles
1035 /// without any errors.
1036 pub fn compilesZIR(
1037 ctx: *TestContext,
1038 name: []const u8,
1039 target: CrossTarget,
1040 src: [:0]const u8,
1041 ) void {
1042 ctx.addCompiles(name, target, .ZIR, src);
1043 }
1044
1045 /// Adds a test case that first ensures that the Zig given in `src` fails
1046 /// to compile for the reasons given in sequential order in
1047 /// `expected_errors` in the form `:line:column: error: message`, then
1048 /// asserts that fixing the source (updating with `fixed_src`) isn't broken
1049 /// by incremental compilation.
1050 pub fn incrementalFailure(
1051 ctx: *TestContext,
1052 name: []const u8,
1053 target: CrossTarget,
1054 src: [:0]const u8,
1055 expected_errors: []const []const u8,
1056 fixed_src: [:0]const u8,
1057 ) void {
1058 var case = ctx.addObj(name, target);
1059 case.addError(src, expected_errors);
1060 case.compiles(fixed_src);
1061 }
1062
1063 /// Adds a test case that first ensures that the ZIR given in `src` fails
1064 /// to compile for the reasons given in sequential order in
1065 /// `expected_errors` in the form `:line:column: error: message`, then
1066 /// asserts that fixing the source (updating with `fixed_src`) isn't broken
1067 /// by incremental compilation.
1068 pub fn incrementalFailureZIR(
1069 ctx: *TestContext,
1070 name: []const u8,
1071 target: CrossTarget,
1072 src: [:0]const u8,
1073 expected_errors: []const []const u8,
1074 fixed_src: [:0]const u8,
1075 ) void {
1076 var case = ctx.addObj(name, target, .ZIR);
1077 case.addError(src, expected_errors);
1078 case.compiles(fixed_src);
1079 }
1080
1081 /// Adds a test for each file in the provided directory.
1082 /// Testing strategy (TestStrategy) is inferred automatically from filenames.
1083 /// Recurses nested directories.
1084 ///
1085 /// Each file should include a test manifest as a contiguous block of comments at
1086 /// the end of the file. The first line should be the test type, followed by a set of
1087 /// key-value config values, followed by a blank line, then the expected output.
1088 pub fn addTestCasesFromDir(ctx: *TestContext, dir: std.fs.IterableDir) void {
1089 var current_file: []const u8 = "none";
1090 ctx.addTestCasesFromDirInner(dir, &current_file) catch |err| {
1091 std.debug.panic("test harness failed to process file '{s}': {s}\n", .{
1092 current_file, @errorName(err),
1093 });
1094 };
1095 }
1096
1097 fn addTestCasesFromDirInner(
1098 ctx: *TestContext,
1099 iterable_dir: std.fs.IterableDir,
1100 /// This is kept up to date with the currently being processed file so
1101 /// that if any errors occur the caller knows it happened during this file.
1102 current_file: *[]const u8,
1103 ) !void {
1104 var it = try iterable_dir.walk(ctx.arena);
1105 var filenames = std.ArrayList([]const u8).init(ctx.arena);
1106
1107 while (try it.next()) |entry| {
1108 if (entry.kind != .File) continue;
1109
1110 // Ignore stuff such as .swp files
1111 switch (Compilation.classifyFileExt(entry.basename)) {
1112 .unknown => continue,
1113 else => {},
1114 }
1115 try filenames.append(try ctx.arena.dupe(u8, entry.path));
1116 }
1117
1118 // Sort filenames, so that incremental tests are contiguous and in-order
1119 sortTestFilenames(filenames.items);
1120
1121 var test_it = TestIterator{ .filenames = filenames.items };
1122 while (test_it.next()) |maybe_batch| {
1123 const batch = maybe_batch orelse break;
1124 const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent;
1125 var cases = std.ArrayList(usize).init(ctx.arena);
1126
1127 for (batch) |filename| {
1128 current_file.* = filename;
1129
1130 const max_file_size = 10 * 1024 * 1024;
1131 const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);
1132
1133 // Parse the manifest
1134 var manifest = try TestManifest.parse(ctx.arena, src);
1135
1136 if (cases.items.len == 0) {
1137 const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend);
1138 const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget);
1139 const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
1140 const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
1141
1142 const name_prefix = blk: {
1143 const ext_index = std.mem.lastIndexOfScalar(u8, current_file.*, '.') orelse
1144 return error.InvalidFilename;
1145 const index = std.mem.lastIndexOfScalar(u8, current_file.*[0..ext_index], '.') orelse ext_index;
1146 break :blk current_file.*[0..index];
1147 };
1148
1149 // Cross-product to get all possible test combinations
1150 for (backends) |backend| {
1151 for (targets) |target| {
1152 const name = try std.fmt.allocPrint(ctx.arena, "{s} ({s}, {s})", .{
1153 name_prefix,
1154 @tagName(backend),
1155 try target.zigTriple(ctx.arena),
1156 });
1157 const next = ctx.cases.items.len;
1158 try ctx.cases.append(.{
1159 .name = name,
1160 .target = target,
1161 .backend = backend,
1162 .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator),
1163 .is_test = is_test,
1164 .output_mode = output_mode,
1165 .link_libc = backend == .llvm,
1166 .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator),
1167 .deps = std.ArrayList(DepModule).init(ctx.cases.allocator),
1168 });
1169 try cases.append(next);
1170 }
1171 }
1172 }
1173
1174 for (cases.items) |case_index| {
1175 const case = &ctx.cases.items[case_index];
1176 switch (manifest.type) {
1177 .@"error" => {
1178 const errors = try manifest.trailingAlloc(ctx.arena);
1179 switch (strategy) {
1180 .independent => {
1181 case.addError(src, errors);
1182 },
1183 .incremental => {
1184 case.addErrorNamed("update", src, errors);
1185 },
1186 }
1187 },
1188 .run => {
1189 var output = std.ArrayList(u8).init(ctx.arena);
1190 var trailing_it = manifest.trailing();
1191 while (trailing_it.next()) |line| {
1192 try output.appendSlice(line);
1193 try output.append('\n');
1194 }
1195 if (output.items.len > 0) {
1196 try output.resize(output.items.len - 1);
1197 }
1198 case.addCompareOutput(src, try output.toOwnedSlice());
1199 },
1200 .cli => @panic("TODO cli tests"),
1201 }
1202 }
1203 }
1204 } else |err| {
1205 // make sure the current file is set to the file that produced an error
1206 current_file.* = test_it.currentFilename();
1207 return err;
1208 }
1209 }
1210
1211 fn init(gpa: Allocator, arena: Allocator) TestContext {
1212 return .{
1213 .gpa = gpa,
1214 .cases = std.ArrayList(Case).init(gpa),
1215 .arena = arena,
1216 };
1217 }
1218
1219 fn deinit(self: *TestContext) void {
1220 for (self.cases.items) |case| {
1221 for (case.updates.items) |u| {
1222 if (u.case == .Error) {
1223 case.updates.allocator.free(u.case.Error);
1224 }
1225 }
1226 case.updates.deinit();
1227 }
1228 self.cases.deinit();
1229 self.* = undefined;
1230 }
1231
1232 fn run(self: *TestContext) !void {
1233 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1234 const zig_exe_path = try std.process.getEnvVarOwned(self.arena, "ZIG_EXE");
1235
1236 var progress = std.Progress{};
1237 const root_node = progress.start("compiler", self.cases.items.len);
1238 defer root_node.end();
1239
1240 var zig_lib_directory = try introspect.findZigLibDir(self.gpa);
1241 defer zig_lib_directory.handle.close();
1242 defer self.gpa.free(zig_lib_directory.path.?);
1243
1244 var aux_thread_pool: ThreadPool = undefined;
1245 try aux_thread_pool.init(.{ .allocator = self.gpa });
1246 defer aux_thread_pool.deinit();
1247
1248 // Use the same global cache dir for all the tests, such that we for example don't have to
1249 // rebuild musl libc for every case (when LLVM backend is enabled).
1250 var global_tmp = std.testing.tmpDir(.{});
1251 defer global_tmp.cleanup();
1252
1253 var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{});
1254 defer cache_dir.close();
1255 const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path });
1256 defer self.gpa.free(tmp_dir_path);
1257
1258 const global_cache_directory: Compilation.Directory = .{
1259 .handle = cache_dir,
1260 .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }),
1261 };
1262 defer self.gpa.free(global_cache_directory.path.?);
1263
1264 {
1265 for (self.cases.items) |*case| {
1266 if (build_options.skip_non_native) {
1267 if (case.target.getCpuArch() != builtin.cpu.arch)
1268 continue;
1269 if (case.target.getObjectFormat() != builtin.object_format)
1270 continue;
1271 }
1272
1273 // Skip tests that require LLVM backend when it is not available
1274 if (!build_options.have_llvm and case.backend == .llvm)
1275 continue;
1276
1277 if (skip_stage1 and case.backend == .stage1)
1278 continue;
1279
1280 if (build_options.test_filter) |test_filter| {
1281 if (std.mem.indexOf(u8, case.name, test_filter) == null) continue;
1282 }
1283
1284 var prg_node = root_node.start(case.name, case.updates.items.len);
1285 prg_node.activate();
1286 defer prg_node.end();
1287
1288 case.result = runOneCase(
1289 self.gpa,
1290 &prg_node,
1291 case.*,
1292 zig_lib_directory,
1293 zig_exe_path,
1294 &aux_thread_pool,
1295 global_cache_directory,
1296 host,
1297 );
1298 }
1299 }
1300
1301 var fail_count: usize = 0;
1302 for (self.cases.items) |*case| {
1303 case.result catch |err| {
1304 fail_count += 1;
1305 print("{s} failed: {s}\n", .{ case.name, @errorName(err) });
1306 };
1307 }
1308
1309 if (fail_count != 0) {
1310 print("{d} tests failed\n", .{fail_count});
1311 return error.TestFailed;
1312 }
1313 }
1314
1315 fn runOneCase(
1316 allocator: Allocator,
1317 root_node: *std.Progress.Node,
1318 case: Case,
1319 zig_lib_directory: Compilation.Directory,
1320 zig_exe_path: []const u8,
1321 thread_pool: *ThreadPool,
1322 global_cache_directory: Compilation.Directory,
1323 host: std.zig.system.NativeTargetInfo,
1324 ) !void {
1325 const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
1326 const target = target_info.target;
1327
1328 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
1329 defer arena_allocator.deinit();
1330 const arena = arena_allocator.allocator();
1331
1332 var tmp = std.testing.tmpDir(.{});
1333 defer tmp.cleanup();
1334
1335 var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});
1336 defer cache_dir.close();
1337
1338 const tmp_dir_path = try std.fs.path.join(
1339 arena,
1340 &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path },
1341 );
1342 const tmp_dir_path_plus_slash = try std.fmt.allocPrint(
1343 arena,
1344 "{s}" ++ std.fs.path.sep_str,
1345 .{tmp_dir_path},
1346 );
1347 const local_cache_path = try std.fs.path.join(
1348 arena,
1349 &[_][]const u8{ tmp_dir_path, "zig-cache" },
1350 );
1351
1352 for (case.files.items) |file| {
1353 try tmp.dir.writeFile(file.path, file.src);
1354 }
1355
1356 if (case.backend == .stage1) {
1357 // stage1 backend has limitations:
1358 // * leaks memory
1359 // * calls exit() when a compile error happens
1360 // * cannot handle updates
1361 // because of this we must spawn a child process rather than
1362 // using Compilation directly.
1363
1364 if (!std.process.can_spawn) {
1365 print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
1366 return; // Pass test.
1367 }
1368
1369 assert(case.updates.items.len == 1);
1370 const update = case.updates.items[0];
1371 try tmp.dir.writeFile(tmp_src_path, update.src);
1372
1373 var zig_args = std.ArrayList([]const u8).init(arena);
1374 try zig_args.append(zig_exe_path);
1375
1376 if (case.is_test) {
1377 try zig_args.append("test");
1378 } else if (update.case == .Execution) {
1379 try zig_args.append("run");
1380 } else switch (case.output_mode) {
1381 .Obj => try zig_args.append("build-obj"),
1382 .Exe => try zig_args.append("build-exe"),
1383 .Lib => try zig_args.append("build-lib"),
1384 }
1385
1386 try zig_args.append(try std.fs.path.join(arena, &.{ tmp_dir_path, tmp_src_path }));
1387
1388 try zig_args.append("--name");
1389 try zig_args.append("test");
1390
1391 try zig_args.append("--cache-dir");
1392 try zig_args.append(local_cache_path);
1393
1394 try zig_args.append("--global-cache-dir");
1395 try zig_args.append(global_cache_directory.path orelse ".");
1396
1397 if (!case.target.isNative()) {
1398 try zig_args.append("-target");
1399 try zig_args.append(try target.zigTriple(arena));
1400 }
1401
1402 try zig_args.append("-O");
1403 try zig_args.append(@tagName(case.optimize_mode));
1404
1405 // Prevent sub-process progress bar from interfering with the
1406 // one in this parent process.
1407 try zig_args.append("--color");
1408 try zig_args.append("off");
1409
1410 const result = try std.ChildProcess.exec(.{
1411 .allocator = arena,
1412 .argv = zig_args.items,
1413 });
1414 switch (update.case) {
1415 .Error => |case_error_list| {
1416 switch (result.term) {
1417 .Exited => |code| {
1418 if (code == 0) {
1419 dumpArgs(zig_args.items);
1420 return error.CompilationIncorrectlySucceeded;
1421 }
1422 },
1423 else => {
1424 std.debug.print("{s}", .{result.stderr});
1425 dumpArgs(zig_args.items);
1426 return error.CompilationCrashed;
1427 },
1428 }
1429 var ok = true;
1430 if (case.expect_exact) {
1431 var err_iter = std.mem.split(u8, result.stderr, "\n");
1432 var i: usize = 0;
1433 ok = while (err_iter.next()) |line| : (i += 1) {
1434 if (i >= case_error_list.len) break false;
1435 const expected = try std.mem.replaceOwned(
1436 u8,
1437 arena,
1438 try std.fmt.allocPrint(arena, "{s}", .{case_error_list[i]}),
1439 "${DIR}",
1440 tmp_dir_path_plus_slash,
1441 );
1442
1443 if (std.mem.indexOf(u8, line, expected) == null) break false;
1444 continue;
1445 } else true;
1446
1447 ok = ok and i == case_error_list.len;
1448
1449 if (!ok) {
1450 print("\n======== Expected these compile errors: ========\n", .{});
1451 for (case_error_list) |msg| {
1452 const expected = try std.fmt.allocPrint(arena, "{s}", .{msg});
1453 print("{s}\n", .{expected});
1454 }
1455 }
1456 } else {
1457 for (case_error_list) |msg| {
1458 const expected = try std.mem.replaceOwned(
1459 u8,
1460 arena,
1461 try std.fmt.allocPrint(arena, "{s}", .{msg}),
1462 "${DIR}",
1463 tmp_dir_path_plus_slash,
1464 );
1465 if (std.mem.indexOf(u8, result.stderr, expected) == null) {
1466 print(
1467 \\
1468 \\=========== Expected compile error: ============
1469 \\{s}
1470 \\
1471 , .{expected});
1472 ok = false;
1473 break;
1474 }
1475 }
1476 }
1477
1478 if (!ok) {
1479 print(
1480 \\================= Full output: =================
1481 \\{s}
1482 \\================================================
1483 \\
1484 , .{result.stderr});
1485 return error.TestFailed;
1486 }
1487 },
1488 .CompareObjectFile => @panic("TODO implement in the test harness"),
1489 .Execution => |expected_stdout| {
1490 switch (result.term) {
1491 .Exited => |code| {
1492 if (code != 0) {
1493 std.debug.print("{s}", .{result.stderr});
1494 dumpArgs(zig_args.items);
1495 return error.CompilationFailed;
1496 }
1497 },
1498 else => {
1499 std.debug.print("{s}", .{result.stderr});
1500 dumpArgs(zig_args.items);
1501 return error.CompilationCrashed;
1502 },
1503 }
1504 try std.testing.expectEqualStrings("", result.stderr);
1505 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
1506 },
1507 .Header => @panic("TODO implement in the test harness"),
1508 }
1509 return;
1510 }
1511
1512 const zig_cache_directory: Compilation.Directory = .{
1513 .handle = cache_dir,
1514 .path = local_cache_path,
1515 };
1516
1517 var main_pkg: Package = .{
1518 .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir },
1519 .root_src_path = tmp_src_path,
1520 };
1521 defer {
1522 var it = main_pkg.table.iterator();
1523 while (it.next()) |kv| {
1524 allocator.free(kv.key_ptr.*);
1525 kv.value_ptr.*.destroy(allocator);
1526 }
1527 main_pkg.table.deinit(allocator);
1528 }
1529
1530 for (case.deps.items) |dep| {
1531 var pkg = try Package.create(
1532 allocator,
1533 tmp_dir_path,
1534 dep.path,
1535 );
1536 errdefer pkg.destroy(allocator);
1537 try main_pkg.add(allocator, dep.name, pkg);
1538 }
1539
1540 const bin_name = try std.zig.binNameAlloc(arena, .{
1541 .root_name = "test_case",
1542 .target = target,
1543 .output_mode = case.output_mode,
1544 });
1545
1546 const emit_directory: Compilation.Directory = .{
1547 .path = tmp_dir_path,
1548 .handle = tmp.dir,
1549 };
1550 const emit_bin: Compilation.EmitLoc = .{
1551 .directory = emit_directory,
1552 .basename = bin_name,
1553 };
1554 const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{
1555 .directory = emit_directory,
1556 .basename = "test_case.h",
1557 } else null;
1558 const use_llvm: bool = switch (case.backend) {
1559 .llvm => true,
1560 else => false,
1561 };
1562 const comp = try Compilation.create(allocator, .{
1563 .local_cache_directory = zig_cache_directory,
1564 .global_cache_directory = global_cache_directory,
1565 .zig_lib_directory = zig_lib_directory,
1566 .thread_pool = thread_pool,
1567 .root_name = "test_case",
1568 .target = target,
1569 // TODO: support tests for object file building, and library builds
1570 // and linking. This will require a rework to support multi-file
1571 // tests.
1572 .output_mode = case.output_mode,
1573 .is_test = case.is_test,
1574 .optimize_mode = case.optimize_mode,
1575 .emit_bin = emit_bin,
1576 .emit_h = emit_h,
1577 .main_pkg = &main_pkg,
1578 .keep_source_files_loaded = true,
1579 .is_native_os = case.target.isNativeOs(),
1580 .is_native_abi = case.target.isNativeAbi(),
1581 .dynamic_linker = target_info.dynamic_linker.get(),
1582 .link_libc = case.link_libc,
1583 .use_llvm = use_llvm,
1584 .self_exe_path = zig_exe_path,
1585 // TODO instead of turning off color, pass in a std.Progress.Node
1586 .color = .off,
1587 .reference_trace = 0,
1588 // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in
1589 // until the auto-select mechanism deems them worthy
1590 .use_lld = switch (case.backend) {
1591 .stage2 => false,
1592 else => null,
1593 },
1594 });
1595 defer comp.destroy();
1596
1597 update: for (case.updates.items, 0..) |update, update_index| {
1598 var update_node = root_node.start(update.name, 3);
1599 update_node.activate();
1600 defer update_node.end();
1601
1602 var sync_node = update_node.start("write", 0);
1603 sync_node.activate();
1604 try tmp.dir.writeFile(tmp_src_path, update.src);
1605 sync_node.end();
1606
1607 var module_node = update_node.start("parse/analysis/codegen", 0);
1608 module_node.activate();
1609 try comp.makeBinFileWritable();
1610 try comp.update(&module_node);
1611 module_node.end();
1612
1613 if (update.case != .Error) {
1614 var all_errors = try comp.getAllErrorsAlloc();
1615 defer all_errors.deinit(allocator);
1616 if (all_errors.errorMessageCount() > 0) {
1617 all_errors.renderToStdErr(std.debug.detectTTYConfig(std.io.getStdErr()));
1618 // TODO print generated C code
1619 return error.UnexpectedCompileErrors;
1620 }
1621 }
1622
1623 switch (update.case) {
1624 .Header => |expected_output| {
1625 var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only });
1626 defer file.close();
1627 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1628
1629 try std.testing.expectEqualStrings(expected_output, out);
1630 },
1631 .CompareObjectFile => |expected_output| {
1632 var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only });
1633 defer file.close();
1634 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1635
1636 try std.testing.expectEqualStrings(expected_output, out);
1637 },
1638 .Error => |case_error_list| {
1639 var test_node = update_node.start("assert", 0);
1640 test_node.activate();
1641 defer test_node.end();
1642
1643 const handled_errors = try arena.alloc(bool, case_error_list.len);
1644 std.mem.set(bool, handled_errors, false);
1645
1646 var actual_errors = try comp.getAllErrorsAlloc();
1647 defer actual_errors.deinit(allocator);
1648
1649 var any_failed = false;
1650 var notes_to_check = std.ArrayList(*const Compilation.AllErrors.Message).init(allocator);
1651 defer notes_to_check.deinit();
1652
1653 for (actual_errors.list) |actual_error| {
1654 for (case_error_list, 0..) |case_msg, i| {
1655 if (handled_errors[i]) continue;
1656
1657 const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg;
1658 switch (actual_error) {
1659 .src => |actual_msg| {
1660 for (actual_msg.notes) |*note| {
1661 try notes_to_check.append(note);
1662 }
1663
1664 if (ex_tag != .src) continue;
1665
1666 const src_path_ok = case_msg.src.src_path.len == 0 or
1667 std.mem.eql(u8, case_msg.src.src_path, actual_msg.src_path);
1668
1669 const expected_msg = try std.mem.replaceOwned(
1670 u8,
1671 arena,
1672 case_msg.src.msg,
1673 "${DIR}",
1674 tmp_dir_path_plus_slash,
1675 );
1676
1677 var buf: [1024]u8 = undefined;
1678 const rendered_msg = blk: {
1679 var msg: Compilation.AllErrors.Message = actual_error;
1680 msg.src.src_path = case_msg.src.src_path;
1681 msg.src.notes = &.{};
1682 msg.src.source_line = null;
1683 var fib = std.io.fixedBufferStream(&buf);
1684 try msg.renderToWriter(.no_color, fib.writer(), "error", .Red, 0);
1685 var it = std.mem.split(u8, fib.getWritten(), "error: ");
1686 _ = it.first();
1687 const rendered = it.rest();
1688 break :blk rendered[0 .. rendered.len - 1]; // trim final newline
1689 };
1690
1691 if (src_path_ok and
1692 (case_msg.src.line == std.math.maxInt(u32) or
1693 actual_msg.line == case_msg.src.line) and
1694 (case_msg.src.column == std.math.maxInt(u32) or
1695 actual_msg.column == case_msg.src.column) and
1696 std.mem.eql(u8, expected_msg, rendered_msg) and
1697 case_msg.src.kind == .@"error" and
1698 actual_msg.count == case_msg.src.count)
1699 {
1700 handled_errors[i] = true;
1701 break;
1702 }
1703 },
1704 .plain => |plain| {
1705 if (ex_tag != .plain) continue;
1706
1707 if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and
1708 case_msg.plain.kind == .@"error" and
1709 case_msg.plain.count == plain.count)
1710 {
1711 handled_errors[i] = true;
1712 break;
1713 }
1714 },
1715 }
1716 } else {
1717 print(
1718 "\nUnexpected error:\n{s}\n{}\n{s}",
1719 .{ hr, ErrorMsg.init(actual_error, .@"error"), hr },
1720 );
1721 any_failed = true;
1722 }
1723 }
1724 while (notes_to_check.popOrNull()) |note| {
1725 for (case_error_list, 0..) |case_msg, i| {
1726 const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg;
1727 switch (note.*) {
1728 .src => |actual_msg| {
1729 for (actual_msg.notes) |*sub_note| {
1730 try notes_to_check.append(sub_note);
1731 }
1732 if (ex_tag != .src) continue;
1733
1734 const expected_msg = try std.mem.replaceOwned(
1735 u8,
1736 arena,
1737 case_msg.src.msg,
1738 "${DIR}",
1739 tmp_dir_path_plus_slash,
1740 );
1741
1742 if ((case_msg.src.line == std.math.maxInt(u32) or
1743 actual_msg.line == case_msg.src.line) and
1744 (case_msg.src.column == std.math.maxInt(u32) or
1745 actual_msg.column == case_msg.src.column) and
1746 std.mem.eql(u8, expected_msg, actual_msg.msg) and
1747 case_msg.src.kind == .note and
1748 actual_msg.count == case_msg.src.count)
1749 {
1750 handled_errors[i] = true;
1751 break;
1752 }
1753 },
1754 .plain => |plain| {
1755 if (ex_tag != .plain) continue;
1756
1757 if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and
1758 case_msg.plain.kind == .note and
1759 case_msg.plain.count == plain.count)
1760 {
1761 handled_errors[i] = true;
1762 break;
1763 }
1764 },
1765 }
1766 } else {
1767 print(
1768 "\nUnexpected note:\n{s}\n{}\n{s}",
1769 .{ hr, ErrorMsg.init(note.*, .note), hr },
1770 );
1771 any_failed = true;
1772 }
1773 }
1774
1775 for (handled_errors, 0..) |handled, i| {
1776 if (!handled) {
1777 print(
1778 "\nExpected error not found:\n{s}\n{}\n{s}",
1779 .{ hr, case_error_list[i], hr },
1780 );
1781 any_failed = true;
1782 }
1783 }
1784
1785 if (any_failed) {
1786 print("\nupdate_index={d}\n", .{update_index});
1787 return error.WrongCompileErrors;
1788 }
1789 },
1790 .Execution => |expected_stdout| {
1791 if (!std.process.can_spawn) {
1792 print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
1793 continue :update; // Pass test.
1794 }
1795
1796 update_node.setEstimatedTotalItems(4);
1797
1798 var argv = std.ArrayList([]const u8).init(allocator);
1799 defer argv.deinit();
1800
1801 var exec_result = x: {
1802 var exec_node = update_node.start("execute", 0);
1803 exec_node.activate();
1804 defer exec_node.end();
1805
1806 // We go out of our way here to use the unique temporary directory name in
1807 // the exe_path so that it makes its way into the cache hash, avoiding
1808 // cache collisions from multiple threads doing `zig run` at the same time
1809 // on the same test_case.c input filename.
1810 const ss = std.fs.path.sep_str;
1811 const exe_path = try std.fmt.allocPrint(
1812 arena,
1813 ".." ++ ss ++ "{s}" ++ ss ++ "{s}",
1814 .{ &tmp.sub_path, bin_name },
1815 );
1816 if (case.target.ofmt != null and case.target.ofmt.? == .c) {
1817 if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
1818 // We wouldn't be able to run the compiled C code.
1819 continue :update; // Pass test.
1820 }
1821 try argv.appendSlice(&[_][]const u8{
1822 zig_exe_path,
1823 "run",
1824 "-cflags",
1825 "-std=c99",
1826 "-pedantic",
1827 "-Werror",
1828 "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
1829 "--",
1830 "-lc",
1831 exe_path,
1832 });
1833 if (zig_lib_directory.path) |p| {
1834 try argv.appendSlice(&.{ "-I", p });
1835 }
1836 } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) {
1837 .native => {
1838 if (case.backend == .stage2 and case.target.getCpuArch() == .arm) {
1839 // https://github.com/ziglang/zig/issues/13623
1840 continue :update; // Pass test.
1841 }
1842 try argv.append(exe_path);
1843 },
1844 .bad_dl, .bad_os_or_cpu => continue :update, // Pass test.
1845
1846 .rosetta => if (enable_rosetta) {
1847 try argv.append(exe_path);
1848 } else {
1849 continue :update; // Rosetta not available, pass test.
1850 },
1851
1852 .qemu => |qemu_bin_name| if (enable_qemu) {
1853 const need_cross_glibc = target.isGnuLibC() and case.link_libc;
1854 const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc)
1855 glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test
1856 else
1857 null;
1858 try argv.append(qemu_bin_name);
1859 if (glibc_dir_arg) |dir| {
1860 const linux_triple = try target.linuxTriple(arena);
1861 const full_dir = try std.fs.path.join(arena, &[_][]const u8{
1862 dir,
1863 linux_triple,
1864 });
1865
1866 try argv.append("-L");
1867 try argv.append(full_dir);
1868 }
1869 try argv.append(exe_path);
1870 } else {
1871 continue :update; // QEMU not available; pass test.
1872 },
1873
1874 .wine => |wine_bin_name| if (enable_wine) {
1875 try argv.append(wine_bin_name);
1876 try argv.append(exe_path);
1877 } else {
1878 continue :update; // Wine not available; pass test.
1879 },
1880
1881 .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
1882 try argv.append(wasmtime_bin_name);
1883 try argv.append("--dir=.");
1884 try argv.append(exe_path);
1885 } else {
1886 continue :update; // wasmtime not available; pass test.
1887 },
1888
1889 .darling => |darling_bin_name| if (enable_darling) {
1890 try argv.append(darling_bin_name);
1891 // Since we use relative to cwd here, we invoke darling with
1892 // "shell" subcommand.
1893 try argv.append("shell");
1894 try argv.append(exe_path);
1895 } else {
1896 continue :update; // Darling not available; pass test.
1897 },
1898 }
1899
1900 try comp.makeBinFileExecutable();
1901
1902 while (true) {
1903 break :x std.ChildProcess.exec(.{
1904 .allocator = allocator,
1905 .argv = argv.items,
1906 .cwd_dir = tmp.dir,
1907 .cwd = tmp_dir_path,
1908 }) catch |err| switch (err) {
1909 error.FileBusy => {
1910 // There is a fundamental design flaw in Unix systems with how
1911 // ETXTBSY interacts with fork+exec.
1912 // https://github.com/golang/go/issues/22315
1913 // https://bugs.openjdk.org/browse/JDK-8068370
1914 // Unfortunately, this could be a real error, but we can't
1915 // tell the difference here.
1916 continue;
1917 },
1918 else => {
1919 print("\n{s}.{d} The following command failed with {s}:\n", .{
1920 case.name, update_index, @errorName(err),
1921 });
1922 dumpArgs(argv.items);
1923 return error.ChildProcessExecution;
1924 },
1925 };
1926 }
1927 };
1928 var test_node = update_node.start("test", 0);
1929 test_node.activate();
1930 defer test_node.end();
1931 defer allocator.free(exec_result.stdout);
1932 defer allocator.free(exec_result.stderr);
1933 switch (exec_result.term) {
1934 .Exited => |code| {
1935 if (code != 0) {
1936 print("\n{s}\n{s}: execution exited with code {d}:\n", .{
1937 exec_result.stderr, case.name, code,
1938 });
1939 dumpArgs(argv.items);
1940 return error.ChildProcessExecution;
1941 }
1942 },
1943 else => {
1944 print("\n{s}\n{s}: execution crashed:\n", .{
1945 exec_result.stderr, case.name,
1946 });
1947 dumpArgs(argv.items);
1948 return error.ChildProcessExecution;
1949 },
1950 }
1951 try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout);
1952 // We allow stderr to have garbage in it because wasmtime prints a
1953 // warning about --invoke even though we don't pass it.
1954 //std.testing.expectEqualStrings("", exec_result.stderr);
1955 },
1956 }
1957 }
1958 }
1959};
1960
1961fn dumpArgs(argv: []const []const u8) void {
1962 for (argv) |arg| {
1963 print("{s} ", .{arg});
1964 }
1965 print("\n", .{});
1966}
1967
1968const tmp_src_path = "tmp.zig";
test/cases.zig+5-5
......@@ -1,8 +1,8 @@
11const std = @import("std");
2const TestContext = @import("../src/test.zig").TestContext;
2const Cases = @import("src/Cases.zig");
33
4pub fn addCases(ctx: *TestContext) !void {
5 try @import("compile_errors.zig").addCases(ctx);
6 try @import("stage2/cbe.zig").addCases(ctx);
7 try @import("stage2/nvptx.zig").addCases(ctx);
4pub fn addCases(cases: *Cases) !void {
5 try @import("compile_errors.zig").addCases(cases);
6 try @import("cbe.zig").addCases(cases);
7 try @import("nvptx.zig").addCases(cases);
88}
test/cases/compile_errors/access_inactive_union_field_comptime.zig+1
......@@ -21,3 +21,4 @@ pub export fn entry1() void {
2121// :9:15: error: access of union field 'a' while field 'b' is active
2222// :2:21: note: union declared here
2323// :14:16: error: access of union field 'a' while field 'b' is active
24// :2:21: note: union declared here
test/cases/compile_errors/bad_import.zig+1-1
......@@ -4,4 +4,4 @@ const bogus = @import("bogus-does-not-exist.zig",);
44// backend=stage2
55// target=native
66//
7// :1:23: error: unable to load '${DIR}bogus-does-not-exist.zig': FileNotFound
7// bogus-does-not-exist.zig': FileNotFound
test/cases/compile_errors/condition_comptime_reason_explained.zig+2
......@@ -45,4 +45,6 @@ pub export fn entry2() void {
4545// :22:13: error: unable to resolve comptime value
4646// :22:13: note: condition in comptime switch must be comptime-known
4747// :21:17: note: expression is evaluated at comptime because the function returns a comptime-only type 'tmp.S'
48// :2:12: note: struct requires comptime because of this field
49// :2:12: note: use '*const fn() void' for a function pointer type
4850// :32:19: note: called from here
test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig+1
......@@ -32,6 +32,7 @@ export fn d() void {
3232// :3:8: error: opaque types have unknown size and therefore cannot be directly embedded in structs
3333// :1:11: note: opaque declared here
3434// :7:10: error: opaque types have unknown size and therefore cannot be directly embedded in unions
35// :1:11: note: opaque declared here
3536// :19:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs
3637// :18:22: note: opaque declared here
3738// :24:23: error: opaque types have unknown size and therefore cannot be directly embedded in structs
test/cases/compile_errors/extern_function_with_comptime_parameter.zig+1-1
......@@ -12,6 +12,6 @@ comptime { _ = entry2; }
1212// backend=stage2
1313// target=native
1414//
15// :1:15: error: comptime parameters not allowed in function with calling convention 'C'
1615// :5:30: error: comptime parameters not allowed in function with calling convention 'C'
1716// :6:30: error: generic parameters not allowed in function with calling convention 'C'
17// :1:15: error: comptime parameters not allowed in function with calling convention 'C'
test/cases/compile_errors/function_parameter_is_opaque.zig+1
......@@ -27,4 +27,5 @@ export fn entry4() void {
2727// :1:17: note: opaque declared here
2828// :8:28: error: parameter of type '@TypeOf(null)' not allowed
2929// :12:8: error: parameter of opaque type 'tmp.FooType' not allowed
30// :1:17: note: opaque declared here
3031// :17:8: error: parameter of type '@TypeOf(null)' not allowed
test/cases/compile_errors/helpful_return_type_error_message.zig+1-1
......@@ -24,9 +24,9 @@ export fn quux() u32 {
2424// :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set'
2525// :7:17: note: function cannot return an error
2626// :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32'
27// :10:17: note: function cannot return an error
2827// :11:15: note: cannot convert error union to payload type
2928// :11:15: note: consider using 'try', 'catch', or 'if'
29// :10:17: note: function cannot return an error
3030// :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32'
3131// :15:14: note: cannot convert error union to payload type
3232// :15:14: note: consider using 'try', 'catch', or 'if'
test/cases/compile_errors/implicit_semicolon-block_expr.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-block_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-comptime_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = comptime {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-comptime_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 comptime ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-defer.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 defer ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-for_expression.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 _ = for(foo()) |_| {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-for_statement.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 for(foo()) |_| ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {} else if(true) {} else {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({}) else if(true) ({}) else ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {} else if(true) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({}) else if(true) ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {} else {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({}) else ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-test_expression.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 _ = if (foo()) |_| {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-test_statement.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 if (foo()) |_| ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = while(true):({}) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 while(true):({}) ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-while_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = while(true) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-while_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 while(true) 1
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/invalid_member_of_builtin_enum.zig+1-1
......@@ -9,4 +9,4 @@ export fn entry() void {
99// target=native
1010//
1111// :3:38: error: enum 'builtin.OptimizeMode' has no member named 'x86'
12// :?:18: note: enum declared here
12// : note: enum declared here
test/cases/compile_errors/invalid_store_to_comptime_field.zig+1-1
......@@ -73,11 +73,11 @@ pub export fn entry8() void {
7373//
7474// :6:19: error: value stored in comptime field does not match the default value of the field
7575// :14:19: error: value stored in comptime field does not match the default value of the field
76// :53:16: error: value stored in comptime field does not match the default value of the field
7776// :19:38: error: value stored in comptime field does not match the default value of the field
7877// :31:19: error: value stored in comptime field does not match the default value of the field
7978// :25:29: note: default value set here
8079// :41:16: error: value stored in comptime field does not match the default value of the field
8180// :45:12: error: value stored in comptime field does not match the default value of the field
81// :53:16: error: value stored in comptime field does not match the default value of the field
8282// :66:43: error: value stored in comptime field does not match the default value of the field
8383// :59:35: error: value stored in comptime field does not match the default value of the field
test/cases/compile_errors/invalid_struct_field.zig+1
......@@ -25,5 +25,6 @@ export fn e() void {
2525// :4:7: error: no field named 'foo' in struct 'tmp.A'
2626// :1:11: note: struct declared here
2727// :10:17: error: no field named 'bar' in struct 'tmp.A'
28// :1:11: note: struct declared here
2829// :18:45: error: no field named 'f' in struct 'tmp.e.B'
2930// :14:15: note: struct declared here
test/cases/compile_errors/missing_main_fn_in_executable.zig+4-2
......@@ -5,5 +5,7 @@
55// target=x86_64-linux
66// output_mode=Exe
77//
8// :?:?: error: root struct of file 'tmp' has no member named 'main'
9// :?:?: note: called from here
8// : error: root struct of file 'tmp' has no member named 'main'
9// : note: called from here
10// : note: called from here
11// : note: called from here
test/cases/compile_errors/private_main_fn.zig+4-2
......@@ -5,6 +5,8 @@ fn main() void {}
55// target=x86_64-linux
66// output_mode=Exe
77//
8// :?:?: error: 'main' is not marked 'pub'
8// : error: 'main' is not marked 'pub'
99// :1:1: note: declared here
10// :?:?: note: called from here
10// : note: called from here
11// : note: called from here
12// : note: called from here
test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig+3-2
......@@ -15,5 +15,6 @@ export fn entry() void {
1515// target=native
1616//
1717// :9:51: error: values of type '[]const builtin.Type.StructField' must be comptime-known, but index value is runtime-known
18// :?:21: note: struct requires comptime because of this field
19// :?:21: note: types are not available at runtime
18// : note: struct requires comptime because of this field
19// : note: types are not available at runtime
20// : struct requires comptime because of this field
test/cases/compile_errors/struct_type_mismatch_in_arg.zig+1-1
......@@ -13,6 +13,6 @@ comptime {
1313// target=native
1414//
1515// :7:16: error: expected type 'tmp.Foo', found 'tmp.Bar'
16// :1:13: note: struct declared here
1716// :2:13: note: struct declared here
17// :1:13: note: struct declared here
1818// :4:18: note: parameter type declared here
test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig+2-1
......@@ -28,10 +28,11 @@ export fn u2m() void {
2828// target=native
2929//
3030// :9:1: error: union initializer must initialize one field
31// :1:12: note: union declared here
3132// :14:20: error: cannot initialize multiple union fields at once, unions can only have one active field
3233// :14:31: note: additional initializer here
34// :1:12: note: union declared here
3335// :18:21: error: union initializer must initialize one field
3436// :22:20: error: cannot initialize multiple union fields at once, unions can only have one active field
3537// :22:31: note: additional initializer here
36// :1:12: note: union declared here
3738// :5:12: note: union declared here
test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig+1-1
......@@ -6,7 +6,7 @@ pub fn main() void {
66 _ = entry;
77}
88
9// error
9// compile
1010// output_mode=Exe
1111// backend=llvm
1212// target=x86_64-linux,x86_64-macos
test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig+1-1
......@@ -6,7 +6,7 @@ pub fn main() void {
66 _ = entry;
77}
88
9// error
9// compile
1010// output_mode=Exe
1111// backend=stage2,llvm
1212// target=x86_64-linux,x86_64-macos
test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/pointer_keeps_address_space.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cbe.zig created+950
......@@ -0,0 +1,950 @@
1const std = @import("std");
2const Cases = @import("src/Cases.zig");
3
4// These tests should work with all platforms, but we're using linux_x64 for
5// now for consistency. Will be expanded eventually.
6const linux_x64 = std.zig.CrossTarget{
7 .cpu_arch = .x86_64,
8 .os_tag = .linux,
9};
10
11pub fn addCases(ctx: *Cases) !void {
12 {
13 var case = ctx.exeFromCompiledC("hello world with updates", .{});
14
15 // Regular old hello world
16 case.addCompareOutput(
17 \\extern fn puts(s: [*:0]const u8) c_int;
18 \\pub export fn main() c_int {
19 \\ _ = puts("hello world!");
20 \\ return 0;
21 \\}
22 , "hello world!" ++ std.cstr.line_sep);
23
24 // Now change the message only
25 case.addCompareOutput(
26 \\extern fn puts(s: [*:0]const u8) c_int;
27 \\pub export fn main() c_int {
28 \\ _ = puts("yo");
29 \\ return 0;
30 \\}
31 , "yo" ++ std.cstr.line_sep);
32
33 // Add an unused Decl
34 case.addCompareOutput(
35 \\extern fn puts(s: [*:0]const u8) c_int;
36 \\pub export fn main() c_int {
37 \\ _ = puts("yo!");
38 \\ return 0;
39 \\}
40 \\fn unused() void {}
41 , "yo!" ++ std.cstr.line_sep);
42
43 // Comptime return type and calling convention expected.
44 case.addError(
45 \\var x: i32 = 1234;
46 \\pub export fn main() x {
47 \\ return 0;
48 \\}
49 \\export fn foo() callconv(y) c_int {
50 \\ return 0;
51 \\}
52 \\var y: @import("std").builtin.CallingConvention = .C;
53 , &.{
54 ":2:22: error: expected type 'type', found 'i32'",
55 ":5:26: error: unable to resolve comptime value",
56 ":5:26: note: calling convention must be comptime-known",
57 });
58 }
59
60 {
61 var case = ctx.exeFromCompiledC("var args", .{});
62
63 case.addCompareOutput(
64 \\extern fn printf(format: [*:0]const u8, ...) c_int;
65 \\
66 \\pub export fn main() c_int {
67 \\ _ = printf("Hello, %s!\n", "world");
68 \\ return 0;
69 \\}
70 , "Hello, world!" ++ std.cstr.line_sep);
71 }
72
73 {
74 var case = ctx.exeFromCompiledC("intToError", .{});
75
76 case.addCompareOutput(
77 \\pub export fn main() c_int {
78 \\ // comptime checks
79 \\ const a = error.A;
80 \\ const b = error.B;
81 \\ const c = @intToError(2);
82 \\ const d = @intToError(1);
83 \\ if (!(c == b)) unreachable;
84 \\ if (!(a == d)) unreachable;
85 \\ // runtime checks
86 \\ var x = error.A;
87 \\ var y = error.B;
88 \\ var z = @intToError(2);
89 \\ var f = @intToError(1);
90 \\ if (!(y == z)) unreachable;
91 \\ if (!(x == f)) unreachable;
92 \\ return 0;
93 \\}
94 , "");
95 case.addError(
96 \\pub export fn main() c_int {
97 \\ _ = @intToError(0);
98 \\ return 0;
99 \\}
100 , &.{":2:21: error: integer value '0' represents no error"});
101 case.addError(
102 \\pub export fn main() c_int {
103 \\ _ = @intToError(3);
104 \\ return 0;
105 \\}
106 , &.{":2:21: error: integer value '3' represents no error"});
107 }
108
109 {
110 var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);
111
112 // Exit with 0
113 case.addCompareOutput(
114 \\fn exitGood() noreturn {
115 \\ asm volatile ("syscall"
116 \\ :
117 \\ : [number] "{rax}" (231),
118 \\ [arg1] "{rdi}" (0)
119 \\ );
120 \\ unreachable;
121 \\}
122 \\
123 \\pub export fn main() c_int {
124 \\ exitGood();
125 \\}
126 , "");
127
128 // Pass a usize parameter to exit
129 case.addCompareOutput(
130 \\pub export fn main() c_int {
131 \\ exit(0);
132 \\}
133 \\
134 \\fn exit(code: usize) noreturn {
135 \\ asm volatile ("syscall"
136 \\ :
137 \\ : [number] "{rax}" (231),
138 \\ [arg1] "{rdi}" (code)
139 \\ );
140 \\ unreachable;
141 \\}
142 , "");
143
144 // Change the parameter to u8
145 case.addCompareOutput(
146 \\pub export fn main() c_int {
147 \\ exit(0);
148 \\}
149 \\
150 \\fn exit(code: u8) noreturn {
151 \\ asm volatile ("syscall"
152 \\ :
153 \\ : [number] "{rax}" (231),
154 \\ [arg1] "{rdi}" (code)
155 \\ );
156 \\ unreachable;
157 \\}
158 , "");
159
160 // Do some arithmetic at the exit callsite
161 case.addCompareOutput(
162 \\pub export fn main() c_int {
163 \\ exitMath(1);
164 \\}
165 \\
166 \\fn exitMath(a: u8) noreturn {
167 \\ exit(0 + a - a);
168 \\}
169 \\
170 \\fn exit(code: u8) noreturn {
171 \\ asm volatile ("syscall"
172 \\ :
173 \\ : [number] "{rax}" (231),
174 \\ [arg1] "{rdi}" (code)
175 \\ );
176 \\ unreachable;
177 \\}
178 \\
179 , "");
180
181 // Invert the arithmetic
182 case.addCompareOutput(
183 \\pub export fn main() c_int {
184 \\ exitMath(1);
185 \\}
186 \\
187 \\fn exitMath(a: u8) noreturn {
188 \\ exit(a + 0 - a);
189 \\}
190 \\
191 \\fn exit(code: u8) noreturn {
192 \\ asm volatile ("syscall"
193 \\ :
194 \\ : [number] "{rax}" (231),
195 \\ [arg1] "{rdi}" (code)
196 \\ );
197 \\ unreachable;
198 \\}
199 \\
200 , "");
201 }
202
203 {
204 var case = ctx.exeFromCompiledC("alloc and retptr", .{});
205
206 case.addCompareOutput(
207 \\fn add(a: i32, b: i32) i32 {
208 \\ return a + b;
209 \\}
210 \\
211 \\fn addIndirect(a: i32, b: i32) i32 {
212 \\ return add(a, b);
213 \\}
214 \\
215 \\pub export fn main() c_int {
216 \\ return addIndirect(1, 2) - 3;
217 \\}
218 , "");
219 }
220
221 {
222 var case = ctx.exeFromCompiledC("inferred local const and var", .{});
223
224 case.addCompareOutput(
225 \\fn add(a: i32, b: i32) i32 {
226 \\ return a + b;
227 \\}
228 \\
229 \\pub export fn main() c_int {
230 \\ const x = add(1, 2);
231 \\ var y = add(3, 0);
232 \\ y -= x;
233 \\ return y;
234 \\}
235 , "");
236 }
237 {
238 var case = ctx.exeFromCompiledC("control flow", .{});
239
240 // Simple while loop
241 case.addCompareOutput(
242 \\pub export fn main() c_int {
243 \\ var a: c_int = 0;
244 \\ while (a < 5) : (a+=1) {}
245 \\ return a - 5;
246 \\}
247 , "");
248 case.addCompareOutput(
249 \\pub export fn main() c_int {
250 \\ var a = true;
251 \\ while (!a) {}
252 \\ return 0;
253 \\}
254 , "");
255
256 // If expression
257 case.addCompareOutput(
258 \\pub export fn main() c_int {
259 \\ var cond: c_int = 0;
260 \\ var a: c_int = @as(c_int, if (cond == 0)
261 \\ 2
262 \\ else
263 \\ 3) + 9;
264 \\ return a - 11;
265 \\}
266 , "");
267
268 // If expression with breakpoint that does not get hit
269 case.addCompareOutput(
270 \\pub export fn main() c_int {
271 \\ var x: i32 = 1;
272 \\ if (x != 1) @breakpoint();
273 \\ return 0;
274 \\}
275 , "");
276
277 // Switch expression
278 case.addCompareOutput(
279 \\pub export fn main() c_int {
280 \\ var cond: c_int = 0;
281 \\ var a: c_int = switch (cond) {
282 \\ 1 => 1,
283 \\ 2 => 2,
284 \\ 99...300, 12 => 3,
285 \\ 0 => 4,
286 \\ else => 5,
287 \\ };
288 \\ return a - 4;
289 \\}
290 , "");
291
292 // Switch expression missing else case.
293 case.addError(
294 \\pub export fn main() c_int {
295 \\ var cond: c_int = 0;
296 \\ const a: c_int = switch (cond) {
297 \\ 1 => 1,
298 \\ 2 => 2,
299 \\ 3 => 3,
300 \\ 4 => 4,
301 \\ };
302 \\ return a - 4;
303 \\}
304 , &.{":3:22: error: switch must handle all possibilities"});
305
306 // Switch expression, has an unreachable prong.
307 case.addCompareOutput(
308 \\pub export fn main() c_int {
309 \\ var cond: c_int = 0;
310 \\ const a: c_int = switch (cond) {
311 \\ 1 => 1,
312 \\ 2 => 2,
313 \\ 99...300, 12 => 3,
314 \\ 0 => 4,
315 \\ 13 => unreachable,
316 \\ else => 5,
317 \\ };
318 \\ return a - 4;
319 \\}
320 , "");
321
322 // Switch expression, has an unreachable prong and prongs write
323 // to result locations.
324 case.addCompareOutput(
325 \\pub export fn main() c_int {
326 \\ var cond: c_int = 0;
327 \\ var a: c_int = switch (cond) {
328 \\ 1 => 1,
329 \\ 2 => 2,
330 \\ 99...300, 12 => 3,
331 \\ 0 => 4,
332 \\ 13 => unreachable,
333 \\ else => 5,
334 \\ };
335 \\ return a - 4;
336 \\}
337 , "");
338
339 // Integer switch expression has duplicate case value.
340 case.addError(
341 \\pub export fn main() c_int {
342 \\ var cond: c_int = 0;
343 \\ const a: c_int = switch (cond) {
344 \\ 1 => 1,
345 \\ 2 => 2,
346 \\ 96, 11...13, 97 => 3,
347 \\ 0 => 4,
348 \\ 90, 12 => 100,
349 \\ else => 5,
350 \\ };
351 \\ return a - 4;
352 \\}
353 , &.{
354 ":8:13: error: duplicate switch value",
355 ":6:15: note: previous value here",
356 });
357
358 // Boolean switch expression has duplicate case value.
359 case.addError(
360 \\pub export fn main() c_int {
361 \\ var a: bool = false;
362 \\ const b: c_int = switch (a) {
363 \\ false => 1,
364 \\ true => 2,
365 \\ false => 3,
366 \\ };
367 \\ _ = b;
368 \\}
369 , &.{
370 ":6:9: error: duplicate switch value",
371 });
372
373 // Sparse (no range capable) switch expression has duplicate case value.
374 case.addError(
375 \\pub export fn main() c_int {
376 \\ const A: type = i32;
377 \\ const b: c_int = switch (A) {
378 \\ i32 => 1,
379 \\ bool => 2,
380 \\ f64, i32 => 3,
381 \\ else => 4,
382 \\ };
383 \\ _ = b;
384 \\}
385 , &.{
386 ":6:14: error: duplicate switch value",
387 ":4:9: note: previous value here",
388 });
389
390 // Ranges not allowed for some kinds of switches.
391 case.addError(
392 \\pub export fn main() c_int {
393 \\ const A: type = i32;
394 \\ const b: c_int = switch (A) {
395 \\ i32 => 1,
396 \\ bool => 2,
397 \\ f16...f64 => 3,
398 \\ else => 4,
399 \\ };
400 \\ _ = b;
401 \\}
402 , &.{
403 ":3:30: error: ranges not allowed when switching on type 'type'",
404 ":6:12: note: range here",
405 });
406
407 // Switch expression has unreachable else prong.
408 case.addError(
409 \\pub export fn main() c_int {
410 \\ var a: u2 = 0;
411 \\ const b: i32 = switch (a) {
412 \\ 0 => 10,
413 \\ 1 => 20,
414 \\ 2 => 30,
415 \\ 3 => 40,
416 \\ else => 50,
417 \\ };
418 \\ _ = b;
419 \\}
420 , &.{
421 ":8:14: error: unreachable else prong; all cases already handled",
422 });
423 }
424 //{
425 // var case = ctx.exeFromCompiledC("optionals", .{});
426
427 // // Simple while loop
428 // case.addCompareOutput(
429 // \\pub export fn main() c_int {
430 // \\ var count: c_int = 0;
431 // \\ var opt_ptr: ?*c_int = &count;
432 // \\ while (opt_ptr) |_| : (count += 1) {
433 // \\ if (count == 4) opt_ptr = null;
434 // \\ }
435 // \\ return count - 5;
436 // \\}
437 // , "");
438
439 // // Same with non pointer optionals
440 // case.addCompareOutput(
441 // \\pub export fn main() c_int {
442 // \\ var count: c_int = 0;
443 // \\ var opt_ptr: ?c_int = count;
444 // \\ while (opt_ptr) |_| : (count += 1) {
445 // \\ if (count == 4) opt_ptr = null;
446 // \\ }
447 // \\ return count - 5;
448 // \\}
449 // , "");
450 //}
451
452 {
453 var case = ctx.exeFromCompiledC("errors", .{});
454 case.addCompareOutput(
455 \\pub export fn main() c_int {
456 \\ var e1 = error.Foo;
457 \\ var e2 = error.Bar;
458 \\ assert(e1 != e2);
459 \\ assert(e1 == error.Foo);
460 \\ assert(e2 == error.Bar);
461 \\ return 0;
462 \\}
463 \\fn assert(b: bool) void {
464 \\ if (!b) unreachable;
465 \\}
466 , "");
467 case.addCompareOutput(
468 \\pub export fn main() c_int {
469 \\ var e: anyerror!c_int = 0;
470 \\ const i = e catch 69;
471 \\ return i;
472 \\}
473 , "");
474 case.addCompareOutput(
475 \\pub export fn main() c_int {
476 \\ var e: anyerror!c_int = error.Foo;
477 \\ const i = e catch 69;
478 \\ return 69 - i;
479 \\}
480 , "");
481 case.addCompareOutput(
482 \\const E = error{e};
483 \\const S = struct { x: u32 };
484 \\fn f() E!u32 {
485 \\ const x = (try @as(E!S, S{ .x = 1 })).x;
486 \\ return x;
487 \\}
488 \\pub export fn main() c_int {
489 \\ const x = f() catch @as(u32, 0);
490 \\ if (x != 1) unreachable;
491 \\ return 0;
492 \\}
493 , "");
494 }
495
496 {
497 var case = ctx.exeFromCompiledC("structs", .{});
498 case.addError(
499 \\const Point = struct { x: i32, y: i32 };
500 \\pub export fn main() c_int {
501 \\ var p: Point = .{
502 \\ .y = 24,
503 \\ .x = 12,
504 \\ .y = 24,
505 \\ };
506 \\ return p.y - p.x - p.x;
507 \\}
508 , &.{
509 ":6:10: error: duplicate field",
510 ":4:10: note: other field here",
511 });
512 case.addError(
513 \\const Point = struct { x: i32, y: i32 };
514 \\pub export fn main() c_int {
515 \\ var p: Point = .{
516 \\ .y = 24,
517 \\ };
518 \\ return p.y - p.x - p.x;
519 \\}
520 , &.{
521 ":3:21: error: missing struct field: x",
522 ":1:15: note: struct 'tmp.Point' declared here",
523 });
524 case.addError(
525 \\const Point = struct { x: i32, y: i32 };
526 \\pub export fn main() c_int {
527 \\ var p: Point = .{
528 \\ .x = 12,
529 \\ .y = 24,
530 \\ .z = 48,
531 \\ };
532 \\ return p.y - p.x - p.x;
533 \\}
534 , &.{
535 ":6:10: error: no field named 'z' in struct 'tmp.Point'",
536 ":1:15: note: struct declared here",
537 });
538 case.addCompareOutput(
539 \\const Point = struct { x: i32, y: i32 };
540 \\pub export fn main() c_int {
541 \\ var p: Point = .{
542 \\ .x = 12,
543 \\ .y = 24,
544 \\ };
545 \\ return p.y - p.x - p.x;
546 \\}
547 , "");
548 case.addCompareOutput(
549 \\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 };
550 \\pub export fn main() c_int {
551 \\ var p: Point = .{
552 \\ .x = 18,
553 \\ .y = 24,
554 \\ .z = 1,
555 \\ .a = 2,
556 \\ .b = 3,
557 \\ };
558 \\ return p.y - p.x - p.z - p.a - p.b;
559 \\}
560 , "");
561 }
562
563 {
564 var case = ctx.exeFromCompiledC("unions", .{});
565
566 case.addError(
567 \\const U = union {
568 \\ a: u32,
569 \\ b
570 \\};
571 , &.{
572 ":3:5: error: union field missing type",
573 });
574
575 case.addError(
576 \\const E = enum { a, b };
577 \\const U = union(E) {
578 \\ a: u32 = 1,
579 \\ b: f32 = 2,
580 \\};
581 , &.{
582 ":2:11: error: explicitly valued tagged union requires inferred enum tag type",
583 ":3:14: note: tag value specified here",
584 });
585
586 case.addError(
587 \\const U = union(enum) {
588 \\ a: u32 = 1,
589 \\ b: f32 = 2,
590 \\};
591 , &.{
592 ":1:11: error: explicitly valued tagged union missing integer tag type",
593 ":2:14: note: tag value specified here",
594 });
595 }
596
597 {
598 var case = ctx.exeFromCompiledC("enums", .{});
599
600 case.addError(
601 \\const E1 = packed enum { a, b, c };
602 \\const E2 = extern enum { a, b, c };
603 \\export fn foo() void {
604 \\ _ = E1.a;
605 \\}
606 \\export fn bar() void {
607 \\ _ = E2.a;
608 \\}
609 , &.{
610 ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
611 ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
612 });
613
614 // comptime and types are caught in AstGen.
615 case.addError(
616 \\const E1 = enum {
617 \\ a,
618 \\ comptime b,
619 \\ c,
620 \\};
621 \\const E2 = enum {
622 \\ a,
623 \\ b: i32,
624 \\ c,
625 \\};
626 \\export fn foo() void {
627 \\ _ = E1.a;
628 \\}
629 \\export fn bar() void {
630 \\ _ = E2.a;
631 \\}
632 , &.{
633 ":3:5: error: enum fields cannot be marked comptime",
634 ":8:8: error: enum fields do not have types",
635 ":6:12: note: consider 'union(enum)' here to make it a tagged union",
636 });
637
638 // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch
639 case.addCompareOutput(
640 \\const Number = enum { One, Two, Three };
641 \\
642 \\pub export fn main() c_int {
643 \\ var number1 = Number.One;
644 \\ var number2: Number = .Two;
645 \\ const number3 = @intToEnum(Number, 2);
646 \\ if (number1 == number2) return 1;
647 \\ if (number2 == number3) return 1;
648 \\ if (@enumToInt(number1) != 0) return 1;
649 \\ if (@enumToInt(number2) != 1) return 1;
650 \\ if (@enumToInt(number3) != 2) return 1;
651 \\ var x: Number = .Two;
652 \\ if (number2 != x) return 1;
653 \\ switch (x) {
654 \\ .One => return 1,
655 \\ .Two => return 0,
656 \\ number3 => return 2,
657 \\ }
658 \\}
659 , "");
660
661 // Specifying alignment is a parse error.
662 // This also tests going from a successful build to a parse error.
663 case.addError(
664 \\const E1 = enum {
665 \\ a,
666 \\ b align(4),
667 \\ c,
668 \\};
669 \\export fn foo() void {
670 \\ _ = E1.a;
671 \\}
672 , &.{
673 ":3:13: error: enum fields cannot be aligned",
674 });
675
676 // Redundant non-exhaustive enum mark.
677 // This also tests going from a parse error to an AstGen error.
678 case.addError(
679 \\const E1 = enum {
680 \\ a,
681 \\ _,
682 \\ b,
683 \\ c,
684 \\ _,
685 \\};
686 \\export fn foo() void {
687 \\ _ = E1.a;
688 \\}
689 , &.{
690 ":6:5: error: redundant non-exhaustive enum mark",
691 ":3:5: note: other mark here",
692 });
693
694 case.addError(
695 \\const E1 = enum {
696 \\ a,
697 \\ b,
698 \\ c,
699 \\ _ = 10,
700 \\};
701 \\export fn foo() void {
702 \\ _ = E1.a;
703 \\}
704 , &.{
705 ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
706 });
707
708 case.addError(
709 \\const E1 = enum { a, b, _ };
710 \\export fn foo() void {
711 \\ _ = E1.a;
712 \\}
713 , &.{
714 ":1:12: error: non-exhaustive enum missing integer tag type",
715 ":1:25: note: marked non-exhaustive here",
716 });
717
718 case.addError(
719 \\const E1 = enum { a, b, c, b, d };
720 \\pub export fn main() c_int {
721 \\ _ = E1.a;
722 \\}
723 , &.{
724 ":1:28: error: duplicate enum field 'b'",
725 ":1:22: note: other field here",
726 });
727
728 case.addError(
729 \\pub export fn main() c_int {
730 \\ const a = true;
731 \\ _ = @enumToInt(a);
732 \\}
733 , &.{
734 ":3:20: error: expected enum or tagged union, found 'bool'",
735 });
736
737 case.addError(
738 \\pub export fn main() c_int {
739 \\ const a = 1;
740 \\ _ = @intToEnum(bool, a);
741 \\}
742 , &.{
743 ":3:20: error: expected enum, found 'bool'",
744 });
745
746 case.addError(
747 \\const E = enum { a, b, c };
748 \\pub export fn main() c_int {
749 \\ _ = @intToEnum(E, 3);
750 \\}
751 , &.{
752 ":3:9: error: enum 'tmp.E' has no tag with value '3'",
753 ":1:11: note: enum declared here",
754 });
755
756 case.addError(
757 \\const E = enum { a, b, c };
758 \\pub export fn main() c_int {
759 \\ var x: E = .a;
760 \\ switch (x) {
761 \\ .a => {},
762 \\ .c => {},
763 \\ }
764 \\}
765 , &.{
766 ":4:5: error: switch must handle all possibilities",
767 ":1:21: note: unhandled enumeration value: 'b'",
768 ":1:11: note: enum 'tmp.E' declared here",
769 });
770
771 case.addError(
772 \\const E = enum { a, b, c };
773 \\pub export fn main() c_int {
774 \\ var x: E = .a;
775 \\ switch (x) {
776 \\ .a => {},
777 \\ .b => {},
778 \\ .b => {},
779 \\ .c => {},
780 \\ }
781 \\}
782 , &.{
783 ":7:10: error: duplicate switch value",
784 ":6:10: note: previous value here",
785 });
786
787 case.addError(
788 \\const E = enum { a, b, c };
789 \\pub export fn main() c_int {
790 \\ var x: E = .a;
791 \\ switch (x) {
792 \\ .a => {},
793 \\ .b => {},
794 \\ .c => {},
795 \\ else => {},
796 \\ }
797 \\}
798 , &.{
799 ":8:14: error: unreachable else prong; all cases already handled",
800 });
801
802 case.addError(
803 \\const E = enum { a, b, c };
804 \\pub export fn main() c_int {
805 \\ var x: E = .a;
806 \\ switch (x) {
807 \\ .a => {},
808 \\ .b => {},
809 \\ _ => {},
810 \\ }
811 \\}
812 , &.{
813 ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums",
814 ":7:11: note: '_' prong here",
815 });
816
817 case.addError(
818 \\const E = enum { a, b, c };
819 \\pub export fn main() c_int {
820 \\ _ = E.d;
821 \\}
822 , &.{
823 ":3:11: error: enum 'tmp.E' has no member named 'd'",
824 ":1:11: note: enum declared here",
825 });
826
827 case.addError(
828 \\const E = enum { a, b, c };
829 \\pub export fn main() c_int {
830 \\ var x: E = .d;
831 \\ _ = x;
832 \\}
833 , &.{
834 ":3:17: error: no field named 'd' in enum 'tmp.E'",
835 ":1:11: note: enum declared here",
836 });
837 }
838
839 {
840 var case = ctx.exeFromCompiledC("shift right and left", .{});
841 case.addCompareOutput(
842 \\pub export fn main() c_int {
843 \\ var i: u32 = 16;
844 \\ assert(i >> 1, 8);
845 \\ return 0;
846 \\}
847 \\fn assert(a: u32, b: u32) void {
848 \\ if (a != b) unreachable;
849 \\}
850 , "");
851
852 case.addCompareOutput(
853 \\pub export fn main() c_int {
854 \\ var i: u32 = 16;
855 \\ assert(i << 1, 32);
856 \\ return 0;
857 \\}
858 \\fn assert(a: u32, b: u32) void {
859 \\ if (a != b) unreachable;
860 \\}
861 , "");
862 }
863
864 {
865 var case = ctx.exeFromCompiledC("inferred error sets", .{});
866
867 case.addCompareOutput(
868 \\pub export fn main() c_int {
869 \\ if (foo()) |_| {
870 \\ @panic("test fail");
871 \\ } else |err| {
872 \\ if (err != error.ItBroke) {
873 \\ @panic("test fail");
874 \\ }
875 \\ }
876 \\ return 0;
877 \\}
878 \\fn foo() !void {
879 \\ return error.ItBroke;
880 \\}
881 , "");
882 }
883
884 {
885 // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64)
886 var case = ctx.exeFromCompiledC("add and sub wrapping operations", .{});
887 case.addCompareOutput(
888 \\pub export fn main() c_int {
889 \\ // Addition
890 \\ if (!add_u3(1, 1, 2)) return 1;
891 \\ if (!add_u3(7, 1, 0)) return 1;
892 \\ if (!add_i3(1, 1, 2)) return 1;
893 \\ if (!add_i3(3, 2, -3)) return 1;
894 \\ if (!add_i3(-3, -2, 3)) return 1;
895 \\ if (!add_c_int(1, 1, 2)) return 1;
896 \\ // TODO enable these when stage2 supports std.math.maxInt
897 \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1;
898 \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1;
899 \\
900 \\ // Subtraction
901 \\ if (!sub_u3(2, 1, 1)) return 1;
902 \\ if (!sub_u3(0, 1, 7)) return 1;
903 \\ if (!sub_i3(2, 1, 1)) return 1;
904 \\ if (!sub_i3(3, -2, -3)) return 1;
905 \\ if (!sub_i3(-3, 2, 3)) return 1;
906 \\ if (!sub_c_int(2, 1, 1)) return 1;
907 \\ // TODO enable these when stage2 supports std.math.maxInt
908 \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1;
909 \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1;
910 \\
911 \\ return 0;
912 \\}
913 \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool {
914 \\ return expected == lhs +% rhs;
915 \\}
916 \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool {
917 \\ return expected == lhs +% rhs;
918 \\}
919 \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
920 \\ return expected == lhs +% rhs;
921 \\}
922 \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool {
923 \\ return expected == lhs -% rhs;
924 \\}
925 \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool {
926 \\ return expected == lhs -% rhs;
927 \\}
928 \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
929 \\ return expected == lhs -% rhs;
930 \\}
931 , "");
932 }
933
934 {
935 var case = ctx.exeFromCompiledC("rem", linux_x64);
936 case.addCompareOutput(
937 \\fn assert(ok: bool) void {
938 \\ if (!ok) unreachable;
939 \\}
940 \\fn rem(lhs: i32, rhs: i32, expected: i32) bool {
941 \\ return @rem(lhs, rhs) == expected;
942 \\}
943 \\pub export fn main() c_int {
944 \\ assert(rem(-5, 3, -2));
945 \\ assert(rem(5, 3, 2));
946 \\ return 0;
947 \\}
948 , "");
949 }
950}
test/compile_errors.zig+25-199
......@@ -1,146 +1,10 @@
11const std = @import("std");
22const builtin = @import("builtin");
3const TestContext = @import("../src/test.zig").TestContext;
4
5pub fn addCases(ctx: *TestContext) !void {
6 {
7 const case = ctx.obj("wrong same named struct", .{});
8 case.backend = .stage1;
9
10 case.addSourceFile("a.zig",
11 \\pub const Foo = struct {
12 \\ x: i32,
13 \\};
14 );
15
16 case.addSourceFile("b.zig",
17 \\pub const Foo = struct {
18 \\ z: f64,
19 \\};
20 );
21
22 case.addError(
23 \\const a = @import("a.zig");
24 \\const b = @import("b.zig");
25 \\
26 \\export fn entry() void {
27 \\ var a1: a.Foo = undefined;
28 \\ bar(&a1);
29 \\}
30 \\
31 \\fn bar(x: *b.Foo) void {_ = x;}
32 , &[_][]const u8{
33 "tmp.zig:6:10: error: expected type '*b.Foo', found '*a.Foo'",
34 "tmp.zig:6:10: note: pointer type child 'a.Foo' cannot cast into pointer type child 'b.Foo'",
35 "a.zig:1:17: note: a.Foo declared here",
36 "b.zig:1:17: note: b.Foo declared here",
37 });
38 }
39
40 {
41 const case = ctx.obj("multiple files with private function error", .{});
42 case.backend = .stage1;
43
44 case.addSourceFile("foo.zig",
45 \\fn privateFunction() void { }
46 );
47
48 case.addError(
49 \\const foo = @import("foo.zig",);
50 \\
51 \\export fn callPrivFunction() void {
52 \\ foo.privateFunction();
53 \\}
54 , &[_][]const u8{
55 "tmp.zig:4:8: error: 'privateFunction' is private",
56 "foo.zig:1:1: note: declared here",
57 });
58 }
59
60 {
61 const case = ctx.obj("multiple files with private member instance function (canonical invocation) error", .{});
62 case.backend = .stage1;
63
64 case.addSourceFile("foo.zig",
65 \\pub const Foo = struct {
66 \\ fn privateFunction(self: *Foo) void { _ = self; }
67 \\};
68 );
69
70 case.addError(
71 \\const Foo = @import("foo.zig",).Foo;
72 \\
73 \\export fn callPrivFunction() void {
74 \\ var foo = Foo{};
75 \\ Foo.privateFunction(foo);
76 \\}
77 , &[_][]const u8{
78 "tmp.zig:5:8: error: 'privateFunction' is private",
79 "foo.zig:2:5: note: declared here",
80 });
81 }
82
83 {
84 const case = ctx.obj("multiple files with private member instance function error", .{});
85 case.backend = .stage1;
86
87 case.addSourceFile("foo.zig",
88 \\pub const Foo = struct {
89 \\ fn privateFunction(self: *Foo) void { _ = self; }
90 \\};
91 );
92
93 case.addError(
94 \\const Foo = @import("foo.zig",).Foo;
95 \\
96 \\export fn callPrivFunction() void {
97 \\ var foo = Foo{};
98 \\ foo.privateFunction();
99 \\}
100 , &[_][]const u8{
101 "tmp.zig:5:8: error: 'privateFunction' is private",
102 "foo.zig:2:5: note: declared here",
103 });
104 }
105
106 {
107 const case = ctx.obj("export collision", .{});
108 case.backend = .stage1;
109
110 case.addSourceFile("foo.zig",
111 \\export fn bar() void {}
112 \\pub const baz = 1234;
113 );
114
115 case.addError(
116 \\const foo = @import("foo.zig",);
117 \\
118 \\export fn bar() usize {
119 \\ return foo.baz;
120 \\}
121 , &[_][]const u8{
122 "foo.zig:1:1: error: exported symbol collision: 'bar'",
123 "tmp.zig:3:1: note: other symbol here",
124 });
125 }
126
127 ctx.objErrStage1("non-printable invalid character", "\xff\xfe" ++
128 "fn foo() bool {\r\n" ++
129 " return true;\r\n" ++
130 "}\r\n", &[_][]const u8{
131 "tmp.zig:1:1: error: expected test, comptime, var decl, or container field, found 'invalid bytes'",
132 "tmp.zig:1:1: note: invalid byte: '\\xff'",
133 });
134
135 ctx.objErrStage1("non-printable invalid character with escape alternative", "fn foo() bool {\n" ++
136 "\treturn true;\n" ++
137 "}\n", &[_][]const u8{
138 "tmp.zig:2:1: error: invalid character: '\\t'",
139 });
3const Cases = @import("src/Cases.zig");
1404
5pub fn addCases(ctx: *Cases) !void {
1416 {
1427 const case = ctx.obj("multiline error messages", .{});
143 case.backend = .stage2;
1448
1459 case.addError(
14610 \\comptime {
......@@ -176,7 +40,6 @@ pub fn addCases(ctx: *TestContext) !void {
17640
17741 {
17842 const case = ctx.obj("isolated carriage return in multiline string literal", .{});
179 case.backend = .stage2;
18043
18144 case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{
18245 ":1:19: error: expected ';' after declaration",
......@@ -195,16 +58,6 @@ pub fn addCases(ctx: *TestContext) !void {
19558
19659 {
19760 const case = ctx.obj("argument causes error", .{});
198 case.backend = .stage2;
199
200 case.addSourceFile("b.zig",
201 \\pub const ElfDynLib = struct {
202 \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T {
203 \\ _ = self;
204 \\ return undefined;
205 \\ }
206 \\};
207 );
20861
20962 case.addError(
21063 \\pub export fn entry() void {
......@@ -216,15 +69,18 @@ pub fn addCases(ctx: *TestContext) !void {
21669 ":3:12: note: argument to function being called at comptime must be comptime-known",
21770 ":2:55: note: expression is evaluated at comptime because the generic function was instantiated with a comptime-only return type",
21871 });
72 case.addSourceFile("b.zig",
73 \\pub const ElfDynLib = struct {
74 \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T {
75 \\ _ = self;
76 \\ return undefined;
77 \\ }
78 \\};
79 );
21980 }
22081
22182 {
22283 const case = ctx.obj("astgen failure in file struct", .{});
223 case.backend = .stage2;
224
225 case.addSourceFile("b.zig",
226 \\+
227 );
22884
22985 case.addError(
23086 \\pub export fn entry() void {
......@@ -233,21 +89,13 @@ pub fn addCases(ctx: *TestContext) !void {
23389 , &[_][]const u8{
23490 ":1:1: error: expected type expression, found '+'",
23591 });
92 case.addSourceFile("b.zig",
93 \\+
94 );
23695 }
23796
23897 {
23998 const case = ctx.obj("invalid store to comptime field", .{});
240 case.backend = .stage2;
241
242 case.addSourceFile("a.zig",
243 \\pub const S = struct {
244 \\ comptime foo: u32 = 1,
245 \\ bar: u32,
246 \\ pub fn foo(x: @This()) void {
247 \\ _ = x;
248 \\ }
249 \\};
250 );
25199
252100 case.addError(
253101 \\const a = @import("a.zig");
......@@ -259,44 +107,19 @@ pub fn addCases(ctx: *TestContext) !void {
259107 ":4:23: error: value stored in comptime field does not match the default value of the field",
260108 ":2:25: note: default value set here",
261109 });
110 case.addSourceFile("a.zig",
111 \\pub const S = struct {
112 \\ comptime foo: u32 = 1,
113 \\ bar: u32,
114 \\ pub fn foo(x: @This()) void {
115 \\ _ = x;
116 \\ }
117 \\};
118 );
262119 }
263120
264 // TODO test this in stage2, but we won't even try in stage1
265 //ctx.objErrStage1("inline fn calls itself indirectly",
266 // \\export fn foo() void {
267 // \\ bar();
268 // \\}
269 // \\fn bar() callconv(.Inline) void {
270 // \\ baz();
271 // \\ quux();
272 // \\}
273 // \\fn baz() callconv(.Inline) void {
274 // \\ bar();
275 // \\ quux();
276 // \\}
277 // \\extern fn quux() void;
278 //, &[_][]const u8{
279 // "tmp.zig:4:1: error: unable to inline function",
280 //});
281
282 //ctx.objErrStage1("save reference to inline function",
283 // \\export fn foo() void {
284 // \\ quux(@ptrToInt(bar));
285 // \\}
286 // \\fn bar() callconv(.Inline) void { }
287 // \\extern fn quux(usize) void;
288 //, &[_][]const u8{
289 // "tmp.zig:4:1: error: unable to inline function",
290 //});
291
292121 {
293122 const case = ctx.obj("file in multiple modules", .{});
294 case.backend = .stage2;
295
296 case.addSourceFile("foo.zig",
297 \\const dummy = 0;
298 );
299
300123 case.addDepModule("foo", "foo.zig");
301124
302125 case.addError(
......@@ -309,5 +132,8 @@ pub fn addCases(ctx: *TestContext) !void {
309132 ":1:1: note: root of module root.foo",
310133 ":3:17: note: imported from module root",
311134 });
135 case.addSourceFile("foo.zig",
136 \\const dummy = 0;
137 );
312138 }
313139}
test/link/macho/dead_strip/build.zig+2-2
......@@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void {
1313 // Without -dead_strip, we expect `iAmUnused` symbol present
1414 const exe = createScenario(b, optimize, target, "no-gc");
1515
16 const check = exe.checkObject(.macho);
16 const check = exe.checkObject();
1717 check.checkInSymtab();
1818 check.checkNext("{*} (__TEXT,__text) external _iAmUnused");
1919
......@@ -27,7 +27,7 @@ pub fn build(b: *std.Build) void {
2727 const exe = createScenario(b, optimize, target, "yes-gc");
2828 exe.link_gc_sections = true;
2929
30 const check = exe.checkObject(.macho);
30 const check = exe.checkObject();
3131 check.checkInSymtab();
3232 check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused");
3333
test/link/macho/dead_strip_dylibs/build.zig+1-1
......@@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1818 // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
1919 const exe = createScenario(b, optimize, "no-dead-strip");
2020
21 const check = exe.checkObject(.macho);
21 const check = exe.checkObject();
2222 check.checkStart("cmd LOAD_DYLIB");
2323 check.checkNext("name {*}Cocoa");
2424
test/link/macho/dylib/build.zig+2-2
......@@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2424 dylib.addCSourceFile("a.c", &.{});
2525 dylib.linkLibC();
2626
27 const check_dylib = dylib.checkObject(.macho);
27 const check_dylib = dylib.checkObject();
2828 check_dylib.checkStart("cmd ID_DYLIB");
2929 check_dylib.checkNext("name @rpath/liba.dylib");
3030 check_dylib.checkNext("timestamp 2");
......@@ -44,7 +44,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
4444 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
4545 exe.linkLibC();
4646
47 const check_exe = exe.checkObject(.macho);
47 const check_exe = exe.checkObject();
4848 check_exe.checkStart("cmd LOAD_DYLIB");
4949 check_exe.checkNext("name @rpath/liba.dylib");
5050 check_exe.checkNext("timestamp 2");
test/link/macho/entry/build.zig+1-1
......@@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2222 exe.linkLibC();
2323 exe.entry_symbol_name = "_non_main";
2424
25 const check_exe = exe.checkObject(.macho);
25 const check_exe = exe.checkObject();
2626
2727 check_exe.checkStart("segname __TEXT");
2828 check_exe.checkNext("vmaddr {vmaddr}");
test/link/macho/headerpad/build.zig+4-4
......@@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2020 const exe = simpleExe(b, optimize);
2121 exe.headerpad_max_install_names = true;
2222
23 const check = exe.checkObject(.macho);
23 const check = exe.checkObject();
2424 check.checkStart("sectname __text");
2525 check.checkNext("offset {offset}");
2626
......@@ -45,7 +45,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
4545 const exe = simpleExe(b, optimize);
4646 exe.headerpad_size = 0x10000;
4747
48 const check = exe.checkObject(.macho);
48 const check = exe.checkObject();
4949 check.checkStart("sectname __text");
5050 check.checkNext("offset {offset}");
5151 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
......@@ -62,7 +62,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
6262 exe.headerpad_max_install_names = true;
6363 exe.headerpad_size = 0x10000;
6464
65 const check = exe.checkObject(.macho);
65 const check = exe.checkObject();
6666 check.checkStart("sectname __text");
6767 check.checkNext("offset {offset}");
6868 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
......@@ -79,7 +79,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
7979 exe.headerpad_size = 0x1000;
8080 exe.headerpad_max_install_names = true;
8181
82 const check = exe.checkObject(.macho);
82 const check = exe.checkObject();
8383 check.checkStart("sectname __text");
8484 check.checkNext("offset {offset}");
8585
test/link/macho/linksection/build.zig+1-1
......@@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2222 .target = target,
2323 });
2424
25 const check = obj.checkObject(.macho);
25 const check = obj.checkObject();
2626
2727 check.checkInSymtab();
2828 check.checkNext("{*} (__DATA,__TestGlobal) external _test_global");
test/link/macho/needed_framework/build.zig+1-1
......@@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2525 exe.linkFrameworkNeeded("Cocoa");
2626 exe.dead_strip_dylibs = true;
2727
28 const check = exe.checkObject(.macho);
28 const check = exe.checkObject();
2929 check.checkStart("cmd LOAD_DYLIB");
3030 check.checkNext("name {*}Cocoa");
3131 test_step.dependOn(&check.step);
test/link/macho/needed_library/build.zig+1-1
......@@ -38,7 +38,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3838 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
3939 exe.dead_strip_dylibs = true;
4040
41 const check = exe.checkObject(.macho);
41 const check = exe.checkObject();
4242 check.checkStart("cmd LOAD_DYLIB");
4343 check.checkNext("name @rpath/liba.dylib");
4444
test/link/macho/pagezero/build.zig+2-2
......@@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void {
1919 exe.linkLibC();
2020 exe.pagezero_size = 0x4000;
2121
22 const check = exe.checkObject(.macho);
22 const check = exe.checkObject();
2323 check.checkStart("LC 0");
2424 check.checkNext("segname __PAGEZERO");
2525 check.checkNext("vmaddr 0");
......@@ -41,7 +41,7 @@ pub fn build(b: *std.Build) void {
4141 exe.linkLibC();
4242 exe.pagezero_size = 0;
4343
44 const check = exe.checkObject(.macho);
44 const check = exe.checkObject();
4545 check.checkStart("LC 0");
4646 check.checkNext("segname __TEXT");
4747 check.checkNext("vmaddr 0");
test/link/macho/search_strategy/build.zig+1-1
......@@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2020 const exe = createScenario(b, optimize, target, "search_dylibs_first");
2121 exe.search_strategy = .dylibs_first;
2222
23 const check = exe.checkObject(.macho);
23 const check = exe.checkObject();
2424 check.checkStart("cmd LOAD_DYLIB");
2525 check.checkNext("name @rpath/libsearch_dylibs_first.dylib");
2626
test/link/macho/stack_size/build.zig+1-1
......@@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2424 exe.linkLibC();
2525 exe.stack_size = 0x100000000;
2626
27 const check_exe = exe.checkObject(.macho);
27 const check_exe = exe.checkObject();
2828 check_exe.checkStart("cmd MAIN");
2929 check_exe.checkNext("stacksize 100000000");
3030
test/link/macho/strict_validation/build.zig+1-1
......@@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2424 });
2525 exe.linkLibC();
2626
27 const check_exe = exe.checkObject(.macho);
27 const check_exe = exe.checkObject();
2828
2929 check_exe.checkStart("cmd SEGMENT_64");
3030 check_exe.checkNext("segname __LINKEDIT");
test/link/macho/unwind_info/build.zig+1-1
......@@ -31,7 +31,7 @@ fn testUnwindInfo(
3131 const exe = createScenario(b, optimize, target, name);
3232 exe.link_gc_sections = dead_strip;
3333
34 const check = exe.checkObject(.macho);
34 const check = exe.checkObject();
3535 check.checkStart("segname __TEXT");
3636 check.checkNext("sectname __gcc_except_tab");
3737 check.checkNext("sectname __unwind_info");
test/link/macho/weak_framework/build.zig+1-1
......@@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2222 exe.linkLibC();
2323 exe.linkFrameworkWeak("Cocoa");
2424
25 const check = exe.checkObject(.macho);
25 const check = exe.checkObject();
2626 check.checkStart("cmd LOAD_WEAK_DYLIB");
2727 check.checkNext("name {*}Cocoa");
2828 test_step.dependOn(&check.step);
test/link/macho/weak_library/build.zig+1-1
......@@ -36,7 +36,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3636 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
3737 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
3838
39 const check = exe.checkObject(.macho);
39 const check = exe.checkObject();
4040 check.checkStart("cmd LOAD_WEAK_DYLIB");
4141 check.checkNext("name @rpath/liba.dylib");
4242
test/link/wasm/archive/build.zig+1-1
......@@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2525 lib.use_lld = false;
2626 lib.strip = false;
2727
28 const check = lib.checkObject(.wasm);
28 const check = lib.checkObject();
2929 check.checkStart("Section custom");
3030 check.checkNext("name __truncsfhf2"); // Ensure it was imported and resolved
3131
test/link/wasm/basic-features/build.zig+1-1
......@@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void {
1919 lib.use_lld = false;
2020
2121 // Verify the result contains the features explicitly set on the target for the library.
22 const check = lib.checkObject(.wasm);
22 const check = lib.checkObject();
2323 check.checkStart("name target_features");
2424 check.checkNext("features 1");
2525 check.checkNext("+ atomics");
test/link/wasm/bss/build.zig+1-1
......@@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void {
1919 lib.import_memory = true;
2020 lib.install();
2121
22 const check_lib = lib.checkObject(.wasm);
22 const check_lib = lib.checkObject();
2323
2424 // since we import memory, make sure it exists with the correct naming
2525 check_lib.checkStart("Section import");
test/link/wasm/export-data/build.zig+1-1
......@@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void {
1919 lib.export_symbol_names = &.{ "foo", "bar" };
2020 lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse
2121
22 const check_lib = lib.checkObject(.wasm);
22 const check_lib = lib.checkObject();
2323
2424 check_lib.checkStart("Section global");
2525 check_lib.checkNext("entries 3");
test/link/wasm/export/build.zig+3-3
......@@ -42,19 +42,19 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
4242 force_export.use_llvm = false;
4343 force_export.use_lld = false;
4444
45 const check_no_export = no_export.checkObject(.wasm);
45 const check_no_export = no_export.checkObject();
4646 check_no_export.checkStart("Section export");
4747 check_no_export.checkNext("entries 1");
4848 check_no_export.checkNext("name memory");
4949 check_no_export.checkNext("kind memory");
5050
51 const check_dynamic_export = dynamic_export.checkObject(.wasm);
51 const check_dynamic_export = dynamic_export.checkObject();
5252 check_dynamic_export.checkStart("Section export");
5353 check_dynamic_export.checkNext("entries 2");
5454 check_dynamic_export.checkNext("name foo");
5555 check_dynamic_export.checkNext("kind function");
5656
57 const check_force_export = force_export.checkObject(.wasm);
57 const check_force_export = force_export.checkObject();
5858 check_force_export.checkStart("Section export");
5959 check_force_export.checkNext("entries 2");
6060 check_force_export.checkNext("name foo");
test/link/wasm/extern-mangle/build.zig+1-1
......@@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2020 lib.import_symbols = true; // import `a` and `b`
2121 lib.rdynamic = true; // export `foo`
2222
23 const check_lib = lib.checkObject(.wasm);
23 const check_lib = lib.checkObject();
2424 check_lib.checkStart("Section import");
2525 check_lib.checkNext("entries 2"); // a.hello & b.hello
2626 check_lib.checkNext("module a");
test/link/wasm/function-table/build.zig+3-3
......@@ -42,9 +42,9 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
4242 regular_table.use_llvm = false;
4343 regular_table.use_lld = false;
4444
45 const check_import = import_table.checkObject(.wasm);
46 const check_export = export_table.checkObject(.wasm);
47 const check_regular = regular_table.checkObject(.wasm);
45 const check_import = import_table.checkObject();
46 const check_export = export_table.checkObject();
47 const check_regular = regular_table.checkObject();
4848
4949 check_import.checkStart("Section import");
5050 check_import.checkNext("entries 1");
test/link/wasm/infer-features/build.zig+1-1
......@@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void {
3232 lib.addObject(c_obj);
3333
3434 // Verify the result contains the features from the C Object file.
35 const check = lib.checkObject(.wasm);
35 const check = lib.checkObject();
3636 check.checkStart("name target_features");
3737 check.checkNext("features 7");
3838 check.checkNext("+ atomics");
test/link/wasm/producers/build.zig+1-1
......@@ -27,7 +27,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2727
2828 const version_fmt = "version " ++ builtin.zig_version_string;
2929
30 const check_lib = lib.checkObject(.wasm);
30 const check_lib = lib.checkObject();
3131 check_lib.checkStart("name producers");
3232 check_lib.checkNext("fields 2");
3333 check_lib.checkNext("field_name language");
test/link/wasm/segments/build.zig+1-1
......@@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2424 lib.strip = false;
2525 lib.install();
2626
27 const check_lib = lib.checkObject(.wasm);
27 const check_lib = lib.checkObject();
2828 check_lib.checkStart("Section data");
2929 check_lib.checkNext("entries 2"); // rodata & data, no bss because we're exporting memory
3030
test/link/wasm/stack_pointer/build.zig+1-1
......@@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2525 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
2626 lib.install();
2727
28 const check_lib = lib.checkObject(.wasm);
28 const check_lib = lib.checkObject();
2929
3030 // ensure global exists and its initial value is equal to explitic stack size
3131 check_lib.checkStart("Section global");
test/link/wasm/type/build.zig+1-1
......@@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2424 lib.strip = false;
2525 lib.install();
2626
27 const check_lib = lib.checkObject(.wasm);
27 const check_lib = lib.checkObject();
2828 check_lib.checkStart("Section type");
2929 // only 2 entries, although we have 3 functions.
3030 // This is to test functions with the same function signature
test/nvptx.zig created+106
......@@ -0,0 +1,106 @@
1const std = @import("std");
2const Cases = @import("src/Cases.zig");
3
4pub fn addCases(ctx: *Cases) !void {
5 {
6 var case = addPtx(ctx, "simple addition and subtraction");
7
8 case.addCompile(
9 \\fn add(a: i32, b: i32) i32 {
10 \\ return a + b;
11 \\}
12 \\
13 \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void {
14 \\ const x = add(a, 7);
15 \\ var y = add(2, 0);
16 \\ y -= x;
17 \\ out.* = y;
18 \\}
19 );
20 }
21
22 {
23 var case = addPtx(ctx, "read special registers");
24
25 case.addCompile(
26 \\fn threadIdX() u32 {
27 \\ return asm ("mov.u32 \t%[r], %tid.x;"
28 \\ : [r] "=r" (-> u32),
29 \\ );
30 \\}
31 \\
32 \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void {
33 \\ const i = threadIdX();
34 \\ out[i] = a[i] + 7;
35 \\}
36 );
37 }
38
39 {
40 var case = addPtx(ctx, "address spaces");
41
42 case.addCompile(
43 \\var x: i32 addrspace(.global) = 0;
44 \\
45 \\pub export fn increment(out: *i32) callconv(.PtxKernel) void {
46 \\ x += 1;
47 \\ out.* = x;
48 \\}
49 );
50 }
51
52 {
53 var case = addPtx(ctx, "reduce in shared mem");
54 case.addCompile(
55 \\fn threadIdX() u32 {
56 \\ return asm ("mov.u32 \t%[r], %tid.x;"
57 \\ : [r] "=r" (-> u32),
58 \\ );
59 \\}
60 \\
61 \\ var _sdata: [1024]f32 addrspace(.shared) = undefined;
62 \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void {
63 \\ var sdata = @addrSpaceCast(.generic, &_sdata);
64 \\ const tid: u32 = threadIdX();
65 \\ var sum = d_x[tid];
66 \\ sdata[tid] = sum;
67 \\ asm volatile ("bar.sync \t0;");
68 \\ var s: u32 = 512;
69 \\ while (s > 0) : (s = s >> 1) {
70 \\ if (tid < s) {
71 \\ sum += sdata[tid + s];
72 \\ sdata[tid] = sum;
73 \\ }
74 \\ asm volatile ("bar.sync \t0;");
75 \\ }
76 \\
77 \\ if (tid == 0) {
78 \\ out.* = sum;
79 \\ }
80 \\ }
81 );
82 }
83}
84
85const nvptx_target = std.zig.CrossTarget{
86 .cpu_arch = .nvptx64,
87 .os_tag = .cuda,
88};
89
90pub fn addPtx(
91 ctx: *Cases,
92 name: []const u8,
93) *Cases.Case {
94 ctx.cases.append(.{
95 .name = name,
96 .target = nvptx_target,
97 .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator),
98 .output_mode = .Obj,
99 .deps = std.ArrayList(Cases.DepModule).init(ctx.cases.allocator),
100 .link_libc = false,
101 .backend = .llvm,
102 // Bug in Debug mode
103 .optimize_mode = .ReleaseSafe,
104 }) catch @panic("out of memory");
105 return &ctx.cases.items[ctx.cases.items.len - 1];
106}
test/src/Cases.zig created+1587
......@@ -0,0 +1,1587 @@
1gpa: Allocator,
2arena: Allocator,
3cases: std.ArrayList(Case),
4incremental_cases: std.ArrayList(IncrementalCase),
5
6pub const IncrementalCase = struct {
7 base_path: []const u8,
8};
9
10pub const Update = struct {
11 /// The input to the current update. We simulate an incremental update
12 /// with the file's contents changed to this value each update.
13 ///
14 /// This value can change entirely between updates, which would be akin
15 /// to deleting the source file and creating a new one from scratch; or
16 /// you can keep it mostly consistent, with small changes, testing the
17 /// effects of the incremental compilation.
18 files: std.ArrayList(File),
19 /// This is a description of what happens with the update, for debugging
20 /// purposes.
21 name: []const u8,
22 case: union(enum) {
23 /// Check that it compiles with no errors.
24 Compile: void,
25 /// Check the main binary output file against an expected set of bytes.
26 /// This is most useful with, for example, `-ofmt=c`.
27 CompareObjectFile: []const u8,
28 /// An error update attempts to compile bad code, and ensures that it
29 /// fails to compile, and for the expected reasons.
30 /// A slice containing the expected stderr template, which
31 /// gets some values substituted.
32 Error: []const []const u8,
33 /// An execution update compiles and runs the input, testing the
34 /// stdout against the expected results
35 /// This is a slice containing the expected message.
36 Execution: []const u8,
37 /// A header update compiles the input with the equivalent of
38 /// `-femit-h` and tests the produced header against the
39 /// expected result
40 Header: []const u8,
41 },
42
43 pub fn addSourceFile(update: *Update, name: []const u8, src: [:0]const u8) void {
44 update.files.append(.{ .path = name, .src = src }) catch @panic("out of memory");
45 }
46};
47
48pub const File = struct {
49 src: [:0]const u8,
50 path: []const u8,
51};
52
53pub const DepModule = struct {
54 name: []const u8,
55 path: []const u8,
56};
57
58pub const Backend = enum {
59 stage1,
60 stage2,
61 llvm,
62};
63
64/// A `Case` consists of a list of `Update`. The same `Compilation` is used for each
65/// update, so each update's source is treated as a single file being
66/// updated by the test harness and incrementally compiled.
67pub const Case = struct {
68 /// The name of the test case. This is shown if a test fails, and
69 /// otherwise ignored.
70 name: []const u8,
71 /// The platform the test targets. For non-native platforms, an emulator
72 /// such as QEMU is required for tests to complete.
73 target: CrossTarget,
74 /// In order to be able to run e.g. Execution updates, this must be set
75 /// to Executable.
76 output_mode: std.builtin.OutputMode,
77 optimize_mode: std.builtin.Mode = .Debug,
78 updates: std.ArrayList(Update),
79 emit_h: bool = false,
80 is_test: bool = false,
81 expect_exact: bool = false,
82 backend: Backend = .stage2,
83 link_libc: bool = false,
84
85 deps: std.ArrayList(DepModule),
86
87 pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void {
88 const update = &case.updates.items[case.updates.items.len - 1];
89 update.files.append(.{ .path = name, .src = src }) catch @panic("OOM");
90 }
91
92 pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void {
93 case.deps.append(.{
94 .name = name,
95 .path = path,
96 }) catch @panic("out of memory");
97 }
98
99 /// Adds a subcase in which the module is updated with `src`, compiled,
100 /// run, and the output is tested against `result`.
101 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
102 self.updates.append(.{
103 .files = std.ArrayList(File).init(self.updates.allocator),
104 .name = "update",
105 .case = .{ .Execution = result },
106 }) catch @panic("out of memory");
107 addSourceFile(self, "tmp.zig", src);
108 }
109
110 pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {
111 return self.addErrorNamed("update", src, errors);
112 }
113
114 /// Adds a subcase in which the module is updated with `src`, which
115 /// should contain invalid input, and ensures that compilation fails
116 /// for the expected reasons, given in sequential order in `errors` in
117 /// the form `:line:column: error: message`.
118 pub fn addErrorNamed(
119 self: *Case,
120 name: []const u8,
121 src: [:0]const u8,
122 errors: []const []const u8,
123 ) void {
124 assert(errors.len != 0);
125 self.updates.append(.{
126 .files = std.ArrayList(File).init(self.updates.allocator),
127 .name = name,
128 .case = .{ .Error = errors },
129 }) catch @panic("out of memory");
130 addSourceFile(self, "tmp.zig", src);
131 }
132
133 /// Adds a subcase in which the module is updated with `src`, and
134 /// asserts that it compiles without issue
135 pub fn addCompile(self: *Case, src: [:0]const u8) void {
136 self.updates.append(.{
137 .files = std.ArrayList(File).init(self.updates.allocator),
138 .name = "compile",
139 .case = .{ .Compile = {} },
140 }) catch @panic("out of memory");
141 addSourceFile(self, "tmp.zig", src);
142 }
143};
144
145pub fn addExe(
146 ctx: *Cases,
147 name: []const u8,
148 target: CrossTarget,
149) *Case {
150 ctx.cases.append(Case{
151 .name = name,
152 .target = target,
153 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
154 .output_mode = .Exe,
155 .deps = std.ArrayList(DepModule).init(ctx.arena),
156 }) catch @panic("out of memory");
157 return &ctx.cases.items[ctx.cases.items.len - 1];
158}
159
160/// Adds a test case for Zig input, producing an executable
161pub fn exe(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
162 return ctx.addExe(name, target);
163}
164
165pub fn exeFromCompiledC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
166 var target_adjusted = target;
167 target_adjusted.ofmt = .c;
168 ctx.cases.append(Case{
169 .name = name,
170 .target = target_adjusted,
171 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
172 .output_mode = .Exe,
173 .deps = std.ArrayList(DepModule).init(ctx.arena),
174 .link_libc = true,
175 }) catch @panic("out of memory");
176 return &ctx.cases.items[ctx.cases.items.len - 1];
177}
178
179/// Adds a test case that uses the LLVM backend to emit an executable.
180/// Currently this implies linking libc, because only then we can generate a testable executable.
181pub fn exeUsingLlvmBackend(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
182 ctx.cases.append(Case{
183 .name = name,
184 .target = target,
185 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
186 .output_mode = .Exe,
187 .deps = std.ArrayList(DepModule).init(ctx.arena),
188 .backend = .llvm,
189 .link_libc = true,
190 }) catch @panic("out of memory");
191 return &ctx.cases.items[ctx.cases.items.len - 1];
192}
193
194pub fn addObj(
195 ctx: *Cases,
196 name: []const u8,
197 target: CrossTarget,
198) *Case {
199 ctx.cases.append(Case{
200 .name = name,
201 .target = target,
202 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
203 .output_mode = .Obj,
204 .deps = std.ArrayList(DepModule).init(ctx.arena),
205 }) catch @panic("out of memory");
206 return &ctx.cases.items[ctx.cases.items.len - 1];
207}
208
209pub fn addTest(
210 ctx: *Cases,
211 name: []const u8,
212 target: CrossTarget,
213) *Case {
214 ctx.cases.append(Case{
215 .name = name,
216 .target = target,
217 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
218 .output_mode = .Exe,
219 .is_test = true,
220 .deps = std.ArrayList(DepModule).init(ctx.arena),
221 }) catch @panic("out of memory");
222 return &ctx.cases.items[ctx.cases.items.len - 1];
223}
224
225/// Adds a test case for Zig input, producing an object file.
226pub fn obj(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
227 return ctx.addObj(name, target);
228}
229
230/// Adds a test case for ZIR input, producing an object file.
231pub fn objZIR(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
232 return ctx.addObj(name, target, .ZIR);
233}
234
235/// Adds a test case for Zig or ZIR input, producing C code.
236pub fn addC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
237 var target_adjusted = target;
238 target_adjusted.ofmt = std.Target.ObjectFormat.c;
239 ctx.cases.append(Case{
240 .name = name,
241 .target = target_adjusted,
242 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
243 .output_mode = .Obj,
244 .deps = std.ArrayList(DepModule).init(ctx.arena),
245 }) catch @panic("out of memory");
246 return &ctx.cases.items[ctx.cases.items.len - 1];
247}
248
249pub fn addCompareOutput(
250 ctx: *Cases,
251 name: []const u8,
252 src: [:0]const u8,
253 expected_stdout: []const u8,
254) void {
255 ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout);
256}
257
258/// Adds a test case that compiles the Zig source given in `src`, executes
259/// it, runs it, and tests the output against `expected_stdout`
260pub fn compareOutput(
261 ctx: *Cases,
262 name: []const u8,
263 src: [:0]const u8,
264 expected_stdout: []const u8,
265) void {
266 return ctx.addCompareOutput(name, src, expected_stdout);
267}
268
269pub fn addTransform(
270 ctx: *Cases,
271 name: []const u8,
272 target: CrossTarget,
273 src: [:0]const u8,
274 result: [:0]const u8,
275) void {
276 ctx.addObj(name, target).addTransform(src, result);
277}
278
279/// Adds a test case that compiles the Zig given in `src` to ZIR and tests
280/// the ZIR against `result`
281pub fn transform(
282 ctx: *Cases,
283 name: []const u8,
284 target: CrossTarget,
285 src: [:0]const u8,
286 result: [:0]const u8,
287) void {
288 ctx.addTransform(name, target, src, result);
289}
290
291pub fn addError(
292 ctx: *Cases,
293 name: []const u8,
294 target: CrossTarget,
295 src: [:0]const u8,
296 expected_errors: []const []const u8,
297) void {
298 ctx.addObj(name, target).addError(src, expected_errors);
299}
300
301/// Adds a test case that ensures that the Zig given in `src` fails to
302/// compile for the expected reasons, given in sequential order in
303/// `expected_errors` in the form `:line:column: error: message`.
304pub fn compileError(
305 ctx: *Cases,
306 name: []const u8,
307 target: CrossTarget,
308 src: [:0]const u8,
309 expected_errors: []const []const u8,
310) void {
311 ctx.addError(name, target, src, expected_errors);
312}
313
314/// Adds a test case that asserts that the Zig given in `src` compiles
315/// without any errors.
316pub fn addCompile(
317 ctx: *Cases,
318 name: []const u8,
319 target: CrossTarget,
320 src: [:0]const u8,
321) void {
322 ctx.addObj(name, target).addCompile(src);
323}
324
325/// Adds a test for each file in the provided directory.
326/// Testing strategy (TestStrategy) is inferred automatically from filenames.
327/// Recurses nested directories.
328///
329/// Each file should include a test manifest as a contiguous block of comments at
330/// the end of the file. The first line should be the test type, followed by a set of
331/// key-value config values, followed by a blank line, then the expected output.
332pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void {
333 var current_file: []const u8 = "none";
334 ctx.addFromDirInner(dir, &current_file) catch |err| {
335 std.debug.panic("test harness failed to process file '{s}': {s}\n", .{
336 current_file, @errorName(err),
337 });
338 };
339}
340
341fn addFromDirInner(
342 ctx: *Cases,
343 iterable_dir: std.fs.IterableDir,
344 /// This is kept up to date with the currently being processed file so
345 /// that if any errors occur the caller knows it happened during this file.
346 current_file: *[]const u8,
347) !void {
348 var it = try iterable_dir.walk(ctx.arena);
349 var filenames = std.ArrayList([]const u8).init(ctx.arena);
350
351 while (try it.next()) |entry| {
352 if (entry.kind != .File) continue;
353
354 // Ignore stuff such as .swp files
355 switch (Compilation.classifyFileExt(entry.basename)) {
356 .unknown => continue,
357 else => {},
358 }
359 try filenames.append(try ctx.arena.dupe(u8, entry.path));
360 }
361
362 // Sort filenames, so that incremental tests are contiguous and in-order
363 sortTestFilenames(filenames.items);
364
365 var test_it = TestIterator{ .filenames = filenames.items };
366 while (test_it.next()) |maybe_batch| {
367 const batch = maybe_batch orelse break;
368 const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent;
369 const filename = batch[0];
370 current_file.* = filename;
371 if (strategy == .incremental) {
372 try ctx.incremental_cases.append(.{ .base_path = filename });
373 continue;
374 }
375
376 const max_file_size = 10 * 1024 * 1024;
377 const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);
378
379 // Parse the manifest
380 var manifest = try TestManifest.parse(ctx.arena, src);
381
382 const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend);
383 const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget);
384 const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
385 const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
386
387 var cases = std.ArrayList(usize).init(ctx.arena);
388
389 // Cross-product to get all possible test combinations
390 for (backends) |backend| {
391 for (targets) |target| {
392 const next = ctx.cases.items.len;
393 try ctx.cases.append(.{
394 .name = std.fs.path.stem(filename),
395 .target = target,
396 .backend = backend,
397 .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator),
398 .is_test = is_test,
399 .output_mode = output_mode,
400 .link_libc = backend == .llvm,
401 .deps = std.ArrayList(DepModule).init(ctx.cases.allocator),
402 });
403 try cases.append(next);
404 }
405 }
406
407 for (cases.items) |case_index| {
408 const case = &ctx.cases.items[case_index];
409 switch (manifest.type) {
410 .compile => {
411 case.addCompile(src);
412 },
413 .@"error" => {
414 const errors = try manifest.trailingAlloc(ctx.arena);
415 case.addError(src, errors);
416 },
417 .run => {
418 var output = std.ArrayList(u8).init(ctx.arena);
419 var trailing_it = manifest.trailing();
420 while (trailing_it.next()) |line| {
421 try output.appendSlice(line);
422 try output.append('\n');
423 }
424 if (output.items.len > 0) {
425 try output.resize(output.items.len - 1);
426 }
427 case.addCompareOutput(src, try output.toOwnedSlice());
428 },
429 .cli => @panic("TODO cli tests"),
430 }
431 }
432 } else |err| {
433 // make sure the current file is set to the file that produced an error
434 current_file.* = test_it.currentFilename();
435 return err;
436 }
437}
438
439pub fn init(gpa: Allocator, arena: Allocator) Cases {
440 return .{
441 .gpa = gpa,
442 .cases = std.ArrayList(Case).init(gpa),
443 .incremental_cases = std.ArrayList(IncrementalCase).init(gpa),
444 .arena = arena,
445 };
446}
447
448pub fn lowerToBuildSteps(
449 self: *Cases,
450 b: *std.Build,
451 parent_step: *std.Build.Step,
452 opt_test_filter: ?[]const u8,
453 cases_dir_path: []const u8,
454 incremental_exe: *std.Build.CompileStep,
455) void {
456 for (self.incremental_cases.items) |incr_case| {
457 if (opt_test_filter) |test_filter| {
458 if (std.mem.indexOf(u8, incr_case.base_path, test_filter) == null) continue;
459 }
460 const case_base_path_with_dir = std.fs.path.join(b.allocator, &.{
461 cases_dir_path, incr_case.base_path,
462 }) catch @panic("OOM");
463 const run = b.addRunArtifact(incremental_exe);
464 run.setName(incr_case.base_path);
465 run.addArgs(&.{
466 case_base_path_with_dir,
467 b.zig_exe,
468 });
469 run.expectStdOutEqual("");
470 parent_step.dependOn(&run.step);
471 }
472
473 for (self.cases.items) |case| {
474 if (case.updates.items.len != 1) continue; // handled with incremental_cases above
475 assert(case.updates.items.len == 1);
476 const update = case.updates.items[0];
477
478 if (opt_test_filter) |test_filter| {
479 if (std.mem.indexOf(u8, case.name, test_filter) == null) continue;
480 }
481
482 const writefiles = b.addWriteFiles();
483 for (update.files.items) |file| {
484 writefiles.add(file.path, file.src);
485 }
486 const root_source_file = writefiles.getFileSource(update.files.items[0].path).?;
487
488 const artifact = switch (case.output_mode) {
489 .Obj => b.addObject(.{
490 .root_source_file = root_source_file,
491 .name = case.name,
492 .target = case.target,
493 .optimize = case.optimize_mode,
494 }),
495 .Lib => b.addStaticLibrary(.{
496 .root_source_file = root_source_file,
497 .name = case.name,
498 .target = case.target,
499 .optimize = case.optimize_mode,
500 }),
501 .Exe => if (case.is_test) b.addTest(.{
502 .root_source_file = root_source_file,
503 .name = case.name,
504 .target = case.target,
505 .optimize = case.optimize_mode,
506 }) else b.addExecutable(.{
507 .root_source_file = root_source_file,
508 .name = case.name,
509 .target = case.target,
510 .optimize = case.optimize_mode,
511 }),
512 };
513
514 if (case.link_libc) artifact.linkLibC();
515
516 switch (case.backend) {
517 .stage1 => continue,
518 .stage2 => {
519 artifact.use_llvm = false;
520 artifact.use_lld = false;
521 },
522 .llvm => {
523 artifact.use_llvm = true;
524 },
525 }
526
527 for (case.deps.items) |dep| {
528 artifact.addAnonymousModule(dep.name, .{
529 .source_file = writefiles.getFileSource(dep.path).?,
530 });
531 }
532
533 switch (update.case) {
534 .Compile => {
535 parent_step.dependOn(&artifact.step);
536 },
537 .CompareObjectFile => |expected_output| {
538 const check = b.addCheckFile(artifact.getOutputSource(), .{
539 .expected_exact = expected_output,
540 });
541
542 parent_step.dependOn(&check.step);
543 },
544 .Error => |expected_msgs| {
545 assert(expected_msgs.len != 0);
546 artifact.expect_errors = expected_msgs;
547 parent_step.dependOn(&artifact.step);
548 },
549 .Execution => |expected_stdout| {
550 if (case.is_test) {
551 parent_step.dependOn(&artifact.step);
552 } else {
553 const run = b.addRunArtifact(artifact);
554 run.skip_foreign_checks = true;
555 run.expectStdOutEqual(expected_stdout);
556
557 parent_step.dependOn(&run.step);
558 }
559 },
560 .Header => @panic("TODO"),
561 }
562 }
563}
564
565/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig",
566/// "foo.1.zig", etc.) are contiguous and appear in numerical order.
567fn sortTestFilenames(filenames: [][]const u8) void {
568 const Context = struct {
569 pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool {
570 const a_parts = getTestFileNameParts(a);
571 const b_parts = getTestFileNameParts(b);
572
573 // Sort "<base_name>.X.<file_ext>" based on "<base_name>" and "<file_ext>" first
574 return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) {
575 .lt => true,
576 .gt => false,
577 .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) {
578 .lt => true,
579 .gt => false,
580 .eq => {
581 // a and b differ only in their ".X" part
582
583 // Sort "<base_name>.<file_ext>" before any "<base_name>.X.<file_ext>"
584 if (a_parts.test_index) |a_index| {
585 if (b_parts.test_index) |b_index| {
586 // Make sure that incremental tests appear in linear order
587 return a_index < b_index;
588 } else {
589 return false;
590 }
591 } else {
592 return b_parts.test_index != null;
593 }
594 },
595 },
596 };
597 }
598 };
599 std.sort.sort([]const u8, filenames, Context{}, Context.lessThan);
600}
601
602/// Iterates a set of filenames extracting batches that are either incremental
603/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.).
604/// Assumes filenames are sorted.
605const TestIterator = struct {
606 start: usize = 0,
607 end: usize = 0,
608 filenames: []const []const u8,
609 /// reset on each call to `next`
610 index: usize = 0,
611
612 const Error = error{InvalidIncrementalTestIndex};
613
614 fn next(it: *TestIterator) Error!?[]const []const u8 {
615 try it.nextInner();
616 if (it.start == it.end) return null;
617 return it.filenames[it.start..it.end];
618 }
619
620 fn nextInner(it: *TestIterator) Error!void {
621 it.start = it.end;
622 if (it.end == it.filenames.len) return;
623 if (it.end + 1 == it.filenames.len) {
624 it.end += 1;
625 return;
626 }
627
628 const remaining = it.filenames[it.end..];
629 it.index = 0;
630 while (it.index < remaining.len - 1) : (it.index += 1) {
631 // First, check if this file is part of an incremental update sequence
632 // Split filename into "<base_name>.<index>.<file_ext>"
633 const prev_parts = getTestFileNameParts(remaining[it.index]);
634 const new_parts = getTestFileNameParts(remaining[it.index + 1]);
635
636 // If base_name and file_ext match, these files are in the same test sequence
637 // and the new one should be the incremented version of the previous test
638 if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and
639 std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext))
640 {
641 // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1
642 if (prev_parts.test_index == null)
643 return error.InvalidIncrementalTestIndex;
644 if (new_parts.test_index == null)
645 return error.InvalidIncrementalTestIndex;
646 if (new_parts.test_index.? != prev_parts.test_index.? + 1)
647 return error.InvalidIncrementalTestIndex;
648 } else {
649 // This is not the same test sequence, so the new file must be the first file
650 // in a new sequence ("*.0.zig") or an independent test file ("*.zig")
651 if (new_parts.test_index != null and new_parts.test_index.? != 0)
652 return error.InvalidIncrementalTestIndex;
653
654 it.end += it.index + 1;
655 break;
656 }
657 } else {
658 it.end += remaining.len;
659 }
660 }
661
662 /// In the event of an `error.InvalidIncrementalTestIndex`, this function can
663 /// be used to find the current filename that was being processed.
664 /// Asserts the iterator hasn't reached the end.
665 fn currentFilename(it: TestIterator) []const u8 {
666 assert(it.end != it.filenames.len);
667 const remaining = it.filenames[it.end..];
668 return remaining[it.index + 1];
669 }
670};
671
672/// For a filename in the format "<filename>.X.<ext>" or "<filename>.<ext>", returns
673/// "<filename>", "<ext>" and X parsed as a decimal number. If X is not present, or
674/// cannot be parsed as a decimal number, it is treated as part of <filename>
675fn getTestFileNameParts(name: []const u8) struct {
676 base_name: []const u8,
677 file_ext: []const u8,
678 test_index: ?usize,
679} {
680 const file_ext = std.fs.path.extension(name);
681 const trimmed = name[0 .. name.len - file_ext.len]; // Trim off ".<ext>"
682 const maybe_index = std.fs.path.extension(trimmed); // Extract ".X"
683
684 // Attempt to parse index
685 const index: ?usize = if (maybe_index.len > 0)
686 std.fmt.parseInt(usize, maybe_index[1..], 10) catch null
687 else
688 null;
689
690 // Adjust "<filename>" extent based on parsing success
691 const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0;
692 return .{
693 .base_name = name[0..base_name_end],
694 .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext,
695 .test_index = index,
696 };
697}
698
699const TestStrategy = enum {
700 /// Execute tests as independent compilations, unless they are explicitly
701 /// incremental ("foo.0.zig", "foo.1.zig", etc.)
702 independent,
703 /// Execute all tests as incremental updates to a single compilation. Explicitly
704 /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order
705 incremental,
706};
707
708/// Default config values for known test manifest key-value pairings.
709/// Currently handled defaults are:
710/// * backend
711/// * target
712/// * output_mode
713/// * is_test
714const TestManifestConfigDefaults = struct {
715 /// Asserts if the key doesn't exist - yep, it's an oversight alright.
716 fn get(@"type": TestManifest.Type, key: []const u8) []const u8 {
717 if (std.mem.eql(u8, key, "backend")) {
718 return "stage2";
719 } else if (std.mem.eql(u8, key, "target")) {
720 if (@"type" == .@"error") {
721 return "native";
722 }
723 comptime {
724 var defaults: []const u8 = "";
725 // TODO should we only return "mainstream" targets by default here?
726 // TODO we should also specify ABIs explicitly as the backends are
727 // getting more and more complete
728 // Linux
729 inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| {
730 defaults = defaults ++ arch ++ "-linux" ++ ",";
731 }
732 // macOS
733 inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| {
734 defaults = defaults ++ arch ++ "-macos" ++ ",";
735 }
736 // Windows
737 defaults = defaults ++ "x86_64-windows" ++ ",";
738 // Wasm
739 defaults = defaults ++ "wasm32-wasi";
740 return defaults;
741 }
742 } else if (std.mem.eql(u8, key, "output_mode")) {
743 return switch (@"type") {
744 .@"error" => "Obj",
745 .run => "Exe",
746 .compile => "Obj",
747 .cli => @panic("TODO test harness for CLI tests"),
748 };
749 } else if (std.mem.eql(u8, key, "is_test")) {
750 return "0";
751 } else unreachable;
752 }
753};
754
755/// Manifest syntax example:
756/// (see https://github.com/ziglang/zig/issues/11288)
757///
758/// error
759/// backend=stage1,stage2
760/// output_mode=exe
761///
762/// :3:19: error: foo
763///
764/// run
765/// target=x86_64-linux,aarch64-macos
766///
767/// I am expected stdout! Hello!
768///
769/// cli
770///
771/// build test
772const TestManifest = struct {
773 type: Type,
774 config_map: std.StringHashMap([]const u8),
775 trailing_bytes: []const u8 = "",
776
777 const Type = enum {
778 @"error",
779 run,
780 cli,
781 compile,
782 };
783
784 const TrailingIterator = struct {
785 inner: std.mem.TokenIterator(u8),
786
787 fn next(self: *TrailingIterator) ?[]const u8 {
788 const next_inner = self.inner.next() orelse return null;
789 return std.mem.trim(u8, next_inner[2..], " \t");
790 }
791 };
792
793 fn ConfigValueIterator(comptime T: type) type {
794 return struct {
795 inner: std.mem.SplitIterator(u8),
796
797 fn next(self: *@This()) !?T {
798 const next_raw = self.inner.next() orelse return null;
799 const parseFn = getDefaultParser(T);
800 return try parseFn(next_raw);
801 }
802 };
803 }
804
805 fn parse(arena: Allocator, bytes: []const u8) !TestManifest {
806 // The manifest is the last contiguous block of comments in the file
807 // We scan for the beginning by searching backward for the first non-empty line that does not start with "//"
808 var start: ?usize = null;
809 var end: usize = bytes.len;
810 if (bytes.len > 0) {
811 var cursor: usize = bytes.len - 1;
812 while (true) {
813 // Move to beginning of line
814 while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1;
815
816 if (std.mem.startsWith(u8, bytes[cursor..], "//")) {
817 start = cursor; // Contiguous comment line, include in manifest
818 } else {
819 if (start != null) break; // Encountered non-comment line, end of manifest
820
821 // We ignore all-whitespace lines following the comment block, but anything else
822 // means that there is no manifest present.
823 if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) {
824 end = cursor;
825 } else break; // If it's not whitespace, there is no manifest
826 }
827
828 // Move to previous line
829 if (cursor != 0) cursor -= 1 else break;
830 }
831 }
832
833 const actual_start = start orelse return error.MissingTestManifest;
834 const manifest_bytes = bytes[actual_start..end];
835
836 var it = std.mem.tokenize(u8, manifest_bytes, "\r\n");
837
838 // First line is the test type
839 const tt: Type = blk: {
840 const line = it.next() orelse return error.MissingTestCaseType;
841 const raw = std.mem.trim(u8, line[2..], " \t");
842 if (std.mem.eql(u8, raw, "error")) {
843 break :blk .@"error";
844 } else if (std.mem.eql(u8, raw, "run")) {
845 break :blk .run;
846 } else if (std.mem.eql(u8, raw, "cli")) {
847 break :blk .cli;
848 } else if (std.mem.eql(u8, raw, "compile")) {
849 break :blk .compile;
850 } else {
851 std.log.warn("unknown test case type requested: {s}", .{raw});
852 return error.UnknownTestCaseType;
853 }
854 };
855
856 var manifest: TestManifest = .{
857 .type = tt,
858 .config_map = std.StringHashMap([]const u8).init(arena),
859 };
860
861 // Any subsequent line until a blank comment line is key=value(s) pair
862 while (it.next()) |line| {
863 const trimmed = std.mem.trim(u8, line[2..], " \t");
864 if (trimmed.len == 0) break;
865
866 // Parse key=value(s)
867 var kv_it = std.mem.split(u8, trimmed, "=");
868 const key = kv_it.first();
869 try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig);
870 }
871
872 // Finally, trailing is expected output
873 manifest.trailing_bytes = manifest_bytes[it.index..];
874
875 return manifest;
876 }
877
878 fn getConfigForKey(
879 self: TestManifest,
880 key: []const u8,
881 comptime T: type,
882 ) ConfigValueIterator(T) {
883 const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key);
884 return ConfigValueIterator(T){
885 .inner = std.mem.split(u8, bytes, ","),
886 };
887 }
888
889 fn getConfigForKeyAlloc(
890 self: TestManifest,
891 allocator: Allocator,
892 key: []const u8,
893 comptime T: type,
894 ) ![]const T {
895 var out = std.ArrayList(T).init(allocator);
896 defer out.deinit();
897 var it = self.getConfigForKey(key, T);
898 while (try it.next()) |item| {
899 try out.append(item);
900 }
901 return try out.toOwnedSlice();
902 }
903
904 fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T {
905 var it = self.getConfigForKey(key, T);
906 const res = (try it.next()) orelse unreachable;
907 assert((try it.next()) == null);
908 return res;
909 }
910
911 fn trailing(self: TestManifest) TrailingIterator {
912 return .{
913 .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"),
914 };
915 }
916
917 fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 {
918 var out = std.ArrayList([]const u8).init(allocator);
919 defer out.deinit();
920 var it = self.trailing();
921 while (it.next()) |line| {
922 try out.append(line);
923 }
924 return try out.toOwnedSlice();
925 }
926
927 fn ParseFn(comptime T: type) type {
928 return fn ([]const u8) anyerror!T;
929 }
930
931 fn getDefaultParser(comptime T: type) ParseFn(T) {
932 if (T == CrossTarget) return struct {
933 fn parse(str: []const u8) anyerror!T {
934 var opts = CrossTarget.ParseOptions{
935 .arch_os_abi = str,
936 };
937 return try CrossTarget.parse(opts);
938 }
939 }.parse;
940
941 switch (@typeInfo(T)) {
942 .Int => return struct {
943 fn parse(str: []const u8) anyerror!T {
944 return try std.fmt.parseInt(T, str, 0);
945 }
946 }.parse,
947 .Bool => return struct {
948 fn parse(str: []const u8) anyerror!T {
949 const as_int = try std.fmt.parseInt(u1, str, 0);
950 return as_int > 0;
951 }
952 }.parse,
953 .Enum => return struct {
954 fn parse(str: []const u8) anyerror!T {
955 return std.meta.stringToEnum(T, str) orelse {
956 std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str });
957 return error.UnknownEnumVariant;
958 };
959 }
960 }.parse,
961 .Struct => @compileError("no default parser for " ++ @typeName(T)),
962 else => @compileError("no default parser for " ++ @typeName(T)),
963 }
964 }
965};
966
967const Cases = @This();
968const builtin = @import("builtin");
969const std = @import("std");
970const assert = std.debug.assert;
971const Allocator = std.mem.Allocator;
972const CrossTarget = std.zig.CrossTarget;
973const Compilation = @import("../../src/Compilation.zig");
974const zig_h = @import("../../src/link.zig").File.C.zig_h;
975const introspect = @import("../../src/introspect.zig");
976const ThreadPool = std.Thread.Pool;
977const WaitGroup = std.Thread.WaitGroup;
978const build_options = @import("build_options");
979const Package = @import("../../src/Package.zig");
980
981pub const std_options = struct {
982 pub const log_level: std.log.Level = .err;
983};
984
985var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{
986 .stack_trace_frames = build_options.mem_leak_frames,
987}){};
988
989// TODO: instead of embedding the compiler in this process, spawn the compiler
990// as a sub-process and communicate the updates using the compiler protocol.
991pub fn main() !void {
992 const use_gpa = build_options.force_gpa or !builtin.link_libc;
993 const gpa = gpa: {
994 if (use_gpa) {
995 break :gpa general_purpose_allocator.allocator();
996 }
997 // We would prefer to use raw libc allocator here, but cannot
998 // use it if it won't support the alignment we need.
999 if (@alignOf(std.c.max_align_t) < @alignOf(i128)) {
1000 break :gpa std.heap.c_allocator;
1001 }
1002 break :gpa std.heap.raw_c_allocator;
1003 };
1004
1005 var single_threaded_arena = std.heap.ArenaAllocator.init(gpa);
1006 defer single_threaded_arena.deinit();
1007
1008 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
1009 .child_allocator = single_threaded_arena.allocator(),
1010 };
1011 const arena = thread_safe_arena.allocator();
1012
1013 const args = try std.process.argsAlloc(arena);
1014 const case_file_path = args[1];
1015 const zig_exe_path = args[2];
1016
1017 var filenames = std.ArrayList([]const u8).init(arena);
1018
1019 const case_dirname = std.fs.path.dirname(case_file_path).?;
1020 var iterable_dir = try std.fs.cwd().openIterableDir(case_dirname, .{});
1021 defer iterable_dir.close();
1022
1023 if (std.mem.endsWith(u8, case_file_path, ".0.zig")) {
1024 const stem = case_file_path[case_dirname.len + 1 .. case_file_path.len - "0.zig".len];
1025 var it = iterable_dir.iterate();
1026 while (try it.next()) |entry| {
1027 if (entry.kind != .File) continue;
1028 if (!std.mem.startsWith(u8, entry.name, stem)) continue;
1029 try filenames.append(try std.fs.path.join(arena, &.{ case_dirname, entry.name }));
1030 }
1031 } else {
1032 try filenames.append(case_file_path);
1033 }
1034
1035 if (filenames.items.len == 0) {
1036 std.debug.print("failed to find the input source file(s) from '{s}'\n", .{
1037 case_file_path,
1038 });
1039 std.process.exit(1);
1040 }
1041
1042 // Sort filenames, so that incremental tests are contiguous and in-order
1043 sortTestFilenames(filenames.items);
1044
1045 var ctx = Cases.init(gpa, arena);
1046
1047 var test_it = TestIterator{ .filenames = filenames.items };
1048 while (test_it.next()) |maybe_batch| {
1049 const batch = maybe_batch orelse break;
1050 const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent;
1051 var cases = std.ArrayList(usize).init(arena);
1052
1053 for (batch) |filename| {
1054 const max_file_size = 10 * 1024 * 1024;
1055 const src = try iterable_dir.dir.readFileAllocOptions(arena, filename, max_file_size, null, 1, 0);
1056
1057 // Parse the manifest
1058 var manifest = try TestManifest.parse(arena, src);
1059
1060 if (cases.items.len == 0) {
1061 const backends = try manifest.getConfigForKeyAlloc(arena, "backend", Backend);
1062 const targets = try manifest.getConfigForKeyAlloc(arena, "target", CrossTarget);
1063 const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
1064 const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
1065
1066 // Cross-product to get all possible test combinations
1067 for (backends) |backend| {
1068 for (targets) |target| {
1069 const next = ctx.cases.items.len;
1070 try ctx.cases.append(.{
1071 .name = std.fs.path.stem(filename),
1072 .target = target,
1073 .backend = backend,
1074 .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator),
1075 .is_test = is_test,
1076 .output_mode = output_mode,
1077 .link_libc = backend == .llvm,
1078 .deps = std.ArrayList(DepModule).init(ctx.cases.allocator),
1079 });
1080 try cases.append(next);
1081 }
1082 }
1083 }
1084
1085 for (cases.items) |case_index| {
1086 const case = &ctx.cases.items[case_index];
1087 switch (manifest.type) {
1088 .compile => {
1089 case.addCompile(src);
1090 },
1091 .@"error" => {
1092 const errors = try manifest.trailingAlloc(arena);
1093 switch (strategy) {
1094 .independent => {
1095 case.addError(src, errors);
1096 },
1097 .incremental => {
1098 case.addErrorNamed("update", src, errors);
1099 },
1100 }
1101 },
1102 .run => {
1103 var output = std.ArrayList(u8).init(arena);
1104 var trailing_it = manifest.trailing();
1105 while (trailing_it.next()) |line| {
1106 try output.appendSlice(line);
1107 try output.append('\n');
1108 }
1109 if (output.items.len > 0) {
1110 try output.resize(output.items.len - 1);
1111 }
1112 case.addCompareOutput(src, try output.toOwnedSlice());
1113 },
1114 .cli => @panic("TODO cli tests"),
1115 }
1116 }
1117 }
1118 } else |err| {
1119 return err;
1120 }
1121
1122 return runCases(&ctx, zig_exe_path);
1123}
1124
1125fn runCases(self: *Cases, zig_exe_path: []const u8) !void {
1126 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1127
1128 var progress = std.Progress{};
1129 const root_node = progress.start("compiler", self.cases.items.len);
1130 progress.terminal = null;
1131 defer root_node.end();
1132
1133 var zig_lib_directory = try introspect.findZigLibDir(self.gpa);
1134 defer zig_lib_directory.handle.close();
1135 defer self.gpa.free(zig_lib_directory.path.?);
1136
1137 var aux_thread_pool: ThreadPool = undefined;
1138 try aux_thread_pool.init(.{ .allocator = self.gpa });
1139 defer aux_thread_pool.deinit();
1140
1141 // Use the same global cache dir for all the tests, such that we for example don't have to
1142 // rebuild musl libc for every case (when LLVM backend is enabled).
1143 var global_tmp = std.testing.tmpDir(.{});
1144 defer global_tmp.cleanup();
1145
1146 var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{});
1147 defer cache_dir.close();
1148 const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path });
1149 defer self.gpa.free(tmp_dir_path);
1150
1151 const global_cache_directory: Compilation.Directory = .{
1152 .handle = cache_dir,
1153 .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }),
1154 };
1155 defer self.gpa.free(global_cache_directory.path.?);
1156
1157 {
1158 for (self.cases.items) |*case| {
1159 if (build_options.skip_non_native) {
1160 if (case.target.getCpuArch() != builtin.cpu.arch)
1161 continue;
1162 if (case.target.getObjectFormat() != builtin.object_format)
1163 continue;
1164 }
1165
1166 // Skip tests that require LLVM backend when it is not available
1167 if (!build_options.have_llvm and case.backend == .llvm)
1168 continue;
1169
1170 assert(case.backend != .stage1);
1171
1172 if (build_options.test_filter) |test_filter| {
1173 if (std.mem.indexOf(u8, case.name, test_filter) == null) continue;
1174 }
1175
1176 var prg_node = root_node.start(case.name, case.updates.items.len);
1177 prg_node.activate();
1178 defer prg_node.end();
1179
1180 try runOneCase(
1181 self.gpa,
1182 &prg_node,
1183 case.*,
1184 zig_lib_directory,
1185 zig_exe_path,
1186 &aux_thread_pool,
1187 global_cache_directory,
1188 host,
1189 );
1190 }
1191 }
1192}
1193
1194fn runOneCase(
1195 allocator: Allocator,
1196 root_node: *std.Progress.Node,
1197 case: Case,
1198 zig_lib_directory: Compilation.Directory,
1199 zig_exe_path: []const u8,
1200 thread_pool: *ThreadPool,
1201 global_cache_directory: Compilation.Directory,
1202 host: std.zig.system.NativeTargetInfo,
1203) !void {
1204 const tmp_src_path = "tmp.zig";
1205 const enable_rosetta = build_options.enable_rosetta;
1206 const enable_qemu = build_options.enable_qemu;
1207 const enable_wine = build_options.enable_wine;
1208 const enable_wasmtime = build_options.enable_wasmtime;
1209 const enable_darling = build_options.enable_darling;
1210 const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir;
1211
1212 const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
1213 const target = target_info.target;
1214
1215 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
1216 defer arena_allocator.deinit();
1217 const arena = arena_allocator.allocator();
1218
1219 var tmp = std.testing.tmpDir(.{});
1220 defer tmp.cleanup();
1221
1222 var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});
1223 defer cache_dir.close();
1224
1225 const tmp_dir_path = try std.fs.path.join(
1226 arena,
1227 &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path },
1228 );
1229 const local_cache_path = try std.fs.path.join(
1230 arena,
1231 &[_][]const u8{ tmp_dir_path, "zig-cache" },
1232 );
1233
1234 const zig_cache_directory: Compilation.Directory = .{
1235 .handle = cache_dir,
1236 .path = local_cache_path,
1237 };
1238
1239 var main_pkg: Package = .{
1240 .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir },
1241 .root_src_path = tmp_src_path,
1242 };
1243 defer {
1244 var it = main_pkg.table.iterator();
1245 while (it.next()) |kv| {
1246 allocator.free(kv.key_ptr.*);
1247 kv.value_ptr.*.destroy(allocator);
1248 }
1249 main_pkg.table.deinit(allocator);
1250 }
1251
1252 for (case.deps.items) |dep| {
1253 var pkg = try Package.create(
1254 allocator,
1255 tmp_dir_path,
1256 dep.path,
1257 );
1258 errdefer pkg.destroy(allocator);
1259 try main_pkg.add(allocator, dep.name, pkg);
1260 }
1261
1262 const bin_name = try std.zig.binNameAlloc(arena, .{
1263 .root_name = "test_case",
1264 .target = target,
1265 .output_mode = case.output_mode,
1266 });
1267
1268 const emit_directory: Compilation.Directory = .{
1269 .path = tmp_dir_path,
1270 .handle = tmp.dir,
1271 };
1272 const emit_bin: Compilation.EmitLoc = .{
1273 .directory = emit_directory,
1274 .basename = bin_name,
1275 };
1276 const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{
1277 .directory = emit_directory,
1278 .basename = "test_case.h",
1279 } else null;
1280 const use_llvm: bool = switch (case.backend) {
1281 .llvm => true,
1282 else => false,
1283 };
1284 const comp = try Compilation.create(allocator, .{
1285 .local_cache_directory = zig_cache_directory,
1286 .global_cache_directory = global_cache_directory,
1287 .zig_lib_directory = zig_lib_directory,
1288 .thread_pool = thread_pool,
1289 .root_name = "test_case",
1290 .target = target,
1291 // TODO: support tests for object file building, and library builds
1292 // and linking. This will require a rework to support multi-file
1293 // tests.
1294 .output_mode = case.output_mode,
1295 .is_test = case.is_test,
1296 .optimize_mode = case.optimize_mode,
1297 .emit_bin = emit_bin,
1298 .emit_h = emit_h,
1299 .main_pkg = &main_pkg,
1300 .keep_source_files_loaded = true,
1301 .is_native_os = case.target.isNativeOs(),
1302 .is_native_abi = case.target.isNativeAbi(),
1303 .dynamic_linker = target_info.dynamic_linker.get(),
1304 .link_libc = case.link_libc,
1305 .use_llvm = use_llvm,
1306 .self_exe_path = zig_exe_path,
1307 // TODO instead of turning off color, pass in a std.Progress.Node
1308 .color = .off,
1309 .reference_trace = 0,
1310 // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in
1311 // until the auto-select mechanism deems them worthy
1312 .use_lld = switch (case.backend) {
1313 .stage2 => false,
1314 else => null,
1315 },
1316 });
1317 defer comp.destroy();
1318
1319 update: for (case.updates.items, 0..) |update, update_index| {
1320 var update_node = root_node.start(update.name, 3);
1321 update_node.activate();
1322 defer update_node.end();
1323
1324 var sync_node = update_node.start("write", 0);
1325 sync_node.activate();
1326 for (update.files.items) |file| {
1327 try tmp.dir.writeFile(file.path, file.src);
1328 }
1329 sync_node.end();
1330
1331 var module_node = update_node.start("parse/analysis/codegen", 0);
1332 module_node.activate();
1333 try comp.makeBinFileWritable();
1334 try comp.update(&module_node);
1335 module_node.end();
1336
1337 if (update.case != .Error) {
1338 var all_errors = try comp.getAllErrorsAlloc();
1339 defer all_errors.deinit(allocator);
1340 if (all_errors.errorMessageCount() > 0) {
1341 all_errors.renderToStdErr(.{
1342 .ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()),
1343 });
1344 // TODO print generated C code
1345 return error.UnexpectedCompileErrors;
1346 }
1347 }
1348
1349 switch (update.case) {
1350 .Header => |expected_output| {
1351 var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only });
1352 defer file.close();
1353 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1354
1355 try std.testing.expectEqualStrings(expected_output, out);
1356 },
1357 .CompareObjectFile => |expected_output| {
1358 var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only });
1359 defer file.close();
1360 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1361
1362 try std.testing.expectEqualStrings(expected_output, out);
1363 },
1364 .Compile => {},
1365 .Error => |expected_errors| {
1366 var test_node = update_node.start("assert", 0);
1367 test_node.activate();
1368 defer test_node.end();
1369
1370 var error_bundle = try comp.getAllErrorsAlloc();
1371 defer error_bundle.deinit(allocator);
1372
1373 if (error_bundle.errorMessageCount() == 0) {
1374 return error.ExpectedCompilationErrors;
1375 }
1376
1377 var actual_stderr = std.ArrayList(u8).init(arena);
1378 try error_bundle.renderToWriter(.{
1379 .ttyconf = .no_color,
1380 .include_reference_trace = false,
1381 .include_source_line = false,
1382 }, actual_stderr.writer());
1383
1384 // Render the expected lines into a string that we can compare verbatim.
1385 var expected_generated = std.ArrayList(u8).init(arena);
1386
1387 var actual_line_it = std.mem.split(u8, actual_stderr.items, "\n");
1388 for (expected_errors) |expect_line| {
1389 const actual_line = actual_line_it.next() orelse {
1390 try expected_generated.appendSlice(expect_line);
1391 try expected_generated.append('\n');
1392 continue;
1393 };
1394 if (std.mem.endsWith(u8, actual_line, expect_line)) {
1395 try expected_generated.appendSlice(actual_line);
1396 try expected_generated.append('\n');
1397 continue;
1398 }
1399 if (std.mem.startsWith(u8, expect_line, ":?:?: ")) {
1400 if (std.mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) {
1401 try expected_generated.appendSlice(actual_line);
1402 try expected_generated.append('\n');
1403 continue;
1404 }
1405 }
1406 try expected_generated.appendSlice(expect_line);
1407 try expected_generated.append('\n');
1408 }
1409
1410 try std.testing.expectEqualStrings(expected_generated.items, actual_stderr.items);
1411 },
1412 .Execution => |expected_stdout| {
1413 if (!std.process.can_spawn) {
1414 std.debug.print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
1415 continue :update; // Pass test.
1416 }
1417
1418 update_node.setEstimatedTotalItems(4);
1419
1420 var argv = std.ArrayList([]const u8).init(allocator);
1421 defer argv.deinit();
1422
1423 var exec_result = x: {
1424 var exec_node = update_node.start("execute", 0);
1425 exec_node.activate();
1426 defer exec_node.end();
1427
1428 // We go out of our way here to use the unique temporary directory name in
1429 // the exe_path so that it makes its way into the cache hash, avoiding
1430 // cache collisions from multiple threads doing `zig run` at the same time
1431 // on the same test_case.c input filename.
1432 const ss = std.fs.path.sep_str;
1433 const exe_path = try std.fmt.allocPrint(
1434 arena,
1435 ".." ++ ss ++ "{s}" ++ ss ++ "{s}",
1436 .{ &tmp.sub_path, bin_name },
1437 );
1438 if (case.target.ofmt != null and case.target.ofmt.? == .c) {
1439 if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
1440 // We wouldn't be able to run the compiled C code.
1441 continue :update; // Pass test.
1442 }
1443 try argv.appendSlice(&[_][]const u8{
1444 zig_exe_path,
1445 "run",
1446 "-cflags",
1447 "-std=c99",
1448 "-pedantic",
1449 "-Werror",
1450 "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
1451 "--",
1452 "-lc",
1453 exe_path,
1454 });
1455 if (zig_lib_directory.path) |p| {
1456 try argv.appendSlice(&.{ "-I", p });
1457 }
1458 } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) {
1459 .native => {
1460 if (case.backend == .stage2 and case.target.getCpuArch() == .arm) {
1461 // https://github.com/ziglang/zig/issues/13623
1462 continue :update; // Pass test.
1463 }
1464 try argv.append(exe_path);
1465 },
1466 .bad_dl, .bad_os_or_cpu => continue :update, // Pass test.
1467
1468 .rosetta => if (enable_rosetta) {
1469 try argv.append(exe_path);
1470 } else {
1471 continue :update; // Rosetta not available, pass test.
1472 },
1473
1474 .qemu => |qemu_bin_name| if (enable_qemu) {
1475 const need_cross_glibc = target.isGnuLibC() and case.link_libc;
1476 const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc)
1477 glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test
1478 else
1479 null;
1480 try argv.append(qemu_bin_name);
1481 if (glibc_dir_arg) |dir| {
1482 const linux_triple = try target.linuxTriple(arena);
1483 const full_dir = try std.fs.path.join(arena, &[_][]const u8{
1484 dir,
1485 linux_triple,
1486 });
1487
1488 try argv.append("-L");
1489 try argv.append(full_dir);
1490 }
1491 try argv.append(exe_path);
1492 } else {
1493 continue :update; // QEMU not available; pass test.
1494 },
1495
1496 .wine => |wine_bin_name| if (enable_wine) {
1497 try argv.append(wine_bin_name);
1498 try argv.append(exe_path);
1499 } else {
1500 continue :update; // Wine not available; pass test.
1501 },
1502
1503 .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
1504 try argv.append(wasmtime_bin_name);
1505 try argv.append("--dir=.");
1506 try argv.append(exe_path);
1507 } else {
1508 continue :update; // wasmtime not available; pass test.
1509 },
1510
1511 .darling => |darling_bin_name| if (enable_darling) {
1512 try argv.append(darling_bin_name);
1513 // Since we use relative to cwd here, we invoke darling with
1514 // "shell" subcommand.
1515 try argv.append("shell");
1516 try argv.append(exe_path);
1517 } else {
1518 continue :update; // Darling not available; pass test.
1519 },
1520 }
1521
1522 try comp.makeBinFileExecutable();
1523
1524 while (true) {
1525 break :x std.ChildProcess.exec(.{
1526 .allocator = allocator,
1527 .argv = argv.items,
1528 .cwd_dir = tmp.dir,
1529 .cwd = tmp_dir_path,
1530 }) catch |err| switch (err) {
1531 error.FileBusy => {
1532 // There is a fundamental design flaw in Unix systems with how
1533 // ETXTBSY interacts with fork+exec.
1534 // https://github.com/golang/go/issues/22315
1535 // https://bugs.openjdk.org/browse/JDK-8068370
1536 // Unfortunately, this could be a real error, but we can't
1537 // tell the difference here.
1538 continue;
1539 },
1540 else => {
1541 std.debug.print("\n{s}.{d} The following command failed with {s}:\n", .{
1542 case.name, update_index, @errorName(err),
1543 });
1544 dumpArgs(argv.items);
1545 return error.ChildProcessExecution;
1546 },
1547 };
1548 }
1549 };
1550 var test_node = update_node.start("test", 0);
1551 test_node.activate();
1552 defer test_node.end();
1553 defer allocator.free(exec_result.stdout);
1554 defer allocator.free(exec_result.stderr);
1555 switch (exec_result.term) {
1556 .Exited => |code| {
1557 if (code != 0) {
1558 std.debug.print("\n{s}\n{s}: execution exited with code {d}:\n", .{
1559 exec_result.stderr, case.name, code,
1560 });
1561 dumpArgs(argv.items);
1562 return error.ChildProcessExecution;
1563 }
1564 },
1565 else => {
1566 std.debug.print("\n{s}\n{s}: execution crashed:\n", .{
1567 exec_result.stderr, case.name,
1568 });
1569 dumpArgs(argv.items);
1570 return error.ChildProcessExecution;
1571 },
1572 }
1573 try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout);
1574 // We allow stderr to have garbage in it because wasmtime prints a
1575 // warning about --invoke even though we don't pass it.
1576 //std.testing.expectEqualStrings("", exec_result.stderr);
1577 },
1578 }
1579 }
1580}
1581
1582fn dumpArgs(argv: []const []const u8) void {
1583 for (argv) |arg| {
1584 std.debug.print("{s} ", .{arg});
1585 }
1586 std.debug.print("\n", .{});
1587}
test/stage2/cbe.zig deleted-1015
......@@ -1,1015 +0,0 @@
1const std = @import("std");
2const TestContext = @import("../../src/test.zig").TestContext;
3
4// These tests should work with all platforms, but we're using linux_x64 for
5// now for consistency. Will be expanded eventually.
6const linux_x64 = std.zig.CrossTarget{
7 .cpu_arch = .x86_64,
8 .os_tag = .linux,
9};
10
11pub fn addCases(ctx: *TestContext) !void {
12 {
13 var case = ctx.exeFromCompiledC("hello world with updates", .{});
14
15 // Regular old hello world
16 case.addCompareOutput(
17 \\extern fn puts(s: [*:0]const u8) c_int;
18 \\pub export fn main() c_int {
19 \\ _ = puts("hello world!");
20 \\ return 0;
21 \\}
22 , "hello world!" ++ std.cstr.line_sep);
23
24 // Now change the message only
25 case.addCompareOutput(
26 \\extern fn puts(s: [*:0]const u8) c_int;
27 \\pub export fn main() c_int {
28 \\ _ = puts("yo");
29 \\ return 0;
30 \\}
31 , "yo" ++ std.cstr.line_sep);
32
33 // Add an unused Decl
34 case.addCompareOutput(
35 \\extern fn puts(s: [*:0]const u8) c_int;
36 \\pub export fn main() c_int {
37 \\ _ = puts("yo!");
38 \\ return 0;
39 \\}
40 \\fn unused() void {}
41 , "yo!" ++ std.cstr.line_sep);
42
43 // Comptime return type and calling convention expected.
44 case.addError(
45 \\var x: i32 = 1234;
46 \\pub export fn main() x {
47 \\ return 0;
48 \\}
49 \\export fn foo() callconv(y) c_int {
50 \\ return 0;
51 \\}
52 \\var y: @import("std").builtin.CallingConvention = .C;
53 , &.{
54 ":2:22: error: expected type 'type', found 'i32'",
55 ":5:26: error: unable to resolve comptime value",
56 ":5:26: note: calling convention must be comptime-known",
57 });
58 }
59
60 {
61 var case = ctx.exeFromCompiledC("var args", .{});
62
63 case.addCompareOutput(
64 \\extern fn printf(format: [*:0]const u8, ...) c_int;
65 \\
66 \\pub export fn main() c_int {
67 \\ _ = printf("Hello, %s!\n", "world");
68 \\ return 0;
69 \\}
70 , "Hello, world!" ++ std.cstr.line_sep);
71 }
72
73 {
74 var case = ctx.exeFromCompiledC("@intToError", .{});
75
76 case.addCompareOutput(
77 \\pub export fn main() c_int {
78 \\ // comptime checks
79 \\ const a = error.A;
80 \\ const b = error.B;
81 \\ const c = @intToError(2);
82 \\ const d = @intToError(1);
83 \\ if (!(c == b)) unreachable;
84 \\ if (!(a == d)) unreachable;
85 \\ // runtime checks
86 \\ var x = error.A;
87 \\ var y = error.B;
88 \\ var z = @intToError(2);
89 \\ var f = @intToError(1);
90 \\ if (!(y == z)) unreachable;
91 \\ if (!(x == f)) unreachable;
92 \\ return 0;
93 \\}
94 , "");
95 case.addError(
96 \\pub export fn main() c_int {
97 \\ _ = @intToError(0);
98 \\ return 0;
99 \\}
100 , &.{":2:21: error: integer value '0' represents no error"});
101 case.addError(
102 \\pub export fn main() c_int {
103 \\ _ = @intToError(3);
104 \\ return 0;
105 \\}
106 , &.{":2:21: error: integer value '3' represents no error"});
107 }
108
109 {
110 var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);
111
112 // Exit with 0
113 case.addCompareOutput(
114 \\fn exitGood() noreturn {
115 \\ asm volatile ("syscall"
116 \\ :
117 \\ : [number] "{rax}" (231),
118 \\ [arg1] "{rdi}" (0)
119 \\ );
120 \\ unreachable;
121 \\}
122 \\
123 \\pub export fn main() c_int {
124 \\ exitGood();
125 \\}
126 , "");
127
128 // Pass a usize parameter to exit
129 case.addCompareOutput(
130 \\pub export fn main() c_int {
131 \\ exit(0);
132 \\}
133 \\
134 \\fn exit(code: usize) noreturn {
135 \\ asm volatile ("syscall"
136 \\ :
137 \\ : [number] "{rax}" (231),
138 \\ [arg1] "{rdi}" (code)
139 \\ );
140 \\ unreachable;
141 \\}
142 , "");
143
144 // Change the parameter to u8
145 case.addCompareOutput(
146 \\pub export fn main() c_int {
147 \\ exit(0);
148 \\}
149 \\
150 \\fn exit(code: u8) noreturn {
151 \\ asm volatile ("syscall"
152 \\ :
153 \\ : [number] "{rax}" (231),
154 \\ [arg1] "{rdi}" (code)
155 \\ );
156 \\ unreachable;
157 \\}
158 , "");
159
160 // Do some arithmetic at the exit callsite
161 case.addCompareOutput(
162 \\pub export fn main() c_int {
163 \\ exitMath(1);
164 \\}
165 \\
166 \\fn exitMath(a: u8) noreturn {
167 \\ exit(0 + a - a);
168 \\}
169 \\
170 \\fn exit(code: u8) noreturn {
171 \\ asm volatile ("syscall"
172 \\ :
173 \\ : [number] "{rax}" (231),
174 \\ [arg1] "{rdi}" (code)
175 \\ );
176 \\ unreachable;
177 \\}
178 \\
179 , "");
180
181 // Invert the arithmetic
182 case.addCompareOutput(
183 \\pub export fn main() c_int {
184 \\ exitMath(1);
185 \\}
186 \\
187 \\fn exitMath(a: u8) noreturn {
188 \\ exit(a + 0 - a);
189 \\}
190 \\
191 \\fn exit(code: u8) noreturn {
192 \\ asm volatile ("syscall"
193 \\ :
194 \\ : [number] "{rax}" (231),
195 \\ [arg1] "{rdi}" (code)
196 \\ );
197 \\ unreachable;
198 \\}
199 \\
200 , "");
201 }
202
203 {
204 var case = ctx.exeFromCompiledC("alloc and retptr", .{});
205
206 case.addCompareOutput(
207 \\fn add(a: i32, b: i32) i32 {
208 \\ return a + b;
209 \\}
210 \\
211 \\fn addIndirect(a: i32, b: i32) i32 {
212 \\ return add(a, b);
213 \\}
214 \\
215 \\pub export fn main() c_int {
216 \\ return addIndirect(1, 2) - 3;
217 \\}
218 , "");
219 }
220
221 {
222 var case = ctx.exeFromCompiledC("inferred local const and var", .{});
223
224 case.addCompareOutput(
225 \\fn add(a: i32, b: i32) i32 {
226 \\ return a + b;
227 \\}
228 \\
229 \\pub export fn main() c_int {
230 \\ const x = add(1, 2);
231 \\ var y = add(3, 0);
232 \\ y -= x;
233 \\ return y;
234 \\}
235 , "");
236 }
237 {
238 var case = ctx.exeFromCompiledC("control flow", .{});
239
240 // Simple while loop
241 case.addCompareOutput(
242 \\pub export fn main() c_int {
243 \\ var a: c_int = 0;
244 \\ while (a < 5) : (a+=1) {}
245 \\ return a - 5;
246 \\}
247 , "");
248 case.addCompareOutput(
249 \\pub export fn main() c_int {
250 \\ var a = true;
251 \\ while (!a) {}
252 \\ return 0;
253 \\}
254 , "");
255
256 // If expression
257 case.addCompareOutput(
258 \\pub export fn main() c_int {
259 \\ var cond: c_int = 0;
260 \\ var a: c_int = @as(c_int, if (cond == 0)
261 \\ 2
262 \\ else
263 \\ 3) + 9;
264 \\ return a - 11;
265 \\}
266 , "");
267
268 // If expression with breakpoint that does not get hit
269 case.addCompareOutput(
270 \\pub export fn main() c_int {
271 \\ var x: i32 = 1;
272 \\ if (x != 1) @breakpoint();
273 \\ return 0;
274 \\}
275 , "");
276
277 // Switch expression
278 case.addCompareOutput(
279 \\pub export fn main() c_int {
280 \\ var cond: c_int = 0;
281 \\ var a: c_int = switch (cond) {
282 \\ 1 => 1,
283 \\ 2 => 2,
284 \\ 99...300, 12 => 3,
285 \\ 0 => 4,
286 \\ else => 5,
287 \\ };
288 \\ return a - 4;
289 \\}
290 , "");
291
292 // Switch expression missing else case.
293 case.addError(
294 \\pub export fn main() c_int {
295 \\ var cond: c_int = 0;
296 \\ const a: c_int = switch (cond) {
297 \\ 1 => 1,
298 \\ 2 => 2,
299 \\ 3 => 3,
300 \\ 4 => 4,
301 \\ };
302 \\ return a - 4;
303 \\}
304 , &.{":3:22: error: switch must handle all possibilities"});
305
306 // Switch expression, has an unreachable prong.
307 case.addCompareOutput(
308 \\pub export fn main() c_int {
309 \\ var cond: c_int = 0;
310 \\ const a: c_int = switch (cond) {
311 \\ 1 => 1,
312 \\ 2 => 2,
313 \\ 99...300, 12 => 3,
314 \\ 0 => 4,
315 \\ 13 => unreachable,
316 \\ else => 5,
317 \\ };
318 \\ return a - 4;
319 \\}
320 , "");
321
322 // Switch expression, has an unreachable prong and prongs write
323 // to result locations.
324 case.addCompareOutput(
325 \\pub export fn main() c_int {
326 \\ var cond: c_int = 0;
327 \\ var a: c_int = switch (cond) {
328 \\ 1 => 1,
329 \\ 2 => 2,
330 \\ 99...300, 12 => 3,
331 \\ 0 => 4,
332 \\ 13 => unreachable,
333 \\ else => 5,
334 \\ };
335 \\ return a - 4;
336 \\}
337 , "");
338
339 // Integer switch expression has duplicate case value.
340 case.addError(
341 \\pub export fn main() c_int {
342 \\ var cond: c_int = 0;
343 \\ const a: c_int = switch (cond) {
344 \\ 1 => 1,
345 \\ 2 => 2,
346 \\ 96, 11...13, 97 => 3,
347 \\ 0 => 4,
348 \\ 90, 12 => 100,
349 \\ else => 5,
350 \\ };
351 \\ return a - 4;
352 \\}
353 , &.{
354 ":8:13: error: duplicate switch value",
355 ":6:15: note: previous value here",
356 });
357
358 // Boolean switch expression has duplicate case value.
359 case.addError(
360 \\pub export fn main() c_int {
361 \\ var a: bool = false;
362 \\ const b: c_int = switch (a) {
363 \\ false => 1,
364 \\ true => 2,
365 \\ false => 3,
366 \\ };
367 \\ _ = b;
368 \\}
369 , &.{
370 ":6:9: error: duplicate switch value",
371 });
372
373 // Sparse (no range capable) switch expression has duplicate case value.
374 case.addError(
375 \\pub export fn main() c_int {
376 \\ const A: type = i32;
377 \\ const b: c_int = switch (A) {
378 \\ i32 => 1,
379 \\ bool => 2,
380 \\ f64, i32 => 3,
381 \\ else => 4,
382 \\ };
383 \\ _ = b;
384 \\}
385 , &.{
386 ":6:14: error: duplicate switch value",
387 ":4:9: note: previous value here",
388 });
389
390 // Ranges not allowed for some kinds of switches.
391 case.addError(
392 \\pub export fn main() c_int {
393 \\ const A: type = i32;
394 \\ const b: c_int = switch (A) {
395 \\ i32 => 1,
396 \\ bool => 2,
397 \\ f16...f64 => 3,
398 \\ else => 4,
399 \\ };
400 \\ _ = b;
401 \\}
402 , &.{
403 ":3:30: error: ranges not allowed when switching on type 'type'",
404 ":6:12: note: range here",
405 });
406
407 // Switch expression has unreachable else prong.
408 case.addError(
409 \\pub export fn main() c_int {
410 \\ var a: u2 = 0;
411 \\ const b: i32 = switch (a) {
412 \\ 0 => 10,
413 \\ 1 => 20,
414 \\ 2 => 30,
415 \\ 3 => 40,
416 \\ else => 50,
417 \\ };
418 \\ _ = b;
419 \\}
420 , &.{
421 ":8:14: error: unreachable else prong; all cases already handled",
422 });
423 }
424 //{
425 // var case = ctx.exeFromCompiledC("optionals", .{});
426
427 // // Simple while loop
428 // case.addCompareOutput(
429 // \\pub export fn main() c_int {
430 // \\ var count: c_int = 0;
431 // \\ var opt_ptr: ?*c_int = &count;
432 // \\ while (opt_ptr) |_| : (count += 1) {
433 // \\ if (count == 4) opt_ptr = null;
434 // \\ }
435 // \\ return count - 5;
436 // \\}
437 // , "");
438
439 // // Same with non pointer optionals
440 // case.addCompareOutput(
441 // \\pub export fn main() c_int {
442 // \\ var count: c_int = 0;
443 // \\ var opt_ptr: ?c_int = count;
444 // \\ while (opt_ptr) |_| : (count += 1) {
445 // \\ if (count == 4) opt_ptr = null;
446 // \\ }
447 // \\ return count - 5;
448 // \\}
449 // , "");
450 //}
451
452 {
453 var case = ctx.exeFromCompiledC("errors", .{});
454 case.addCompareOutput(
455 \\pub export fn main() c_int {
456 \\ var e1 = error.Foo;
457 \\ var e2 = error.Bar;
458 \\ assert(e1 != e2);
459 \\ assert(e1 == error.Foo);
460 \\ assert(e2 == error.Bar);
461 \\ return 0;
462 \\}
463 \\fn assert(b: bool) void {
464 \\ if (!b) unreachable;
465 \\}
466 , "");
467 case.addCompareOutput(
468 \\pub export fn main() c_int {
469 \\ var e: anyerror!c_int = 0;
470 \\ const i = e catch 69;
471 \\ return i;
472 \\}
473 , "");
474 case.addCompareOutput(
475 \\pub export fn main() c_int {
476 \\ var e: anyerror!c_int = error.Foo;
477 \\ const i = e catch 69;
478 \\ return 69 - i;
479 \\}
480 , "");
481 case.addCompareOutput(
482 \\const E = error{e};
483 \\const S = struct { x: u32 };
484 \\fn f() E!u32 {
485 \\ const x = (try @as(E!S, S{ .x = 1 })).x;
486 \\ return x;
487 \\}
488 \\pub export fn main() c_int {
489 \\ const x = f() catch @as(u32, 0);
490 \\ if (x != 1) unreachable;
491 \\ return 0;
492 \\}
493 , "");
494 }
495
496 {
497 var case = ctx.exeFromCompiledC("structs", .{});
498 case.addError(
499 \\const Point = struct { x: i32, y: i32 };
500 \\pub export fn main() c_int {
501 \\ var p: Point = .{
502 \\ .y = 24,
503 \\ .x = 12,
504 \\ .y = 24,
505 \\ };
506 \\ return p.y - p.x - p.x;
507 \\}
508 , &.{
509 ":6:10: error: duplicate field",
510 ":4:10: note: other field here",
511 });
512 case.addError(
513 \\const Point = struct { x: i32, y: i32 };
514 \\pub export fn main() c_int {
515 \\ var p: Point = .{
516 \\ .y = 24,
517 \\ };
518 \\ return p.y - p.x - p.x;
519 \\}
520 , &.{
521 ":3:21: error: missing struct field: x",
522 ":1:15: note: struct 'tmp.Point' declared here",
523 });
524 case.addError(
525 \\const Point = struct { x: i32, y: i32 };
526 \\pub export fn main() c_int {
527 \\ var p: Point = .{
528 \\ .x = 12,
529 \\ .y = 24,
530 \\ .z = 48,
531 \\ };
532 \\ return p.y - p.x - p.x;
533 \\}
534 , &.{
535 ":6:10: error: no field named 'z' in struct 'tmp.Point'",
536 ":1:15: note: struct declared here",
537 });
538 case.addCompareOutput(
539 \\const Point = struct { x: i32, y: i32 };
540 \\pub export fn main() c_int {
541 \\ var p: Point = .{
542 \\ .x = 12,
543 \\ .y = 24,
544 \\ };
545 \\ return p.y - p.x - p.x;
546 \\}
547 , "");
548 case.addCompareOutput(
549 \\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 };
550 \\pub export fn main() c_int {
551 \\ var p: Point = .{
552 \\ .x = 18,
553 \\ .y = 24,
554 \\ .z = 1,
555 \\ .a = 2,
556 \\ .b = 3,
557 \\ };
558 \\ return p.y - p.x - p.z - p.a - p.b;
559 \\}
560 , "");
561 }
562
563 {
564 var case = ctx.exeFromCompiledC("unions", .{});
565
566 case.addError(
567 \\const U = union {
568 \\ a: u32,
569 \\ b
570 \\};
571 , &.{
572 ":3:5: error: union field missing type",
573 });
574
575 case.addError(
576 \\const E = enum { a, b };
577 \\const U = union(E) {
578 \\ a: u32 = 1,
579 \\ b: f32 = 2,
580 \\};
581 , &.{
582 ":2:11: error: explicitly valued tagged union requires inferred enum tag type",
583 ":3:14: note: tag value specified here",
584 });
585
586 case.addError(
587 \\const U = union(enum) {
588 \\ a: u32 = 1,
589 \\ b: f32 = 2,
590 \\};
591 , &.{
592 ":1:11: error: explicitly valued tagged union missing integer tag type",
593 ":2:14: note: tag value specified here",
594 });
595 }
596
597 {
598 var case = ctx.exeFromCompiledC("enums", .{});
599
600 case.addError(
601 \\const E1 = packed enum { a, b, c };
602 \\const E2 = extern enum { a, b, c };
603 \\export fn foo() void {
604 \\ _ = E1.a;
605 \\}
606 \\export fn bar() void {
607 \\ _ = E2.a;
608 \\}
609 , &.{
610 ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
611 ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
612 });
613
614 // comptime and types are caught in AstGen.
615 case.addError(
616 \\const E1 = enum {
617 \\ a,
618 \\ comptime b,
619 \\ c,
620 \\};
621 \\const E2 = enum {
622 \\ a,
623 \\ b: i32,
624 \\ c,
625 \\};
626 \\export fn foo() void {
627 \\ _ = E1.a;
628 \\}
629 \\export fn bar() void {
630 \\ _ = E2.a;
631 \\}
632 , &.{
633 ":3:5: error: enum fields cannot be marked comptime",
634 ":8:8: error: enum fields do not have types",
635 ":6:12: note: consider 'union(enum)' here to make it a tagged union",
636 });
637
638 // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch
639 case.addCompareOutput(
640 \\const Number = enum { One, Two, Three };
641 \\
642 \\pub export fn main() c_int {
643 \\ var number1 = Number.One;
644 \\ var number2: Number = .Two;
645 \\ const number3 = @intToEnum(Number, 2);
646 \\ if (number1 == number2) return 1;
647 \\ if (number2 == number3) return 1;
648 \\ if (@enumToInt(number1) != 0) return 1;
649 \\ if (@enumToInt(number2) != 1) return 1;
650 \\ if (@enumToInt(number3) != 2) return 1;
651 \\ var x: Number = .Two;
652 \\ if (number2 != x) return 1;
653 \\ switch (x) {
654 \\ .One => return 1,
655 \\ .Two => return 0,
656 \\ number3 => return 2,
657 \\ }
658 \\}
659 , "");
660
661 // Specifying alignment is a parse error.
662 // This also tests going from a successful build to a parse error.
663 case.addError(
664 \\const E1 = enum {
665 \\ a,
666 \\ b align(4),
667 \\ c,
668 \\};
669 \\export fn foo() void {
670 \\ _ = E1.a;
671 \\}
672 , &.{
673 ":3:13: error: enum fields cannot be aligned",
674 });
675
676 // Redundant non-exhaustive enum mark.
677 // This also tests going from a parse error to an AstGen error.
678 case.addError(
679 \\const E1 = enum {
680 \\ a,
681 \\ _,
682 \\ b,
683 \\ c,
684 \\ _,
685 \\};
686 \\export fn foo() void {
687 \\ _ = E1.a;
688 \\}
689 , &.{
690 ":6:5: error: redundant non-exhaustive enum mark",
691 ":3:5: note: other mark here",
692 });
693
694 case.addError(
695 \\const E1 = enum {
696 \\ a,
697 \\ b,
698 \\ c,
699 \\ _ = 10,
700 \\};
701 \\export fn foo() void {
702 \\ _ = E1.a;
703 \\}
704 , &.{
705 ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
706 });
707
708 case.addError(
709 \\const E1 = enum { a, b, _ };
710 \\export fn foo() void {
711 \\ _ = E1.a;
712 \\}
713 , &.{
714 ":1:12: error: non-exhaustive enum missing integer tag type",
715 ":1:25: note: marked non-exhaustive here",
716 });
717
718 case.addError(
719 \\const E1 = enum { a, b, c, b, d };
720 \\pub export fn main() c_int {
721 \\ _ = E1.a;
722 \\}
723 , &.{
724 ":1:28: error: duplicate enum field 'b'",
725 ":1:22: note: other field here",
726 });
727
728 case.addError(
729 \\pub export fn main() c_int {
730 \\ const a = true;
731 \\ _ = @enumToInt(a);
732 \\}
733 , &.{
734 ":3:20: error: expected enum or tagged union, found 'bool'",
735 });
736
737 case.addError(
738 \\pub export fn main() c_int {
739 \\ const a = 1;
740 \\ _ = @intToEnum(bool, a);
741 \\}
742 , &.{
743 ":3:20: error: expected enum, found 'bool'",
744 });
745
746 case.addError(
747 \\const E = enum { a, b, c };
748 \\pub export fn main() c_int {
749 \\ _ = @intToEnum(E, 3);
750 \\}
751 , &.{
752 ":3:9: error: enum 'tmp.E' has no tag with value '3'",
753 ":1:11: note: enum declared here",
754 });
755
756 case.addError(
757 \\const E = enum { a, b, c };
758 \\pub export fn main() c_int {
759 \\ var x: E = .a;
760 \\ switch (x) {
761 \\ .a => {},
762 \\ .c => {},
763 \\ }
764 \\}
765 , &.{
766 ":4:5: error: switch must handle all possibilities",
767 ":1:21: note: unhandled enumeration value: 'b'",
768 ":1:11: note: enum 'tmp.E' declared here",
769 });
770
771 case.addError(
772 \\const E = enum { a, b, c };
773 \\pub export fn main() c_int {
774 \\ var x: E = .a;
775 \\ switch (x) {
776 \\ .a => {},
777 \\ .b => {},
778 \\ .b => {},
779 \\ .c => {},
780 \\ }
781 \\}
782 , &.{
783 ":7:10: error: duplicate switch value",
784 ":6:10: note: previous value here",
785 });
786
787 case.addError(
788 \\const E = enum { a, b, c };
789 \\pub export fn main() c_int {
790 \\ var x: E = .a;
791 \\ switch (x) {
792 \\ .a => {},
793 \\ .b => {},
794 \\ .c => {},
795 \\ else => {},
796 \\ }
797 \\}
798 , &.{
799 ":8:14: error: unreachable else prong; all cases already handled",
800 });
801
802 case.addError(
803 \\const E = enum { a, b, c };
804 \\pub export fn main() c_int {
805 \\ var x: E = .a;
806 \\ switch (x) {
807 \\ .a => {},
808 \\ .b => {},
809 \\ _ => {},
810 \\ }
811 \\}
812 , &.{
813 ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums",
814 ":7:11: note: '_' prong here",
815 });
816
817 case.addError(
818 \\const E = enum { a, b, c };
819 \\pub export fn main() c_int {
820 \\ _ = E.d;
821 \\}
822 , &.{
823 ":3:11: error: enum 'tmp.E' has no member named 'd'",
824 ":1:11: note: enum declared here",
825 });
826
827 case.addError(
828 \\const E = enum { a, b, c };
829 \\pub export fn main() c_int {
830 \\ var x: E = .d;
831 \\ _ = x;
832 \\}
833 , &.{
834 ":3:17: error: no field named 'd' in enum 'tmp.E'",
835 ":1:11: note: enum declared here",
836 });
837 }
838
839 {
840 var case = ctx.exeFromCompiledC("shift right + left", .{});
841 case.addCompareOutput(
842 \\pub export fn main() c_int {
843 \\ var i: u32 = 16;
844 \\ assert(i >> 1, 8);
845 \\ return 0;
846 \\}
847 \\fn assert(a: u32, b: u32) void {
848 \\ if (a != b) unreachable;
849 \\}
850 , "");
851
852 case.addCompareOutput(
853 \\pub export fn main() c_int {
854 \\ var i: u32 = 16;
855 \\ assert(i << 1, 32);
856 \\ return 0;
857 \\}
858 \\fn assert(a: u32, b: u32) void {
859 \\ if (a != b) unreachable;
860 \\}
861 , "");
862 }
863
864 {
865 var case = ctx.exeFromCompiledC("inferred error sets", .{});
866
867 case.addCompareOutput(
868 \\pub export fn main() c_int {
869 \\ if (foo()) |_| {
870 \\ @panic("test fail");
871 \\ } else |err| {
872 \\ if (err != error.ItBroke) {
873 \\ @panic("test fail");
874 \\ }
875 \\ }
876 \\ return 0;
877 \\}
878 \\fn foo() !void {
879 \\ return error.ItBroke;
880 \\}
881 , "");
882 }
883
884 {
885 // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64)
886 var case = ctx.exeFromCompiledC("add/sub wrapping operations", .{});
887 case.addCompareOutput(
888 \\pub export fn main() c_int {
889 \\ // Addition
890 \\ if (!add_u3(1, 1, 2)) return 1;
891 \\ if (!add_u3(7, 1, 0)) return 1;
892 \\ if (!add_i3(1, 1, 2)) return 1;
893 \\ if (!add_i3(3, 2, -3)) return 1;
894 \\ if (!add_i3(-3, -2, 3)) return 1;
895 \\ if (!add_c_int(1, 1, 2)) return 1;
896 \\ // TODO enable these when stage2 supports std.math.maxInt
897 \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1;
898 \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1;
899 \\
900 \\ // Subtraction
901 \\ if (!sub_u3(2, 1, 1)) return 1;
902 \\ if (!sub_u3(0, 1, 7)) return 1;
903 \\ if (!sub_i3(2, 1, 1)) return 1;
904 \\ if (!sub_i3(3, -2, -3)) return 1;
905 \\ if (!sub_i3(-3, 2, 3)) return 1;
906 \\ if (!sub_c_int(2, 1, 1)) return 1;
907 \\ // TODO enable these when stage2 supports std.math.maxInt
908 \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1;
909 \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1;
910 \\
911 \\ return 0;
912 \\}
913 \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool {
914 \\ return expected == lhs +% rhs;
915 \\}
916 \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool {
917 \\ return expected == lhs +% rhs;
918 \\}
919 \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
920 \\ return expected == lhs +% rhs;
921 \\}
922 \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool {
923 \\ return expected == lhs -% rhs;
924 \\}
925 \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool {
926 \\ return expected == lhs -% rhs;
927 \\}
928 \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
929 \\ return expected == lhs -% rhs;
930 \\}
931 , "");
932 }
933
934 {
935 var case = ctx.exeFromCompiledC("@rem", linux_x64);
936 case.addCompareOutput(
937 \\fn assert(ok: bool) void {
938 \\ if (!ok) unreachable;
939 \\}
940 \\fn rem(lhs: i32, rhs: i32, expected: i32) bool {
941 \\ return @rem(lhs, rhs) == expected;
942 \\}
943 \\pub export fn main() c_int {
944 \\ assert(rem(-5, 3, -2));
945 \\ assert(rem(5, 3, 2));
946 \\ return 0;
947 \\}
948 , "");
949 }
950
951 ctx.h("simple header", linux_x64,
952 \\export fn start() void{}
953 ,
954 \\zig_extern void start(void);
955 \\
956 );
957 ctx.h("header with single param function", linux_x64,
958 \\export fn start(a: u8) void{
959 \\ _ = a;
960 \\}
961 ,
962 \\zig_extern void start(uint8_t const a0);
963 \\
964 );
965 ctx.h("header with multiple param function", linux_x64,
966 \\export fn start(a: u8, b: u8, c: u8) void{
967 \\ _ = a; _ = b; _ = c;
968 \\}
969 ,
970 \\zig_extern void start(uint8_t const a0, uint8_t const a1, uint8_t const a2);
971 \\
972 );
973 ctx.h("header with u32 param function", linux_x64,
974 \\export fn start(a: u32) void{ _ = a; }
975 ,
976 \\zig_extern void start(uint32_t const a0);
977 \\
978 );
979 ctx.h("header with usize param function", linux_x64,
980 \\export fn start(a: usize) void{ _ = a; }
981 ,
982 \\zig_extern void start(uintptr_t const a0);
983 \\
984 );
985 ctx.h("header with bool param function", linux_x64,
986 \\export fn start(a: bool) void{_ = a;}
987 ,
988 \\zig_extern void start(bool const a0);
989 \\
990 );
991 ctx.h("header with noreturn function", linux_x64,
992 \\export fn start() noreturn {
993 \\ unreachable;
994 \\}
995 ,
996 \\zig_extern zig_noreturn void start(void);
997 \\
998 );
999 ctx.h("header with multiple functions", linux_x64,
1000 \\export fn a() void{}
1001 \\export fn b() void{}
1002 \\export fn c() void{}
1003 ,
1004 \\zig_extern void a(void);
1005 \\zig_extern void b(void);
1006 \\zig_extern void c(void);
1007 \\
1008 );
1009 ctx.h("header with multiple includes", linux_x64,
1010 \\export fn start(a: u32, b: usize) void{ _ = a; _ = b; }
1011 ,
1012 \\zig_extern void start(uint32_t const a0, uintptr_t const a1);
1013 \\
1014 );
1015}
test/stage2/nvptx.zig deleted-107
......@@ -1,107 +0,0 @@
1const std = @import("std");
2const TestContext = @import("../../src/test.zig").TestContext;
3
4pub fn addCases(ctx: *TestContext) !void {
5 {
6 var case = addPtx(ctx, "nvptx: simple addition and subtraction");
7
8 case.compiles(
9 \\fn add(a: i32, b: i32) i32 {
10 \\ return a + b;
11 \\}
12 \\
13 \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void {
14 \\ const x = add(a, 7);
15 \\ var y = add(2, 0);
16 \\ y -= x;
17 \\ out.* = y;
18 \\}
19 );
20 }
21
22 {
23 var case = addPtx(ctx, "nvptx: read special registers");
24
25 case.compiles(
26 \\fn threadIdX() u32 {
27 \\ return asm ("mov.u32 \t%[r], %tid.x;"
28 \\ : [r] "=r" (-> u32),
29 \\ );
30 \\}
31 \\
32 \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void {
33 \\ const i = threadIdX();
34 \\ out[i] = a[i] + 7;
35 \\}
36 );
37 }
38
39 {
40 var case = addPtx(ctx, "nvptx: address spaces");
41
42 case.compiles(
43 \\var x: i32 addrspace(.global) = 0;
44 \\
45 \\pub export fn increment(out: *i32) callconv(.PtxKernel) void {
46 \\ x += 1;
47 \\ out.* = x;
48 \\}
49 );
50 }
51
52 {
53 var case = addPtx(ctx, "nvptx: reduce in shared mem");
54 case.compiles(
55 \\fn threadIdX() u32 {
56 \\ return asm ("mov.u32 \t%[r], %tid.x;"
57 \\ : [r] "=r" (-> u32),
58 \\ );
59 \\}
60 \\
61 \\ var _sdata: [1024]f32 addrspace(.shared) = undefined;
62 \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void {
63 \\ var sdata = @addrSpaceCast(.generic, &_sdata);
64 \\ const tid: u32 = threadIdX();
65 \\ var sum = d_x[tid];
66 \\ sdata[tid] = sum;
67 \\ asm volatile ("bar.sync \t0;");
68 \\ var s: u32 = 512;
69 \\ while (s > 0) : (s = s >> 1) {
70 \\ if (tid < s) {
71 \\ sum += sdata[tid + s];
72 \\ sdata[tid] = sum;
73 \\ }
74 \\ asm volatile ("bar.sync \t0;");
75 \\ }
76 \\
77 \\ if (tid == 0) {
78 \\ out.* = sum;
79 \\ }
80 \\ }
81 );
82 }
83}
84
85const nvptx_target = std.zig.CrossTarget{
86 .cpu_arch = .nvptx64,
87 .os_tag = .cuda,
88};
89
90pub fn addPtx(
91 ctx: *TestContext,
92 name: []const u8,
93) *TestContext.Case {
94 ctx.cases.append(TestContext.Case{
95 .name = name,
96 .target = nvptx_target,
97 .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator),
98 .output_mode = .Obj,
99 .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator),
100 .deps = std.ArrayList(TestContext.DepModule).init(ctx.cases.allocator),
101 .link_libc = false,
102 .backend = .llvm,
103 // Bug in Debug mode
104 .optimize_mode = .ReleaseSafe,
105 }) catch @panic("out of memory");
106 return &ctx.cases.items[ctx.cases.items.len - 1];
107}
test/tests.zig+27
......@@ -1055,3 +1055,30 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S
10551055 }
10561056 return step;
10571057}
1058
1059pub fn addCases(
1060 b: *std.Build,
1061 parent_step: *Step,
1062 opt_test_filter: ?[]const u8,
1063 check_case_exe: *std.Build.CompileStep,
1064) !void {
1065 const arena = b.allocator;
1066 const gpa = b.allocator;
1067
1068 var cases = @import("src/Cases.zig").init(gpa, arena);
1069
1070 var dir = try b.build_root.handle.openIterableDir("test/cases", .{});
1071 defer dir.close();
1072
1073 cases.addFromDir(dir);
1074 try @import("cases.zig").addCases(&cases);
1075
1076 const cases_dir_path = try b.build_root.join(b.allocator, &.{ "test", "cases" });
1077 cases.lowerToBuildSteps(
1078 b,
1079 parent_step,
1080 opt_test_filter,
1081 cases_dir_path,
1082 check_case_exe,
1083 );
1084}