authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-10-14 11:11:03-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-21 10:05:29-07:00
log809fcd4910552b57e0a61567fd5706719315bb22
treebbfc7d3401a9fc9359eef9417572591fad493770
parent203bbaee07dfd8e67ea102dbb29afb75be246e53

test/Runner: support more syntax for command contents


64 files changed, 2587 insertions(+), 2711 deletions(-)

build.zig+8-10
......@@ -793,7 +793,7 @@ pub fn build(b: *std.Build) !void {
793793 const runner = b.addExecutable(.{
794794 .name = "runner",
795795 .root_module = b.createModule(.{
796 .root_source_file = b.path("test/src/Runner.zig"),
796 .root_source_file = b.path("test/Runner.zig"),
797797 .target = b.graph.host,
798798 }),
799799 });
......@@ -813,17 +813,15 @@ pub fn build(b: *std.Build) !void {
813813 .lldb = b.option([]const u8, "lldb", "path to lldb binary"),
814814 };
815815
816 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation tests");
817 try tests.addRunnerTests(b, runner, test_incremental_step, "test/incremental", tests.incremental_matrix, runner_opts);
818 test_step.dependOn(test_incremental_step);
819
820816 const test_debugger_step = b.step("test-debugger", "Run the debugger tests");
821 if (runner_opts.gdb == null and runner_opts.lldb == null) {
822 test_debugger_step.dependOn(&b.addFail("test-debugger requires -Dgdb and/or -Dlldb").step);
823 } else {
824 try tests.addRunnerTests(b, runner, test_debugger_step, "test/debugger", tests.debugger_matrix, runner_opts);
817 if (runner_opts.gdb != null or runner_opts.lldb != null) {
818 try tests.addRunnerTests(b, runner, test_debugger_step, "test/debugger", @import("test/debugger/matrix.zon"), runner_opts);
825819 test_step.dependOn(test_debugger_step);
826 }
820 } else test_debugger_step.dependOn(&b.addFail("test-debugger requires -Dgdb and/or -Dlldb").step);
821
822 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation tests");
823 try tests.addRunnerTests(b, runner, test_incremental_step, "test/incremental", @import("test/incremental/matrix.zon"), runner_opts);
824 test_step.dependOn(test_incremental_step);
827825}
828826
829827fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
lib/compiler/Maker/Package/Manifest.zig+3-89
......@@ -494,95 +494,9 @@ const Parse = struct {
494494 };
495495 switch (result) {
496496 .success => {},
497 .failure => |err| try p.appendStrLitError(err, token, bytes, offset),
498 }
499 }
500
501 /// TODO: try to DRY this with AstGen.failWithStrLitError
502 fn appendStrLitError(
503 p: *Parse,
504 err: std.zig.string_literal.Error,
505 token: Ast.TokenIndex,
506 bytes: []const u8,
507 offset: u32,
508 ) Allocator.Error!void {
509 const raw_string = bytes[offset..];
510 switch (err) {
511 .invalid_escape_character => |bad_index| {
512 try p.appendErrorOff(
513 token,
514 offset + @as(u32, @intCast(bad_index)),
515 "invalid escape character: '{c}'",
516 .{raw_string[bad_index]},
517 );
518 },
519 .expected_hex_digit => |bad_index| {
520 try p.appendErrorOff(
521 token,
522 offset + @as(u32, @intCast(bad_index)),
523 "expected hex digit, found '{c}'",
524 .{raw_string[bad_index]},
525 );
526 },
527 .empty_unicode_escape_sequence => |bad_index| {
528 try p.appendErrorOff(
529 token,
530 offset + @as(u32, @intCast(bad_index)),
531 "empty unicode escape sequence",
532 .{},
533 );
534 },
535 .expected_hex_digit_or_rbrace => |bad_index| {
536 try p.appendErrorOff(
537 token,
538 offset + @as(u32, @intCast(bad_index)),
539 "expected hex digit or '}}', found '{c}'",
540 .{raw_string[bad_index]},
541 );
542 },
543 .invalid_unicode_codepoint => |bad_index| {
544 try p.appendErrorOff(
545 token,
546 offset + @as(u32, @intCast(bad_index)),
547 "unicode escape does not correspond to a unicode code point",
548 .{},
549 );
550 },
551 .expected_lbrace => |bad_index| {
552 try p.appendErrorOff(
553 token,
554 offset + @as(u32, @intCast(bad_index)),
555 "expected '{{', found '{c}",
556 .{raw_string[bad_index]},
557 );
558 },
559 .expected_rbrace => |bad_index| {
560 try p.appendErrorOff(
561 token,
562 offset + @as(u32, @intCast(bad_index)),
563 "expected '}}', found '{c}",
564 .{raw_string[bad_index]},
565 );
566 },
567 .expected_single_quote => |bad_index| {
568 try p.appendErrorOff(
569 token,
570 offset + @as(u32, @intCast(bad_index)),
571 "expected single quote ('), found '{c}",
572 .{raw_string[bad_index]},
573 );
574 },
575 .invalid_character => |bad_index| {
576 try p.appendErrorOff(
577 token,
578 offset + @as(u32, @intCast(bad_index)),
579 "invalid byte in string or character literal: '{c}'",
580 .{raw_string[bad_index]},
581 );
582 },
583 .empty_char_literal => {
584 try p.appendErrorOff(token, offset, "empty character literal", .{});
585 },
497 .failure => |err| try p.appendErrorOff(token, @intCast(offset + err.offset()), "{f}", .{
498 err.fmt(bytes[offset..]),
499 }),
586500 }
587501 }
588502
lib/std/zig/string_literal.zig+17-1
......@@ -38,6 +38,11 @@ pub const Error = union(enum) {
3838 invalid_character: usize,
3939 /// `''`. Not returned for string literals.
4040 empty_char_literal,
41 /// The input ended before the end of the literal.
42 end_of_input: usize,
43 /// The literal ended before the end of the input. There was an otherwise successful
44 /// parse of a prefix of the input of length given by this payload written to the writer.
45 end_of_literal: usize,
4146
4247 const FormatMessage = struct {
4348 err: Error,
......@@ -83,6 +88,12 @@ pub const Error = union(enum) {
8388 .empty_char_literal => try writer.writeAll(
8489 "empty character literal",
8590 ),
91 .end_of_input => try writer.writeAll(
92 "expected end of string literal",
93 ),
94 .end_of_literal => try writer.writeAll(
95 "unexpected end of string literal",
96 ),
8697 }
8798 }
8899
......@@ -104,6 +115,8 @@ pub const Error = union(enum) {
104115 .expected_rbrace,
105116 .expected_single_quote,
106117 .invalid_character,
118 .end_of_input,
119 .end_of_literal,
107120 => |n| n,
108121 .empty_char_literal => 0,
109122 };
......@@ -339,13 +352,16 @@ pub fn parseWrite(writer: *Writer, bytes: []const u8) Writer.Error!Result {
339352 try writer.writeAll(buf[0..len]);
340353 } else {
341354 try writer.writeByte(@as(u8, @intCast(codepoint)));
355 if (index == bytes.len) return .{ .failure = .{ .end_of_input = index } };
342356 }
343357 },
344358 .failure => |err| return .{ .failure = err },
345359 }
346360 },
347361 '\n' => return .{ .failure = .{ .invalid_character = index } },
348 '"' => return .success,
362 '"' => return if (index < bytes.len - 1) .{
363 .failure = .{ .end_of_literal = index },
364 } else .success,
349365 else => {
350366 try writer.writeByte(b);
351367 index += 1;
test/Runner.zig created+2240
......@@ -0,0 +1,2240 @@
1const assert = std.debug.assert;
2const builtin = @import("builtin");
3const std = @import("std");
4const Runner = @This();
5
6gpa: std.mem.Allocator,
7arena: *std.heap.ArenaAllocator,
8environ_map: std.process.Environ.Map,
9io: std.Io,
10prog_node: std.Progress.Node,
11args: Args,
12host: std.Target,
13server: std.zig.Server,
14eb_wip: std.zig.ErrorBundle.Wip,
15
16const Args = struct {
17 @"test": ?struct {
18 input_dir: std.zig.Server.Message.InputDir,
19 dir: std.Io.Dir,
20 },
21 manifest_file: ?std.Io.File,
22 zig_exe: ?std.Io.File,
23 lib_dir: ?std.Io.Dir,
24 src_dir: ?std.Io.Dir,
25 targets: std.ArrayList(u32),
26 target_bytes: std.ArrayList(u8),
27
28 libc_runtimes_dir: ?std.Io.Dir,
29 enable_darling: bool,
30 enable_qemu: bool,
31 enable_rosetta: bool,
32 enable_wasmtime: bool,
33 enable_wine: bool,
34
35 gdb_exe: ?std.Io.File,
36 lldb_exe: ?std.Io.File,
37
38 quiet: bool,
39
40 fn deinit(args: *Args, gpa: std.mem.Allocator) void {
41 args.targets.deinit(gpa);
42 args.target_bytes.deinit(gpa);
43 args.* = undefined;
44 }
45
46 fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void {
47 try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1);
48 try args.targets.append(gpa, @intCast(args.target_bytes.items.len));
49 args.target_bytes.appendSliceAssumeCapacity(target);
50 args.target_bytes.appendAssumeCapacity(0);
51 }
52};
53
54fn deinit(runner: *Runner) void {
55 runner.environ_map.deinit();
56 runner.prog_node.end();
57 runner.args.deinit(runner.gpa);
58 runner.eb_wip.deinit();
59 runner.* = undefined;
60}
61
62const FailError = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable;
63fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) FailError {
64 return runner.failString(try runner.eb_wip.printString(fmt, args));
65}
66fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) FailError {
67 try runner.eb_wip.addRootErrorMessage(.{ .msg = msg });
68 return error.AlreadyReported;
69}
70
71pub const ProtocolError = error{
72 ServerReadFailed,
73 ServerWriteFailed,
74 ServerEndOfStream,
75} || FailError;
76pub fn runServer(runner: *Runner) ProtocolError {
77 runner.server.serveStringMessage(.zig_version, builtin.zig_version_string) catch |err| switch (err) {
78 error.WriteFailed => return error.ServerWriteFailed,
79 };
80 while (true) {
81 _ = runner.arena.reset(.retain_capacity);
82 const arena = runner.arena.allocator();
83 const hdr = runner.server.receiveMessage() catch |err| switch (err) {
84 error.ReadFailed => return error.ServerReadFailed,
85 error.EndOfStream => return error.ServerEndOfStream,
86 };
87 switch (hdr.tag) {
88 else => |tag| return runner.fail("unsupported message: {t}", .{tag}),
89 .exit => std.process.exit(0),
90 .args => {
91 const args_body = try arena.alloc(u8, hdr.bytes_len);
92 runner.server.in.readSliceAll(args_body) catch unreachable;
93 const State = enum {
94 positional,
95 @"--zig",
96 @"--lib",
97 @"--src",
98 @"--target",
99 @"--libc-runtimes",
100 @"--gdb",
101 @"--lldb",
102 };
103 var state: State = .positional;
104 const state_expected: std.enums.EnumArray(
105 State,
106 enum { path, dir, file, string },
107 ) = .init(.{
108 .positional = .path,
109 .@"--zig" = .file,
110 .@"--lib" = .dir,
111 .@"--src" = .dir,
112 .@"--target" = .string,
113 .@"--libc-runtimes" = .dir,
114 .@"--gdb" = .file,
115 .@"--lldb" = .file,
116 });
117 var input_dir: std.zig.Server.Message.InputDir = .cwd;
118 var args_body_offset: usize = 0;
119 while (args_body.len - args_body_offset > 0) {
120 const arg: std.zig.Client.Message.Arg =
121 @fromBackingInt(args_body[args_body_offset]);
122 args_body_offset += 1;
123 arg: switch (arg) {
124 .string => {
125 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
126 const string = args_body[args_body_offset..end];
127 args_body_offset = end + 1;
128 switch (state) {
129 .positional => if (std.mem.eql(u8, string, "--target")) {
130 state = .@"--target";
131 continue;
132 } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| {
133 try runner.args.addTarget(runner.gpa, target);
134 } else if (std.mem.eql(u8, string, "--libc-runtimes")) {
135 state = .@"--libc-runtimes";
136 continue;
137 } else if (std.mem.eql(u8, string, "-fdarling")) {
138 runner.args.enable_darling = true;
139 } else if (std.mem.eql(u8, string, "-fqemu")) {
140 runner.args.enable_qemu = true;
141 } else if (std.mem.eql(u8, string, "-frosetta")) {
142 runner.args.enable_rosetta = true;
143 } else if (std.mem.eql(u8, string, "-fwasmtime")) {
144 runner.args.enable_wasmtime = true;
145 } else if (std.mem.eql(u8, string, "-fwine")) {
146 runner.args.enable_wine = true;
147 } else if (std.mem.eql(u8, string, "--gdb")) {
148 state = .@"--gdb";
149 continue;
150 } else if (std.mem.eql(u8, string, "--lldb")) {
151 state = .@"--lldb";
152 continue;
153 } else if (std.mem.eql(u8, string, "--quiet")) {
154 runner.args.quiet = true;
155 },
156 .@"--target" => try runner.args.addTarget(runner.gpa, string),
157 else => return runner.fail(
158 "\"{t}\" expected {t}, got {t}",
159 .{ state, state_expected.get(state), arg },
160 ),
161 }
162 },
163 .prefix => {
164 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
165 const string = args_body[args_body_offset..end];
166 args_body_offset = end + 1;
167 assert(state == .positional);
168 state = if (std.mem.eql(u8, string, "--zig="))
169 .@"--zig"
170 else if (std.mem.eql(u8, string, "--lib="))
171 .@"--lib"
172 else if (std.mem.eql(u8, string, "--src="))
173 .@"--src"
174 else if (std.mem.eql(u8, string, "--libc-runtimes="))
175 .@"--libc-runtimes"
176 else if (std.mem.eql(u8, string, "--gdb="))
177 .@"--gdb"
178 else if (std.mem.eql(u8, string, "--lldb="))
179 .@"--lldb"
180 else
181 return runner.fail("unsupported arg prefix: {q}", .{string});
182 continue;
183 },
184 .suffix => {
185 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
186 const string = args_body[args_body_offset..end];
187 args_body_offset = end + 1;
188 return runner.fail("unsupported arg suffix: {q}", .{string});
189 },
190 .input_dir => {
191 input_dir = @fromBackingInt(@backingInt(input_dir) + 1);
192 continue :arg .output_dir;
193 },
194 .output_dir => {
195 const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast(
196 args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)],
197 );
198 args_body_offset += @sizeOf(std.Io.Dir.Handle);
199 const dir = runner.io.vtable.inheritParentDir(
200 runner.io.userdata,
201 dir_handle.*,
202 ) catch |err| switch (err) {
203 error.Canceled => |e| return e,
204 else => |e| return runner.fail(
205 "unable to inherit parent dir: {t}",
206 .{e},
207 ),
208 };
209 switch (state) {
210 .positional => {
211 if (runner.args.@"test" != null or
212 runner.args.manifest_file != null) return runner.fail(
213 "{t} specified multiple times",
214 .{state},
215 );
216 runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir };
217 const manifest_path = "manifest";
218 runner.args.manifest_file = dir.openFile(
219 runner.io,
220 manifest_path,
221 .{},
222 ) catch |err| switch (err) {
223 error.Canceled => |e| return e,
224 else => |e| return runner.fail(
225 "unable to open manifest file: {t}",
226 .{e},
227 ),
228 };
229 try runner.discoverInput(input_dir, manifest_path);
230 },
231 .@"--lib" => {
232 if (runner.args.lib_dir != null) return runner.fail(
233 "\"{t}\" specified multiple times",
234 .{state},
235 );
236 runner.args.lib_dir = dir;
237 },
238 .@"--src" => {
239 if (runner.args.src_dir != null) return runner.fail(
240 "\"{t}\" specified multiple times",
241 .{state},
242 );
243 runner.args.src_dir = dir;
244 },
245 .@"--libc-runtimes" => {
246 if (runner.args.libc_runtimes_dir != null) return runner.fail(
247 "\"{t}\" specified multiple times",
248 .{state},
249 );
250 runner.args.libc_runtimes_dir = dir;
251 },
252 else => return runner.fail(
253 "\"{t}\" expected {t}, got {t}",
254 .{ state, state_expected.get(state), arg },
255 ),
256 }
257 },
258 .input_file, .input_file_content, .output_file => {
259 const file_handle: *align(1) const std.Io.File.Handle = @ptrCast(
260 args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)],
261 );
262 args_body_offset += @sizeOf(std.Io.File.Handle);
263 const file = runner.io.vtable.inheritParentFile(
264 runner.io.userdata,
265 file_handle.*,
266 .{ .nonblocking = false },
267 ) catch |err| switch (err) {
268 error.Canceled => |e| return e,
269 else => |e| return runner.fail(
270 "unable to inherit parent file: {t}",
271 .{e},
272 ),
273 };
274 switch (state) {
275 .positional => {
276 if (runner.args.@"test" != null or
277 runner.args.manifest_file != null) return runner.fail(
278 "{t} specified multiple times",
279 .{state},
280 );
281 runner.args.manifest_file = file;
282 },
283 .@"--zig" => {
284 if (runner.args.zig_exe != null) return runner.fail(
285 "\"{t}\" specified multiple times",
286 .{state},
287 );
288 runner.args.zig_exe = file;
289 },
290 .@"--gdb" => {
291 if (runner.args.gdb_exe != null) return runner.fail(
292 "\"{t}\" specified multiple times",
293 .{state},
294 );
295 runner.args.gdb_exe = file;
296 },
297 .@"--lldb" => {
298 if (runner.args.lldb_exe != null) return runner.fail(
299 "\"{t}\" specified multiple times",
300 .{state},
301 );
302 runner.args.lldb_exe = file;
303 },
304 else => return runner.fail(
305 "\"{t}\" expected {t}, got {t}",
306 .{ state, state_expected.get(state), arg },
307 ),
308 }
309 },
310 }
311 state = .positional;
312 }
313 },
314 .query_test_metadata => {
315 const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len);
316 @memset(expected_panic_msgs, 0);
317 runner.server.serveTestMetadata(.{
318 .names = runner.args.targets.items,
319 .expected_panic_msgs = expected_panic_msgs,
320 .string_bytes = runner.args.target_bytes.items,
321 }) catch |err| switch (err) {
322 error.WriteFailed => return error.ServerWriteFailed,
323 };
324 },
325 .run_test => {
326 const test_index = runner.server.receiveBody_u32() catch unreachable;
327 runner.server.serveBodylessMessage(.test_started) catch |err| switch (err) {
328 error.WriteFailed => return error.ServerWriteFailed,
329 };
330 runner.server.serveTestResults(.{
331 .index = test_index,
332 .flags = .{
333 .status = if (runner.testOne(test_index))
334 .pass
335 else |err| status: switch (err) {
336 error.Canceled,
337 error.OutOfMemory,
338 error.ServerWriteFailed,
339 => |e| return e,
340 error.AlreadyReported => {
341 var eb = try runner.eb_wip.toOwnedBundle("");
342 defer eb.deinit(runner.gpa);
343 assert(eb.errorMessageCount() > 0); // already reported what?
344 runner.server.serveErrorBundle(
345 .error_bundle,
346 eb,
347 ) catch |server_err| switch (server_err) {
348 error.WriteFailed => return error.ServerWriteFailed,
349 };
350 const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa);
351 runner.eb_wip.deinit();
352 runner.eb_wip = eb_wip;
353 break :status .fail;
354 },
355 error.SkipTest => .skip,
356 },
357 .fuzz = false,
358 .log_err_count = 0,
359 .leak_count = 0,
360 },
361 }) catch |err| switch (err) {
362 error.WriteFailed => return error.ServerWriteFailed,
363 };
364 assert(runner.eb_wip.root_list.items.len == 0); // failed to report
365 },
366 }
367 }
368}
369
370fn discoverInput(
371 runner: *Runner,
372 dir: std.zig.Server.Message.InputDir,
373 sub_path: []const u8,
374) error{ServerWriteFailed}!void {
375 return runner.discoverInputInner(dir, sub_path) catch |err| switch (err) {
376 error.WriteFailed => return error.ServerWriteFailed,
377 };
378}
379fn discoverInputInner(
380 runner: *Runner,
381 dir: std.zig.Server.Message.InputDir,
382 sub_path: []const u8,
383) std.Io.Writer.Error!void {
384 try runner.server.serveMessageHeader(.{
385 .tag = .discovered_inputs,
386 .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) +
387 sub_path.len + 1),
388 });
389 try runner.server.out.writeInt(u32, @backingInt(dir), .little);
390 try runner.server.out.writeAll(sub_path);
391 try runner.server.out.writeByte(0);
392 try runner.server.out.flush();
393}
394
395pub const SourceReader = struct {
396 const Io = std.Io;
397
398 backing: *std.Io.Reader,
399 line_aw: std.Io.Writer.Allocating,
400 interface: std.Io.Reader,
401 line: u32,
402 eb_wip: *std.zig.ErrorBundle.Wip,
403 src_path: std.zig.ErrorBundle.String,
404
405 pub fn init(
406 reader: *Io.Reader,
407 gpa: std.mem.Allocator,
408 eb_wip: *std.zig.ErrorBundle.Wip,
409 src_path: std.zig.ErrorBundle.String,
410 ) SourceReader {
411 return .{
412 .backing = reader,
413 .line_aw = .init(gpa),
414 .interface = .{
415 .vtable = &.{
416 .stream = stream,
417 .discard = discard,
418 .readVec = readVec,
419 .rebase = rebase,
420 },
421 .buffer = &.{},
422 .seek = 0,
423 .end = 0,
424 },
425 .line = 0,
426 .eb_wip = eb_wip,
427 .src_path = src_path,
428 };
429 }
430 fn deinit(s: *SourceReader) void {
431 s.line_aw.deinit();
432 }
433 fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize {
434 const s: *SourceReader = @fieldParentPtr("interface", r);
435 try s.bufferLine();
436 return 0;
437 }
438 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
439 const s: *SourceReader = @fieldParentPtr("interface", r);
440 const remaining = r.buffered();
441 if (remaining.len > 0) {
442 const n = limit.minInt(remaining.len);
443 r.toss(n);
444 return n;
445 }
446 try s.bufferLine();
447 return 0;
448 }
449 fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize {
450 const s: *SourceReader = @fieldParentPtr("interface", r);
451 try s.bufferLine();
452 return 0;
453 }
454 fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void {
455 const s: *SourceReader = @fieldParentPtr("interface", r);
456 defer s.updateBuffer();
457 s.line_aw.ensureUnusedCapacity(capacity) catch |err| switch (err) {
458 error.OutOfMemory => return error.ReadFailed,
459 };
460 }
461 fn updateBuffer(s: *SourceReader) void {
462 s.interface.buffer = s.line_aw.writer.buffer;
463 s.interface.end = s.line_aw.writer.end;
464 }
465 fn bufferLine(s: *SourceReader) Io.Reader.Error!void {
466 defer s.updateBuffer();
467 {
468 const written = s.line_aw.written();
469 const start = if (std.mem.findScalarLast(u8, written[0..s.interface.seek], '\n')) |newline|
470 newline + 1
471 else
472 0;
473 s.line += @intCast(std.mem.countScalar(u8, written[0..start], '\n'));
474 const preserve = written[start..];
475 @memmove(written[0..preserve.len], preserve);
476 s.line_aw.shrinkRetainingCapacity(preserve.len);
477 s.interface.seek -= start;
478 }
479 const n = s.backing.streamDelimiterEnding(&s.line_aw.writer, '\n') catch |err| switch (err) {
480 error.ReadFailed => |e| return e,
481 error.WriteFailed => return error.ReadFailed,
482 };
483 if (std.mem.endsWith(u8, s.line_aw.written(), "\r")) s.line_aw.writer.end -= 1;
484 const buffered = s.backing.buffered();
485 if (n == 0 and buffered.len == 0) return error.EndOfStream;
486 if (std.mem.startsWith(u8, buffered, "\n")) {
487 s.line_aw.writer.writeByte('\n') catch |err| switch (err) {
488 error.WriteFailed => return error.ReadFailed,
489 };
490 s.backing.toss(1);
491 }
492 }
493 fn fail(
494 s: *SourceReader,
495 src_loc: []const u8,
496 comptime fmt: []const u8,
497 args: anytype,
498 ) FailError {
499 return s.failString(src_loc, try s.eb_wip.printString(fmt, args));
500 }
501 fn failString(
502 s: *SourceReader,
503 src_loc: []const u8,
504 msg: std.zig.ErrorBundle.String,
505 ) FailError {
506 try s.eb_wip.addRootErrorMessage(.{ .msg = msg, .src_loc = try s.srcLoc(src_loc) });
507 return error.AlreadyReported;
508 }
509 fn srcLoc(
510 s: *SourceReader,
511 src_loc: []const u8,
512 ) std.mem.Allocator.Error!std.zig.ErrorBundle.SourceLocationIndex {
513 const line: u32, const column: u32, const source_line = src_loc: {
514 const buffer = s.interface.buffer[0..s.interface.end];
515 const pos = src_loc.ptr - buffer.ptr;
516 const end = std.mem.findScalarPos(u8, buffer, pos, '\n') orelse buffer.len;
517 const start =
518 if (std.mem.findScalarLast(u8, buffer[0..end], '\n')) |newline| newline + 1 else 0;
519 break :src_loc .{
520 @intCast(s.line + std.mem.countScalar(u8, buffer[0..start], '\n')),
521 @intCast(pos - start),
522 buffer[start..end],
523 };
524 };
525 return s.eb_wip.addSourceLocation(.{
526 .src_path = s.src_path,
527 .line = line,
528 .column = column,
529 .span_start = column,
530 .span_main = column,
531 .span_end = @intCast(column + src_loc.len),
532 .source_line = try s.eb_wip.addString(source_line),
533 });
534 }
535};
536pub const DelimitedReader = struct {
537 const Io = std.Io;
538
539 unlimited: *Io.Reader,
540 remaining: Io.Limit,
541 delimiter: []const u8,
542 interface: Io.Reader,
543
544 pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader {
545 return .{
546 .unlimited = reader,
547 .remaining = .nothing,
548 .delimiter = delimiter,
549 .interface = .{
550 .vtable = &.{
551 .stream = stream,
552 .discard = discard,
553 },
554 .buffer = buffer,
555 .seek = 0,
556 .end = 0,
557 },
558 };
559 }
560 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
561 const d: *DelimitedReader = @fieldParentPtr("interface", r);
562 const block = try d.unlimited.peekGreedy(d.delimiter.len);
563 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
564 block.len - d.delimiter.len + 1);
565 if (d.remaining == .nothing) return error.EndOfStream;
566 const n = try d.unlimited.stream(w, limit.min(d.remaining));
567 d.remaining = d.remaining.subtract(n).?;
568 return n;
569 }
570 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
571 const d: *DelimitedReader = @fieldParentPtr("interface", r);
572 const block = try d.unlimited.peekGreedy(d.delimiter.len);
573 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
574 block.len - d.delimiter.len + 1);
575 if (d.remaining == .nothing) return error.EndOfStream;
576 const n = try d.unlimited.discard(limit.min(d.remaining));
577 d.remaining = d.remaining.subtract(n).?;
578 return n;
579 }
580};
581
582const Update = struct {
583 target_query: []const u8,
584 manifest_sr: SourceReader,
585 compiler: ?Compiler,
586 src_dir: std.Io.Dir,
587 eb_wip: *std.zig.ErrorBundle.Wip,
588 cmd_src_loc: std.zig.ErrorBundle.SourceLocationIndex,
589 src_loc: ?std.zig.ErrorBundle.SourceLocationIndex,
590 num: usize,
591 name: []const u8,
592 prog_node: std.Progress.Node,
593 mtime: std.Io.Timestamp,
594 allow_skip: bool,
595
596 fn checkMatches(
597 update: *const Update,
598 what: []const u8,
599 expected: []const u8,
600 actual: []const u8,
601 ) FailError!void {
602 var expected_match_it = std.mem.splitSequence(u8, expected, "#...");
603 var pos: usize = 0;
604 while (expected_match_it.next()) |expected_match| : (pos += expected_match.len) {
605 var context_len: usize = expected_match.len;
606 pos = while (true) : (context_len -= 1) break std.mem.findPos(
607 u8,
608 actual,
609 pos,
610 expected_match[0..context_len],
611 ) orelse continue;
612 const actual_match = actual[pos..];
613 std.testing.expectEqualStrings(
614 expected_match,
615 actual_match[0..@min(actual_match.len, expected_match.len)],
616 ) catch |err| switch (err) {
617 error.TestExpectedEqual => {
618 std.testing.failPrint("\n=============== full output: =========\n", .{});
619 std.testing.failPrintWithVisibleNewlines(actual_match);
620 std.testing.failPrint("\n======================================\n", .{});
621 return update.fail("{s} did not match expected", .{what});
622 },
623 };
624 }
625 }
626
627 fn fail(update: *const Update, comptime fmt: []const u8, args: anytype) FailError {
628 return update.failString(try update.eb_wip.printString(fmt, args));
629 }
630 fn failString(update: *const Update, msg: std.zig.ErrorBundle.String) FailError {
631 var notes_buffer: [1]std.zig.ErrorBundle.ErrorMessage = undefined;
632 var notes_len: u32 = 0;
633 if (update.src_loc) |src_loc| {
634 notes_buffer[notes_len] = .{
635 .msg = try update.eb_wip.addString("after this update"),
636 .src_loc = src_loc,
637 };
638 notes_len += 1;
639 }
640 try update.eb_wip.addRootErrorMessageWithNotes(.{
641 .msg = msg,
642 .src_loc = update.cmd_src_loc,
643 .notes_len = 1,
644 }, notes_buffer[0..notes_len]);
645 return error.AlreadyReported;
646 }
647};
648
649const TestError = error{
650 SkipTest,
651} || FailError;
652fn testOne(runner: *Runner, test_index: u32) (error{ServerWriteFailed} || TestError)!void {
653 const target_query =
654 std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0);
655 const manifest_file = runner.args.manifest_file orelse
656 return runner.fail("missing \"path/to/test\" arg", .{});
657 var manifest_buffer: [512]u8 = undefined;
658 var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer);
659 var update: Update = .{
660 .target_query = target_query,
661 .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: {
662 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
663 const src_path = src_path: {
664 break :src_path path_buffer[0 .. manifest_file.realPath(
665 runner.io,
666 &path_buffer,
667 ) catch |err| switch (err) {
668 error.Canceled => |e| return e,
669 else => break :src_path "manifest",
670 }];
671 };
672 break :src_path_string try runner.eb_wip.addString(src_path);
673 }),
674 .compiler = null,
675 .src_dir = (runner.args.src_dir orelse
676 return runner.fail("missing \"--src=path/to/src\" arg", .{})).createDirPathOpen(
677 runner.io,
678 target_query,
679 .{},
680 ) catch |err| switch (err) {
681 error.Canceled => |e| return e,
682 else => |e| return runner.fail("unable to create {q}: {t}", .{ target_query, e }),
683 },
684 .eb_wip = &runner.eb_wip,
685 .cmd_src_loc = undefined,
686 .src_loc = null,
687 .num = 0,
688 .name = undefined,
689 .prog_node = .none,
690 .mtime = .now(runner.io, .real),
691 .allow_skip = true,
692 };
693 defer {
694 update.manifest_sr.deinit();
695 update.src_dir.close(runner.io);
696 update.prog_node.end();
697 }
698
699 // hack around `std.Io.Reader` interface bugs by ensuring the
700 // buffer is larger than the longest supported manifest line
701 try update.manifest_sr.line_aw.ensureTotalCapacityPrecise(512);
702 update.manifest_sr.updateBuffer();
703
704 var skip_delimiter = false;
705 while (update.manifest_sr.interface.takeSentinel('\n')) |line| {
706 var line_it = std.mem.tokenizeScalar(u8, line, ' ');
707 const cmd_src_loc = line_it.next() orelse continue;
708 if (skip_delimiter) {
709 assert(std.mem.eql(u8, cmd_src_loc, "#}"));
710 skip_delimiter = false;
711 const unexpected = line_it.rest();
712 if (unexpected.len > 0)
713 return update.manifest_sr.fail(unexpected, "unexpected argument {q}", .{unexpected});
714 continue;
715 }
716 update.cmd_src_loc = try update.manifest_sr.srcLoc(cmd_src_loc);
717 const cmd = std.meta.stringToEnum(Command, std.mem.cutPrefix(u8, cmd_src_loc, "#") orelse
718 return update.manifest_sr.fail(cmd_src_loc, "expected command", .{})) orelse
719 return update.manifest_sr.fail(cmd_src_loc, "unknown command", .{});
720 const arg = switch (cmd) {
721 .todo, .exe, .lib, .obj, .update => null,
722 .skip, .write, .delete, .check => line_it.next() orelse
723 return update.manifest_sr.fail(line_it.rest()[0..0], "missing argument", .{}),
724 };
725 var contents_file: ?std.Io.File = null;
726 defer if (contents_file) |file| file.close(runner.io);
727 var contents_buffer: [512]u8 = undefined;
728 var contents_impl: union(enum) {
729 r: std.Io.Reader,
730 dr: DelimitedReader,
731 fr: std.Io.File.Reader,
732 } = undefined;
733 const contents_r: *std.Io.Reader = contents_r: switch (cmd) {
734 .skip, .delete => {
735 const unexpected = line_it.rest();
736 if (unexpected.len > 0) return update.manifest_sr.fail(
737 unexpected,
738 "unexpected argument {q}",
739 .{unexpected},
740 );
741 contents_impl = .{ .r = .ending_instance };
742 break :contents_r &contents_impl.r;
743 },
744 else => {
745 const contents = line_it.rest();
746 if (contents.len == 0) {
747 contents_impl = .{ .r = .ending_instance };
748 break :contents_r &contents_impl.r;
749 }
750 contents: switch (contents[0]) {
751 else => return update.manifest_sr.fail(contents, "invalid contents syntax", .{}),
752 '"' => {
753 if (contents.len < 2 or contents[contents.len - 1] != '"') continue :contents 0;
754 var contents_fw: std.Io.Writer = .fixed(&contents_buffer);
755 switch (std.zig.string_literal.parseWrite(
756 &contents_fw,
757 contents,
758 ) catch |err| switch (err) {
759 error.WriteFailed => return update.manifest_sr.fail(
760 contents,
761 "contents string too long, use inline or file syntax instead",
762 .{},
763 ),
764 }) {
765 .success => {
766 contents_impl = .{ .r = .fixed(contents_fw.buffered()) };
767 break :contents_r &contents_impl.r;
768 },
769 .failure => |err| return update.manifest_sr.fail(
770 contents[err.offset()..][0..0],
771 "{f}",
772 .{err.fmt(contents)},
773 ),
774 }
775 },
776 '{' => {
777 if (contents.len != 1) continue :contents 0;
778
779 contents_impl = .{
780 .dr = .init(&update.manifest_sr.interface, "#}", &contents_buffer),
781 };
782 skip_delimiter = true;
783 break :contents_r &contents_impl.dr.interface;
784 },
785 '@' => {
786 const args_test = runner.args.@"test" orelse return update.manifest_sr.fail(
787 contents,
788 "contents file requires directory test",
789 .{},
790 );
791 const contents_path = contents[1..];
792 try runner.discoverInput(args_test.input_dir, contents_path);
793
794 contents_file = args_test.dir.openFile(
795 runner.io,
796 contents_path,
797 .{},
798 ) catch |err| switch (err) {
799 error.Canceled => |e| return e,
800 else => |e| return update.manifest_sr.fail(
801 contents_path,
802 "unable to open contents file: {t}",
803 .{e},
804 ),
805 };
806 contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) };
807 break :contents_r &contents_impl.fr.interface;
808 },
809 }
810 },
811 };
812
813 runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) {
814 error.Canceled,
815 error.OutOfMemory,
816 error.AlreadyReported,
817 error.SkipTest,
818 => |e| return e,
819 error.ContentsReadFailed => switch (contents_impl) {
820 .r => unreachable,
821 .dr => switch (manifest_fr.err orelse return error.OutOfMemory) {
822 error.Canceled => |e| return e,
823 else => |e| return update.fail("unable to read manifest: {t}", .{e}),
824 },
825 .fr => |fr| switch (fr.err.?) {
826 error.Canceled => |e| return e,
827 else => |e| return update.fail("unable to read contents file: {t}", .{e}),
828 },
829 },
830 };
831 } else |err| switch (err) {
832 error.ReadFailed => switch (manifest_fr.err orelse return error.OutOfMemory) {
833 error.Canceled => |e| return e,
834 else => |e| return update.fail("unable to read manifest: {t}", .{e}),
835 },
836 error.StreamTooLong => unreachable,
837 error.EndOfStream => {
838 const final_line = update.manifest_sr.interface.buffered();
839 switch (final_line.len) {
840 0 => if (skip_delimiter) return update.manifest_sr.fail(
841 final_line[0..0],
842 "missing \"#}}\" delimiter",
843 .{},
844 ),
845 else => return update.manifest_sr.fail(
846 final_line[final_line.len..],
847 "missing trailing newline",
848 .{},
849 ),
850 }
851 },
852 }
853 if (update.compiler) |*comp| {
854 if (comp.state != .idle) return update.fail("missing \"#check\"", .{});
855 try comp.exit();
856 }
857}
858const Command = enum { todo, skip, exe, lib, obj, write, delete, update, check };
859fn handleCommand(
860 runner: *Runner,
861 update: *Update,
862 cmd_src_loc: []const u8,
863 cmd: Command,
864 arg: ?[]const u8,
865 contents_r: *std.Io.Reader,
866) (error{ContentsReadFailed} || TestError)!void {
867 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
868 const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t}{s}{s}", .{
869 cmd, if (arg) |_| " " else "", arg orelse "",
870 }) catch &name_buffer, 0);
871 defer cmd_prog_node.end();
872
873 cmd: switch (cmd) {
874 .todo => _ = contents_r.discardRemaining() catch |err| switch (err) {
875 error.ReadFailed => {},
876 },
877 .skip => if (!update.allow_skip) return update.manifest_sr.fail(
878 cmd_src_loc,
879 "\"#skip\" must appear before other commands",
880 .{},
881 ) else if (std.mem.eql(u8, update.target_query, arg.?)) return error.SkipTest,
882 .exe, .lib, .obj => runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) {
883 else => unreachable,
884 .exe => .Exe,
885 .lib => .Lib,
886 .obj => .Obj,
887 }, contents_r, target: {
888 var component_it = std.mem.splitBackwardsScalar(u8, update.target_query, '-');
889 const pic_str = component_it.next() orelse
890 return update.fail("target {q} missing pic", .{update.target_query});
891 const pic = std.meta.stringToEnum(Compiler.Target.Pic, pic_str) orelse
892 return update.fail("target {q} unknown pic {q}", .{ update.target_query, pic_str });
893 const linker_str = component_it.next() orelse
894 return update.fail("target {q} missing linker", .{update.target_query});
895 const linker = std.meta.stringToEnum(Compiler.Target.Linker, linker_str) orelse
896 return update.fail("target {q} unknown linker {q}", .{
897 update.target_query, linker_str,
898 });
899 const backend_str = component_it.next() orelse
900 return update.fail("target {q} missing backend", .{update.target_query});
901 const backend = std.meta.stringToEnum(Compiler.Target.Backend, backend_str) orelse
902 return update.fail("target {q} unknown backend {q}", .{
903 update.target_query, backend_str,
904 });
905 const cache_mode_str = component_it.next() orelse
906 return update.fail("target {q} missing cache mode", .{update.target_query});
907 const cache_mode = std.meta.stringToEnum(Compiler.Target.CacheMode, cache_mode_str) orelse
908 return update.fail("target {q} unknown cache mode {q}", .{
909 update.target_query, cache_mode_str,
910 });
911 const triple = component_it.rest();
912 break :target .{
913 .triple = triple,
914 .resolved = std.zig.system.resolveTargetQuery(
915 runner.io,
916 std.Build.parseTargetQuery(.{
917 .arch_os_abi = triple,
918 .object_format = switch (backend) {
919 .sema, .selfhosted, .llvm => null,
920 .cbe => "c",
921 },
922 }) catch |err| switch (err) {
923 error.ParseFailed => return update.fail("unable to parse triple {q}", .{
924 triple,
925 }),
926 },
927 ) catch |err| switch (err) {
928 error.Canceled => |e| return e,
929 else => |e| return update.fail("unable to resolve target {q}: {t}", .{ triple, e }),
930 },
931 .cache_mode = cache_mode,
932 .backend = backend,
933 .linker = linker,
934 .pic = pic,
935 };
936 }) catch |err| switch (err) {
937 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
938 error.ReadFailed => return error.ContentsReadFailed,
939 },
940 .write => {
941 const file = update.src_dir.createFile(runner.io, arg.?, .{}) catch |err| switch (err) {
942 error.Canceled => |e| return e,
943 else => |e| return update.manifest_sr.fail(arg.?, "unable to create file: {t}", .{e}),
944 };
945 defer file.close(runner.io);
946 var fw_buffer: [512]u8 = undefined;
947 var fw = file.writer(runner.io, &fw_buffer);
948 _ = contents_r.streamRemaining(&fw.interface) catch |err| switch (err) {
949 error.ReadFailed => return error.ContentsReadFailed,
950 error.WriteFailed => switch (fw.err.?) {
951 error.Canceled => |e| return e,
952 else => |e| return update.manifest_sr.fail(
953 arg.?,
954 "unable to write file: {t}",
955 .{e},
956 ),
957 },
958 };
959 fw.flush() catch |err| switch (err) {
960 error.Canceled => |e| return e,
961 else => |e| return update.manifest_sr.fail(
962 arg.?,
963 "unable to write file: {t}",
964 .{e},
965 ),
966 };
967 file.setTimestamps(runner.io, .{ .modify_timestamp = .{
968 .new = update.mtime,
969 } }) catch |err| switch (err) {
970 error.Canceled => |e| return e,
971 else => |e| return update.manifest_sr.fail(
972 arg.?,
973 "unable to update file timestamps: {t}",
974 .{e},
975 ),
976 };
977 },
978 .delete => update.src_dir.deleteFile(runner.io, arg.?) catch |err| switch (err) {
979 error.Canceled => |e| return e,
980 else => |e| return update.manifest_sr.fail(arg.?, "unable to delete file: {t}", .{e}),
981 },
982 .update => {
983 update.src_loc = update.cmd_src_loc;
984 const comp = &(update.compiler orelse return update.manifest_sr.fail(
985 cmd_src_loc,
986 "missing \"#exe\", \"#lib\", or \"#obj\"",
987 .{},
988 ));
989 update.prog_node.end();
990 const arena = runner.arena.allocator();
991 update.name = switch (contents_r.readSliceShort(&name_buffer) catch |err| switch (err) {
992 error.ReadFailed => return error.ContentsReadFailed,
993 }) {
994 0 => switch (update.num) {
995 0 => "initial update",
996 else => |num| try arena.print("update {d}", .{num}),
997 },
998 else => |name_len| try arena.dupe(u8, name_buffer[0..name_len]),
999 };
1000 update.prog_node = runner.prog_node.start(update.name, 0);
1001 _ = contents_r.discardRemaining() catch |err| switch (err) {
1002 error.ReadFailed => return error.ContentsReadFailed,
1003 };
1004 if (comp.state != .idle)
1005 return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{});
1006 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
1007 error.WriteFailed => switch (comp.fw.err.?) {
1008 error.Canceled => |e| return e,
1009 else => |e| return update.fail("unable to send message to compiler: {t}", .{e}),
1010 },
1011 };
1012 comp.state = .update;
1013 update.mtime = update.mtime.addDuration(.fromSeconds(2));
1014 update.num += 1;
1015 },
1016 .check => {
1017 const check = std.meta.stringToEnum(Compiler.Check, arg.?) orelse
1018 return update.manifest_sr.fail(arg.?, "unknown check", .{});
1019 const expected =
1020 contents_r.allocRemaining(runner.gpa, .unlimited) catch |err| switch (err) {
1021 error.OutOfMemory => |e| return e,
1022 error.ReadFailed => return error.ContentsReadFailed,
1023 error.StreamTooLong => unreachable, // .unlimited
1024 };
1025 defer runner.gpa.free(expected);
1026
1027 const comp = &(update.compiler orelse return update.manifest_sr.fail(
1028 cmd_src_loc,
1029 "missing \"#exe\", \"#lib\", or \"#obj\"",
1030 .{},
1031 ));
1032 if (comp.state != .update)
1033 return update.manifest_sr.fail(cmd_src_loc, "missing \"#update\"", .{});
1034 const stderr = comp.mr.reader(1);
1035 while (try comp.receiveMessage()) |message| switch (message.tag) {
1036 else => {}, // Ignore other messages,
1037 .config => {
1038 var body_r: std.Io.Reader = .fixed(message.body);
1039 comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch
1040 unreachable;
1041 },
1042 .emit_digest => {
1043 var body_r: std.Io.Reader = .fixed(message.body);
1044 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch
1045 unreachable;
1046 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch
1047 unreachable;
1048 try comp.checkSuccess(update, check, expected, digest);
1049 },
1050 .error_bundle => {
1051 const eb = std.zig.Server.allocErrorBundle(
1052 runner.arena.allocator(),
1053 message.body,
1054 ) catch |err| switch (err) {
1055 error.OutOfMemory => |e| return e,
1056 error.EndOfStream => return update.fail("error bundle truncated", .{}),
1057 };
1058 switch (check) {
1059 .errors => {
1060 var error_aw: std.Io.Writer.Allocating = .init(runner.gpa);
1061 defer error_aw.deinit();
1062 eb.renderToWriter(.{
1063 .include_reference_trace = false,
1064 .include_source_line = false,
1065 .include_log_text = true,
1066 }, &error_aw.writer) catch |err| switch (err) {
1067 error.WriteFailed => return error.OutOfMemory,
1068 };
1069 const actual = error_aw.written();
1070 std.mem.replaceScalar(u8, actual, '\\', '/');
1071 std.testing.expectEqualStrings(expected, actual) catch |err| switch (err) {
1072 error.TestExpectedEqual => return update.fail(
1073 "errors did not match expected",
1074 .{},
1075 ),
1076 };
1077 },
1078 .stdout, .exit, .gdb, .lldb => if (eb.errorMessageCount() > 0) {
1079 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
1080 error.Canceled => {},
1081 else => {},
1082 };
1083 return update.fail("unexpected compile errors", .{});
1084 },
1085 }
1086 comp.state = .idle;
1087 break :cmd;
1088 },
1089 };
1090
1091 const stderr_buffered = stderr.buffered();
1092 if (stderr_buffered.len > 0) {
1093 if (comp.allow_compiler_stderr) {
1094 std.log.info("#update {q} stderr:\n{s}", .{ update.name, stderr_buffered });
1095 } else return update.fail("unexpected stderr:\n{s}", .{stderr_buffered});
1096 }
1097
1098 try comp.exit();
1099 return update.fail("compiler failed to send terminating error_bundle", .{});
1100 },
1101 }
1102 assert(contents_r.discardRemaining() catch |err| switch (err) {
1103 error.ReadFailed => return error.ContentsReadFailed,
1104 } == 0);
1105 switch (cmd) {
1106 .todo, .skip => {},
1107 else => update.allow_skip = false,
1108 }
1109}
1110
1111fn spawnCompiler(
1112 runner: *Runner,
1113 compiler: *?Compiler,
1114 src_dir: std.Io.Dir,
1115 output_mode: std.lang.OutputMode,
1116 args_r: *std.Io.Reader,
1117 target: Compiler.Target,
1118) (std.Io.Reader.ShortError || TestError)!void {
1119 const gpa = runner.gpa;
1120 const arena = runner.arena.allocator();
1121 if (compiler.*) |*comp| {
1122 try comp.exit();
1123 compiler.* = null;
1124 }
1125
1126 var argv: std.ArrayList([]const u8) = .empty;
1127 defer argv.deinit(gpa);
1128 try argv.appendSlice(gpa, &.{
1129 "zig",
1130 switch (output_mode) {
1131 .Obj => "build-obj",
1132 .Exe => "build-exe",
1133 .Lib => "build-lib",
1134 },
1135 "-target",
1136 target.triple,
1137 "--cache-dir",
1138 ".zig-cache",
1139 });
1140 switch (target.cache_mode) {
1141 .whole => {},
1142 .incremental => try argv.append(gpa, "-fincremental"),
1143 }
1144 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
1145 if (target.backend) |b| switch (b) {
1146 .sema => try argv.append(gpa, "-fno-emit-bin"),
1147 .selfhosted => try argv.append(gpa, "-fno-llvm"),
1148 .llvm => try argv.append(gpa, "-fllvm"),
1149 .cbe => try argv.append(gpa, "-ofmt=c"),
1150 } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse
1151 return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{}))
1152 .realPath(runner.io, &path_buffer) catch |err| switch (err) {
1153 error.Canceled => |e| return e,
1154 else => |e| return runner.fail("unable to get lib path: {t}", .{e}),
1155 }] });
1156 switch (target.linker) {
1157 .lld => try argv.appendSlice(gpa, &.{ "-flld", "-fno-new-linker" }),
1158 .old => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fno-new-linker" }),
1159 .new => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fnew-linker" }),
1160 }
1161 switch (target.pic) {
1162 .nopic => try argv.appendSlice(gpa, &.{ "-fno-PIC", "-fno-PIE" }),
1163 .pic => try argv.appendSlice(gpa, &.{ "-fPIC", "-fno-PIE" }),
1164 .pie => try argv.appendSlice(gpa, &.{ "-fPIC", "-fPIE" }),
1165 }
1166
1167 var need: usize = 1;
1168 var state: enum { positional, @"--name" } = .positional;
1169 var root_name: ?[]const u8 = null;
1170 while (true) {
1171 const done = if (args_r.fill(need)) false else |err| switch (err) {
1172 error.ReadFailed => |e| return e,
1173 error.EndOfStream => true,
1174 };
1175 const buffered = args_r.buffered();
1176 const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len;
1177 const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len;
1178 if (end - start > 0) {
1179 const arg = try arena.dupe(u8, buffered[start..end]);
1180 switch (state) {
1181 .positional => if (std.mem.eql(u8, arg, "--name")) {
1182 state = .@"--name";
1183 } else if (std.mem.cutPrefix(u8, arg, "-M")) |module| {
1184 var module_name_it = std.mem.splitScalar(u8, module, '=');
1185 const module_name = module_name_it.next().?;
1186 root_name = root_name orelse module_name;
1187 },
1188 .@"--name" => {
1189 root_name = arg;
1190 state = .positional;
1191 },
1192 }
1193 try argv.append(gpa, arg);
1194 args_r.toss(end);
1195 need = 1;
1196 } else {
1197 args_r.toss(start);
1198 need = buffered.len - start + 1;
1199 }
1200 if (done) break;
1201 }
1202 try argv.append(gpa, "--listen=-");
1203
1204 const out_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
1205 .root_name = root_name orelse "root",
1206 .cpu_arch = target.resolved.cpu.arch,
1207 .os_tag = target.resolved.os.tag,
1208 .ofmt = target.resolved.ofmt,
1209 .abi = target.resolved.abi,
1210 .output_mode = output_mode,
1211 });
1212
1213 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1214 const comp_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{
1215 out_name,
1216 }) catch &name_buffer, 0);
1217 errdefer comp_prog_node.end();
1218
1219 const child = std.process.spawn(runner.io, .{
1220 .exe = .{ .file = runner.args.zig_exe orelse
1221 return runner.fail("missing \"--zig=path/to/zig\" arg", .{}) },
1222 .argv = argv.items,
1223 .cwd = .{ .dir = src_dir },
1224 .progress_node = comp_prog_node,
1225 .stdin = .pipe,
1226 .stdout = .pipe,
1227 .stderr = .pipe,
1228 }) catch |err| switch (err) {
1229 error.Canceled, error.OutOfMemory => |e| return e,
1230 error.OperationUnsupported => return error.SkipTest,
1231 else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}),
1232 };
1233 compiler.* = .{
1234 .runner = runner,
1235 .prog_node = comp_prog_node,
1236 .allow_compiler_stderr = true,
1237 .output_mode = output_mode,
1238 .state = .idle,
1239 .child = child,
1240 .fw = undefined,
1241 .mr_buffer = undefined,
1242 .mr = undefined,
1243 .client = undefined,
1244 .target = target,
1245 .out_name = out_name,
1246 .config = null,
1247 };
1248 const comp = &compiler.*.?;
1249 comp.fw = child.stdin.?.writerStreaming(runner.io, &.{});
1250 comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{
1251 child.stdout.?, child.stderr.?,
1252 });
1253 comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface };
1254}
1255const Compiler = struct {
1256 runner: *Runner,
1257 prog_node: std.Progress.Node,
1258 allow_compiler_stderr: bool,
1259 output_mode: std.lang.OutputMode,
1260 state: enum { idle, update },
1261 child: std.process.Child,
1262 fw: std.Io.File.Writer,
1263 mr_buffer: std.Io.File.MultiReader.Buffer(2),
1264 mr: std.Io.File.MultiReader,
1265 client: std.zig.Client,
1266 target: Target,
1267 out_name: []const u8,
1268 config: ?std.zig.Server.Message.Config,
1269
1270 const Target = struct {
1271 triple: []const u8,
1272 resolved: std.Target,
1273 cache_mode: CacheMode,
1274 backend: ?Backend,
1275 linker: Linker,
1276 pic: Pic,
1277
1278 const CacheMode = enum { whole, incremental };
1279
1280 const Backend = enum {
1281 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
1282 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
1283 sema,
1284 /// Use the self-hosted code generation backend for this target.
1285 /// Corresponds to `-fno-llvm -fno-lld`.
1286 selfhosted,
1287 /// Use the LLVM backend.
1288 /// Corresponds to `-fllvm -flld`.
1289 llvm,
1290 /// Use the C backend. The output is compiled with `zig cc`.
1291 /// Corresponds to `-ofmt=c`.
1292 cbe,
1293 };
1294
1295 const Linker = enum { old, new, lld };
1296
1297 const Pic = enum { nopic, pic, pie };
1298 };
1299
1300 fn receiveMessage(comp: *Compiler) FailError!?struct {
1301 tag: std.zig.Server.Message.Tag,
1302 body: []const u8,
1303 } {
1304 const header = comp.client.receiveMessageWithMultiReader(
1305 &comp.mr,
1306 .none,
1307 ) catch |err| switch (err) {
1308 error.Canceled, error.OutOfMemory => |e| return e,
1309 error.Timeout => unreachable,
1310 error.EndOfStream => return null,
1311 else => |e| return comp.runner.fail("unable to receive message: {t}", .{e}),
1312 };
1313 const body = comp.client.in.take(header.bytes_len) catch unreachable;
1314 const stderr = comp.mr.reader(1);
1315 if (stderr.bufferedLen() > 0) {
1316 if (comp.allow_compiler_stderr) {
1317 std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() });
1318 } else {
1319 return comp.runner.fail("{t} unexpected stderr:\n{s}", .{
1320 header.tag, stderr.buffered(),
1321 });
1322 }
1323 stderr.tossBuffered();
1324 }
1325 return .{ .tag = header.tag, .body = body };
1326 }
1327
1328 const Check = enum { errors, stdout, exit, gdb, lldb };
1329 fn checkSuccess(
1330 comp: *Compiler,
1331 update: *const Update,
1332 check: Check,
1333 expected: []const u8,
1334 digest: *const std.Build.Cache.BinDigest,
1335 ) TestError!void {
1336 const runner = comp.runner;
1337 const gpa = runner.gpa;
1338 const arena = runner.arena.allocator();
1339 const io = runner.io;
1340 const target = &comp.target.resolved;
1341 const config = &comp.config.?;
1342 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
1343
1344 // After a failure while running a script, the debugger starts accepting commands from stdin, and
1345 // because it is empty, the debugger exits normally with status 0. Choose a non-zero status to
1346 // return from the debugger script instead to detect it running to completion and indicate success.
1347 const debug_success = 99;
1348
1349 const tmp_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ "tmp";
1350 update.src_dir.createDirPath(io, tmp_dir_path) catch |err| switch (err) {
1351 error.Canceled => |e| return e,
1352 else => |e| return update.fail("unable to create dir path {q}: {t}", .{
1353 tmp_dir_path, e,
1354 }),
1355 };
1356
1357 var argv: std.ArrayList([]const u8) = .initBuffer(try arena.alloc([]const u8, 5 + 4));
1358 var environ_map: std.process.Environ.Map = .init(gpa);
1359 defer environ_map.deinit();
1360 try environ_map.putAll(&runner.environ_map);
1361 var script_buffer: [512]u8 = undefined;
1362 const script_path, const script_file, var script_fw = script: switch (check) {
1363 .errors => return,
1364 .stdout, .exit => .{ undefined, null, undefined },
1365 .gdb => {
1366 const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "gdb";
1367 argv.appendSliceAssumeCapacity(&.{ "gdb", "--batch", "--command", script_path, "--args" });
1368 const script_file =
1369 update.src_dir.createFile(io, script_path, .{}) catch |err| switch (err) {
1370 error.Canceled => |e| return e,
1371 else => |e| return update.fail("unable to create debugger script {q}: {t}", .{
1372 script_path, e,
1373 }),
1374 };
1375 errdefer script_file.close(io);
1376 var script_fw = script_file.writer(io, &script_buffer);
1377 script_fw.interface.writeAll(
1378 \\set remotetimeout 0
1379 \\
1380 \\
1381 ) catch |err| switch (err) {
1382 error.WriteFailed => switch (script_fw.err.?) {
1383 error.Canceled => |e| return e,
1384 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1385 script_path, e,
1386 }),
1387 },
1388 };
1389 break :script .{ script_path, script_file, script_fw };
1390 },
1391 .lldb => {
1392 const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "lldb";
1393 argv.appendSliceAssumeCapacity(&.{ "lldb", "--batch", "--source", script_path, "--" });
1394 try environ_map.put("LANG", "C.UTF-8"); // affects output formatting
1395 const script_file =
1396 update.src_dir.createFile(io, script_path, .{}) catch |err| switch (err) {
1397 error.Canceled => |e| return e,
1398 else => |e| return update.fail("unable to create debugger script {q}: {t}", .{
1399 script_path, e,
1400 }),
1401 };
1402 errdefer script_file.close(io);
1403 var script_fw = script_file.writer(io, &script_buffer);
1404 script_fw.interface.writeAll(
1405 \\settings set frame-format 'frame #${frame.index}:{ ${module.file.basename}{\`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${line.file.basename}:${line.number}{:${line.column}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\n'
1406 \\settings set plugin.process.gdb-remote.packet-timeout 0
1407 \\settings set stop-line-count-after 0
1408 \\settings set stop-line-count-before 0
1409 \\
1410 ) catch |err| switch (err) {
1411 error.WriteFailed => switch (script_fw.err.?) {
1412 error.Canceled => |e| return e,
1413 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1414 script_path, e,
1415 }),
1416 },
1417 };
1418 break :script .{ script_path, script_file, script_fw };
1419 },
1420 };
1421 defer if (script_file) |file| file.close(io);
1422
1423 const out_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1424 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1425 const out_path = try std.Io.Dir.path.join(arena, &.{ out_dir_path, comp.out_name });
1426 const bin_path = switch (comp.target.backend.?) {
1427 .sema => return,
1428 .selfhosted, .llvm => out_path,
1429 .cbe => try comp.compileC(update.src_dir, out_path),
1430 };
1431
1432 const need_cross_libc =
1433 target.os.tag == .linux and config.flags.link_libc and config.flags.link_mode == .dynamic;
1434 const use_executor = use_executor: switch (std.zig.system.getExternalExecutor(io, target, .{
1435 .host_cpu_arch = runner.host.cpu.arch,
1436 .host_os_tag = runner.host.os.tag,
1437 .qemu_fixes_dl = need_cross_libc and runner.args.libc_runtimes_dir != null,
1438 .link_mode = config.flags.link_mode,
1439 .link_libc = config.flags.link_libc,
1440 })) {
1441 .bad_dl, .bad_os_or_cpu => {
1442 // This binary cannot be executed on this host.
1443 if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++
1444 "execute binaries for foreign target {q}", .{
1445 try runner.host.zigTriple(arena), comp.target.triple,
1446 });
1447 return;
1448 },
1449 .native => false,
1450 .darling => |executor| if (runner.args.enable_darling) {
1451 argv.appendAssumeCapacity(executor);
1452 break :use_executor true;
1453 } else continue :use_executor .bad_os_or_cpu,
1454 .qemu => |executor| if (runner.args.enable_qemu) {
1455 argv.appendAssumeCapacity(executor);
1456 if (need_cross_libc) {
1457 const libc_runtimes_dir = runner.args.libc_runtimes_dir orelse
1458 continue :use_executor .bad_os_or_cpu;
1459 const libc_runtimes_path = path_buffer[0 .. libc_runtimes_dir.realPath(
1460 io,
1461 &path_buffer,
1462 ) catch |err| switch (err) {
1463 error.Canceled => |e| return e,
1464 else => |e| return update.fail("unable to get libc runtimes path: {t}", .{e}),
1465 }];
1466 argv.appendSliceAssumeCapacity(&.{ "-L", try std.Io.Dir.path.join(arena, &.{
1467 libc_runtimes_path,
1468 try if (target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
1469 arena,
1470 target.cpu.arch,
1471 target.os.tag,
1472 target.abi,
1473 ) else if (target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
1474 arena,
1475 target.cpu.arch,
1476 target.abi,
1477 ) else unreachable,
1478 }) });
1479 }
1480 break :use_executor true;
1481 } else continue :use_executor .bad_os_or_cpu,
1482 .rosetta => if (runner.args.enable_rosetta)
1483 true
1484 else
1485 continue :use_executor .bad_os_or_cpu,
1486 .wine => |executor| if (runner.args.enable_wine) {
1487 if (!environ_map.contains("WINEDEBUG")) try environ_map.put("WINEDEBUG", "-all");
1488 argv.appendAssumeCapacity(executor);
1489 break :use_executor true;
1490 } else continue :use_executor .bad_os_or_cpu,
1491 .wasmtime => |executor| if (runner.args.enable_wasmtime) {
1492 if (!environ_map.contains("WASMTIME_BACKTRACE_DETAILS"))
1493 try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1");
1494 argv.appendSliceAssumeCapacity(&.{ executor, "--dir=.", "-Sinherit-env" });
1495 break :use_executor true;
1496 } else continue :use_executor .bad_os_or_cpu,
1497 };
1498 argv.appendAssumeCapacity(bin_path);
1499
1500 if (!runner.args.quiet) std.log.info("{f}", .{std.zig.SubprocessCommand{
1501 .argv = argv.items,
1502 .cwd = cwd_path: {
1503 break :cwd_path path_buffer[0 .. update.src_dir.realPath(
1504 io,
1505 &path_buffer,
1506 ) catch |err| switch (err) {
1507 error.Canceled => |e| return e,
1508 else => break :cwd_path null,
1509 }];
1510 },
1511 .parent_env = &runner.environ_map,
1512 .child_env = &environ_map,
1513 }});
1514
1515 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1516 const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{
1517 comp.out_name,
1518 }) catch &name_buffer, 0);
1519 defer run_prog_node.end();
1520
1521 const bin_file = update.src_dir.openFile(io, bin_path, .{}) catch |err| switch (err) {
1522 error.Canceled => |e| return e,
1523 else => |e| return update.fail("unable to open {q}: {t}", .{ bin_path, e }),
1524 };
1525 defer bin_file.close(io);
1526 const result = std.process.run(gpa, io, .{
1527 .exe = exe: switch (check) {
1528 .errors => unreachable,
1529 .stdout, .exit => if (use_executor) .search else .{ .file = bin_file },
1530 .gdb, .lldb => {
1531 if (use_executor) {
1532 if (!runner.args.quiet) std.log.warn(
1533 "skipping debugging of {q} via executor for foreign target {q}",
1534 .{ bin_path, comp.target.triple },
1535 );
1536 return;
1537 }
1538
1539 var line_it = std.mem.splitScalar(u8, expected, '\n');
1540 while (line_it.next()) |line| if (std.mem.cutPrefix(u8, line, switch (check) {
1541 else => unreachable,
1542 .gdb => "(gdb) ",
1543 .lldb => "(lldb) ",
1544 })) |cmd| script_fw.interface.print(
1545 \\{s}
1546 \\
1547 , .{cmd}) catch |err| switch (err) {
1548 error.WriteFailed => switch (script_fw.err.?) {
1549 error.Canceled => |e| return e,
1550 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1551 script_path, e,
1552 }),
1553 },
1554 };
1555 script_fw.interface.print(
1556 \\
1557 \\quit {d}
1558 \\
1559 , .{debug_success}) catch |err| switch (err) {
1560 error.WriteFailed => switch (script_fw.err.?) {
1561 error.Canceled => |e| return e,
1562 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1563 script_path, e,
1564 }),
1565 },
1566 };
1567 script_fw.flush() catch |err| switch (err) {
1568 error.Canceled => |e| return e,
1569 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1570 script_path, e,
1571 }),
1572 };
1573 break :exe .{ .file = switch (check) {
1574 else => unreachable,
1575 .gdb => runner.args.gdb_exe,
1576 .lldb => runner.args.lldb_exe,
1577 } orelse {
1578 if (!runner.args.quiet) std.log.warn(
1579 "skipping debugging of {q} because of missing \"--{t}=path/to/{[1]t}\" arg",
1580 .{ bin_path, check },
1581 );
1582 return;
1583 } };
1584 },
1585 },
1586 .argv = argv.items,
1587 .cwd = .{ .dir = update.src_dir },
1588 .environ_map = &environ_map,
1589 .progress_node = run_prog_node,
1590 }) catch |err| if (use_executor) {
1591 // Chances are the foreign executor isn't available. Skip this evaluation.
1592 if (!runner.args.quiet) std.log.warn(
1593 "skipping execution of {q} via executor for foreign target {q}: {t}",
1594 .{ bin_path, comp.target.triple, err },
1595 );
1596 return;
1597 } else return update.fail("unable to run the generated executable {q}: {t}", .{
1598 bin_path, err,
1599 });
1600 defer {
1601 gpa.free(result.stdout);
1602 gpa.free(result.stderr);
1603 }
1604 switch (result.term) {
1605 .exited => |code| switch (check) {
1606 .errors => unreachable,
1607 .stdout => {
1608 if (code != 0) return update.fail(
1609 "generated executable {q} failed with code {d}",
1610 .{ bin_path, code },
1611 );
1612 std.testing.expectEqualStrings(
1613 expected,
1614 result.stdout,
1615 ) catch |err| switch (err) {
1616 error.TestExpectedEqual => return update.fail(
1617 "stdout did not match expected",
1618 .{},
1619 ),
1620 };
1621 },
1622 .exit => {
1623 var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 =
1624 undefined;
1625 std.testing.expectEqualStrings(
1626 expected,
1627 std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable,
1628 ) catch |err| switch (err) {
1629 error.TestExpectedEqual => return update.fail(
1630 "exit code did not match expected",
1631 .{},
1632 ),
1633 };
1634 },
1635 .gdb, .lldb => {
1636 if (code != debug_success) return update.fail(
1637 "debugging {q} failed with code {d}",
1638 .{ bin_path, code },
1639 );
1640 try update.checkMatches("stdout", expected, result.stdout);
1641 },
1642 },
1643 .signal => |sig| return update.fail(
1644 "generated executable {q} terminated with signal {t}",
1645 .{ bin_path, sig },
1646 ),
1647 .stopped => |sig| return update.fail(
1648 "generated executable {q} stopped with signal {t}",
1649 .{ bin_path, sig },
1650 ),
1651 .unknown => return update.fail(
1652 "generated executable {q} terminated unexpectedly",
1653 .{bin_path},
1654 ),
1655 }
1656 if (!use_executor and result.stderr.len > 0) {
1657 std.log.err("#update {q} generated executable {q} had unexpected stderr:\n{s}", .{
1658 update.name, bin_path, result.stderr,
1659 });
1660 }
1661 }
1662
1663 fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) TestError![]const u8 {
1664 const runner = parent_comp.runner;
1665 const arena = runner.arena.allocator();
1666 var compiler: ?Compiler = null;
1667 var args_r: std.Io.Reader = .fixed(c_path);
1668 parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{
1669 .triple = parent_comp.target.triple,
1670 .resolved = resolved: {
1671 var resolved = parent_comp.target.resolved;
1672 resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch);
1673 break :resolved resolved;
1674 },
1675 .cache_mode = .whole,
1676 .backend = null,
1677 .linker = .lld,
1678 .pic = parent_comp.target.pic,
1679 }) catch |err| switch (err) {
1680 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
1681 error.ReadFailed => unreachable, // .fixed
1682 };
1683 const comp = &compiler.?;
1684 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
1685 error.WriteFailed => switch (comp.fw.err.?) {
1686 error.Canceled => |e| return e,
1687 else => |e| return runner.fail("unable to send message: {t}", .{e}),
1688 },
1689 };
1690 comp.state = .update;
1691 var out_path: ?[]const u8 = null;
1692 while (try comp.receiveMessage()) |message| switch (message.tag) {
1693 else => {}, // Ignore other messages,
1694 .config => {
1695 var body_r: std.Io.Reader = .fixed(message.body);
1696 comp.config =
1697 body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable;
1698 },
1699 .emit_digest => {
1700 var body_r: std.Io.Reader = .fixed(message.body);
1701 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
1702 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable;
1703
1704 const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1705 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1706 out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name });
1707 },
1708 .error_bundle => {
1709 const eb = std.zig.Server.allocErrorBundle(
1710 runner.arena.allocator(),
1711 message.body,
1712 ) catch |err| switch (err) {
1713 error.OutOfMemory => |e| return e,
1714 error.EndOfStream => return runner.fail("error bundle truncated", .{}),
1715 };
1716 if (eb.errorMessageCount() > 0) {
1717 try runner.eb_wip.addBundleAsRoots(eb);
1718 return runner.fail("unexpected compile errors", .{});
1719 }
1720 comp.state = .idle;
1721 break;
1722 },
1723 };
1724 try comp.exit();
1725 return out_path.?;
1726 }
1727
1728 fn exit(comp: *Compiler) FailError!void {
1729 const runner = comp.runner;
1730 comp.client.serveBodylessMessage(.exit) catch |err| switch (err) {
1731 error.WriteFailed => switch (comp.fw.err.?) {
1732 error.Canceled => |e| return e,
1733 error.BrokenPipe => {},
1734 else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}),
1735 },
1736 };
1737 comp.child.stdin.?.close(runner.io);
1738 comp.child.stdin = null;
1739 while (try comp.receiveMessage()) |_| {}
1740 if (comp.client.in.bufferedLen() > 0) return runner.fail("unable to receive messages", .{});
1741 const term = comp.child.wait(runner.io) catch |err|
1742 return runner.fail("child process failed: {t}", .{err});
1743 comp.prog_node.end();
1744 switch (term) {
1745 .exited => |code| if (code != 0)
1746 return runner.fail("compiler failed with code {d}", .{code}),
1747 .signal => |sig| return runner.fail("compiler terminated with signal {t}", .{sig}),
1748 .stopped => |sig| return runner.fail("compiler stopped unexpectedly with signal {t}", .{
1749 sig,
1750 }),
1751 .unknown => return runner.fail("compiler terminated unexpectedly", .{}),
1752 }
1753 }
1754};
1755
1756const ClientArgs = struct {
1757 test_file: std.Io.File,
1758 test_kind: std.Io.File.Kind,
1759 zig_exe: std.Io.File,
1760 lib_dir: std.Io.Dir,
1761 src_dir: std.Io.Dir,
1762 targets: std.ArrayList([]const u8),
1763
1764 libc_runtimes_dir: ?std.Io.Dir,
1765 enable_darling: bool,
1766 enable_qemu: bool,
1767 enable_rosetta: bool,
1768 enable_wine: bool,
1769 enable_wasmtime: bool,
1770
1771 gdb_exe: ?std.Io.File,
1772 lldb_exe: ?std.Io.File,
1773
1774 quiet: bool,
1775 keep_src: bool,
1776};
1777pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 {
1778 const fatal = struct {
1779 fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
1780 std.log.err(fmt, args);
1781 std.process.exit(1);
1782 }
1783 }.fatal;
1784 const arena = init.arena.allocator();
1785
1786 var test_path_arg: ?[]const u8 = null;
1787 var zig_path_arg: ?[]const u8 = null;
1788 var lib_path_arg: ?[]const u8 = null;
1789 var libc_runtimes_path_arg: ?[]const u8 = null;
1790 var gdb_path_arg: ?[]const u8 = null;
1791 var lldb_path_arg: ?[]const u8 = null;
1792 var args: ClientArgs = .{
1793 .test_file = undefined,
1794 .test_kind = undefined,
1795 .zig_exe = undefined,
1796 .lib_dir = undefined,
1797 .src_dir = undefined,
1798 .targets = .empty,
1799
1800 .libc_runtimes_dir = null,
1801 .enable_darling = false,
1802 .enable_qemu = false,
1803 .enable_rosetta = false,
1804 .enable_wine = false,
1805 .enable_wasmtime = false,
1806
1807 .gdb_exe = null,
1808 .lldb_exe = null,
1809
1810 .quiet = false,
1811 .keep_src = false,
1812 };
1813 defer args.targets.deinit(init.gpa);
1814
1815 var arg_it = try init.minimal.args.iterateAllocator(arena);
1816 const self = arg_it.next().?;
1817 while (arg_it.next()) |arg| {
1818 if (std.mem.eql(u8, arg, "--listen=-")) {
1819 var stdin_buffer: [512]u8 = undefined;
1820 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer);
1821 var stdout_buffer: [512]u8 = undefined;
1822 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
1823 var runner: Runner = .{
1824 .gpa = init.gpa,
1825 .arena = init.arena,
1826 .environ_map = init.minimal.environ.createMap(init.gpa) catch |err| switch (err) {
1827 error.OutOfMemory => |e| return e,
1828 else => |e| fatal("unable to get environment: {t}", .{e}),
1829 },
1830 .io = init.io,
1831 .prog_node = std.Progress.start(init.io, .{}),
1832 .args = .{
1833 .@"test" = null,
1834 .manifest_file = null,
1835 .zig_exe = null,
1836 .lib_dir = null,
1837 .src_dir = null,
1838 .targets = .empty,
1839 .target_bytes = .empty,
1840
1841 .libc_runtimes_dir = null,
1842 .enable_darling = false,
1843 .enable_qemu = false,
1844 .enable_rosetta = false,
1845 .enable_wasmtime = false,
1846 .enable_wine = false,
1847
1848 .gdb_exe = null,
1849 .lldb_exe = null,
1850
1851 .quiet = false,
1852 },
1853 .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) {
1854 error.Canceled => |e| return e,
1855 else => |e| fatal("unable to resolve host target: {t}", .{e}),
1856 },
1857 .server = .{
1858 .in = &stdin.interface,
1859 .out = &stdout.interface,
1860 },
1861 .eb_wip = try .init(init.gpa),
1862 };
1863 defer runner.deinit();
1864 switch (runner.runServer()) {
1865 error.Canceled, error.OutOfMemory => |e| return e,
1866 error.ServerReadFailed => switch (stdin.err.?) {
1867 error.Canceled => |e| return e,
1868 else => |e| fatal("unable to receive message: {t}", .{e}),
1869 },
1870 error.ServerWriteFailed => switch (stdout.err.?) {
1871 error.Canceled => |e| return e,
1872 else => |e| fatal("unable to send message: {t}", .{e}),
1873 },
1874 error.ServerEndOfStream => fatal("no more messages", .{}),
1875 error.AlreadyReported => {
1876 var eb = try runner.eb_wip.toOwnedBundle("");
1877 defer eb.deinit(init.gpa);
1878 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
1879 error.Canceled => |e| return e,
1880 else => {},
1881 };
1882 std.process.exit(1);
1883 },
1884 }
1885 } else if (std.mem.eql(u8, arg, "--zig")) {
1886 zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1887 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
1888 zig_path_arg = zig_path;
1889 } else if (std.mem.eql(u8, arg, "--lib")) {
1890 lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1891 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
1892 lib_path_arg = lib_path;
1893 } else if (std.mem.eql(u8, arg, "--target")) {
1894 try args.targets.append(
1895 init.gpa,
1896 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})),
1897 );
1898 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
1899 try args.targets.append(init.gpa, try arena.dupe(u8, target));
1900 } else if (std.mem.eql(u8, arg, "--libc-runtimes")) {
1901 libc_runtimes_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1902 } else if (std.mem.cutPrefix(u8, arg, "--libc-runtimes=")) |libc_runtimes_path| {
1903 libc_runtimes_path_arg = libc_runtimes_path;
1904 } else if (std.mem.eql(u8, arg, "-fdarling")) {
1905 args.enable_darling = true;
1906 } else if (std.mem.eql(u8, arg, "-fqemu")) {
1907 args.enable_qemu = true;
1908 } else if (std.mem.eql(u8, arg, "-frosetta")) {
1909 args.enable_rosetta = true;
1910 } else if (std.mem.eql(u8, arg, "-fwine")) {
1911 args.enable_wine = true;
1912 } else if (std.mem.eql(u8, arg, "-fwasmtime")) {
1913 args.enable_wasmtime = true;
1914 } else if (std.mem.eql(u8, arg, "--gdb")) {
1915 gdb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1916 } else if (std.mem.cutPrefix(u8, arg, "--gdb=")) |gdb_path| {
1917 gdb_path_arg = gdb_path;
1918 } else if (std.mem.eql(u8, arg, "--lldb")) {
1919 lldb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1920 } else if (std.mem.cutPrefix(u8, arg, "--lldb=")) |lldb_path| {
1921 lldb_path_arg = lldb_path;
1922 } else if (std.mem.eql(u8, arg, "--quiet")) {
1923 args.quiet = true;
1924 } else if (std.mem.eql(u8, arg, "--keep-src")) {
1925 args.keep_src = true;
1926 } else {
1927 if (test_path_arg) |_| fatal("unexpected arg {q}", .{arg});
1928 test_path_arg = arg;
1929 }
1930 }
1931
1932 const test_path = test_path_arg orelse fatal("missing \"path/to/test\" arg", .{});
1933 const zig_path = zig_path_arg orelse fatal("missing \"--zig=path/to/zig\" arg", .{});
1934 const lib_path = lib_path_arg orelse fatal("missing \"--lib=path/to/lib\" arg", .{});
1935
1936 const cwd: std.Io.Dir = .cwd();
1937 args.test_file = cwd.openFile(init.io, test_path, .{
1938 .allow_directory = true,
1939 }) catch |err| switch (err) {
1940 error.Canceled => |e| return e,
1941 else => |e| fatal("unable to open {q}: {t}", .{ test_path, e }),
1942 };
1943 defer args.test_file.close(init.io);
1944 args.test_kind = (args.test_file.stat(init.io) catch |err| switch (err) {
1945 error.Canceled => |e| return e,
1946 else => |e| fatal("unable to stat {q}: {t}", .{ test_path, e }),
1947 }).kind;
1948 args.zig_exe = cwd.openFile(init.io, zig_path, .{}) catch |err| switch (err) {
1949 error.Canceled => |e| return e,
1950 else => |e| fatal("unable to open {q}: {t}", .{ zig_path, e }),
1951 };
1952 defer args.zig_exe.close(init.io);
1953 args.lib_dir = cwd.openDir(init.io, lib_path, .{}) catch |err| switch (err) {
1954 error.Canceled => |e| return e,
1955 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
1956 };
1957 defer args.lib_dir.close(init.io);
1958 if (libc_runtimes_path_arg) |libc_runtimes_path| args.libc_runtimes_dir = cwd.openDir(
1959 init.io,
1960 libc_runtimes_path,
1961 .{},
1962 ) catch |err| switch (err) {
1963 error.Canceled => |e| return e,
1964 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
1965 };
1966 defer if (args.libc_runtimes_dir) |libc_runtimes_dir| libc_runtimes_dir.close(init.io);
1967 if (gdb_path_arg) |gdb_path| args.gdb_exe = cwd.openFile(init.io, gdb_path, .{}) catch |err| switch (err) {
1968 error.Canceled => |e| return e,
1969 else => |e| fatal("unable to open {q}: {t}", .{ gdb_path, e }),
1970 };
1971 defer if (args.gdb_exe) |gdb_exe| gdb_exe.close(init.io);
1972 if (lldb_path_arg) |lldb_path| args.lldb_exe = cwd.openFile(init.io, lldb_path, .{}) catch |err| switch (err) {
1973 error.Canceled => |e| return e,
1974 else => |e| fatal("unable to open {q}: {t}", .{ lldb_path, e }),
1975 };
1976 defer if (args.lldb_exe) |lldb_exe| lldb_exe.close(init.io);
1977
1978 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
1979 var rand_int: u64 = undefined;
1980 init.io.random(@ptrCast(&rand_int));
1981 break :rand_int rand_int;
1982 });
1983 args.src_dir = cwd.createDirPathOpen(init.io, src_dir_path, .{}) catch |err| switch (err) {
1984 error.Canceled => |e| return e,
1985 else => |e| fatal("unable to create {q}: {t}", .{ src_dir_path, e }),
1986 };
1987 defer {
1988 args.src_dir.close(init.io);
1989 if (!args.keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| {
1990 std.log.warn("unable to delete tree {q}: {t}", .{ src_dir_path, err });
1991 };
1992 }
1993
1994 var inherit_dirs: std.ArrayList(std.Io.Dir) = .initBuffer(try arena.alloc(std.Io.Dir, 3));
1995 var inherit_files: std.ArrayList(std.Io.File) = .initBuffer(try arena.alloc(std.Io.File, 4));
1996 inherit_files.appendSliceAssumeCapacity(&.{ args.test_file, args.zig_exe });
1997 inherit_dirs.appendSliceAssumeCapacity(&.{ args.lib_dir, args.src_dir });
1998 if (args.libc_runtimes_dir) |libc_runtimes_dir| inherit_dirs.appendAssumeCapacity(libc_runtimes_dir);
1999 if (args.gdb_exe) |gdb_exe| inherit_files.appendAssumeCapacity(gdb_exe);
2000 if (args.lldb_exe) |lldb_exe| inherit_files.appendAssumeCapacity(lldb_exe);
2001 var child = std.process.spawn(init.io, .{
2002 .argv = &.{ self, "--listen=-" },
2003 .stdin = .pipe,
2004 .stdout = .pipe,
2005 .inherit_dirs = inherit_dirs.items,
2006 .inherit_files = inherit_files.items,
2007 }) catch |err| switch (err) {
2008 error.Canceled, error.OutOfMemory => |e| return e,
2009 else => |e| fatal("unable to spawn runner: {t}", .{e}),
2010 };
2011
2012 var child_reader_buffer: [512]u8 = undefined;
2013 var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer);
2014 var child_writer_buffer: [512]u8 = undefined;
2015 var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer);
2016 var client: std.zig.Client = .{
2017 .in = &child_reader.interface,
2018 .out = &child_writer.interface,
2019 };
2020 const success = runClient(init.gpa, arena, init.io, &client, &args) catch |err| switch (err) {
2021 error.OutOfMemory => |e| return e,
2022 error.ReadFailed => switch (child_reader.err.?) {
2023 error.Canceled => |e| return e,
2024 else => |e| fatal("unable to receive message: {t}", .{e}),
2025 },
2026 error.WriteFailed => switch (child_writer.err.?) {
2027 error.Canceled => |e| return e,
2028 else => |e| fatal("unable to send message: {t}", .{e}),
2029 },
2030 error.EndOfStream => fatal("no more messages", .{}),
2031 };
2032 _ = child.wait(init.io) catch |err| switch (err) {
2033 error.Canceled => |e| return e,
2034 else => |e| fatal("unable to wait for runner: {t}", .{e}),
2035 };
2036 return if (success) 0 else 1;
2037}
2038fn runClient(
2039 gpa: std.mem.Allocator,
2040 arena: std.mem.Allocator,
2041 io: std.Io,
2042 client: *std.zig.Client,
2043 args: *const ClientArgs,
2044) (std.mem.Allocator.Error || std.Io.Reader.Error || std.Io.Writer.Error)!bool {
2045 var stdout_buffer: [512]u8 = undefined;
2046 var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
2047 const stdout = &stdout_fw.interface;
2048 var stderr_buffer: [512]u8 = undefined;
2049 var stderr_fw: std.Io.File.Writer = .initStreaming(.stderr(), io, &stderr_buffer);
2050 const stderr = &stderr_fw.interface;
2051
2052 var argv: std.ArrayList(u8) = .empty;
2053 defer argv.deinit(gpa);
2054 const Arg = std.zig.Client.Message.Arg;
2055
2056 try argv.append(gpa, @backingInt(@as(Arg, switch (args.test_kind) {
2057 else => unreachable,
2058 .file => .input_file,
2059 .directory => .input_dir,
2060 })));
2061 try argv.appendSlice(gpa, @ptrCast(&args.test_file.handle));
2062
2063 try argv.append(gpa, @backingInt(Arg.prefix));
2064 try argv.appendSlice(gpa, "--zig=");
2065 try argv.append(gpa, 0);
2066 try argv.append(gpa, @backingInt(Arg.input_file));
2067 try argv.appendSlice(gpa, @ptrCast(&args.zig_exe.handle));
2068
2069 try argv.append(gpa, @backingInt(Arg.prefix));
2070 try argv.appendSlice(gpa, "--lib=");
2071 try argv.append(gpa, 0);
2072 try argv.append(gpa, @backingInt(Arg.input_dir));
2073 try argv.appendSlice(gpa, @ptrCast(&args.lib_dir.handle));
2074
2075 try argv.append(gpa, @backingInt(Arg.prefix));
2076 try argv.appendSlice(gpa, "--src=");
2077 try argv.append(gpa, 0);
2078 try argv.append(gpa, @backingInt(Arg.output_dir));
2079 try argv.appendSlice(gpa, @ptrCast(&args.src_dir.handle));
2080
2081 for (args.targets.items) |target| {
2082 try argv.append(gpa, @backingInt(Arg.string));
2083 try argv.appendSlice(gpa, "--target");
2084 try argv.append(gpa, 0);
2085
2086 try argv.append(gpa, @backingInt(Arg.string));
2087 try argv.appendSlice(gpa, target);
2088 try argv.append(gpa, 0);
2089 }
2090
2091 if (args.libc_runtimes_dir) |libc_runtimes_dir| {
2092 try argv.append(gpa, @backingInt(Arg.prefix));
2093 try argv.appendSlice(gpa, "--libc-runtimes=");
2094 try argv.append(gpa, 0);
2095 try argv.append(gpa, @backingInt(Arg.input_dir));
2096 try argv.appendSlice(gpa, @ptrCast(&libc_runtimes_dir.handle));
2097 }
2098 if (args.enable_darling) {
2099 try argv.append(gpa, @backingInt(Arg.string));
2100 try argv.appendSlice(gpa, "-fdarling");
2101 try argv.append(gpa, 0);
2102 }
2103 if (args.enable_qemu) {
2104 try argv.append(gpa, @backingInt(Arg.string));
2105 try argv.appendSlice(gpa, "-fqemu");
2106 try argv.append(gpa, 0);
2107 }
2108 if (args.enable_rosetta) {
2109 try argv.append(gpa, @backingInt(Arg.string));
2110 try argv.appendSlice(gpa, "-frosetta");
2111 try argv.append(gpa, 0);
2112 }
2113 if (args.enable_wine) {
2114 try argv.append(gpa, @backingInt(Arg.string));
2115 try argv.appendSlice(gpa, "-fwine");
2116 try argv.append(gpa, 0);
2117 }
2118 if (args.enable_wasmtime) {
2119 try argv.append(gpa, @backingInt(Arg.string));
2120 try argv.appendSlice(gpa, "-fwasmtime");
2121 try argv.append(gpa, 0);
2122 }
2123
2124 if (args.gdb_exe) |gdb_exe| {
2125 try argv.append(gpa, @backingInt(Arg.prefix));
2126 try argv.appendSlice(gpa, "--gdb=");
2127 try argv.append(gpa, 0);
2128 try argv.append(gpa, @backingInt(Arg.input_file));
2129 try argv.appendSlice(gpa, @ptrCast(&gdb_exe.handle));
2130 }
2131 if (args.lldb_exe) |lldb_exe| {
2132 try argv.append(gpa, @backingInt(Arg.prefix));
2133 try argv.appendSlice(gpa, "--lldb=");
2134 try argv.append(gpa, 0);
2135 try argv.append(gpa, @backingInt(Arg.input_file));
2136 try argv.appendSlice(gpa, @ptrCast(&lldb_exe.handle));
2137 }
2138
2139 if (args.quiet) {
2140 try argv.append(gpa, @backingInt(Arg.string));
2141 try argv.appendSlice(gpa, "--quiet");
2142 try argv.append(gpa, 0);
2143 }
2144
2145 try client.serveMessageHeader(.{
2146 .tag = .args,
2147 .bytes_len = @intCast(argv.items.len),
2148 });
2149 try client.out.writeAll(argv.items);
2150
2151 var success = true;
2152 var metadata: struct {
2153 index: u32,
2154 len: u32,
2155 names: []u32,
2156 expected_panic_msgs: []u32,
2157 string_bytes: []u8,
2158
2159 fn name(m: *@This()) []const u8 {
2160 return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0);
2161 }
2162
2163 fn expectedPanicMsg(m: *@This()) ?[:0]const u8 {
2164 return switch (m.expected_panic_msgs[m.index - 1]) {
2165 0 => null,
2166 else => |offset| m.string_bytes[offset..std.mem.findScalarPos(
2167 u8,
2168 m.string_bytes,
2169 offset,
2170 0,
2171 ).? :0],
2172 };
2173 }
2174
2175 fn next(m: *@This()) ?u32 {
2176 if (m.len - m.index == 0) return null;
2177 defer m.index += 1;
2178 return m.index;
2179 }
2180 } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} };
2181 try client.serveBodylessMessage(.query_test_metadata);
2182 while (true) {
2183 const hdr = try client.receiveMessage();
2184 switch (hdr.tag) {
2185 else => try client.in.discardAll(hdr.bytes_len),
2186 .zig_version => {
2187 const actual_version = try client.in.take(hdr.bytes_len);
2188 if (!std.mem.eql(u8, builtin.zig_version_string, actual_version)) {
2189 try stderr.print(
2190 "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n",
2191 .{ builtin.zig_version_string, actual_version },
2192 );
2193 try stderr.flush();
2194 std.process.exit(1);
2195 }
2196 },
2197 .error_bundle => {
2198 const body = try arena.alloc(u8, hdr.bytes_len);
2199 try client.in.readSliceAll(body);
2200 const eb = std.zig.Server.allocErrorBundle(arena, body) catch |err| switch (err) {
2201 error.OutOfMemory => |e| return e,
2202 error.EndOfStream => {
2203 try stderr.writeAll("error bundle truncated\n");
2204 try stderr.flush();
2205 std.process.exit(1);
2206 },
2207 };
2208 success = false;
2209 eb.renderToStderr(io, .{}, .auto) catch {};
2210 },
2211 .test_metadata => {
2212 const tm_hdr =
2213 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
2214 metadata = .{
2215 .index = 0,
2216 .len = tm_hdr.tests_len,
2217 .names = try arena.alloc(u32, tm_hdr.tests_len),
2218 .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len),
2219 .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len),
2220 };
2221 try client.in.readSliceEndian(u32, metadata.names, .little);
2222 try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little);
2223 try client.in.readSliceAll(metadata.string_bytes);
2224 try client.serveRunTest(metadata.next() orelse break);
2225 },
2226 .test_results => {
2227 const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little);
2228 switch (tr.flags.status) {
2229 .pass, .skip => {},
2230 .fail => success = false,
2231 }
2232 try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() });
2233 try stdout.flush();
2234 try client.serveRunTest(metadata.next() orelse break);
2235 },
2236 }
2237 }
2238 try client.serveBodylessMessage(.exit);
2239 return success;
2240}
test/debugger/basic/manifest+2-2
......@@ -3,6 +3,6 @@
33-Mroot=basic.zig
44#}
55
6#write basic.zig basic.zig
6#write basic.zig @basic.zig
77#update
8#check lldb lldb
8#check lldb @lldb
test/debugger/enums+1-3
......@@ -1,6 +1,4 @@
1#todo {
2passes, but prints errors
3#}
1#todo "passes, but prints errors"
42#skip x86_64-linux-none-whole-selfhosted-new-nopic
53#skip x86_64-linux-none-whole-selfhosted-new-pic
64#skip x86_64-linux-none-incremental-selfhosted-new-nopic
test/debugger/errors+1-3
......@@ -1,6 +1,4 @@
1#todo {
2passes, but prints errors
3#}
1#todo "passes, but prints errors"
42#skip x86_64-linux-none-whole-selfhosted-new-nopic
53#skip x86_64-linux-none-whole-selfhosted-new-pic
64#skip x86_64-linux-none-incremental-selfhosted-new-nopic
test/debugger/inline_call/manifest+6-8
......@@ -1,6 +1,4 @@
1#todo {
2implement inline line info in lldb
3#}
1#todo "implement inline line info in lldb"
42#skip x86_64-linux-none-incremental-selfhosted-new-nopic
53#skip x86_64-linux-none-incremental-selfhosted-new-pic
64
......@@ -10,9 +8,9 @@ implement inline line info in lldb
108-Mmodule=mod0.zig
119#}
1210
13#write root0.zig root0.zig
14#write root1.zig root1.zig
15#write mod0.zig mod0.zig
16#write mod1.zig mod1.zig
11#write root0.zig @root0.zig
12#write root1.zig @root1.zig
13#write mod0.zig @mod0.zig
14#write mod1.zig @mod1.zig
1715#update
18#check lldb lldb
16#check lldb @lldb
test/debugger/matrix.zon created+68
......@@ -0,0 +1,68 @@
1.{
2 .{
3 .target = .{
4 .cpu_arch = .x86_64,
5 .os_tag = .linux,
6 .abi = .none,
7 },
8 .cache_mode = .whole,
9 .backend = .selfhosted,
10 .linker = .old,
11 .pic = .nopic,
12 },
13 .{
14 .target = .{
15 .cpu_arch = .x86_64,
16 .os_tag = .linux,
17 .abi = .none,
18 },
19 .cache_mode = .whole,
20 .backend = .selfhosted,
21 .linker = .old,
22 .pic = .pic,
23 },
24 .{
25 .target = .{
26 .cpu_arch = .x86_64,
27 .os_tag = .linux,
28 .abi = .none,
29 },
30 .cache_mode = .whole,
31 .backend = .selfhosted,
32 .linker = .new,
33 .pic = .nopic,
34 },
35 .{
36 .target = .{
37 .cpu_arch = .x86_64,
38 .os_tag = .linux,
39 .abi = .none,
40 },
41 .cache_mode = .whole,
42 .backend = .selfhosted,
43 .linker = .new,
44 .pic = .pic,
45 },
46 .{
47 .target = .{
48 .cpu_arch = .x86_64,
49 .os_tag = .linux,
50 .abi = .none,
51 },
52 .cache_mode = .incremental,
53 .backend = .selfhosted,
54 .linker = .new,
55 .pic = .nopic,
56 },
57 .{
58 .target = .{
59 .cpu_arch = .x86_64,
60 .os_tag = .linux,
61 .abi = .none,
62 },
63 .cache_mode = .incremental,
64 .backend = .selfhosted,
65 .linker = .new,
66 .pic = .pic,
67 },
68}
test/debugger/multi_array_list/manifest+2-2
......@@ -3,6 +3,6 @@
33-Mroot=main.zig
44#}
55
6#write main.zig main.zig
6#write main.zig @main.zig
77#update
8#check lldb lldb
8#check lldb @lldb
test/debugger/step_single_stmt_loops/manifest+2-2
......@@ -3,6 +3,6 @@
33-Mroot=step_single_stmt_loops.zig
44#}
55
6#write step_single_stmt_loops.zig step_single_stmt_loops.zig
6#write step_single_stmt_loops.zig @step_single_stmt_loops.zig
77#update
8#check lldb lldb
8#check lldb @lldb
test/debugger/unions+1-3
......@@ -1,6 +1,4 @@
1#todo {
2passes, but prints errors
3#}
1#todo "passes, but prints errors"
42#skip x86_64-linux-none-whole-selfhosted-new-nopic
53#skip x86_64-linux-none-whole-selfhosted-new-pic
64#skip x86_64-linux-none-incremental-selfhosted-new-nopic
test/incremental/add_decl+5-10
......@@ -24,8 +24,7 @@ const foo = "good morning\n";
2424const bar = "good evening\n";
2525const io = std.Io.Threaded.global_single_threaded.io();
2626#}
27#update {
28add new declaration#}
27#update "add new declaration"
2928#check stdout {
3029good morning
3130#}
......@@ -39,8 +38,7 @@ const foo = "good morning\n";
3938const bar = "good evening\n";
4039const io = std.Io.Threaded.global_single_threaded.io();
4140#}
42#update {
43reference new declaration#}
41#update "reference new declaration"
4442#check stdout {
4543good evening
4644#}
......@@ -54,8 +52,7 @@ const foo = "good morning\n";
5452const bar = "good evening\n";
5553const io = std.Io.Threaded.global_single_threaded.io();
5654#}
57#update {
58reference missing declaration#}
55#update "reference missing declaration"
5956#check errors {
6057main.zig:3:52: error: use of undeclared identifier 'qux'
6158#}
......@@ -70,8 +67,7 @@ const bar = "good evening\n";
7067const qux = "good night\n";
7168const io = std.Io.Threaded.global_single_threaded.io();
7269#}
73#update {
74add missing declaration#}
70#update "add missing declaration"
7571#check stdout {
7672good night
7773#}
......@@ -84,8 +80,7 @@ pub fn main() !void {
8480const qux = "good night\n";
8581const io = std.Io.Threaded.global_single_threaded.io();
8682#}
87#update {
88remove unused declarations#}
83#update "remove unused declarations"
8984#check stdout {
9085good night
9186#}
test/incremental/add_decl_namespaced+5-10
......@@ -24,8 +24,7 @@ const foo = "good morning\n";
2424const bar = "good evening\n";
2525const io = std.Io.Threaded.global_single_threaded.io();
2626#}
27#update {
28add new declaration#}
27#update "add new declaration"
2928#check stdout {
3029good morning
3130#}
......@@ -39,8 +38,7 @@ const foo = "good morning\n";
3938const bar = "good evening\n";
4039const io = std.Io.Threaded.global_single_threaded.io();
4140#}
42#update {
43reference new declaration#}
41#update "reference new declaration"
4442#check stdout {
4543good evening
4644#}
......@@ -54,8 +52,7 @@ const foo = "good morning\n";
5452const bar = "good evening\n";
5553const io = std.Io.Threaded.global_single_threaded.io();
5654#}
57#update {
58reference missing declaration#}
55#update "reference missing declaration"
5956#check errors {
6057main.zig:3:59: error: root source file struct 'main' has no member named 'qux'
6158main.zig:1:1: note: struct declared here
......@@ -71,8 +68,7 @@ const bar = "good evening\n";
7168const qux = "good night\n";
7269const io = std.Io.Threaded.global_single_threaded.io();
7370#}
74#update {
75add missing declaration#}
71#update "add missing declaration"
7672#check stdout {
7773good night
7874#}
......@@ -85,8 +81,7 @@ pub fn main() !void {
8581const qux = "good night\n";
8682const io = std.Io.Threaded.global_single_threaded.io();
8783#}
88#update {
89remove unused declarations#}
84#update "remove unused declarations"
9085#check stdout {
9186good night
9287#}
test/incremental/add_decls_around_bad_field_expr+1-2
......@@ -32,8 +32,7 @@ pub fn main() void {
3232 _ = @as(@This(), undefined);
3333}
3434#}
35#update {
36add more decls around error#}
35#update "add more decls around error"
3736#check errors {
3837main.zig:4:15: error: root source file struct 'main' has no member named 'does_not_exist'
3938main.zig:1:1: note: struct declared here
test/incremental/add_field_and_nested_struct_uses_changed_decl+3-6
......@@ -14,8 +14,7 @@ const S = struct {
1414};
1515#}
1616#update
17#check stdout {
18#}
17#check stdout
1918
2019#write main.zig {
2120pub fn main() void {
......@@ -29,7 +28,5 @@ const S = struct {
2928 bar: u32,
3029};
3130#}
32#update {
33add field to outer struct, change decl used by inner struct#}
34#check stdout {
35#}
31#update "add field to outer struct, change decl used by inner struct"
32#check stdout
test/incremental/add_remove_struct_fields/manifest+7-10
......@@ -2,30 +2,27 @@
22-Mroot=main.zig
33#}
44
5#write main.zig 0/main.zig
5#write main.zig @0/main.zig
66#update
77#check stdout {
881 100
99#}
1010
11#write main.zig 1/main.zig
12#update {
13add a field#}
11#write main.zig @1/main.zig
12#update "add a field"
1413#check stdout {
15142 100
1615#}
1716
18#write main.zig 2/main.zig
19#update {
20remove all fields#}
17#write main.zig @2/main.zig
18#update "remove all fields"
2119#check errors {
2220main.zig:15:24: error: no field named 'x' in struct 'main.S'
2321main.zig:1:11: note: struct declared here
2422#}
2523
26#write main.zig 3/main.zig
27#update {
28remove reference to non-existent field#}
24#write main.zig @3/main.zig
25#update "remove reference to non-existent field"
2926#check stdout {
30270 <no fields>
3128#}
test/incremental/add_remove_toplevel_fields/manifest+7-10
......@@ -2,30 +2,27 @@
22-Mroot=main.zig
33#}
44
5#write main.zig 0/main.zig
5#write main.zig @0/main.zig
66#update
77#check stdout {
881 100
99#}
1010
11#write main.zig 1/main.zig
12#update {
13add a field#}
11#write main.zig @1/main.zig
12#update "add a field"
1413#check stdout {
15142 100
1615#}
1716
18#write main.zig 2/main.zig
19#update {
20remove all fields#}
17#write main.zig @2/main.zig
18#update "remove all fields"
2119#check errors {
2220main.zig:15:24: error: no field named 'x' in struct 'main'
2321main.zig:1:1: note: struct declared here
2422#}
2523
26#write main.zig 3/main.zig
27#update {
28remove reference to non-existent field#}
24#write main.zig @3/main.zig
25#update "remove reference to non-existent field"
2926#check stdout {
30270 <no fields>
3128#}
test/incremental/analysis_error_and_syntax_error+6-12
......@@ -16,8 +16,7 @@ main.zig:2:5: error: uh oh
1616pub fn main() !void {
1717 @compileError("uh oh");
1818#}
19#update {
20add parse error#}
19#update "add parse error"
2120#check errors {
2221main.zig:3:1: error: expected statement, found 'EOF'
2322#}
......@@ -27,8 +26,7 @@ pub fn main() !void {
2726 @compileError("uh oh");
2827}
2928#}
30#update {
31fix parse error#}
29#update "fix parse error"
3230#check errors {
3331main.zig:2:5: error: uh oh
3432#}
......@@ -37,8 +35,7 @@ main.zig:2:5: error: uh oh
3735pub fn main() !void {
3836 @compileError("uh oh");
3937#}
40#update {
41add parse error again#}
38#update "add parse error again"
4239#check errors {
4340main.zig:3:1: error: expected statement, found 'EOF'
4441#}
......@@ -47,8 +44,7 @@ main.zig:3:1: error: expected statement, found 'EOF'
4744pub fn main() !void {
4845 //@compileError("uh oh");
4946#}
50#update {
51comment @compileError call#}
47#update "comment @compileError call"
5248#check errors {
5349main.zig:3:1: error: expected statement, found 'EOF'
5450#}
......@@ -58,7 +54,5 @@ pub fn main() !void {
5854 //@compileError("uh oh");
5955}
6056#}
61#update {
62fix parse error again#}
63#check stdout {
64#}
57#update "fix parse error again"
58#check stdout
test/incremental/bad_import+2-4
......@@ -26,8 +26,7 @@ comptime {
2626 _ = @import("this_is/not_real.zig");
2727}
2828#}
29#update {
30change bad import#}
29#update "change bad import"
3130#check errors {
3231this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound
3332foo.zig:2:17: note: file imported here
......@@ -41,8 +40,7 @@ pub fn main() !void {
4140const std = @import("std");
4241const io = std.Io.Threaded.global_single_threaded.io();
4342#}
44#update {
45remove import of 'foo.zig'#}
43#update "remove import of 'foo.zig'"
4644#check stdout {
4745success
4846#}
test/incremental/change_bitpack_backing_int+3-6
......@@ -38,8 +38,7 @@ comptime {
3838const std = @import("std");
3939const io = std.Io.Threaded.global_single_threaded.io();
4040#}
41#update {
42make backing types signed#}
41#update "make backing types signed"
4342#check errors {
4443main.zig:5:5: error: found compile log statement
4544main.zig:8:5: note: also here
......@@ -62,8 +61,7 @@ comptime {
6261const std = @import("std");
6362const io = std.Io.Threaded.global_single_threaded.io();
6463#}
65#update {
66make backing types too small#}
64#update "make backing types too small"
6765#check errors {
6866main.zig:1:20: error: backing integer bit width does not match total bit width of fields
6967main.zig:1:27: note: backing integer 'i5' has bit width '5'
......@@ -87,8 +85,7 @@ comptime {
8785const std = @import("std");
8886const io = std.Io.Threaded.global_single_threaded.io();
8987#}
90#update {
91make backing types too big#}
88#update "make backing types too big"
9289#check errors {
9390main.zig:1:20: error: backing integer bit width does not match total bit width of fields
9491main.zig:1:27: note: backing integer 'u10' has bit width '10'
test/incremental/change_container_kind+8-16
......@@ -12,8 +12,7 @@ pub fn main() void {
1212#write foo.zig {
1313pub const T = struct { x: u8 };
1414#}
15#update {
16T is struct#}
15#update "T is struct"
1716#check errors {
1817main.zig:3:5: error: found compile log statement
1918
......@@ -25,8 +24,7 @@ Compile Log Output:
2524#write foo.zig {
2625pub const T = union {};
2726#}
28#update {
29T is union#}
27#update "T is union"
3028#check errors {
3129main.zig:3:5: error: found compile log statement
3230
......@@ -38,8 +36,7 @@ Compile Log Output:
3836#write foo.zig {
3937pub const T = enum {};
4038#}
41#update {
42T is enum#}
39#update "T is enum"
4340#check errors {
4441main.zig:3:5: error: found compile log statement
4542
......@@ -51,8 +48,7 @@ Compile Log Output:
5148#write foo.zig {
5249pub const T = opaque {};
5350#}
54#update {
55T is opaque#}
51#update "T is opaque"
5652#check errors {
5753main.zig:3:5: error: found compile log statement
5854
......@@ -64,8 +60,7 @@ Compile Log Output:
6460#write foo.zig {
6561pub const T = packed struct {};
6662#}
67#update {
68T is packed struct#}
63#update "T is packed struct"
6964#check errors {
7065main.zig:3:5: error: found compile log statement
7166
......@@ -77,8 +72,7 @@ Compile Log Output:
7772#write foo.zig {
7873pub const T = packed union { x: void }; // packed unions need at least 1 field
7974#}
80#update {
81T is packed union#}
75#update "T is packed union"
8276#check errors {
8377main.zig:3:5: error: found compile log statement
8478
......@@ -90,8 +84,7 @@ Compile Log Output:
9084#write foo.zig {
9185pub const T = extern struct {};
9286#}
93#update {
94T is extern struct#}
87#update "T is extern struct"
9588#check errors {
9689main.zig:3:5: error: found compile log statement
9790
......@@ -103,8 +96,7 @@ Compile Log Output:
10396#write foo.zig {
10497pub const T = extern union { x: void }; // extern unions need at least 1 field
10598#}
106#update {
107T is extern union#}
99#update "T is extern union"
108100#check errors {
109101main.zig:3:5: error: found compile log statement
110102
test/incremental/change_embed_file+5-10
......@@ -21,15 +21,13 @@ Hello, World!
2121#write string.txt {
2222Hello again, World!
2323#}
24#update {
25change file contents#}
24#update "change file contents"
2625#check stdout {
2726Hello again, World!
2827#}
2928
3029#delete string.txt
31#update {
32delete file#}
30#update "delete file"
3331#check errors {
3432main.zig:2:27: error: failed opening "string.txt": FileNotFound
3533#}
......@@ -42,8 +40,7 @@ pub fn main() !void {
4240}
4341const io = std.Io.Threaded.global_single_threaded.io();
4442#}
45#update {
46remove reference to file#}
43#update "remove reference to file"
4744#check stdout {
4845a hardcoded string
4946#}
......@@ -56,8 +53,7 @@ pub fn main() !void {
5653}
5754const io = std.Io.Threaded.global_single_threaded.io();
5855#}
59#update {
60re-introduce reference to file#}
56#update "re-introduce reference to file"
6157#check errors {
6258main.zig:2:27: error: failed opening "string.txt": FileNotFound
6359#}
......@@ -65,8 +61,7 @@ main.zig:2:27: error: failed opening "string.txt": FileNotFound
6561#write string.txt {
6662We're back, World!
6763#}
68#update {
69recreate file#}
64#update "recreate file"
7065#check stdout {
7166We're back, World!
7267#}
test/incremental/change_enum_tag_type/manifest+9-13
......@@ -2,29 +2,26 @@
22-Mroot=main.zig
33#}
44
5#write main.zig 0/main.zig
5#write main.zig @0/main.zig
66#update
77#check stdout {
88a
99#}
1010
11#write main.zig 1/main.zig
12#update {
13too many enum fields#}
11#write main.zig @1/main.zig
12#update "too many enum fields"
1413#check errors {
1514main.zig:7:5: error: enum tag value '4' too large for type 'u2'
1615#}
1716
18#write main.zig 2/main.zig
19#update {
20increase tag size#}
17#write main.zig @2/main.zig
18#update "increase tag size"
2119#check stdout {
2220a
2321#}
2422
25#write main.zig 3/main.zig
26#update {
27specify tag directly#}
23#write main.zig @3/main.zig
24#update "specify tag directly"
2825#check errors {
2926main.zig:9:5: error: found compile log statement
3027
......@@ -32,9 +29,8 @@ Compile Log Output:
3229@as(type, u3)
3330#}
3431
35#write main.zig 4/main.zig
36#update {
37change directly-specified tag type#}
32#write main.zig @4/main.zig
33#update "change directly-specified tag type"
3834#check errors {
3935main.zig:9:5: error: found compile log statement
4036
test/incremental/change_exports/manifest+14-21
......@@ -2,50 +2,43 @@
22-Mroot=main.zig
33#}
44
5#write main.zig 0/main.zig
5#write main.zig @0/main.zig
66#update
77#check stdout {
88123
99#}
1010
11#write main.zig 1/main.zig
12#update {
13add conflict#}
11#write main.zig @1/main.zig
12#update "add conflict"
1413#check errors {
1514main.zig:6:5: error: exported symbol collision: foo
1615main.zig:1:1: note: other symbol here
1716#}
1817
19#write main.zig 2/main.zig
20#update {
21resolve conflict#}
18#write main.zig @2/main.zig
19#update "resolve conflict"
2220#check stdout {
2321123 456
2422#}
2523
26#write main.zig 3/main.zig
27#update {
28put exports in decl#}
24#write main.zig @3/main.zig
25#update "put exports in decl"
2926#check stdout {
3027123 456
3128#}
3229
33#write main.zig 4/main.zig
34#update {
35remove reference to exporting decl#}
36#check stdout {
37#}
30#write main.zig @4/main.zig
31#update "remove reference to exporting decl"
32#check stdout
3833
39#write main.zig 5/main.zig
40#update {
41mark consts as export#}
34#write main.zig @5/main.zig
35#update "mark consts as export"
4236#check stdout {
4337123 456
4438#}
4539
46#write main.zig 6/main.zig
47#update {
48reintroduce reference to exporting decl, introducing conflict#}
40#write main.zig @6/main.zig
41#update "reintroduce reference to exporting decl, introducing conflict"
4942#check errors {
5043main.zig:5:5: error: exported symbol collision: bar
5144main.zig:2:1: note: other symbol here
test/incremental/change_fn_type+2-4
......@@ -29,8 +29,7 @@ fn foo(x: i64) !void {
2929const std = @import("std");
3030const io = std.Io.Threaded.global_single_threaded.io();
3131#}
32#update {
33change function type#}
32#update "change function type"
3433#check stdout {
3534123
3635#}
......@@ -46,8 +45,7 @@ fn foo(x: i64) !void {
4645const std = @import("std");
4746const io = std.Io.Threaded.global_single_threaded.io();
4847#}
49#update {
50change function argument#}
48#update "change function argument"
5149#check stdout {
5250-42
5351#}
test/incremental/change_generic_line_number+1-2
......@@ -39,8 +39,7 @@ pub fn main() !void {
3939 try Printer("bar\n").print();
4040}
4141#}
42#update {
43change line number#}
42#update "change line number"
4443#check stdout {
4544foo
4645bar
test/incremental/change_line_number+1-2
......@@ -22,8 +22,7 @@ pub fn main() !void {
2222}
2323const io = std.Io.Threaded.global_single_threaded.io();
2424#}
25#update {
26change line number#}
25#update "change line number"
2726#check stdout {
2827foo
2928#}
test/incremental/change_module+4-8
......@@ -41,8 +41,7 @@ comptime {
4141 //_ = @import("other.zig");
4242}
4343#}
44#update {
45change module of other.zig#}
44#update "change module of other.zig"
4645#check errors {
4746other.zig:2:5: error: found compile log statement
4847
......@@ -61,8 +60,7 @@ comptime {
6160 _ = @import("other.zig");
6261}
6362#}
64#update {
65put other.zig in both modules#}
63#update "put other.zig in both modules"
6664#check errors {
6765other.zig:1:1: error: file exists in modules 'root' and 'foo'
6866other.zig:1:1: note: files must belong to only one module
......@@ -81,7 +79,5 @@ comptime {
8179 //_ = @import("other.zig");
8280}
8381#}
84#update {
85put other.zig in no modules#}
86#check stdout {
87#}
82#update "put other.zig in no modules"
83#check stdout
test/incremental/change_panic_handler+2-4
......@@ -39,8 +39,7 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn {
3939const std = @import("std");
4040const io = std.Io.Threaded.global_single_threaded.io();
4141#}
42#update {
43change the panic handler body#}
42#update "change the panic handler body"
4443#check stdout {
4544new panic message: integer overflow
4645#}
......@@ -61,8 +60,7 @@ fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
6160const std = @import("std");
6261const io = std.Io.Threaded.global_single_threaded.io();
6362#}
64#update {
65change the panic handler function value#}
63#update "change the panic handler function value"
6664#check stdout {
6765third panic message: integer overflow
6866#}
test/incremental/change_panic_handler_explicit/manifest+5-7
......@@ -2,22 +2,20 @@
22-Mroot=main.zig
33#}
44
5#write main.zig 0/main.zig
5#write main.zig @0/main.zig
66#update
77#check stdout {
88panic message: integer overflow
99#}
1010
11#write main.zig 1/main.zig
12#update {
13change the panic handler body#}
11#write main.zig @1/main.zig
12#update "change the panic handler body"
1413#check stdout {
1514new panic message: integer overflow
1615#}
1716
18#write main.zig 2/main.zig
19#update {
20change the panic handler function value#}
17#write main.zig @2/main.zig
18#update "change the panic handler function value"
2119#check stdout {
2220third panic message: integer overflow
2321#}
test/incremental/change_reify_struct_field_type+3-6
......@@ -13,8 +13,7 @@ const Enum = enum { foo, bar };
1313const std = @import("std");
1414#}
1515#update
16#check stdout {
17foo#}
16#check stdout "foo"
1817
1918#write main.zig {
2019fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{Enum}, &.{.{}})) Enum {
......@@ -26,7 +25,5 @@ pub fn main(init: std.process.Init) !void {
2625const Enum = enum { foo, bar };
2726const std = @import("std");
2827#}
29#update {
30change field type#}
31#check stdout {
32bar#}
28#update "change field type"
29#check stdout "bar"
test/incremental/change_shift_op+1-2
......@@ -29,8 +29,7 @@ fn foo(x: u16) !void {
2929const std = @import("std");
3030const io = std.Io.Threaded.global_single_threaded.io();
3131#}
32#update {
33change to right shift#}
32#update "change to right shift"
3433#check stdout {
35340x130
3635#}
test/incremental/change_struct_same_fields+2-4
......@@ -39,8 +39,7 @@ fn foo(val: *const S) !void {
3939const std = @import("std");
4040const io = std.Io.Threaded.global_single_threaded.io();
4141#}
42#update {
43change struct layout#}
42#update "change struct layout"
4443#check stdout {
4544100 200
4645#}
......@@ -61,8 +60,7 @@ fn foo(val: *const S) !void {
6160const std = @import("std");
6261const io = std.Io.Threaded.global_single_threaded.io();
6362#}
64#update {
65change values#}
63#update "change values"
6664#check stdout {
67651234 5678
6866#}
test/incremental/change_union_tag_type+2-4
......@@ -29,8 +29,7 @@ pub fn main(init: std.process.Init) !void {
2929}
3030const std = @import("std");
3131#}
32#update {
33change tag type#}
32#update "change tag type"
3433#check errors {
3534main.zig:3:24: error: no field named 'a' in enum 'main.B'
3635main.zig:2:11: note: enum declared here
......@@ -47,8 +46,7 @@ pub fn main(init: std.process.Init) !void {
4746}
4847const std = @import("std");
4948#}
50#update {
51change field name to match new tag type#}
49#update "change field name to match new tag type"
5250#check stdout {
5351b
5452#}
test/incremental/change_zon_file+5-10
......@@ -21,15 +21,13 @@ Hello, World!
2121#write message.zon {
2222"Hello again, World!\n"
2323#}
24#update {
25change ZON file contents#}
24#update "change ZON file contents"
2625#check stdout {
2726Hello again, World!
2827#}
2928
3029#delete message.zon
31#update {
32delete file#}
30#update "delete file"
3331#check errors {
3432message.zon:1:1: error: unable to load "message.zon": FileNotFound
3533main.zig:2:37: note: file imported here
......@@ -43,8 +41,7 @@ pub fn main() !void {
4341}
4442const io = std.Io.Threaded.global_single_threaded.io();
4543#}
46#update {
47remove reference to ZON file#}
44#update "remove reference to ZON file"
4845#check errors {
4946message.zon:1:1: error: unable to load "message.zon": FileNotFound
5047main.zig:2:37: note: file imported here
......@@ -53,8 +50,7 @@ main.zig:2:37: note: file imported here
5350#write message.zon {
5451"We're back, World!\n"
5552#}
56#update {
57recreate ZON file#}
53#update "recreate ZON file"
5854#check stdout {
5955a hardcoded string
6056#}
......@@ -67,8 +63,7 @@ pub fn main() !void {
6763}
6864const io = std.Io.Threaded.global_single_threaded.io();
6965#}
70#update {
71re-introduce reference to ZON file#}
66#update "re-introduce reference to ZON file"
7267#check stdout {
7368We're back, World!
7469#}
test/incremental/change_zon_file_no_result_type+1-2
......@@ -26,8 +26,7 @@ Hello, World!
2626 .b_number = 0,
2727}
2828#}
29#update {
30change ZON file#}
29#update "change ZON file"
3130#check stdout {
3231Hello again, World!
3332#}
test/incremental/compile_error_then_log+2-4
......@@ -11,8 +11,7 @@ comptime {
1111 @compileLog("this is a log");
1212}
1313#}
14#update {
15compile error#}
14#update "compile error"
1615#check errors {
1716main.zig:3:5: error: this is an error
1817
......@@ -29,8 +28,7 @@ comptime {
2928 @compileLog("this is a log");
3029}
3130#}
32#update {
33remove the compile error#}
31#update "remove the compile error"
3432#check errors {
3533main.zig:6:5: error: found compile log statement
3634
test/incremental/compile_log+3-6
......@@ -9,8 +9,7 @@ pub fn main() !void {
99}
1010const io = std.Io.Threaded.global_single_threaded.io();
1111#}
12#update {
13no compile log#}
12#update "no compile log"
1413#check stdout {
1514Hello, World!
1615#}
......@@ -23,8 +22,7 @@ pub fn main() !void {
2322}
2423const io = std.Io.Threaded.global_single_threaded.io();
2524#}
26#update {
27add compile log#}
25#update "add compile log"
2826#check errors {
2927main.zig:4:5: error: found compile log statement
3028
......@@ -39,8 +37,7 @@ pub fn main() !void {
3937}
4038const io = std.Io.Threaded.global_single_threaded.io();
4139#}
42#update {
43remove compile log#}
40#update "remove compile log"
4441#check stdout {
4542Hello, World!
4643#}
test/incremental/delete_comptime_decls+1-2
......@@ -44,8 +44,7 @@ comptime {
4444 _ = y;
4545}
4646#}
47#update {
48delete and modify comptime decls#}
47#update "delete and modify comptime decls"
4948#check errors {
5049main.zig:6:16: error: slice of null pointer
5150#}
test/incremental/dependency_on_type_of_inferred_global+2-4
......@@ -15,8 +15,7 @@ comptime {
1515pub fn main() void {}
1616#}
1717#update
18#check stdout {
19#}
18#check stdout
2019
2120#write main.zig {
2221const foo = @as(u16, 123);
......@@ -30,8 +29,7 @@ comptime {
3029}
3130pub fn main() void {}
3231#}
33#update {
34change the type#}
32#update "change the type"
3533#check errors {
3634main.zig:8:37: error: reached unreachable code
3735#}
test/incremental/do_nothing+4-9
......@@ -1,6 +1,4 @@
1#todo {
2it'd be great if we could actually check that no analysis happened!
3#}
1#todo "it'd be great if we could actually check that no analysis happened!"
42#exe {
53-Mroot=main.zig
64#}
......@@ -25,8 +23,7 @@ const O = opaque {
2523};
2624#}
2725#update
28#check stdout {
29#}
26#check stdout
3027
3128#write main.zig {
3229pub fn main() void {
......@@ -47,7 +44,5 @@ const O = opaque {
4744 }
4845};
4946#}
50#update {
51do literally nothing#}
52#check stdout {
53#}
47#update "do literally nothing"
48#check stdout
test/incremental/fix_astgen_failure+4-8
......@@ -12,8 +12,7 @@ pub fn hello() !void {
1212 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1313}
1414#}
15#update {
16error#}
15#update "error"
1716#check errors {
1817foo.zig:2:9: error: use of undeclared identifier 'std'
1918#}
......@@ -25,8 +24,7 @@ pub fn hello() !void {
2524}
2625const io = std.Io.Threaded.global_single_threaded.io();
2726#}
28#update {
29fix the error#}
27#update "fix the error"
3028#check stdout {
3129Hello, World!
3230#}
......@@ -38,8 +36,7 @@ pub fn hello() !void {
3836}
3937const io = std.Io.Threaded.global_single_threaded.io();
4038#}
41#update {
42add new error#}
39#update "add new error"
4340#check errors {
4441foo.zig:3:52: error: use of undeclared identifier 'hello_str'
4542#}
......@@ -52,8 +49,7 @@ pub fn hello() !void {
5249}
5350const io = std.Io.Threaded.global_single_threaded.io();
5451#}
55#update {
56fix the new error#}
52#update "fix the new error"
5753#check stdout {
5854Hello, World! Again!
5955#}
test/incremental/function_becomes_inline+3-6
......@@ -12,8 +12,7 @@ fn foo() !void {
1212const std = @import("std");
1313const io = std.Io.Threaded.global_single_threaded.io();
1414#}
15#update {
16non-inline version#}
15#update "non-inline version"
1716#check stdout {
1817Hello, World!
1918#}
......@@ -28,8 +27,7 @@ inline fn foo() !void {
2827const std = @import("std");
2928const io = std.Io.Threaded.global_single_threaded.io();
3029#}
31#update {
32make function inline#}
30#update "make function inline"
3331#check stdout {
3432Hello, World!
3533#}
......@@ -44,8 +42,7 @@ inline fn foo() !void {
4442const std = @import("std");
4543const io = std.Io.Threaded.global_single_threaded.io();
4644#}
47#update {
48change string#}
45#update "change string"
4946#check stdout {
5047Hello, `inline` World!
5148#}
test/incremental/hello+1-2
......@@ -21,8 +21,7 @@ pub fn main() !void {
2121 try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n");
2222}
2323#}
24#update {
25change the string#}
24#update "change the string"
2625#check stdout {
2726おはようございます
2827#}
test/incremental/increase_array_field_length+1-2
......@@ -35,8 +35,7 @@ pub fn main(init: std.process.Init) !void {
3535}
3636const std = @import("std");
3737#}
38#update {
39increase array length#}
38#update "increase array length"
4039#check stdout {
4140arr[999] = 234
4241#}
test/incremental/make_decl_pub+1-2
......@@ -28,8 +28,7 @@ pub fn hello() !void {
2828}
2929const io = std.Io.Threaded.global_single_threaded.io();
3030#}
31#update {
32make hello pub#}
31#update "make hello pub"
3332#check stdout {
3433Hello, World!
3534#}
test/incremental/matrix.zon created+61
......@@ -0,0 +1,61 @@
1.{
2 // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others
3 // to run due to the output (C source code or LLVM IR) being built non-incrementally (by Clang
4 // or LLVM). We just have a couple here to make sure that it works.
5 .{
6 .target = .{
7 .cpu_arch = .x86_64,
8 .os_tag = .linux,
9 .abi = .musl,
10 },
11 .cache_mode = .incremental,
12 .backend = .cbe,
13 .linker = .old,
14 .pic = .nopic,
15 },
16 .{
17 .target = .{
18 .cpu_arch = .x86_64,
19 .os_tag = .linux,
20 .abi = .none,
21 },
22 .cache_mode = .incremental,
23 .backend = .llvm,
24 .linker = .lld,
25 .pic = .nopic,
26 },
27
28 .{
29 .target = .{
30 .cpu_arch = .x86_64,
31 .os_tag = .linux,
32 .abi = .none,
33 },
34 .cache_mode = .incremental,
35 .backend = .selfhosted,
36 .linker = .new,
37 .pic = .nopic,
38 },
39 .{
40 .target = .{
41 .cpu_arch = .x86_64,
42 .os_tag = .windows,
43 .abi = .none,
44 },
45 .cache_mode = .incremental,
46 .backend = .selfhosted,
47 .linker = .old,
48 .pic = .pic,
49 },
50 .{
51 .target = .{
52 .cpu_arch = .wasm32,
53 .os_tag = .wasi,
54 .abi = .none,
55 },
56 .cache_mode = .incremental,
57 .backend = .selfhosted,
58 .linker = .old,
59 .pic = .nopic,
60 },
61}
test/incremental/modify_inline_fn+1-2
......@@ -29,8 +29,7 @@ inline fn getStr() []const u8 {
2929}
3030const io = std.Io.Threaded.global_single_threaded.io();
3131#}
32#update {
33change the string#}
32#update "change the string"
3433#check stdout {
3534bar
3635#}
test/incremental/move_src+1-2
......@@ -36,8 +36,7 @@ fn bar() u32 {
3636}
3737const io = std.Io.Threaded.global_single_threaded.io();
3838#}
39#update {
40add newline#}
39#update "add newline"
4140#check stdout {
42418 123
4342#}
test/incremental/pointer_to_decl_with_astgen_error+4-8
......@@ -9,8 +9,7 @@ pub fn main() void {
99}
1010#}
1111#update
12#check stdout {
13#}
12#check stdout
1413
1514#write main.zig {
1615const foo = something;
......@@ -18,8 +17,7 @@ pub fn main() void {
1817 _ = &foo;
1918}
2019#}
21#update {
22introduce use of undeclared identifier#}
20#update "introduce use of undeclared identifier"
2321#check errors {
2422main.zig:1:13: error: use of undeclared identifier 'something'
2523#}
......@@ -30,7 +28,5 @@ pub fn main() void {
3028 _ = &foo;
3129}
3230#}
33#update {
34revert use of undeclared identifier#}
35#check stdout {
36#}
31#update "revert use of undeclared identifier"
32#check stdout
test/incremental/recursive_function_becomes_non_recursive+1-2
......@@ -31,8 +31,7 @@ fn foo(recurse: bool) !void {
3131const std = @import("std");
3232const io = std.Io.Threaded.global_single_threaded.io();
3333#}
34#update {
35eliminate recursion and change argument#}
34#update "eliminate recursion and change argument"
3635#check stdout {
3736x==1
3837#}
test/incremental/remove_enum_field+1-2
......@@ -31,8 +31,7 @@ pub fn main() !void {
3131const std = @import("std");
3232const io = std.Io.Threaded.global_single_threaded.io();
3333#}
34#update {
35remove enum field#}
34#update "remove enum field"
3635#check errors {
3736main.zig:7:68: error: enum 'main.MyEnum' has no member named 'foo'
3837main.zig:1:16: note: enum declared here
test/incremental/remove_function_with_changed_callee+3-6
......@@ -28,8 +28,7 @@ pub fn main() !void {
2828}
2929#}
3030#update
31#check stdout {
320#}
31#check stdout "0"
3332
3433#write main.zig {
3534//! This update must preserve `bar` `InternPool.Index` value, while changing its wasm function type.
......@@ -54,7 +53,5 @@ pub fn main() !void {
5453 try bar(.one);
5554}
5655#}
57#update {
58remove foo and change bar wasm function type#}
59#check stdout {
601#}
56#update "remove foo and change bar wasm function type"
57#check stdout "1"
test/incremental/remove_invalid_union_backing_enum+2-4
......@@ -33,7 +33,5 @@ pub fn main() void {
3333 _ = u;
3434}
3535#}
36#update {
37remove invalid backing enum#}
38#check stdout {
39#}
36#update "remove invalid backing enum"
37#check stdout
test/incremental/struct_reification_becomes_anon_init+1-2
......@@ -23,8 +23,7 @@ pub fn main(init: std.process.Init) !void {
2323}
2424const std = @import("std");
2525#}
26#update {
27change to `@TypeOf` anonymous init#}
26#update "change to `@TypeOf` anonymous init"
2827#check stdout {
2928anon_field
3029#}
test/incremental/tag_name+5-10
......@@ -11,8 +11,7 @@ pub fn main() !void {
1111}
1212#}
1313#update
14#check stdout {
15first#}
14#check stdout "first"
1615
1716#write main.zig {
1817const std = @import("std");
......@@ -22,10 +21,8 @@ pub fn main() !void {
2221 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
2322}
2423#}
25#update {
26no change#}
27#check stdout {
28first#}
24#update "no change"
25#check stdout "first"
2926
3027#write main.zig {
3128const std = @import("std");
......@@ -35,7 +32,5 @@ pub fn main() !void {
3532 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
3633}
3734#}
38#update {
39swap fields#}
40#check stdout {
41first#}
35#update "swap fields"
36#check stdout "first"
test/incremental/temporary_analysis_error_in_generic_signature+2-4
......@@ -33,8 +33,7 @@ fn bar(comptime x: u8) @This().FooAlias {
3333}
3434const std = @import("std");
3535#}
36#update {
37change generic signature to use non-existent member#}
36#update "change generic signature to use non-existent member"
3837#check errors {
3938main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias'
4039main.zig:1:1: note: struct declared here
......@@ -54,8 +53,7 @@ fn bar(comptime x: u8) @This().FooAlias {
5453}
5554const std = @import("std");
5655#}
57#update {
58add that member, fixing the error#}
56#update "add that member, fixing the error"
5957#check stdout {
6058Z
6159#}
test/incremental/temporary_parse_error+4-8
......@@ -7,14 +7,12 @@ const std = @import("std");
77pub fn main() !void {}
88#}
99#update
10#check stdout {
11#}
10#check stdout
1211
1312#write main.zig {
1413pub fn main() !void {
1514#}
16#update {
17introduce parse error#}
15#update "introduce parse error"
1816#check errors {
1917main.zig:2:1: error: expected statement, found 'EOF'
2018#}
......@@ -22,7 +20,5 @@ main.zig:2:1: error: expected statement, found 'EOF'
2220#write main.zig {
2321pub fn main() !void {}
2422#}
25#update {
26fix parse error#}
27#check stdout {
28#}
23#update "fix parse error"
24#check stdout
test/incremental/type_becomes_comptime_only+5-10
......@@ -14,8 +14,7 @@ pub fn main() void {
1414}
1515#}
1616#update
17#check stdout {
18#}
17#check stdout
1918
2019#write main.zig {
2120const SomeType = comptime_int;
......@@ -28,10 +27,8 @@ pub fn main() void {
2827 s.foo();
2928}
3029#}
31#update {
32make S comptime-only#}
33#check stdout {
34#}
30#update "make S comptime-only"
31#check stdout
3532
3633#write main.zig {
3734const SomeType = u16;
......@@ -44,7 +41,5 @@ pub fn main() void {
4441 s.foo();
4542}
4643#}
47#update {
48make S runtime again#}
49#check stdout {
50#}
44#update "make S runtime again"
45#check stdout
test/incremental/type_dependency_loop+7-14
......@@ -24,10 +24,8 @@ pub fn main() void {
2424 _ = B;
2525}
2626#}
27#update {
28remove reference to dependency loop#}
29#check stdout {
30#}
27#update "remove reference to dependency loop"
28#check stdout
3129
3230#write main.zig {
3331pub const A = struct { b: B };
......@@ -36,10 +34,8 @@ pub fn main() void {
3634 _ = B;
3735}
3836#}
39#update {
40change dependency loop without fixing it#}
41#check stdout {
42#}
37#update "change dependency loop without fixing it"
38#check stdout
4339
4440#write main.zig {
4541pub const A = struct { b: B };
......@@ -48,8 +44,7 @@ pub fn main() void {
4844 _ = @as(B, undefined);
4945}
5046#}
51#update {
52reference dependency loop again#}
47#update "reference dependency loop again"
5348#check errors {
5449error: dependency loop with length 2
5550 main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here
......@@ -64,7 +59,5 @@ pub fn main() void {
6459 _ = @as(B, undefined);
6560}
6661#}
67#update {
68fix dependency loop#}
69#check stdout {
70#}
62#update "fix dependency loop"
63#check stdout
test/incremental/unreferenced_error+3-6
......@@ -23,8 +23,7 @@ pub fn main() !void {
2323const a = @compileError("bad a");
2424const io = std.Io.Threaded.global_single_threaded.io();
2525#}
26#update {
27introduce compile error#}
26#update "introduce compile error"
2827#check errors {
2928main.zig:5:11: error: bad a
3029#}
......@@ -38,8 +37,7 @@ const a = @compileError("bad a");
3837const b = "Hi there!\n";
3938const io = std.Io.Threaded.global_single_threaded.io();
4039#}
41#update {
42remove error reference#}
40#update "remove error reference"
4341#check stdout {
4442Hi there!
4543#}
......@@ -53,8 +51,7 @@ const a = "Back to a\n";
5351const b = @compileError("bad b");
5452const io = std.Io.Threaded.global_single_threaded.io();
5553#}
56#update {
57introduce and remove reference to error#}
54#update "introduce and remove reference to error"
5855#check stdout {
5956Back to a
6057#}
test/src/Runner.zig deleted-2141
......@@ -1,2141 +0,0 @@
1const assert = std.debug.assert;
2const builtin = @import("builtin");
3const std = @import("std");
4const Runner = @This();
5
6gpa: std.mem.Allocator,
7arena: *std.heap.ArenaAllocator,
8environ_map: std.process.Environ.Map,
9io: std.Io,
10prog_node: std.Progress.Node,
11args: Args,
12host: std.Target,
13server: std.zig.Server,
14eb_wip: std.zig.ErrorBundle.Wip,
15
16const Args = struct {
17 @"test": ?struct {
18 input_dir: std.zig.Server.Message.InputDir,
19 dir: std.Io.Dir,
20 },
21 manifest_file: ?std.Io.File,
22 zig_exe: ?std.Io.File,
23 lib_dir: ?std.Io.Dir,
24 src_dir: ?std.Io.Dir,
25 targets: std.ArrayList(u32),
26 target_bytes: std.ArrayList(u8),
27
28 libc_runtimes_dir: ?std.Io.Dir,
29 enable_darling: bool,
30 enable_qemu: bool,
31 enable_rosetta: bool,
32 enable_wasmtime: bool,
33 enable_wine: bool,
34
35 gdb_exe: ?std.Io.File,
36 lldb_exe: ?std.Io.File,
37
38 quiet: bool,
39
40 fn deinit(args: *Args, gpa: std.mem.Allocator) void {
41 args.targets.deinit(gpa);
42 args.target_bytes.deinit(gpa);
43 args.* = undefined;
44 }
45
46 fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void {
47 try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1);
48 try args.targets.append(gpa, @intCast(args.target_bytes.items.len));
49 args.target_bytes.appendSliceAssumeCapacity(target);
50 args.target_bytes.appendAssumeCapacity(0);
51 }
52};
53
54fn deinit(runner: *Runner) void {
55 runner.environ_map.deinit();
56 runner.prog_node.end();
57 runner.args.deinit(runner.gpa);
58 runner.eb_wip.deinit();
59 runner.* = undefined;
60}
61
62const FailError = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable;
63fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) FailError {
64 return runner.failString(try runner.eb_wip.printString(fmt, args));
65}
66fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) FailError {
67 try runner.eb_wip.addRootErrorMessage(.{
68 .msg = msg,
69 });
70 return error.AlreadyReported;
71}
72
73pub const ProtocolError = error{
74 ServerReadFailed,
75 ServerWriteFailed,
76 ServerEndOfStream,
77} || FailError;
78pub fn runServer(runner: *Runner) ProtocolError {
79 runner.server.serveStringMessage(.zig_version, builtin.zig_version_string) catch |err| switch (err) {
80 error.WriteFailed => return error.ServerWriteFailed,
81 };
82 while (true) {
83 _ = runner.arena.reset(.retain_capacity);
84 const arena = runner.arena.allocator();
85 const hdr = runner.server.receiveMessage() catch |err| switch (err) {
86 error.ReadFailed => return error.ServerReadFailed,
87 error.EndOfStream => return error.ServerEndOfStream,
88 };
89 switch (hdr.tag) {
90 else => |tag| return runner.fail("unsupported message: {t}", .{tag}),
91 .exit => std.process.exit(0),
92 .args => {
93 const args_body = try arena.alloc(u8, hdr.bytes_len);
94 runner.server.in.readSliceAll(args_body) catch unreachable;
95 const State = enum {
96 positional,
97 @"--zig",
98 @"--lib",
99 @"--src",
100 @"--target",
101 @"--libc-runtimes",
102 @"--gdb",
103 @"--lldb",
104 };
105 var state: State = .positional;
106 const state_expected: std.enums.EnumArray(
107 State,
108 enum { path, dir, file, string },
109 ) = .init(.{
110 .positional = .path,
111 .@"--zig" = .file,
112 .@"--lib" = .dir,
113 .@"--src" = .dir,
114 .@"--target" = .string,
115 .@"--libc-runtimes" = .dir,
116 .@"--gdb" = .file,
117 .@"--lldb" = .file,
118 });
119 var input_dir: std.zig.Server.Message.InputDir = .cwd;
120 var args_body_offset: usize = 0;
121 while (args_body.len - args_body_offset > 0) {
122 const arg: std.zig.Client.Message.Arg =
123 @fromBackingInt(args_body[args_body_offset]);
124 args_body_offset += 1;
125 arg: switch (arg) {
126 .string => {
127 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
128 const string = args_body[args_body_offset..end];
129 args_body_offset = end + 1;
130 switch (state) {
131 .positional => if (std.mem.eql(u8, string, "--target")) {
132 state = .@"--target";
133 continue;
134 } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| {
135 try runner.args.addTarget(runner.gpa, target);
136 } else if (std.mem.eql(u8, string, "--libc-runtimes")) {
137 state = .@"--libc-runtimes";
138 continue;
139 } else if (std.mem.eql(u8, string, "-fdarling")) {
140 runner.args.enable_darling = true;
141 } else if (std.mem.eql(u8, string, "-fqemu")) {
142 runner.args.enable_qemu = true;
143 } else if (std.mem.eql(u8, string, "-frosetta")) {
144 runner.args.enable_rosetta = true;
145 } else if (std.mem.eql(u8, string, "-fwasmtime")) {
146 runner.args.enable_wasmtime = true;
147 } else if (std.mem.eql(u8, string, "-fwine")) {
148 runner.args.enable_wine = true;
149 } else if (std.mem.eql(u8, string, "--gdb")) {
150 state = .@"--gdb";
151 continue;
152 } else if (std.mem.eql(u8, string, "--lldb")) {
153 state = .@"--lldb";
154 continue;
155 } else if (std.mem.eql(u8, string, "--quiet")) {
156 runner.args.quiet = true;
157 },
158 .@"--target" => try runner.args.addTarget(runner.gpa, string),
159 else => return runner.fail(
160 "\"{t}\" expected {t}, got {t}",
161 .{ state, state_expected.get(state), arg },
162 ),
163 }
164 },
165 .prefix => {
166 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
167 const string = args_body[args_body_offset..end];
168 args_body_offset = end + 1;
169 assert(state == .positional);
170 state = if (std.mem.eql(u8, string, "--zig="))
171 .@"--zig"
172 else if (std.mem.eql(u8, string, "--lib="))
173 .@"--lib"
174 else if (std.mem.eql(u8, string, "--src="))
175 .@"--src"
176 else if (std.mem.eql(u8, string, "--libc-runtimes="))
177 .@"--libc-runtimes"
178 else if (std.mem.eql(u8, string, "--gdb="))
179 .@"--gdb"
180 else if (std.mem.eql(u8, string, "--lldb="))
181 .@"--lldb"
182 else
183 return runner.fail("unsupported arg prefix: {q}", .{string});
184 continue;
185 },
186 .suffix => {
187 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
188 const string = args_body[args_body_offset..end];
189 args_body_offset = end + 1;
190 return runner.fail("unsupported arg suffix: {q}", .{string});
191 },
192 .input_dir => {
193 input_dir = @fromBackingInt(@backingInt(input_dir) + 1);
194 continue :arg .output_dir;
195 },
196 .output_dir => {
197 const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast(
198 args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)],
199 );
200 args_body_offset += @sizeOf(std.Io.Dir.Handle);
201 const dir = runner.io.vtable.inheritParentDir(
202 runner.io.userdata,
203 dir_handle.*,
204 ) catch |err| switch (err) {
205 error.Canceled => |e| return e,
206 else => |e| return runner.fail(
207 "unable to inherit parent dir: {t}",
208 .{e},
209 ),
210 };
211 switch (state) {
212 .positional => {
213 if (runner.args.@"test" != null or
214 runner.args.manifest_file != null) return runner.fail(
215 "{t} specified multiple times",
216 .{state},
217 );
218 runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir };
219 const manifest_path = "manifest";
220 runner.args.manifest_file = dir.openFile(
221 runner.io,
222 manifest_path,
223 .{},
224 ) catch |err| switch (err) {
225 error.Canceled => |e| return e,
226 else => |e| return runner.fail(
227 "unable to open manifest file: {t}",
228 .{e},
229 ),
230 };
231 try runner.discoverInput(input_dir, manifest_path);
232 },
233 .@"--lib" => {
234 if (runner.args.lib_dir != null) return runner.fail(
235 "\"{t}\" specified multiple times",
236 .{state},
237 );
238 runner.args.lib_dir = dir;
239 },
240 .@"--src" => {
241 if (runner.args.src_dir != null) return runner.fail(
242 "\"{t}\" specified multiple times",
243 .{state},
244 );
245 runner.args.src_dir = dir;
246 },
247 .@"--libc-runtimes" => {
248 if (runner.args.libc_runtimes_dir != null) return runner.fail(
249 "\"{t}\" specified multiple times",
250 .{state},
251 );
252 runner.args.libc_runtimes_dir = dir;
253 },
254 else => return runner.fail(
255 "\"{t}\" expected {t}, got {t}",
256 .{ state, state_expected.get(state), arg },
257 ),
258 }
259 },
260 .input_file, .input_file_content, .output_file => {
261 const file_handle: *align(1) const std.Io.File.Handle = @ptrCast(
262 args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)],
263 );
264 args_body_offset += @sizeOf(std.Io.File.Handle);
265 const file = runner.io.vtable.inheritParentFile(
266 runner.io.userdata,
267 file_handle.*,
268 .{ .nonblocking = false },
269 ) catch |err| switch (err) {
270 error.Canceled => |e| return e,
271 else => |e| return runner.fail(
272 "unable to inherit parent file: {t}",
273 .{e},
274 ),
275 };
276 switch (state) {
277 .positional => {
278 if (runner.args.@"test" != null or
279 runner.args.manifest_file != null) return runner.fail(
280 "{t} specified multiple times",
281 .{state},
282 );
283 runner.args.manifest_file = file;
284 },
285 .@"--zig" => {
286 if (runner.args.zig_exe != null) return runner.fail(
287 "\"{t}\" specified multiple times",
288 .{state},
289 );
290 runner.args.zig_exe = file;
291 },
292 .@"--gdb" => {
293 if (runner.args.gdb_exe != null) return runner.fail(
294 "\"{t}\" specified multiple times",
295 .{state},
296 );
297 runner.args.gdb_exe = file;
298 },
299 .@"--lldb" => {
300 if (runner.args.lldb_exe != null) return runner.fail(
301 "\"{t}\" specified multiple times",
302 .{state},
303 );
304 runner.args.lldb_exe = file;
305 },
306 else => return runner.fail(
307 "\"{t}\" expected {t}, got {t}",
308 .{ state, state_expected.get(state), arg },
309 ),
310 }
311 },
312 }
313 state = .positional;
314 }
315 },
316 .query_test_metadata => {
317 const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len);
318 @memset(expected_panic_msgs, 0);
319 runner.server.serveTestMetadata(.{
320 .names = runner.args.targets.items,
321 .expected_panic_msgs = expected_panic_msgs,
322 .string_bytes = runner.args.target_bytes.items,
323 }) catch |err| switch (err) {
324 error.WriteFailed => return error.ServerWriteFailed,
325 };
326 },
327 .run_test => {
328 const test_index = runner.server.receiveBody_u32() catch unreachable;
329 runner.server.serveBodylessMessage(.test_started) catch |err| switch (err) {
330 error.WriteFailed => return error.ServerWriteFailed,
331 };
332 runner.server.serveTestResults(.{
333 .index = test_index,
334 .flags = .{
335 .status = if (runner.testOne(test_index))
336 .pass
337 else |err| status: switch (err) {
338 error.Canceled,
339 error.OutOfMemory,
340 error.ServerWriteFailed,
341 => |e| return e,
342 error.AlreadyReported => {
343 var eb = try runner.eb_wip.toOwnedBundle("");
344 defer eb.deinit(runner.gpa);
345 assert(eb.errorMessageCount() > 0); // already reported what?
346 runner.server.serveErrorBundle(
347 .error_bundle,
348 eb,
349 ) catch |server_err| switch (server_err) {
350 error.WriteFailed => return error.ServerWriteFailed,
351 };
352 const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa);
353 runner.eb_wip.deinit();
354 runner.eb_wip = eb_wip;
355 break :status .fail;
356 },
357 error.SkipTest => .skip,
358 },
359 .fuzz = false,
360 .log_err_count = 0,
361 .leak_count = 0,
362 },
363 }) catch |err| switch (err) {
364 error.WriteFailed => return error.ServerWriteFailed,
365 };
366 assert(runner.eb_wip.root_list.items.len == 0); // failed to report
367 },
368 }
369 }
370}
371
372fn discoverInput(
373 runner: *Runner,
374 dir: std.zig.Server.Message.InputDir,
375 sub_path: []const u8,
376) error{ServerWriteFailed}!void {
377 return runner.discoverInputInner(dir, sub_path) catch |err| switch (err) {
378 error.WriteFailed => return error.ServerWriteFailed,
379 };
380}
381fn discoverInputInner(
382 runner: *Runner,
383 dir: std.zig.Server.Message.InputDir,
384 sub_path: []const u8,
385) std.Io.Writer.Error!void {
386 try runner.server.serveMessageHeader(.{
387 .tag = .discovered_inputs,
388 .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) +
389 sub_path.len + 1),
390 });
391 try runner.server.out.writeInt(u32, @backingInt(dir), .little);
392 try runner.server.out.writeAll(sub_path);
393 try runner.server.out.writeByte(0);
394 try runner.server.out.flush();
395}
396
397pub const SourceReader = struct {
398 const Io = std.Io;
399
400 backing: *std.Io.Reader,
401 line_aw: std.Io.Writer.Allocating,
402 interface: std.Io.Reader,
403 line: u32,
404 eb_wip: *std.zig.ErrorBundle.Wip,
405 src_path: std.zig.ErrorBundle.String,
406
407 pub fn init(
408 reader: *Io.Reader,
409 gpa: std.mem.Allocator,
410 eb_wip: *std.zig.ErrorBundle.Wip,
411 src_path: std.zig.ErrorBundle.String,
412 ) SourceReader {
413 return .{
414 .backing = reader,
415 .line_aw = .init(gpa),
416 .interface = .{
417 .vtable = &.{
418 .stream = stream,
419 .discard = discard,
420 .readVec = readVec,
421 .rebase = rebase,
422 },
423 .buffer = &.{},
424 .seek = 0,
425 .end = 0,
426 },
427 .line = 0,
428 .eb_wip = eb_wip,
429 .src_path = src_path,
430 };
431 }
432 fn deinit(s: *SourceReader) void {
433 s.line_aw.deinit();
434 }
435 fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize {
436 const s: *SourceReader = @fieldParentPtr("interface", r);
437 try s.bufferLine();
438 return 0;
439 }
440 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
441 const s: *SourceReader = @fieldParentPtr("interface", r);
442 const remaining = r.buffered();
443 if (remaining.len > 0) {
444 const n = limit.minInt(remaining.len);
445 r.toss(n);
446 return n;
447 }
448 try s.bufferLine();
449 return 0;
450 }
451 fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize {
452 const s: *SourceReader = @fieldParentPtr("interface", r);
453 try s.bufferLine();
454 return 0;
455 }
456 fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void {
457 const s: *SourceReader = @fieldParentPtr("interface", r);
458 defer s.updateBuffer();
459 s.line_aw.ensureUnusedCapacity(capacity) catch |err| switch (err) {
460 error.OutOfMemory => return error.ReadFailed,
461 };
462 }
463 fn updateBuffer(s: *SourceReader) void {
464 s.interface.buffer = s.line_aw.writer.buffer;
465 s.interface.end = s.line_aw.writer.end;
466 }
467 fn bufferLine(s: *SourceReader) Io.Reader.Error!void {
468 defer s.updateBuffer();
469 {
470 const written = s.line_aw.written();
471 const start = if (std.mem.findScalarLast(u8, written[0..s.interface.seek], '\n')) |newline|
472 newline + 1
473 else
474 0;
475 s.line += @intCast(std.mem.countScalar(u8, written[0..start], '\n'));
476 const preserve = written[start..];
477 @memmove(written[0..preserve.len], preserve);
478 s.line_aw.shrinkRetainingCapacity(preserve.len);
479 s.interface.seek -= start;
480 }
481 const n = s.backing.streamDelimiterEnding(&s.line_aw.writer, '\n') catch |err| switch (err) {
482 error.ReadFailed => |e| return e,
483 error.WriteFailed => return error.ReadFailed,
484 };
485 if (std.mem.endsWith(u8, s.line_aw.written(), "\r")) s.line_aw.writer.end -= 1;
486 const buffered = s.backing.buffered();
487 if (n == 0 and buffered.len == 0) return error.EndOfStream;
488 if (std.mem.startsWith(u8, buffered, "\n")) {
489 s.line_aw.writer.writeByte('\n') catch |err| switch (err) {
490 error.WriteFailed => return error.ReadFailed,
491 };
492 s.backing.toss(1);
493 }
494 }
495 fn fail(
496 s: *SourceReader,
497 src_loc: []const u8,
498 comptime fmt: []const u8,
499 args: anytype,
500 ) FailError {
501 return s.failString(src_loc, try s.eb_wip.printString(fmt, args));
502 }
503 fn failString(
504 s: *SourceReader,
505 src_loc: []const u8,
506 msg: std.zig.ErrorBundle.String,
507 ) FailError {
508 const line: u32, const column: u32, const source_line = src_loc: {
509 const pos = src_loc.ptr - s.interface.buffer.ptr;
510 const end = std.mem.findScalarPos(u8, s.interface.buffer, pos, '\n') orelse
511 s.interface.buffer.len;
512 const start = if (std.mem.findScalarLast(u8, s.interface.buffer[0..end], '\n')) |newline|
513 newline + 1
514 else
515 0;
516 break :src_loc .{
517 @intCast(s.line + std.mem.countScalar(u8, s.interface.buffer[0..start], '\n')),
518 @intCast(pos - start),
519 s.interface.buffer[start..end],
520 };
521 };
522 try s.eb_wip.addRootErrorMessage(.{
523 .msg = msg,
524 .src_loc = try s.eb_wip.addSourceLocation(.{
525 .src_path = s.src_path,
526 .line = line,
527 .column = column,
528 .span_start = column,
529 .span_main = column,
530 .span_end = @intCast(column + src_loc.len),
531 .source_line = try s.eb_wip.addString(source_line),
532 }),
533 });
534 return error.AlreadyReported;
535 }
536};
537pub const DelimitedReader = struct {
538 const Io = std.Io;
539
540 unlimited: *Io.Reader,
541 remaining: Io.Limit,
542 delimiter: []const u8,
543 interface: Io.Reader,
544
545 pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader {
546 return .{
547 .unlimited = reader,
548 .remaining = .nothing,
549 .delimiter = delimiter,
550 .interface = .{
551 .vtable = &.{
552 .stream = stream,
553 .discard = discard,
554 },
555 .buffer = buffer,
556 .seek = 0,
557 .end = 0,
558 },
559 };
560 }
561 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
562 const d: *DelimitedReader = @fieldParentPtr("interface", r);
563 const block = try d.unlimited.peekGreedy(d.delimiter.len);
564 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
565 block.len - d.delimiter.len + 1);
566 if (d.remaining == .nothing) return error.EndOfStream;
567 const n = try d.unlimited.stream(w, limit.min(d.remaining));
568 d.remaining = d.remaining.subtract(n).?;
569 return n;
570 }
571 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
572 const d: *DelimitedReader = @fieldParentPtr("interface", r);
573 const block = try d.unlimited.peekGreedy(d.delimiter.len);
574 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
575 block.len - d.delimiter.len + 1);
576 if (d.remaining == .nothing) return error.EndOfStream;
577 const n = try d.unlimited.discard(limit.min(d.remaining));
578 d.remaining = d.remaining.subtract(n).?;
579 return n;
580 }
581};
582
583const Update = struct {
584 target_query: []const u8,
585 manifest_sr: SourceReader,
586 compiler: ?Compiler,
587 src_dir: std.Io.Dir,
588 prog_node: std.Progress.Node,
589 mtime: std.Io.Timestamp,
590 num: usize,
591 allow_skip: bool,
592};
593
594const TestError = error{
595 SkipTest,
596} || FailError;
597fn testOne(runner: *Runner, test_index: u32) (error{ServerWriteFailed} || TestError)!void {
598 const target_query =
599 std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0);
600 const manifest_file = runner.args.manifest_file orelse
601 return runner.fail("missing \"path/to/test\" arg", .{});
602 var manifest_buffer: [512]u8 = undefined;
603 var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer);
604 var update: Update = .{
605 .target_query = target_query,
606 .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: {
607 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
608 const src_path = src_path: {
609 break :src_path path_buffer[0 .. manifest_file.realPath(
610 runner.io,
611 &path_buffer,
612 ) catch |err| switch (err) {
613 error.Canceled => |e| return e,
614 else => break :src_path "manifest",
615 }];
616 };
617 break :src_path_string try runner.eb_wip.addString(src_path);
618 }),
619 .compiler = null,
620 .src_dir = (runner.args.src_dir orelse
621 return runner.fail("missing \"--src=path/to/src\" arg", .{})).createDirPathOpen(
622 runner.io,
623 target_query,
624 .{},
625 ) catch |err| switch (err) {
626 error.Canceled => |e| return e,
627 else => |e| return runner.fail("unable to create {q}: {t}", .{ target_query, e }),
628 },
629 .prog_node = .none,
630 .mtime = .now(runner.io, .real),
631 .num = 0,
632 .allow_skip = true,
633 };
634 defer {
635 update.manifest_sr.deinit();
636 update.src_dir.close(runner.io);
637 update.prog_node.end();
638 }
639
640 // hack around `std.Io.Reader` interface bugs by ensuring the
641 // buffer is larger than the longest supported manifest line
642 try update.manifest_sr.line_aw.ensureTotalCapacityPrecise(512);
643 update.manifest_sr.updateBuffer();
644
645 var skip_delimiter = false;
646 while (update.manifest_sr.interface.takeSentinel('\n')) |line| {
647 var line_it = std.mem.tokenizeScalar(u8, line, ' ');
648 const cmd_src_loc = line_it.next() orelse continue;
649 if (skip_delimiter) {
650 assert(std.mem.eql(u8, cmd_src_loc, "#}"));
651 skip_delimiter = false;
652 const unexpected = line_it.rest();
653 if (unexpected.len > 0)
654 return update.manifest_sr.fail(unexpected, "unexpected argument {q}", .{unexpected});
655 continue;
656 }
657 const cmd = std.meta.stringToEnum(Command, std.mem.cutPrefix(u8, cmd_src_loc, "#") orelse
658 return update.manifest_sr.fail(cmd_src_loc, "expected command", .{})) orelse
659 return update.manifest_sr.fail(cmd_src_loc, "unknown command", .{});
660 const arg = switch (cmd) {
661 .todo, .exe, .lib, .obj, .update => null,
662 .skip, .write, .delete, .check => line_it.next() orelse
663 return update.manifest_sr.fail(line_it.rest()[0..0], "missing argument", .{}),
664 };
665 var contents_file: ?std.Io.File = null;
666 defer if (contents_file) |file| file.close(runner.io);
667 var contents_buffer: [512]u8 = undefined;
668 var contents_impl: union(enum) {
669 ending: std.Io.Reader,
670 dr: DelimitedReader,
671 fr: std.Io.File.Reader,
672 } = undefined;
673 const contents_r: *std.Io.Reader = contents_r: switch (cmd) {
674 .skip, .delete => {
675 const unexpected = line_it.rest();
676 if (unexpected.len > 0) return update.manifest_sr.fail(
677 unexpected,
678 "unexpected argument {q}",
679 .{unexpected},
680 );
681 contents_impl = .{ .ending = .ending_instance };
682 break :contents_r &contents_impl.ending;
683 },
684 else => {
685 const contents_path = line_it.rest();
686 if (contents_path.len == 0) {
687 contents_impl = .{ .ending = .ending_instance };
688 break :contents_r &contents_impl.ending;
689 }
690
691 if (std.mem.eql(u8, contents_path, "{")) {
692 contents_impl = .{
693 .dr = .init(&update.manifest_sr.interface, "#}", &contents_buffer),
694 };
695 skip_delimiter = true;
696 break :contents_r &contents_impl.dr.interface;
697 }
698
699 const args_test = runner.args.@"test" orelse return update.manifest_sr.fail(
700 contents_path,
701 "contents file requires directory test",
702 .{},
703 );
704 try runner.discoverInput(args_test.input_dir, contents_path);
705
706 contents_file =
707 args_test.dir.openFile(runner.io, contents_path, .{}) catch |err| switch (err) {
708 error.Canceled => |e| return e,
709 else => |e| return update.manifest_sr.fail(
710 contents_path,
711 "unable to open contents file: {t}",
712 .{e},
713 ),
714 };
715 contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) };
716 break :contents_r &contents_impl.fr.interface;
717 },
718 };
719
720 runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) {
721 error.Canceled,
722 error.OutOfMemory,
723 error.AlreadyReported,
724 error.SkipTest,
725 => |e| return e,
726 error.ContentsReadFailed => switch (contents_impl) {
727 .ending => unreachable,
728 .dr => switch (manifest_fr.err orelse return error.OutOfMemory) {
729 error.Canceled => |e| return e,
730 else => |e| return runner.fail("unable to read manifest: {t}", .{e}),
731 },
732 .fr => |fr| switch (fr.err.?) {
733 error.Canceled => |e| return e,
734 else => |e| return runner.fail("unable to read contents file: {t}", .{e}),
735 },
736 },
737 };
738 } else |err| switch (err) {
739 error.ReadFailed => switch (manifest_fr.err orelse return error.OutOfMemory) {
740 error.Canceled => |e| return e,
741 else => |e| return runner.fail("unable to read manifest: {t}", .{e}),
742 },
743 error.StreamTooLong => unreachable,
744 error.EndOfStream => if (skip_delimiter)
745 return runner.fail("manifest missing \"#}}\" delimiter", .{}),
746 }
747 if (update.compiler) |*comp| {
748 if (comp.state != .idle) return runner.fail("missing \"#check\"", .{});
749 try comp.exit();
750 }
751}
752const Command = enum { todo, skip, exe, lib, obj, write, delete, update, check };
753fn handleCommand(
754 runner: *Runner,
755 update: *Update,
756 cmd_src_loc: []const u8,
757 cmd: Command,
758 arg: ?[]const u8,
759 contents_r: *std.Io.Reader,
760) (error{ContentsReadFailed} || TestError)!void {
761 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
762 const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t}{s}{s}", .{
763 cmd, if (arg) |_| " " else "", arg orelse "",
764 }) catch &name_buffer, 0);
765 defer cmd_prog_node.end();
766
767 cmd: switch (cmd) {
768 .todo => _ = contents_r.discardRemaining() catch |err| switch (err) {
769 error.ReadFailed => {},
770 },
771 .skip => if (!update.allow_skip) return update.manifest_sr.fail(
772 cmd_src_loc,
773 "\"#skip\" must appear before other commands",
774 .{},
775 ) else if (std.mem.eql(u8, update.target_query, arg.?)) return error.SkipTest,
776 .exe, .lib, .obj => runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) {
777 else => unreachable,
778 .exe => .Exe,
779 .lib => .Lib,
780 .obj => .Obj,
781 }, contents_r, target: {
782 var component_it = std.mem.splitBackwardsScalar(u8, update.target_query, '-');
783 const pic_str = component_it.next() orelse
784 return runner.fail("target {q} missing pic", .{update.target_query});
785 const pic = std.meta.stringToEnum(Compiler.Target.Pic, pic_str) orelse
786 return runner.fail("target {q} unknown pic {q}", .{ update.target_query, pic_str });
787 const linker_str = component_it.next() orelse
788 return runner.fail("target {q} missing linker", .{update.target_query});
789 const linker = std.meta.stringToEnum(Compiler.Target.Linker, linker_str) orelse
790 return runner.fail("target {q} unknown linker {q}", .{ update.target_query, linker_str });
791 const backend_str = component_it.next() orelse
792 return runner.fail("target {q} missing backend", .{update.target_query});
793 const backend = std.meta.stringToEnum(Compiler.Target.Backend, backend_str) orelse
794 return runner.fail("target {q} unknown backend {q}", .{ update.target_query, backend_str });
795 const cache_mode_str = component_it.next() orelse
796 return runner.fail("target {q} missing cache mode", .{update.target_query});
797 const cache_mode = std.meta.stringToEnum(Compiler.Target.CacheMode, cache_mode_str) orelse
798 return runner.fail("target {q} unknown cache mode {q}", .{ update.target_query, cache_mode_str });
799 const triple = component_it.rest();
800 break :target .{
801 .triple = triple,
802 .resolved = std.zig.system.resolveTargetQuery(
803 runner.io,
804 std.Build.parseTargetQuery(.{
805 .arch_os_abi = triple,
806 .object_format = switch (backend) {
807 .sema, .selfhosted, .llvm => null,
808 .cbe => "c",
809 },
810 }) catch |err| switch (err) {
811 error.ParseFailed => return runner.fail("unable to parse triple {q}", .{
812 triple,
813 }),
814 },
815 ) catch |err| switch (err) {
816 error.Canceled => |e| return e,
817 else => |e| return runner.fail("unable to resolve target {q}: {t}", .{ triple, e }),
818 },
819 .cache_mode = cache_mode,
820 .backend = backend,
821 .linker = linker,
822 .pic = pic,
823 };
824 }) catch |err| switch (err) {
825 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
826 error.ReadFailed => return error.ContentsReadFailed,
827 },
828 .write => {
829 const file = update.src_dir.createFile(runner.io, arg.?, .{}) catch |err| switch (err) {
830 error.Canceled => |e| return e,
831 else => |e| return update.manifest_sr.fail(arg.?, "unable to create file: {t}", .{e}),
832 };
833 defer file.close(runner.io);
834 var fw_buffer: [512]u8 = undefined;
835 var fw = file.writer(runner.io, &fw_buffer);
836 _ = contents_r.streamRemaining(&fw.interface) catch |err| switch (err) {
837 error.ReadFailed => return error.ContentsReadFailed,
838 error.WriteFailed => switch (fw.err.?) {
839 error.Canceled => |e| return e,
840 else => |e| return update.manifest_sr.fail(
841 arg.?,
842 "unable to write file: {t}",
843 .{e},
844 ),
845 },
846 };
847 fw.flush() catch |err| switch (err) {
848 error.Canceled => |e| return e,
849 else => |e| return update.manifest_sr.fail(
850 arg.?,
851 "unable to write file: {t}",
852 .{e},
853 ),
854 };
855 file.setTimestamps(runner.io, .{ .modify_timestamp = .{
856 .new = update.mtime,
857 } }) catch |err| switch (err) {
858 error.Canceled => |e| return e,
859 else => |e| return update.manifest_sr.fail(
860 arg.?,
861 "unable to update file timestamps: {t}",
862 .{e},
863 ),
864 };
865 },
866 .delete => update.src_dir.deleteFile(runner.io, arg.?) catch |err| switch (err) {
867 error.Canceled => |e| return e,
868 else => |e| return update.manifest_sr.fail(arg.?, "unable to delete file: {t}", .{e}),
869 },
870 .update => {
871 const comp = &(update.compiler orelse return update.manifest_sr.fail(
872 cmd_src_loc,
873 "missing \"#exe\", \"#lib\", or \"#obj\"",
874 .{},
875 ));
876 update.prog_node.end();
877 update.prog_node = runner.prog_node.start(
878 switch (contents_r.readSliceShort(&name_buffer) catch |err| switch (err) {
879 error.ReadFailed => return error.ContentsReadFailed,
880 }) {
881 0 => switch (update.num) {
882 0 => "initial update",
883 else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch
884 &name_buffer,
885 },
886 else => |name_len| name_buffer[0..name_len],
887 },
888 0,
889 );
890 _ = contents_r.discardRemaining() catch |err| switch (err) {
891 error.ReadFailed => return error.ContentsReadFailed,
892 };
893 if (comp.state != .idle)
894 return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{});
895 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
896 error.WriteFailed => switch (comp.fw.err.?) {
897 error.Canceled => |e| return e,
898 else => |e| return runner.fail("unable to send message to compiler: {t}", .{e}),
899 },
900 };
901 comp.state = .update;
902 update.mtime = update.mtime.addDuration(.fromSeconds(2));
903 update.num += 1;
904 },
905 .check => {
906 const check = std.meta.stringToEnum(Compiler.Check, arg.?) orelse
907 return update.manifest_sr.fail(arg.?, "unknown check", .{});
908 const expected =
909 contents_r.allocRemaining(runner.gpa, .unlimited) catch |err| switch (err) {
910 error.OutOfMemory => |e| return e,
911 error.ReadFailed => return error.ContentsReadFailed,
912 error.StreamTooLong => unreachable, // .unlimited
913 };
914 defer runner.gpa.free(expected);
915
916 const comp = &(update.compiler orelse return update.manifest_sr.fail(
917 cmd_src_loc,
918 "missing \"#exe\", \"#lib\", or \"#obj\"",
919 .{},
920 ));
921 if (comp.state != .update)
922 return update.manifest_sr.fail(cmd_src_loc, "missing \"#update\"", .{});
923 const stderr = comp.mr.reader(1);
924 while (try comp.receiveMessage()) |message| switch (message.tag) {
925 else => {}, // Ignore other messages,
926 .config => {
927 var body_r: std.Io.Reader = .fixed(message.body);
928 comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch
929 unreachable;
930 },
931 .emit_digest => {
932 var body_r: std.Io.Reader = .fixed(message.body);
933 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch
934 unreachable;
935 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch
936 unreachable;
937 try comp.checkSuccess(check, expected, update.src_dir, digest);
938 },
939 .error_bundle => {
940 const eb = std.zig.Server.allocErrorBundle(
941 runner.arena.allocator(),
942 message.body,
943 ) catch |err| switch (err) {
944 error.OutOfMemory => |e| return e,
945 error.EndOfStream => return runner.fail("error bundle truncated", .{}),
946 };
947 switch (check) {
948 .errors => {
949 var error_aw: std.Io.Writer.Allocating = .init(runner.gpa);
950 defer error_aw.deinit();
951 eb.renderToWriter(.{
952 .include_reference_trace = false,
953 .include_source_line = false,
954 .include_log_text = true,
955 }, &error_aw.writer) catch |err| switch (err) {
956 error.WriteFailed => return error.OutOfMemory,
957 };
958 const actual = error_aw.written();
959 std.mem.replaceScalar(u8, actual, '\\', '/');
960 std.testing.expectEqualStrings(expected, actual) catch |err| switch (err) {
961 error.TestExpectedEqual => return runner.fail(
962 "errors did not match expected",
963 .{},
964 ),
965 };
966 },
967 .stdout, .exit, .gdb, .lldb => if (eb.errorMessageCount() > 0) {
968 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
969 error.Canceled => {},
970 else => {},
971 };
972 return runner.fail("unexpected compile errors", .{});
973 },
974 }
975 comp.state = .idle;
976 break :cmd;
977 },
978 };
979
980 const buffered_stderr = stderr.buffered();
981 if (buffered_stderr.len > 0) {
982 if (comp.allow_compiler_stderr) {
983 std.log.info("stderr:\n{s}", .{buffered_stderr});
984 } else {
985 return runner.fail("unexpected stderr:\n{s}", .{buffered_stderr});
986 }
987 }
988
989 try comp.exit();
990 return runner.fail("compiler failed to send terminating error_bundle", .{});
991 },
992 }
993 assert(contents_r.discardRemaining() catch |err| switch (err) {
994 error.ReadFailed => return error.ContentsReadFailed,
995 } == 0);
996 switch (cmd) {
997 .todo, .skip => {},
998 else => update.allow_skip = false,
999 }
1000}
1001
1002fn spawnCompiler(
1003 runner: *Runner,
1004 compiler: *?Compiler,
1005 src_dir: std.Io.Dir,
1006 output_mode: std.lang.OutputMode,
1007 args_r: *std.Io.Reader,
1008 target: Compiler.Target,
1009) (std.Io.Reader.ShortError || TestError)!void {
1010 const gpa = runner.gpa;
1011 const arena = runner.arena.allocator();
1012 if (compiler.*) |*comp| {
1013 try comp.exit();
1014 compiler.* = null;
1015 }
1016
1017 var argv: std.ArrayList([]const u8) = .empty;
1018 defer argv.deinit(gpa);
1019 try argv.appendSlice(gpa, &.{
1020 "zig",
1021 switch (output_mode) {
1022 .Obj => "build-obj",
1023 .Exe => "build-exe",
1024 .Lib => "build-lib",
1025 },
1026 "-target",
1027 target.triple,
1028 "--cache-dir",
1029 ".zig-cache",
1030 });
1031 switch (target.cache_mode) {
1032 .whole => {},
1033 .incremental => try argv.append(gpa, "-fincremental"),
1034 }
1035 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
1036 if (target.backend) |b| switch (b) {
1037 .sema => try argv.append(gpa, "-fno-emit-bin"),
1038 .selfhosted => try argv.append(gpa, "-fno-llvm"),
1039 .llvm => try argv.append(gpa, "-fllvm"),
1040 .cbe => try argv.append(gpa, "-ofmt=c"),
1041 } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse
1042 return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{}))
1043 .realPath(runner.io, &path_buffer) catch |err| switch (err) {
1044 error.Canceled => |e| return e,
1045 else => |e| return runner.fail("unable to get lib path: {t}", .{e}),
1046 }] });
1047 switch (target.linker) {
1048 .lld => try argv.appendSlice(gpa, &.{ "-flld", "-fno-new-linker" }),
1049 .old => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fno-new-linker" }),
1050 .new => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fnew-linker" }),
1051 }
1052 switch (target.pic) {
1053 .nopic => try argv.appendSlice(gpa, &.{ "-fno-PIC", "-fno-PIE" }),
1054 .pic => try argv.appendSlice(gpa, &.{ "-fPIC", "-fno-PIE" }),
1055 .pie => try argv.appendSlice(gpa, &.{ "-fPIC", "-fPIE" }),
1056 }
1057
1058 var need: usize = 1;
1059 var state: enum { positional, @"--name" } = .positional;
1060 var root_name: ?[]const u8 = null;
1061 while (true) {
1062 const done = if (args_r.fill(need)) false else |err| switch (err) {
1063 error.ReadFailed => |e| return e,
1064 error.EndOfStream => true,
1065 };
1066 const buffered = args_r.buffered();
1067 const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len;
1068 const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len;
1069 if (end - start > 0) {
1070 const arg = try arena.dupe(u8, buffered[start..end]);
1071 switch (state) {
1072 .positional => if (std.mem.eql(u8, arg, "--name")) {
1073 state = .@"--name";
1074 } else if (std.mem.cutPrefix(u8, arg, "-M")) |module| {
1075 var module_name_it = std.mem.splitScalar(u8, module, '=');
1076 const module_name = module_name_it.next().?;
1077 root_name = root_name orelse module_name;
1078 },
1079 .@"--name" => {
1080 root_name = arg;
1081 state = .positional;
1082 },
1083 }
1084 try argv.append(gpa, arg);
1085 args_r.toss(end);
1086 need = 1;
1087 } else {
1088 args_r.toss(start);
1089 need = buffered.len - start + 1;
1090 }
1091 if (done) break;
1092 }
1093 try argv.append(gpa, "--listen=-");
1094
1095 const out_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
1096 .root_name = root_name orelse "root",
1097 .cpu_arch = target.resolved.cpu.arch,
1098 .os_tag = target.resolved.os.tag,
1099 .ofmt = target.resolved.ofmt,
1100 .abi = target.resolved.abi,
1101 .output_mode = output_mode,
1102 });
1103
1104 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1105 const comp_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{
1106 out_name,
1107 }) catch &name_buffer, 0);
1108 errdefer comp_prog_node.end();
1109
1110 const child = std.process.spawn(runner.io, .{
1111 .exe = .{ .file = runner.args.zig_exe orelse
1112 return runner.fail("missing \"--zig=path/to/zig\" arg", .{}) },
1113 .argv = argv.items,
1114 .cwd = .{ .dir = src_dir },
1115 .progress_node = comp_prog_node,
1116 .stdin = .pipe,
1117 .stdout = .pipe,
1118 .stderr = .pipe,
1119 }) catch |err| switch (err) {
1120 error.Canceled, error.OutOfMemory => |e| return e,
1121 error.OperationUnsupported => return error.SkipTest,
1122 else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}),
1123 };
1124 compiler.* = .{
1125 .runner = runner,
1126 .prog_node = comp_prog_node,
1127 .allow_compiler_stderr = true,
1128 .output_mode = output_mode,
1129 .state = .idle,
1130 .child = child,
1131 .fw = undefined,
1132 .mr_buffer = undefined,
1133 .mr = undefined,
1134 .client = undefined,
1135 .target = target,
1136 .out_name = out_name,
1137 .config = null,
1138 };
1139 const comp = &compiler.*.?;
1140 comp.fw = child.stdin.?.writerStreaming(runner.io, &.{});
1141 comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{
1142 child.stdout.?, child.stderr.?,
1143 });
1144 comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface };
1145}
1146const Compiler = struct {
1147 runner: *Runner,
1148 prog_node: std.Progress.Node,
1149 allow_compiler_stderr: bool,
1150 output_mode: std.lang.OutputMode,
1151 state: enum { idle, update },
1152 child: std.process.Child,
1153 fw: std.Io.File.Writer,
1154 mr_buffer: std.Io.File.MultiReader.Buffer(2),
1155 mr: std.Io.File.MultiReader,
1156 client: std.zig.Client,
1157 target: Target,
1158 out_name: []const u8,
1159 config: ?std.zig.Server.Message.Config,
1160
1161 const Target = struct {
1162 triple: []const u8,
1163 resolved: std.Target,
1164 cache_mode: CacheMode,
1165 backend: ?Backend,
1166 linker: Linker,
1167 pic: Pic,
1168
1169 const CacheMode = enum { whole, incremental };
1170
1171 const Backend = enum {
1172 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
1173 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
1174 sema,
1175 /// Use the self-hosted code generation backend for this target.
1176 /// Corresponds to `-fno-llvm -fno-lld`.
1177 selfhosted,
1178 /// Use the LLVM backend.
1179 /// Corresponds to `-fllvm -flld`.
1180 llvm,
1181 /// Use the C backend. The output is compiled with `zig cc`.
1182 /// Corresponds to `-ofmt=c`.
1183 cbe,
1184 };
1185
1186 const Linker = enum { old, new, lld };
1187
1188 const Pic = enum { nopic, pic, pie };
1189 };
1190
1191 fn receiveMessage(comp: *Compiler) FailError!?struct {
1192 tag: std.zig.Server.Message.Tag,
1193 body: []const u8,
1194 } {
1195 const header = comp.client.receiveMessageWithMultiReader(
1196 &comp.mr,
1197 .none,
1198 ) catch |err| switch (err) {
1199 error.Canceled, error.OutOfMemory => |e| return e,
1200 error.Timeout => unreachable,
1201 error.EndOfStream => return null,
1202 else => |e| return comp.runner.fail("unable to receive message: {t}", .{e}),
1203 };
1204 const body = comp.client.in.take(header.bytes_len) catch unreachable;
1205 const stderr = comp.mr.reader(1);
1206 if (stderr.bufferedLen() > 0) {
1207 if (comp.allow_compiler_stderr) {
1208 std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() });
1209 } else {
1210 return comp.runner.fail("{t} unexpected stderr:\n{s}", .{
1211 header.tag, stderr.buffered(),
1212 });
1213 }
1214 stderr.tossBuffered();
1215 }
1216 return .{ .tag = header.tag, .body = body };
1217 }
1218
1219 const Check = enum { errors, stdout, exit, gdb, lldb };
1220 fn checkSuccess(
1221 comp: *Compiler,
1222 check: Check,
1223 expected: []const u8,
1224 src_dir: std.Io.Dir,
1225 digest: *const std.Build.Cache.BinDigest,
1226 ) TestError!void {
1227 const runner = comp.runner;
1228 const gpa = runner.gpa;
1229 const arena = runner.arena.allocator();
1230 const io = runner.io;
1231 const target = &comp.target.resolved;
1232 const config = &comp.config.?;
1233 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
1234
1235 // After a failure while running a script, the debugger starts accepting commands from stdin, and
1236 // because it is empty, the debugger exits normally with status 0. Choose a non-zero status to
1237 // return from the debugger script instead to detect it running to completion and indicate success.
1238 const debug_success = 99;
1239
1240 const tmp_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ "tmp";
1241 src_dir.createDirPath(io, tmp_dir_path) catch |err| switch (err) {
1242 error.Canceled => |e| return e,
1243 else => |e| return runner.fail("unable to create dir path {q}: {t}", .{
1244 tmp_dir_path, e,
1245 }),
1246 };
1247
1248 var argv: std.ArrayList([]const u8) = .initBuffer(try arena.alloc([]const u8, 5 + 4));
1249 var environ_map: std.process.Environ.Map = .init(gpa);
1250 defer environ_map.deinit();
1251 try environ_map.putAll(&runner.environ_map);
1252 var script_buffer: [512]u8 = undefined;
1253 const script_path, const script_file, var script_fw = script: switch (check) {
1254 .errors => return,
1255 .stdout, .exit => .{ undefined, null, undefined },
1256 .gdb => {
1257 const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "gdb";
1258 argv.appendSliceAssumeCapacity(&.{ "gdb", "--batch", "--command", script_path, "--args" });
1259 const script_file = src_dir.createFile(io, script_path, .{}) catch |err| switch (err) {
1260 error.Canceled => |e| return e,
1261 else => |e| return runner.fail("unable to create debugger script {q}: {t}", .{
1262 script_path, e,
1263 }),
1264 };
1265 errdefer script_file.close(io);
1266 var script_fw = script_file.writer(io, &script_buffer);
1267 script_fw.interface.writeAll(
1268 \\set remotetimeout 0
1269 \\
1270 \\
1271 ) catch |err| switch (err) {
1272 error.WriteFailed => switch (script_fw.err.?) {
1273 error.Canceled => |e| return e,
1274 else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{ script_path, e }),
1275 },
1276 };
1277 break :script .{ script_path, script_file, script_fw };
1278 },
1279 .lldb => {
1280 const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "lldb";
1281 argv.appendSliceAssumeCapacity(&.{ "lldb", "--batch", "--source", script_path, "--" });
1282 try environ_map.put("LANG", "C.UTF-8"); // affects output formatting
1283 const script_file = src_dir.createFile(io, script_path, .{}) catch |err| switch (err) {
1284 error.Canceled => |e| return e,
1285 else => |e| return runner.fail("unable to create debugger script {q}: {t}", .{
1286 script_path,
1287 e,
1288 }),
1289 };
1290 errdefer script_file.close(io);
1291 var script_fw = script_file.writer(io, &script_buffer);
1292 script_fw.interface.writeAll(
1293 \\settings set frame-format 'frame #${frame.index}:{ ${module.file.basename}{\`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${line.file.basename}:${line.number}{:${line.column}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\n'
1294 \\settings set plugin.process.gdb-remote.packet-timeout 0
1295 \\settings set stop-line-count-after 0
1296 \\settings set stop-line-count-before 0
1297 \\
1298 ) catch |err| switch (err) {
1299 error.WriteFailed => switch (script_fw.err.?) {
1300 error.Canceled => |e| return e,
1301 else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{ script_path, e }),
1302 },
1303 };
1304 break :script .{ script_path, script_file, script_fw };
1305 },
1306 };
1307 defer if (script_file) |file| file.close(io);
1308
1309 const out_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1310 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1311 const out_path = try std.Io.Dir.path.join(arena, &.{ out_dir_path, comp.out_name });
1312 const bin_path = switch (comp.target.backend.?) {
1313 .sema => return,
1314 .selfhosted, .llvm => out_path,
1315 .cbe => try comp.compileC(src_dir, out_path),
1316 };
1317
1318 const need_cross_libc =
1319 target.os.tag == .linux and config.flags.link_libc and config.flags.link_mode == .dynamic;
1320 const use_executor = use_executor: switch (std.zig.system.getExternalExecutor(io, target, .{
1321 .host_cpu_arch = runner.host.cpu.arch,
1322 .host_os_tag = runner.host.os.tag,
1323 .qemu_fixes_dl = need_cross_libc and runner.args.libc_runtimes_dir != null,
1324 .link_mode = config.flags.link_mode,
1325 .link_libc = config.flags.link_libc,
1326 })) {
1327 .bad_dl, .bad_os_or_cpu => {
1328 // This binary cannot be executed on this host.
1329 if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++
1330 "execute binaries for foreign target {q}", .{
1331 try runner.host.zigTriple(arena), comp.target.triple,
1332 });
1333 return;
1334 },
1335 .native => false,
1336 .darling => |executor| if (runner.args.enable_darling) {
1337 argv.appendAssumeCapacity(executor);
1338 break :use_executor true;
1339 } else continue :use_executor .bad_os_or_cpu,
1340 .qemu => |executor| if (runner.args.enable_qemu) {
1341 argv.appendAssumeCapacity(executor);
1342 if (need_cross_libc) {
1343 const libc_runtimes_dir = runner.args.libc_runtimes_dir orelse
1344 continue :use_executor .bad_os_or_cpu;
1345 const libc_runtimes_path = path_buffer[0 .. libc_runtimes_dir.realPath(
1346 io,
1347 &path_buffer,
1348 ) catch |err| switch (err) {
1349 error.Canceled => |e| return e,
1350 else => |e| return runner.fail("unable to get libc runtimes path: {t}", .{e}),
1351 }];
1352 argv.appendSliceAssumeCapacity(&.{ "-L", try std.Io.Dir.path.join(arena, &.{
1353 libc_runtimes_path,
1354 try if (target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
1355 arena,
1356 target.cpu.arch,
1357 target.os.tag,
1358 target.abi,
1359 ) else if (target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
1360 arena,
1361 target.cpu.arch,
1362 target.abi,
1363 ) else unreachable,
1364 }) });
1365 }
1366 break :use_executor true;
1367 } else continue :use_executor .bad_os_or_cpu,
1368 .rosetta => if (runner.args.enable_rosetta)
1369 true
1370 else
1371 continue :use_executor .bad_os_or_cpu,
1372 .wine => |executor| if (runner.args.enable_wine) {
1373 if (!environ_map.contains("WINEDEBUG")) try environ_map.put("WINEDEBUG", "-all");
1374 argv.appendAssumeCapacity(executor);
1375 break :use_executor true;
1376 } else continue :use_executor .bad_os_or_cpu,
1377 .wasmtime => |executor| if (runner.args.enable_wasmtime) {
1378 if (!environ_map.contains("WASMTIME_BACKTRACE_DETAILS"))
1379 try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1");
1380 argv.appendSliceAssumeCapacity(&.{ executor, "--dir=.", "-Sinherit-env" });
1381 break :use_executor true;
1382 } else continue :use_executor .bad_os_or_cpu,
1383 };
1384 argv.appendAssumeCapacity(bin_path);
1385
1386 if (!runner.args.quiet) std.log.info("{f}", .{std.zig.SubprocessCommand{
1387 .argv = argv.items,
1388 .cwd = cwd_path: {
1389 break :cwd_path path_buffer[0 .. src_dir.realPath(io, &path_buffer) catch |err| switch (err) {
1390 error.Canceled => |e| return e,
1391 else => break :cwd_path null,
1392 }];
1393 },
1394 .parent_env = &runner.environ_map,
1395 .child_env = &environ_map,
1396 }});
1397
1398 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1399 const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{
1400 comp.out_name,
1401 }) catch &name_buffer, 0);
1402 defer run_prog_node.end();
1403
1404 const bin_file = src_dir.openFile(io, bin_path, .{}) catch |err| switch (err) {
1405 error.Canceled => |e| return e,
1406 else => |e| return runner.fail("unable to open {q}: {t}", .{ bin_path, e }),
1407 };
1408 defer bin_file.close(io);
1409 const result = std.process.run(gpa, io, .{
1410 .exe = exe: switch (check) {
1411 .errors => unreachable,
1412 .stdout, .exit => if (use_executor) .search else .{ .file = bin_file },
1413 .gdb, .lldb => {
1414 if (use_executor) {
1415 if (!runner.args.quiet) std.log.warn(
1416 "skipping debugging of {q} via executor for foreign target {q}",
1417 .{ bin_path, comp.target.triple },
1418 );
1419 return;
1420 }
1421
1422 var line_it = std.mem.splitScalar(u8, expected, '\n');
1423 while (line_it.next()) |line| if (std.mem.cutPrefix(u8, line, switch (check) {
1424 else => unreachable,
1425 .gdb => "(gdb) ",
1426 .lldb => "(lldb) ",
1427 })) |cmd| script_fw.interface.print(
1428 \\{s}
1429 \\
1430 , .{cmd}) catch |err| switch (err) {
1431 error.WriteFailed => switch (script_fw.err.?) {
1432 error.Canceled => |e| return e,
1433 else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{
1434 script_path, e,
1435 }),
1436 },
1437 };
1438 script_fw.interface.print(
1439 \\
1440 \\quit {d}
1441 \\
1442 , .{debug_success}) catch |err| switch (err) {
1443 error.WriteFailed => switch (script_fw.err.?) {
1444 error.Canceled => |e| return e,
1445 else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{
1446 script_path, e,
1447 }),
1448 },
1449 };
1450 script_fw.flush() catch |err| switch (err) {
1451 error.Canceled => |e| return e,
1452 else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{
1453 script_path, e,
1454 }),
1455 };
1456 break :exe .{ .file = switch (check) {
1457 else => unreachable,
1458 .gdb => runner.args.gdb_exe,
1459 .lldb => runner.args.lldb_exe,
1460 } orelse {
1461 if (!runner.args.quiet) std.log.warn(
1462 "skipping debugging of {q} because of missing \"--{t}=path/to/{[1]t}\" arg",
1463 .{ bin_path, check },
1464 );
1465 return;
1466 } };
1467 },
1468 },
1469 .argv = argv.items,
1470 .cwd = .{ .dir = src_dir },
1471 .environ_map = &environ_map,
1472 .progress_node = run_prog_node,
1473 }) catch |err| if (use_executor) {
1474 // Chances are the foreign executor isn't available. Skip this evaluation.
1475 if (!runner.args.quiet) std.log.warn(
1476 "skipping execution of {q} via executor for foreign target {q}: {t}",
1477 .{ bin_path, comp.target.triple, err },
1478 );
1479 return;
1480 } else return runner.fail("unable to run the generated executable {q}: {t}", .{
1481 bin_path, err,
1482 });
1483 defer {
1484 gpa.free(result.stdout);
1485 gpa.free(result.stderr);
1486 }
1487 switch (result.term) {
1488 .exited => |code| switch (check) {
1489 .errors => unreachable,
1490 .stdout => {
1491 if (code != 0) return runner.fail(
1492 "generated executable {q} failed with code {d}",
1493 .{ bin_path, code },
1494 );
1495 std.testing.expectEqualStrings(
1496 expected,
1497 result.stdout,
1498 ) catch |err| switch (err) {
1499 error.TestExpectedEqual => return runner.fail("stdout did not match expected", .{}),
1500 };
1501 },
1502 .exit => {
1503 var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = undefined;
1504 std.testing.expectEqualStrings(
1505 expected,
1506 std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable,
1507 ) catch |err| switch (err) {
1508 error.TestExpectedEqual => return runner.fail(
1509 "exit code did not match expected",
1510 .{},
1511 ),
1512 };
1513 },
1514 .gdb, .lldb => {
1515 if (code != debug_success) return runner.fail(
1516 "debugging {q} failed with code {d}",
1517 .{ bin_path, code },
1518 );
1519 var expected_match_it = std.mem.splitSequence(u8, expected, "#...");
1520 var pos: usize = 0;
1521 while (expected_match_it.next()) |expected_match| : (pos += expected_match.len) {
1522 var context_len: usize = expected_match.len;
1523 pos = while (true) : (context_len -= 1) break std.mem.findPos(
1524 u8,
1525 result.stdout,
1526 pos,
1527 expected_match[0..context_len],
1528 ) orelse continue;
1529 const actual_match = result.stdout[pos..];
1530 std.testing.expectEqualStrings(
1531 expected_match,
1532 actual_match[0..@min(actual_match.len, expected_match.len)],
1533 ) catch |err| switch (err) {
1534 error.TestExpectedEqual => {
1535 std.testing.failPrint("\n=============== full output: =========\n", .{});
1536 std.testing.failPrintWithVisibleNewlines(actual_match);
1537 std.testing.failPrint("\n======================================\n", .{});
1538 return runner.fail("stdout did not match expected", .{});
1539 },
1540 };
1541 }
1542 },
1543 },
1544 .signal => |sig| return runner.fail(
1545 "generated executable {q} terminated with signal {t}",
1546 .{ bin_path, sig },
1547 ),
1548 .stopped => |sig| return runner.fail(
1549 "generated executable {q} stopped with signal {t}",
1550 .{ bin_path, sig },
1551 ),
1552 .unknown => return runner.fail(
1553 "generated executable {q} terminated unexpectedly",
1554 .{bin_path},
1555 ),
1556 }
1557 if (!use_executor and result.stderr.len > 0) {
1558 std.log.err("generated executable {q} had unexpected stderr:\n{s}", .{
1559 bin_path, result.stderr,
1560 });
1561 }
1562 }
1563
1564 fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) TestError![]const u8 {
1565 const runner = parent_comp.runner;
1566 const arena = runner.arena.allocator();
1567 var compiler: ?Compiler = null;
1568 var args_r: std.Io.Reader = .fixed(c_path);
1569 parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{
1570 .triple = parent_comp.target.triple,
1571 .resolved = resolved: {
1572 var resolved = parent_comp.target.resolved;
1573 resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch);
1574 break :resolved resolved;
1575 },
1576 .cache_mode = .whole,
1577 .backend = null,
1578 .linker = .lld,
1579 .pic = parent_comp.target.pic,
1580 }) catch |err| switch (err) {
1581 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
1582 error.ReadFailed => unreachable, // .fixed
1583 };
1584 const comp = &compiler.?;
1585 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
1586 error.WriteFailed => switch (comp.fw.err.?) {
1587 error.Canceled => |e| return e,
1588 else => |e| return runner.fail("unable to send message: {t}", .{e}),
1589 },
1590 };
1591 comp.state = .update;
1592 var out_path: ?[]const u8 = null;
1593 while (try comp.receiveMessage()) |message| switch (message.tag) {
1594 else => {}, // Ignore other messages,
1595 .config => {
1596 var body_r: std.Io.Reader = .fixed(message.body);
1597 comp.config =
1598 body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable;
1599 },
1600 .emit_digest => {
1601 var body_r: std.Io.Reader = .fixed(message.body);
1602 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
1603 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable;
1604
1605 const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1606 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1607 out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name });
1608 },
1609 .error_bundle => {
1610 const eb = std.zig.Server.allocErrorBundle(
1611 runner.arena.allocator(),
1612 message.body,
1613 ) catch |err| switch (err) {
1614 error.OutOfMemory => |e| return e,
1615 error.EndOfStream => return runner.fail("error bundle truncated", .{}),
1616 };
1617 if (eb.errorMessageCount() > 0) {
1618 try runner.eb_wip.addBundleAsRoots(eb);
1619 return runner.fail("unexpected compile errors", .{});
1620 }
1621 comp.state = .idle;
1622 break;
1623 },
1624 };
1625 try comp.exit();
1626 return out_path.?;
1627 }
1628
1629 fn exit(comp: *Compiler) FailError!void {
1630 const runner = comp.runner;
1631 comp.client.serveBodylessMessage(.exit) catch |err| switch (err) {
1632 error.WriteFailed => switch (comp.fw.err.?) {
1633 error.Canceled => |e| return e,
1634 error.BrokenPipe => {},
1635 else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}),
1636 },
1637 };
1638 comp.child.stdin.?.close(runner.io);
1639 comp.child.stdin = null;
1640 while (try comp.receiveMessage()) |_| {}
1641 if (comp.client.in.bufferedLen() > 0) return runner.fail("unable to receive messages", .{});
1642 const term = comp.child.wait(runner.io) catch |err|
1643 return runner.fail("child process failed: {t}", .{err});
1644 comp.prog_node.end();
1645 switch (term) {
1646 .exited => |code| if (code != 0)
1647 return runner.fail("compiler failed with code {d}", .{code}),
1648 .signal => |sig| return runner.fail("compiler terminated with signal {t}", .{sig}),
1649 .stopped => |sig| return runner.fail("compiler stopped unexpectedly with signal {t}", .{
1650 sig,
1651 }),
1652 .unknown => return runner.fail("compiler terminated unexpectedly", .{}),
1653 }
1654 }
1655};
1656
1657const ClientArgs = struct {
1658 test_file: std.Io.File,
1659 test_kind: std.Io.File.Kind,
1660 zig_exe: std.Io.File,
1661 lib_dir: std.Io.Dir,
1662 src_dir: std.Io.Dir,
1663 targets: std.ArrayList([]const u8),
1664
1665 libc_runtimes_dir: ?std.Io.Dir,
1666 enable_darling: bool,
1667 enable_qemu: bool,
1668 enable_rosetta: bool,
1669 enable_wine: bool,
1670 enable_wasmtime: bool,
1671
1672 gdb_exe: ?std.Io.File,
1673 lldb_exe: ?std.Io.File,
1674
1675 quiet: bool,
1676 keep_src: bool,
1677};
1678pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 {
1679 const fatal = struct {
1680 fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
1681 std.log.err(fmt, args);
1682 std.process.exit(1);
1683 }
1684 }.fatal;
1685 const arena = init.arena.allocator();
1686
1687 var test_path_arg: ?[]const u8 = null;
1688 var zig_path_arg: ?[]const u8 = null;
1689 var lib_path_arg: ?[]const u8 = null;
1690 var libc_runtimes_path_arg: ?[]const u8 = null;
1691 var gdb_path_arg: ?[]const u8 = null;
1692 var lldb_path_arg: ?[]const u8 = null;
1693 var args: ClientArgs = .{
1694 .test_file = undefined,
1695 .test_kind = undefined,
1696 .zig_exe = undefined,
1697 .lib_dir = undefined,
1698 .src_dir = undefined,
1699 .targets = .empty,
1700
1701 .libc_runtimes_dir = null,
1702 .enable_darling = false,
1703 .enable_qemu = false,
1704 .enable_rosetta = false,
1705 .enable_wine = false,
1706 .enable_wasmtime = false,
1707
1708 .gdb_exe = null,
1709 .lldb_exe = null,
1710
1711 .quiet = false,
1712 .keep_src = false,
1713 };
1714 defer args.targets.deinit(init.gpa);
1715
1716 var arg_it = try init.minimal.args.iterateAllocator(arena);
1717 const self = arg_it.next().?;
1718 while (arg_it.next()) |arg| {
1719 if (std.mem.eql(u8, arg, "--listen=-")) {
1720 var stdin_buffer: [512]u8 = undefined;
1721 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer);
1722 var stdout_buffer: [512]u8 = undefined;
1723 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
1724 var runner: Runner = .{
1725 .gpa = init.gpa,
1726 .arena = init.arena,
1727 .environ_map = init.minimal.environ.createMap(init.gpa) catch |err| switch (err) {
1728 error.OutOfMemory => |e| return e,
1729 else => |e| fatal("unable to get environment: {t}", .{e}),
1730 },
1731 .io = init.io,
1732 .prog_node = std.Progress.start(init.io, .{}),
1733 .args = .{
1734 .@"test" = null,
1735 .manifest_file = null,
1736 .zig_exe = null,
1737 .lib_dir = null,
1738 .src_dir = null,
1739 .targets = .empty,
1740 .target_bytes = .empty,
1741
1742 .libc_runtimes_dir = null,
1743 .enable_darling = false,
1744 .enable_qemu = false,
1745 .enable_rosetta = false,
1746 .enable_wasmtime = false,
1747 .enable_wine = false,
1748
1749 .gdb_exe = null,
1750 .lldb_exe = null,
1751
1752 .quiet = false,
1753 },
1754 .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) {
1755 error.Canceled => |e| return e,
1756 else => |e| fatal("unable to resolve host target: {t}", .{e}),
1757 },
1758 .server = .{
1759 .in = &stdin.interface,
1760 .out = &stdout.interface,
1761 },
1762 .eb_wip = try .init(init.gpa),
1763 };
1764 defer runner.deinit();
1765 switch (runner.runServer()) {
1766 error.Canceled, error.OutOfMemory => |e| return e,
1767 error.ServerReadFailed => switch (stdin.err.?) {
1768 error.Canceled => |e| return e,
1769 else => |e| fatal("unable to receive message: {t}", .{e}),
1770 },
1771 error.ServerWriteFailed => switch (stdout.err.?) {
1772 error.Canceled => |e| return e,
1773 else => |e| fatal("unable to send message: {t}", .{e}),
1774 },
1775 error.ServerEndOfStream => fatal("no more messages", .{}),
1776 error.AlreadyReported => {
1777 var eb = try runner.eb_wip.toOwnedBundle("");
1778 defer eb.deinit(init.gpa);
1779 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
1780 error.Canceled => |e| return e,
1781 else => {},
1782 };
1783 std.process.exit(1);
1784 },
1785 }
1786 } else if (std.mem.eql(u8, arg, "--zig")) {
1787 zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1788 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
1789 zig_path_arg = zig_path;
1790 } else if (std.mem.eql(u8, arg, "--lib")) {
1791 lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1792 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
1793 lib_path_arg = lib_path;
1794 } else if (std.mem.eql(u8, arg, "--target")) {
1795 try args.targets.append(
1796 init.gpa,
1797 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})),
1798 );
1799 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
1800 try args.targets.append(init.gpa, try arena.dupe(u8, target));
1801 } else if (std.mem.eql(u8, arg, "--libc-runtimes")) {
1802 libc_runtimes_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1803 } else if (std.mem.cutPrefix(u8, arg, "--libc-runtimes=")) |libc_runtimes_path| {
1804 libc_runtimes_path_arg = libc_runtimes_path;
1805 } else if (std.mem.eql(u8, arg, "-fdarling")) {
1806 args.enable_darling = true;
1807 } else if (std.mem.eql(u8, arg, "-fqemu")) {
1808 args.enable_qemu = true;
1809 } else if (std.mem.eql(u8, arg, "-frosetta")) {
1810 args.enable_rosetta = true;
1811 } else if (std.mem.eql(u8, arg, "-fwine")) {
1812 args.enable_wine = true;
1813 } else if (std.mem.eql(u8, arg, "-fwasmtime")) {
1814 args.enable_wasmtime = true;
1815 } else if (std.mem.eql(u8, arg, "--gdb")) {
1816 gdb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1817 } else if (std.mem.cutPrefix(u8, arg, "--gdb=")) |gdb_path| {
1818 gdb_path_arg = gdb_path;
1819 } else if (std.mem.eql(u8, arg, "--lldb")) {
1820 lldb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1821 } else if (std.mem.cutPrefix(u8, arg, "--lldb=")) |lldb_path| {
1822 lldb_path_arg = lldb_path;
1823 } else if (std.mem.eql(u8, arg, "--quiet")) {
1824 args.quiet = true;
1825 } else if (std.mem.eql(u8, arg, "--keep-src")) {
1826 args.keep_src = true;
1827 } else {
1828 if (test_path_arg) |_| fatal("unexpected arg {q}", .{arg});
1829 test_path_arg = arg;
1830 }
1831 }
1832
1833 const test_path = test_path_arg orelse fatal("missing \"path/to/test\" arg", .{});
1834 const zig_path = zig_path_arg orelse fatal("missing \"--zig=path/to/zig\" arg", .{});
1835 const lib_path = lib_path_arg orelse fatal("missing \"--lib=path/to/lib\" arg", .{});
1836
1837 const cwd: std.Io.Dir = .cwd();
1838 args.test_file = cwd.openFile(init.io, test_path, .{
1839 .allow_directory = true,
1840 }) catch |err| switch (err) {
1841 error.Canceled => |e| return e,
1842 else => |e| fatal("unable to open {q}: {t}", .{ test_path, e }),
1843 };
1844 defer args.test_file.close(init.io);
1845 args.test_kind = (args.test_file.stat(init.io) catch |err| switch (err) {
1846 error.Canceled => |e| return e,
1847 else => |e| fatal("unable to stat {q}: {t}", .{ test_path, e }),
1848 }).kind;
1849 args.zig_exe = cwd.openFile(init.io, zig_path, .{}) catch |err| switch (err) {
1850 error.Canceled => |e| return e,
1851 else => |e| fatal("unable to open {q}: {t}", .{ zig_path, e }),
1852 };
1853 defer args.zig_exe.close(init.io);
1854 args.lib_dir = cwd.openDir(init.io, lib_path, .{}) catch |err| switch (err) {
1855 error.Canceled => |e| return e,
1856 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
1857 };
1858 defer args.lib_dir.close(init.io);
1859 if (libc_runtimes_path_arg) |libc_runtimes_path| args.libc_runtimes_dir = cwd.openDir(
1860 init.io,
1861 libc_runtimes_path,
1862 .{},
1863 ) catch |err| switch (err) {
1864 error.Canceled => |e| return e,
1865 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
1866 };
1867 defer if (args.libc_runtimes_dir) |libc_runtimes_dir| libc_runtimes_dir.close(init.io);
1868 if (gdb_path_arg) |gdb_path| args.gdb_exe = cwd.openFile(init.io, gdb_path, .{}) catch |err| switch (err) {
1869 error.Canceled => |e| return e,
1870 else => |e| fatal("unable to open {q}: {t}", .{ gdb_path, e }),
1871 };
1872 defer if (args.gdb_exe) |gdb_exe| gdb_exe.close(init.io);
1873 if (lldb_path_arg) |lldb_path| args.lldb_exe = cwd.openFile(init.io, lldb_path, .{}) catch |err| switch (err) {
1874 error.Canceled => |e| return e,
1875 else => |e| fatal("unable to open {q}: {t}", .{ lldb_path, e }),
1876 };
1877 defer if (args.lldb_exe) |lldb_exe| lldb_exe.close(init.io);
1878
1879 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
1880 var rand_int: u64 = undefined;
1881 init.io.random(@ptrCast(&rand_int));
1882 break :rand_int rand_int;
1883 });
1884 args.src_dir = cwd.createDirPathOpen(init.io, src_dir_path, .{}) catch |err| switch (err) {
1885 error.Canceled => |e| return e,
1886 else => |e| fatal("unable to create {q}: {t}", .{ src_dir_path, e }),
1887 };
1888 defer {
1889 args.src_dir.close(init.io);
1890 if (!args.keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| {
1891 std.log.warn("unable to delete tree {q}: {t}", .{ src_dir_path, err });
1892 };
1893 }
1894
1895 var inherit_dirs: std.ArrayList(std.Io.Dir) = .initBuffer(try arena.alloc(std.Io.Dir, 3));
1896 var inherit_files: std.ArrayList(std.Io.File) = .initBuffer(try arena.alloc(std.Io.File, 4));
1897 inherit_files.appendSliceAssumeCapacity(&.{ args.test_file, args.zig_exe });
1898 inherit_dirs.appendSliceAssumeCapacity(&.{ args.lib_dir, args.src_dir });
1899 if (args.libc_runtimes_dir) |libc_runtimes_dir| inherit_dirs.appendAssumeCapacity(libc_runtimes_dir);
1900 if (args.gdb_exe) |gdb_exe| inherit_files.appendAssumeCapacity(gdb_exe);
1901 if (args.lldb_exe) |lldb_exe| inherit_files.appendAssumeCapacity(lldb_exe);
1902 var child = std.process.spawn(init.io, .{
1903 .argv = &.{ self, "--listen=-" },
1904 .stdin = .pipe,
1905 .stdout = .pipe,
1906 .inherit_dirs = inherit_dirs.items,
1907 .inherit_files = inherit_files.items,
1908 }) catch |err| switch (err) {
1909 error.Canceled, error.OutOfMemory => |e| return e,
1910 else => |e| fatal("unable to spawn runner: {t}", .{e}),
1911 };
1912
1913 var child_reader_buffer: [512]u8 = undefined;
1914 var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer);
1915 var child_writer_buffer: [512]u8 = undefined;
1916 var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer);
1917 var client: std.zig.Client = .{
1918 .in = &child_reader.interface,
1919 .out = &child_writer.interface,
1920 };
1921 const success = runClient(init.gpa, arena, init.io, &client, &args) catch |err| switch (err) {
1922 error.OutOfMemory => |e| return e,
1923 error.ReadFailed => switch (child_reader.err.?) {
1924 error.Canceled => |e| return e,
1925 else => |e| fatal("unable to receive message: {t}", .{e}),
1926 },
1927 error.WriteFailed => switch (child_writer.err.?) {
1928 error.Canceled => |e| return e,
1929 else => |e| fatal("unable to send message: {t}", .{e}),
1930 },
1931 error.EndOfStream => fatal("no more messages", .{}),
1932 };
1933 _ = child.wait(init.io) catch |err| switch (err) {
1934 error.Canceled => |e| return e,
1935 else => |e| fatal("unable to wait for runner: {t}", .{e}),
1936 };
1937 return if (success) 0 else 1;
1938}
1939fn runClient(
1940 gpa: std.mem.Allocator,
1941 arena: std.mem.Allocator,
1942 io: std.Io,
1943 client: *std.zig.Client,
1944 args: *const ClientArgs,
1945) (std.mem.Allocator.Error || std.Io.Reader.Error || std.Io.Writer.Error)!bool {
1946 var stdout_buffer: [512]u8 = undefined;
1947 var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
1948 const stdout = &stdout_fw.interface;
1949 var stderr_buffer: [512]u8 = undefined;
1950 var stderr_fw: std.Io.File.Writer = .initStreaming(.stderr(), io, &stderr_buffer);
1951 const stderr = &stderr_fw.interface;
1952
1953 var argv: std.ArrayList(u8) = .empty;
1954 defer argv.deinit(gpa);
1955 const Arg = std.zig.Client.Message.Arg;
1956
1957 try argv.append(gpa, @backingInt(@as(Arg, switch (args.test_kind) {
1958 else => unreachable,
1959 .file => .input_file,
1960 .directory => .input_dir,
1961 })));
1962 try argv.appendSlice(gpa, @ptrCast(&args.test_file.handle));
1963
1964 try argv.append(gpa, @backingInt(Arg.prefix));
1965 try argv.appendSlice(gpa, "--zig=");
1966 try argv.append(gpa, 0);
1967 try argv.append(gpa, @backingInt(Arg.input_file));
1968 try argv.appendSlice(gpa, @ptrCast(&args.zig_exe.handle));
1969
1970 try argv.append(gpa, @backingInt(Arg.prefix));
1971 try argv.appendSlice(gpa, "--lib=");
1972 try argv.append(gpa, 0);
1973 try argv.append(gpa, @backingInt(Arg.input_dir));
1974 try argv.appendSlice(gpa, @ptrCast(&args.lib_dir.handle));
1975
1976 try argv.append(gpa, @backingInt(Arg.prefix));
1977 try argv.appendSlice(gpa, "--src=");
1978 try argv.append(gpa, 0);
1979 try argv.append(gpa, @backingInt(Arg.output_dir));
1980 try argv.appendSlice(gpa, @ptrCast(&args.src_dir.handle));
1981
1982 for (args.targets.items) |target| {
1983 try argv.append(gpa, @backingInt(Arg.string));
1984 try argv.appendSlice(gpa, "--target");
1985 try argv.append(gpa, 0);
1986
1987 try argv.append(gpa, @backingInt(Arg.string));
1988 try argv.appendSlice(gpa, target);
1989 try argv.append(gpa, 0);
1990 }
1991
1992 if (args.libc_runtimes_dir) |libc_runtimes_dir| {
1993 try argv.append(gpa, @backingInt(Arg.prefix));
1994 try argv.appendSlice(gpa, "--libc-runtimes=");
1995 try argv.append(gpa, 0);
1996 try argv.append(gpa, @backingInt(Arg.input_dir));
1997 try argv.appendSlice(gpa, @ptrCast(&libc_runtimes_dir.handle));
1998 }
1999 if (args.enable_darling) {
2000 try argv.append(gpa, @backingInt(Arg.string));
2001 try argv.appendSlice(gpa, "-fdarling");
2002 try argv.append(gpa, 0);
2003 }
2004 if (args.enable_qemu) {
2005 try argv.append(gpa, @backingInt(Arg.string));
2006 try argv.appendSlice(gpa, "-fqemu");
2007 try argv.append(gpa, 0);
2008 }
2009 if (args.enable_rosetta) {
2010 try argv.append(gpa, @backingInt(Arg.string));
2011 try argv.appendSlice(gpa, "-frosetta");
2012 try argv.append(gpa, 0);
2013 }
2014 if (args.enable_wine) {
2015 try argv.append(gpa, @backingInt(Arg.string));
2016 try argv.appendSlice(gpa, "-fwine");
2017 try argv.append(gpa, 0);
2018 }
2019 if (args.enable_wasmtime) {
2020 try argv.append(gpa, @backingInt(Arg.string));
2021 try argv.appendSlice(gpa, "-fwasmtime");
2022 try argv.append(gpa, 0);
2023 }
2024
2025 if (args.gdb_exe) |gdb_exe| {
2026 try argv.append(gpa, @backingInt(Arg.prefix));
2027 try argv.appendSlice(gpa, "--gdb=");
2028 try argv.append(gpa, 0);
2029 try argv.append(gpa, @backingInt(Arg.input_file));
2030 try argv.appendSlice(gpa, @ptrCast(&gdb_exe.handle));
2031 }
2032 if (args.lldb_exe) |lldb_exe| {
2033 try argv.append(gpa, @backingInt(Arg.prefix));
2034 try argv.appendSlice(gpa, "--lldb=");
2035 try argv.append(gpa, 0);
2036 try argv.append(gpa, @backingInt(Arg.input_file));
2037 try argv.appendSlice(gpa, @ptrCast(&lldb_exe.handle));
2038 }
2039
2040 if (args.quiet) {
2041 try argv.append(gpa, @backingInt(Arg.string));
2042 try argv.appendSlice(gpa, "--quiet");
2043 try argv.append(gpa, 0);
2044 }
2045
2046 try client.serveMessageHeader(.{
2047 .tag = .args,
2048 .bytes_len = @intCast(argv.items.len),
2049 });
2050 try client.out.writeAll(argv.items);
2051
2052 var success = true;
2053 var metadata: struct {
2054 index: u32,
2055 len: u32,
2056 names: []u32,
2057 expected_panic_msgs: []u32,
2058 string_bytes: []u8,
2059
2060 fn name(m: *@This()) []const u8 {
2061 return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0);
2062 }
2063
2064 fn expectedPanicMsg(m: *@This()) ?[:0]const u8 {
2065 return switch (m.expected_panic_msgs[m.index - 1]) {
2066 0 => null,
2067 else => |offset| m.string_bytes[offset..std.mem.findScalarPos(
2068 u8,
2069 m.string_bytes,
2070 offset,
2071 0,
2072 ).? :0],
2073 };
2074 }
2075
2076 fn next(m: *@This()) ?u32 {
2077 if (m.len - m.index == 0) return null;
2078 defer m.index += 1;
2079 return m.index;
2080 }
2081 } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} };
2082 try client.serveBodylessMessage(.query_test_metadata);
2083 while (true) {
2084 const hdr = try client.receiveMessage();
2085 switch (hdr.tag) {
2086 else => try client.in.discardAll(hdr.bytes_len),
2087 .zig_version => {
2088 const actual_version = try client.in.take(hdr.bytes_len);
2089 if (!std.mem.eql(u8, builtin.zig_version_string, actual_version)) {
2090 try stderr.print(
2091 "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n",
2092 .{ builtin.zig_version_string, actual_version },
2093 );
2094 try stderr.flush();
2095 std.process.exit(1);
2096 }
2097 },
2098 .error_bundle => {
2099 const body = try arena.alloc(u8, hdr.bytes_len);
2100 try client.in.readSliceAll(body);
2101 const eb = std.zig.Server.allocErrorBundle(arena, body) catch |err| switch (err) {
2102 error.OutOfMemory => |e| return e,
2103 error.EndOfStream => {
2104 try stderr.writeAll("error bundle truncated\n");
2105 try stderr.flush();
2106 std.process.exit(1);
2107 },
2108 };
2109 success = false;
2110 eb.renderToStderr(io, .{}, .auto) catch {};
2111 },
2112 .test_metadata => {
2113 const tm_hdr =
2114 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
2115 metadata = .{
2116 .index = 0,
2117 .len = tm_hdr.tests_len,
2118 .names = try arena.alloc(u32, tm_hdr.tests_len),
2119 .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len),
2120 .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len),
2121 };
2122 try client.in.readSliceEndian(u32, metadata.names, .little);
2123 try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little);
2124 try client.in.readSliceAll(metadata.string_bytes);
2125 try client.serveRunTest(metadata.next() orelse break);
2126 },
2127 .test_results => {
2128 const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little);
2129 switch (tr.flags.status) {
2130 .pass, .skip => {},
2131 .fail => success = false,
2132 }
2133 try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() });
2134 try stdout.flush();
2135 try client.serveRunTest(metadata.next() orelse break);
2136 },
2137 }
2138 }
2139 try client.serveBodylessMessage(.exit);
2140 return success;
2141}
test/tests.zig+9-137
......@@ -2201,137 +2201,6 @@ const RunnerTarget = struct {
22012201 pic: enum { nopic, pic, pie },
22022202};
22032203
2204pub const incremental_matrix: []const RunnerTarget = &.{
2205 // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others
2206 // to run due to the output (C source code or LLVM IR) being built non-incrementally (by Clang
2207 // or LLVM). We just have a couple here to make sure that it works.
2208 .{
2209 .target = .{
2210 .cpu_arch = .x86_64,
2211 .os_tag = .linux,
2212 .abi = .musl,
2213 },
2214 .cache_mode = .incremental,
2215 .backend = .cbe,
2216 .linker = .old,
2217 .pic = .nopic,
2218 },
2219 .{
2220 .target = .{
2221 .cpu_arch = .x86_64,
2222 .os_tag = .linux,
2223 .abi = .none,
2224 },
2225 .cache_mode = .incremental,
2226 .backend = .llvm,
2227 .linker = .lld,
2228 .pic = .nopic,
2229 },
2230
2231 .{
2232 .target = .{
2233 .cpu_arch = .x86_64,
2234 .os_tag = .linux,
2235 .abi = .none,
2236 },
2237 .cache_mode = .incremental,
2238 .backend = .selfhosted,
2239 .linker = .new,
2240 .pic = .nopic,
2241 },
2242 .{
2243 .target = .{
2244 .cpu_arch = .x86_64,
2245 .os_tag = .windows,
2246 .abi = .none,
2247 },
2248 .cache_mode = .incremental,
2249 .backend = .selfhosted,
2250 .linker = .old,
2251 .pic = .pic,
2252 },
2253 .{
2254 .target = .{
2255 .cpu_arch = .wasm32,
2256 .os_tag = .wasi,
2257 .abi = .none,
2258 },
2259 .cache_mode = .incremental,
2260 .backend = .selfhosted,
2261 .linker = .old,
2262 .pic = .nopic,
2263 },
2264};
2265
2266pub const debugger_matrix: []const RunnerTarget = &.{
2267 .{
2268 .target = .{
2269 .cpu_arch = .x86_64,
2270 .os_tag = .linux,
2271 .abi = .none,
2272 },
2273 .cache_mode = .whole,
2274 .backend = .selfhosted,
2275 .linker = .old,
2276 .pic = .nopic,
2277 },
2278 .{
2279 .target = .{
2280 .cpu_arch = .x86_64,
2281 .os_tag = .linux,
2282 .abi = .none,
2283 },
2284 .cache_mode = .whole,
2285 .backend = .selfhosted,
2286 .linker = .old,
2287 .pic = .pic,
2288 },
2289 .{
2290 .target = .{
2291 .cpu_arch = .x86_64,
2292 .os_tag = .linux,
2293 .abi = .none,
2294 },
2295 .cache_mode = .whole,
2296 .backend = .selfhosted,
2297 .linker = .new,
2298 .pic = .nopic,
2299 },
2300 .{
2301 .target = .{
2302 .cpu_arch = .x86_64,
2303 .os_tag = .linux,
2304 .abi = .none,
2305 },
2306 .cache_mode = .whole,
2307 .backend = .selfhosted,
2308 .linker = .new,
2309 .pic = .pic,
2310 },
2311 .{
2312 .target = .{
2313 .cpu_arch = .x86_64,
2314 .os_tag = .linux,
2315 .abi = .none,
2316 },
2317 .cache_mode = .incremental,
2318 .backend = .selfhosted,
2319 .linker = .new,
2320 .pic = .nopic,
2321 },
2322 .{
2323 .target = .{
2324 .cpu_arch = .x86_64,
2325 .os_tag = .linux,
2326 .abi = .none,
2327 },
2328 .cache_mode = .incremental,
2329 .backend = .selfhosted,
2330 .linker = .new,
2331 .pic = .pic,
2332 },
2333};
2334
23352204pub fn addStackTraceTests(b: *std.Build, options: StackTracesContext.Options) *Step {
23362205 const step = b.step("test-stack-traces", "Run the stack trace tests");
23372206
......@@ -3224,21 +3093,24 @@ pub fn addRunnerTests(
32243093 defer tests_dir.close(b.graph.io);
32253094 var test_it = tests_dir.iterate();
32263095 while (try test_it.next(b.graph.io)) |@"test"| {
3227 for (options.test_filters) |filter| {
3228 if (std.mem.find(u8, @"test".name, filter) != null) break;
3229 } else if (options.test_filters.len > 0) continue;
3230
3231 const test_path = tests_path.path(b, @"test".name);
32323096 switch (@"test".kind) {
32333097 else => continue,
3234 .file => if (isEditorFileName(@"test".name)) continue,
3098 .file => {
3099 if (std.mem.eql(u8, @"test".name, "matrix.zon")) continue;
3100 if (isEditorFileName(@"test".name)) continue;
3101 },
32353102 .directory => {},
32363103 }
32373104
3105 for (options.test_filters) |filter| {
3106 if (std.mem.find(u8, @"test".name, filter) != null) break;
3107 } else if (options.test_filters.len > 0) continue;
3108
32383109 const run = b.addRunArtifact(runner);
32393110 run.setName(@"test".name);
32403111 run.enableProtocolMode();
32413112
3113 const test_path = tests_path.path(b, @"test".name);
32423114 switch (@"test".kind) {
32433115 else => continue,
32443116 .file => run.addFileArg(test_path),