authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-13 06:31:29-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-26 19:08:50-04:00
log11958a306f30268b17454a25bc365e67aad84efd
tree014d51a6105cb1577763d4f9075a6f930c69e4c7
parent3cb5bbb36d09baf4f08464c1d047607a1f09a967

test: implement new runner


21 files changed, 1324 insertions(+), 171 deletions(-)

build.zig+21
...@@ -804,6 +804,27 @@ pub fn build(b: *std.Build) !void {...@@ -804,6 +804,27 @@ pub fn build(b: *std.Build) !void {
804 .skip_wasm = skip_wasm,804 .skip_wasm = skip_wasm,
805 .max_rss = 4_300_000_000,805 .max_rss = 4_300_000_000,
806 })) |test_libc_nsz_step| test_step.dependOn(test_libc_nsz_step);806 })) |test_libc_nsz_step| test_step.dependOn(test_libc_nsz_step);
807
808 const runner = b.addExecutable(.{
809 .name = "runner",
810 .root_module = b.createModule(.{
811 .root_source_file = b.path("test/src/runner.zig"),
812 .target = b.graph.host,
813 }),
814 });
815 test_step.dependOn(try tests.addNewIncrementalTests(b, runner, .{
816 .test_filters = test_filters,
817 .test_target_filters = test_target_filters,
818 .skip_non_native = skip_non_native,
819 .skip_wasm = skip_wasm,
820 .skip_freebsd = skip_freebsd,
821 .skip_netbsd = skip_netbsd,
822 .skip_openbsd = skip_openbsd,
823 .skip_windows = skip_windows,
824 .skip_darwin = skip_darwin,
825 .skip_linux = skip_linux,
826 .skip_llvm = skip_llvm,
827 }));
807}828}
808829
809fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {830fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
lib/compiler/Maker/Step.zig+4-1
...@@ -270,7 +270,10 @@ pub fn make(...@@ -270,7 +270,10 @@ pub fn make(
270 .compile => break :t null,270 .compile => break :t null,
271 .run => {271 .run => {
272 const run_flags: Configuration.Step.Run.Flags = @bitCast(flags);272 const run_flags: Configuration.Step.Run.Flags = @bitCast(flags);
273 if (run_flags.stdio == .zig_test) break :t null;273 switch (run_flags.stdio) {
274 .infer_from_args, .inherit, .check => {},
275 .zig_test, .protocol => break :t null,
276 }
274 },277 },
275 else => {},278 else => {},
276 }279 }
lib/compiler/Maker/Step/Run.zig+504-113
...@@ -73,18 +73,24 @@ pub fn make(...@@ -73,18 +73,24 @@ pub fn make(
73 man.hash.add(conf_run.flags.color);73 man.hash.add(conf_run.flags.color);
74 man.hash.add(conf_run.flags.disable_zig_progress);74 man.hash.add(conf_run.flags.disable_zig_progress);
7575
76 var any_dep_files = false;76 var any_discovered_inputs = switch (conf_run.flags.stdio) {
77 .infer_from_args, .inherit, .check, .zig_test => false,
78 .protocol => true,
79 };
77 var any_output_args = false;80 var any_output_args = false;
78 var any_cli_positionals = false;81 var any_cli_positionals = false;
7982
80 for (conf_run.args.slice) |arg_index| {83 for (switch (conf_run.flags.stdio) {
84 .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice,
85 .protocol => conf_run.args.slice[0..1], // arguments communicated over protocol
86 }) |arg_index| {
81 const arg = arg_index.get(conf);87 const arg = arg_index.get(conf);
82 try argv_list.ensureUnusedCapacity(gpa, 1);88 try argv_list.ensureUnusedCapacity(gpa, 1);
83 switch (arg.flags.tag) {89 switch (arg.flags.tag) {
84 .string => {90 .string => {
85 const prefix = arg.prefix.value.?.slice(conf);91 const string = arg.prefix.value.?.slice(conf);
86 argv_list.appendAssumeCapacity(prefix);92 argv_list.appendAssumeCapacity(string);
87 man.hash.addBytesZ(prefix);93 man.hash.addBytesZ(string);
88 },94 },
89 .path_file => {95 .path_file => {
90 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";96 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
...@@ -118,14 +124,14 @@ pub fn make(...@@ -118,14 +124,14 @@ pub fn make(
118 result.writer.writeAll(prefix) catch return error.OutOfMemory;124 result.writer.writeAll(prefix) catch return error.OutOfMemory;
119125
120 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|126 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
121 return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err });127 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
122 defer file.close(io);128 defer file.close(io);
123129
124 var file_reader = file.reader(io, &.{});130 var file_reader = file.reader(io, &.{});
125 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {131 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
126 error.ReadFailed => switch (file_reader.err.?) {132 error.ReadFailed => switch (file_reader.err.?) {
127 error.Canceled => |e| return e,133 error.Canceled => |e| return e,
128 else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }),134 else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }),
129 },135 },
130 error.WriteFailed => return error.OutOfMemory,136 error.WriteFailed => return error.OutOfMemory,
131 };137 };
...@@ -133,8 +139,8 @@ pub fn make(...@@ -133,8 +139,8 @@ pub fn make(
133139
134 argv_list.appendAssumeCapacity(result.written());140 argv_list.appendAssumeCapacity(result.written());
135 man.hash.addBytesZ(prefix);141 man.hash.addBytesZ(prefix);
136 man.hash.addBytesZ(suffix);
137 _ = try man.addInputPath(file_path, .{});142 _ = try man.addInputPath(file_path, .{});
143 man.hash.addBytesZ(suffix);
138 },144 },
139 .artifact => {145 .artifact => {
140 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";146 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
...@@ -168,7 +174,7 @@ pub fn make(...@@ -168,7 +174,7 @@ pub fn make(
168 man.hash.addBytesZ(suffix);174 man.hash.addBytesZ(suffix);
169 man.hash.add(arg.flags.dep_file);175 man.hash.add(arg.flags.dep_file);
170176
171 any_dep_files = any_dep_files or arg.flags.dep_file;177 if (arg.flags.dep_file) any_discovered_inputs = true;
172 any_output_args = true;178 any_output_args = true;
173179
174 // Add a placeholder into the argument list because we need the180 // Add a placeholder into the argument list because we need the
...@@ -176,9 +182,10 @@ pub fn make(...@@ -176,9 +182,10 @@ pub fn make(
176 // object directory is computed.182 // object directory is computed.
177 try output_placeholders.append(gpa, .{183 try output_placeholders.append(gpa, .{
178 .index = @intCast(argv_list.items.len),184 .index = @intCast(argv_list.items.len),
185 .offset = 0,
179 .arg_index = arg_index,186 .arg_index = arg_index,
180 });187 });
181 argv_list.items.len += 1;188 _ = argv_list.addOneAssumeCapacity();
182 },189 },
183 .passthru => {190 .passthru => {
184 any_cli_positionals = true;191 any_cli_positionals = true;
...@@ -195,14 +202,285 @@ pub fn make(...@@ -195,14 +202,285 @@ pub fn make(
195 }202 }
196 }203 }
197204
198 man.hash.add(conf_run.flags.test_runner_mode);205 var owned_dirs: std.bit_set.Dynamic = .{};
199 if (conf_run.flags.test_runner_mode) {206 var inherit_dirs: std.ArrayList(Io.Dir) = .empty;
200 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);207 var owned_files: std.bit_set.Dynamic = .{};
208 var inherit_files: std.ArrayList(Io.File) = .empty;
209 var protocol_args: std.ArrayList(u8) = .empty;
210 defer {
211 for (inherit_dirs.items, 0..) |inherit_dir, inherit_dir_index|
212 if (owned_dirs.isSet(inherit_dir_index)) inherit_dir.close(io);
213 for (inherit_files.items, 0..) |inherit_file, inherit_file_index|
214 if (owned_files.isSet(inherit_file_index)) inherit_file.close(io);
215 owned_dirs.deinit(gpa);
216 inherit_dirs.deinit(gpa);
217 owned_files.deinit(gpa);
218 inherit_files.deinit(gpa);
219 protocol_args.deinit(gpa);
220 }
221 switch (conf_run.flags.stdio) {
222 .infer_from_args, .inherit, .check => {},
223 .zig_test => {
224 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
225
226 try argv_list.ensureUnusedCapacity(gpa, 3);
227 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
228 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
229 argv_list.appendAssumeCapacity("--listen=-");
230 },
231 .protocol => for (conf_run.args.slice[1..]) |arg_index| {
232 const arg = arg_index.get(conf);
233 switch (arg.flags.tag) {
234 .string => {
235 const string = arg.prefix.value.?.slice(conf);
236
237 man.hash.addBytesZ(string);
238
239 try protocol_args.ensureUnusedCapacity(gpa, 1 + string.len + 1);
240 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string));
241 protocol_args.appendSliceAssumeCapacity(string);
242 protocol_args.appendAssumeCapacity(0);
243 },
244 .path_file => {
245 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
246 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
247 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
248
249 try owned_files.resize(gpa, owned_files.bit_length + 1, true);
250 try inherit_files.ensureUnusedCapacity(gpa, 1);
251 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
252 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
253 inherit_files.appendAssumeCapacity(file);
254
255 man.hash.addBytesZ(prefix);
256 _ = try man.addInputPath(file_path, .{});
257 man.hash.addBytesZ(suffix);
258
259 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
260 1 + @sizeOf(Io.File.Handle) +
261 1 + suffix.len + 1);
262 if (prefix.len > 0) {
263 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
264 protocol_args.appendSliceAssumeCapacity(prefix);
265 protocol_args.appendAssumeCapacity(0);
266 }
267 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file));
268 const file_handle: *align(1) Io.File.Handle =
269 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)));
270 file_handle.* = file.handle;
271 if (suffix.len > 0) {
272 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
273 protocol_args.appendSliceAssumeCapacity(prefix);
274 protocol_args.appendAssumeCapacity(0);
275 }
276 },
277 .path_directory => {
278 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
279 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
280 const dir_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
281
282 const has_sub_path = dir_path.sub_path.len > 0;
283 try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, has_sub_path);
284 try inherit_dirs.ensureUnusedCapacity(gpa, 1);
285 const dir = if (has_sub_path)
286 dir_path.root_dir.handle.openDir(io, dir_path.sub_path, .{
287 .iterate = true,
288 }) catch |err| return step.fail(maker, "unable to open input dir {qf}: {t}", .{ dir_path, err })
289 else
290 dir_path.root_dir.handle;
291 inherit_dirs.appendAssumeCapacity(dir);
292
293 man.hash.addBytesZ(prefix);
294 man.hash.addOptionalBytes(dir_path.root_dir.path);
295 man.hash.addBytes(dir_path.sub_path);
296 man.hash.addBytesZ(suffix);
297
298 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
299 1 + @sizeOf(Io.Dir.Handle) +
300 1 + suffix.len + 1);
301 if (prefix.len > 0) {
302 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
303 protocol_args.appendSliceAssumeCapacity(prefix);
304 protocol_args.appendAssumeCapacity(0);
305 }
306 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_dir));
307 const dir_handle: *align(1) Io.Dir.Handle =
308 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle)));
309 dir_handle.* = dir.handle;
310 if (suffix.len > 0) {
311 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
312 protocol_args.appendSliceAssumeCapacity(prefix);
313 protocol_args.appendAssumeCapacity(0);
314 }
315 },
316 .file_content => {
317 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
318 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
319 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
320
321 try owned_files.resize(gpa, owned_files.bit_length + 1, true);
322 try inherit_files.ensureUnusedCapacity(gpa, 1);
323 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
324 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
325 inherit_files.appendAssumeCapacity(file);
326
327 man.hash.addBytesZ(prefix);
328 _ = try man.addInputPath(file_path, .{});
329 man.hash.addBytesZ(suffix);
330
331 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
332 1 + @sizeOf(Io.File.Handle) +
333 1 + suffix.len + 1);
334 if (prefix.len > 0) {
335 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
336 protocol_args.appendSliceAssumeCapacity(prefix);
337 protocol_args.appendAssumeCapacity(0);
338 }
339 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file_content));
340 const file_handle: *align(1) Io.File.Handle =
341 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)));
342 file_handle.* = file.handle;
343 if (suffix.len > 0) {
344 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
345 protocol_args.appendSliceAssumeCapacity(prefix);
346 protocol_args.appendAssumeCapacity(0);
347 }
348 },
349 .artifact => {
350 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
351 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
352 const producer_index = arg.producer.value.?;
353 const producer_step = producer_index.ptr(conf);
354 const producer = producer_step.extended.get(conf.extra).compile;
355 const producer_make_comp_step = maker.stepByIndex(producer_index);
356 const producer_make_comp = &producer_make_comp_step.extended.compile;
357
358 const file_path = producer_make_comp.installed_path orelse
359 maker.generatedPath(producer.generated_bin.value.?);
360
361 try owned_files.resize(gpa, owned_files.bit_length + 1, true);
362 try inherit_files.ensureUnusedCapacity(gpa, 1);
363 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
364 return step.fail(maker, "unable to open input artifact {qf}: {t}", .{ file_path, err });
365 inherit_files.appendAssumeCapacity(file);
366
367 man.hash.addBytesZ(prefix);
368 _ = try man.addInputPath(file_path, .{});
369 man.hash.addBytesZ(suffix);
370
371 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
372 1 + @sizeOf(Io.File.Handle) +
373 1 + suffix.len + 1);
374 if (prefix.len > 0) {
375 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
376 protocol_args.appendSliceAssumeCapacity(prefix);
377 protocol_args.appendAssumeCapacity(0);
378 }
379 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file));
380 const file_handle: *align(1) Io.File.Handle =
381 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)));
382 file_handle.* = file.handle;
383 if (suffix.len > 0) {
384 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
385 protocol_args.appendSliceAssumeCapacity(prefix);
386 protocol_args.appendAssumeCapacity(0);
387 }
388 },
389 .output_file => {
390 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
391 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
392 const basename = arg.basename.value.?.slice(conf);
393
394 man.hash.addBytesZ(prefix);
395 man.hash.addBytesZ(basename);
396 man.hash.addBytesZ(suffix);
397 assert(!arg.flags.dep_file);
398
399 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
400 1 + @sizeOf(Io.File.Handle) +
401 1 + suffix.len + 1);
402 if (prefix.len > 0) {
403 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
404 protocol_args.appendSliceAssumeCapacity(prefix);
405 protocol_args.appendAssumeCapacity(0);
406 }
407 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_file));
408
409 // Add a placeholder into the argument list because we need the
410 // manifest hash to be updated with all arguments before the
411 // object directory is computed.
412 try owned_files.resize(gpa, owned_files.bit_length + 1, false);
413 try inherit_files.ensureUnusedCapacity(gpa, 1);
414 try output_placeholders.append(gpa, .{
415 .index = @intCast(inherit_files.items.len),
416 .offset = @intCast(protocol_args.items.len),
417 .arg_index = arg_index,
418 });
419 _ = inherit_files.addOneAssumeCapacity();
420
421 _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle));
422 if (suffix.len > 0) {
423 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
424 protocol_args.appendSliceAssumeCapacity(prefix);
425 protocol_args.appendAssumeCapacity(0);
426 }
427 },
428 .output_directory => {
429 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
430 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
431 const basename = arg.basename.value.?.slice(conf);
432
433 man.hash.addBytesZ(prefix);
434 man.hash.addBytesZ(basename);
435 man.hash.addBytesZ(suffix);
436 assert(!arg.flags.dep_file);
437
438 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
439 1 + @sizeOf(Io.Dir.Handle) +
440 1 + suffix.len + 1);
441 if (prefix.len > 0) {
442 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
443 protocol_args.appendSliceAssumeCapacity(prefix);
444 protocol_args.appendAssumeCapacity(0);
445 }
446 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_dir));
447
448 // Add a placeholder into the argument list because we need the
449 // manifest hash to be updated with all arguments before the
450 // object directory is computed.
451 try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, false);
452 try inherit_dirs.ensureUnusedCapacity(gpa, 1);
453 try output_placeholders.append(gpa, .{
454 .index = @intCast(inherit_dirs.items.len),
455 .offset = @intCast(protocol_args.items.len),
456 .arg_index = arg_index,
457 });
458 _ = inherit_dirs.addOneAssumeCapacity();
201459
202 try argv_list.ensureUnusedCapacity(gpa, 3);460 _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle));
203 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));461 if (suffix.len > 0) {
204 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));462 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
205 argv_list.appendAssumeCapacity("--listen=-");463 protocol_args.appendSliceAssumeCapacity(prefix);
464 protocol_args.appendAssumeCapacity(0);
465 }
466 },
467 .passthru => if (maker.run_args) |run_args| {
468 man.hash.addListOfBytes(run_args);
469
470 for (run_args) |run_arg| {
471 try protocol_args.ensureUnusedCapacity(gpa, 1 + run_arg.len + 1);
472 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string));
473 protocol_args.appendSliceAssumeCapacity(run_arg);
474 protocol_args.appendAssumeCapacity(0);
475 }
476 },
477 .enable_darling => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value),
478 .enable_qemu => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value),
479 .enable_rosetta => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value),
480 .enable_wasmtime => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value),
481 .enable_wine => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),
482 }
483 },
206 }484 }
207485
208 switch (conf_run.stdin.u) {486 switch (conf_run.stdin.u) {
...@@ -227,7 +505,7 @@ pub fn make(...@@ -227,7 +505,7 @@ pub fn make(
227 }505 }
228506
229 switch (conf_run.flags.stdio) {507 switch (conf_run.flags.stdio) {
230 .infer_from_args, .inherit, .zig_test => {},508 .infer_from_args, .inherit, .zig_test, .protocol => {},
231 .check => {509 .check => {
232 man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else "");510 man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else "");
233 man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else "");511 man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else "");
...@@ -256,7 +534,7 @@ pub fn make(...@@ -256,7 +534,7 @@ pub fn make(
256 conf_run.captured_stdout.value == null and534 conf_run.captured_stdout.value == null and
257 conf_run.captured_stderr.value == null,535 conf_run.captured_stderr.value == null,
258 .inherit => true,536 .inherit => true,
259 .check, .zig_test => false,537 .check, .zig_test, .protocol => false,
260 };538 };
261539
262 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {540 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {
...@@ -268,13 +546,22 @@ pub fn make(...@@ -268,13 +546,22 @@ pub fn make(
268 return;546 return;
269 }547 }
270548
271 if (!any_dep_files) {549 if (!any_discovered_inputs) {
272 // We already know the final output paths; use them directly.550 // We already know the final output paths; use them directly.
273 const digest = if (has_side_effects) man.hash.final() else man.missDigestHex();551 const digest = if (has_side_effects) man.hash.final() else man.missDigestHex();
274 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;552 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
275 try populateGeneratedStdIo(maker, &conf_run, &digest);553 try populateGeneratedStdIo(maker, &conf_run, &digest);
276 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items);554 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) {
277 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null);555 .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items },
556 .protocol => .{ .protocol = .{
557 .owned_dirs = &owned_dirs,
558 .inherit_dirs = inherit_dirs.items,
559 .owned_files = &owned_files,
560 .inherit_files = inherit_files.items,
561 .args = protocol_args.items,
562 } },
563 });
564 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, inherit_dirs.items, inherit_files.items, protocol_args.items, has_side_effects, output_dir_path, null);
278 if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man);565 if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man);
279 return;566 return;
280 }567 }
...@@ -284,8 +571,17 @@ pub fn make(...@@ -284,8 +571,17 @@ pub fn make(
284 io.random(@ptrCast(&rand_int));571 io.random(@ptrCast(&rand_int));
285 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);572 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
286573
287 try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, argv_list.items);574 try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) {
288 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null);575 .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items },
576 .protocol => .{ .protocol = .{
577 .owned_dirs = &owned_dirs,
578 .inherit_dirs = inherit_dirs.items,
579 .owned_files = &owned_files,
580 .inherit_files = inherit_files.items,
581 .args = protocol_args.items,
582 } },
583 });
584 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, inherit_dirs.items, inherit_files.items, protocol_args.items, has_side_effects, tmp_dir_path, null);
289585
290 for (output_placeholders.items) |placeholder| {586 for (output_placeholders.items) |placeholder| {
291 const arg = placeholder.arg_index.get(conf);587 const arg = placeholder.arg_index.get(conf);
...@@ -296,10 +592,10 @@ pub fn make(...@@ -296,10 +592,10 @@ pub fn make(
296 var diagnostic: Cache.DepTokenizer.Token = undefined;592 var diagnostic: Cache.DepTokenizer.Token = undefined;
297 man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) {593 man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) {
298 error.OutOfMemory, error.Canceled => |e| return e,594 error.OutOfMemory, error.Canceled => |e| return e,
299 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{595 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{
300 generated_path, diagnostic,596 generated_path, diagnostic,
301 }),597 }),
302 else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{598 else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{
303 generated_path, e,599 generated_path, e,
304 }),600 }),
305 };601 };
...@@ -307,13 +603,13 @@ pub fn make(...@@ -307,13 +603,13 @@ pub fn make(
307 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;603 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;
308 man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) {604 man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) {
309 error.OutOfMemory, error.Canceled => |e| return e,605 error.OutOfMemory, error.Canceled => |e| return e,
310 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{606 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{
311 generated_path, diagnostic.dep_tokenizer,607 generated_path, diagnostic.dep_tokenizer,
312 }),608 }),
313 error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {f} to cache: {f}", .{609 error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {qf} to cache: {f}", .{
314 generated_path, diagnostic.add_discovered_path,610 generated_path, diagnostic.add_discovered_path,
315 }),611 }),
316 else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{612 else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{
317 generated_path, e,613 generated_path, e,
318 }),614 }),
319 };615 };
...@@ -342,7 +638,7 @@ pub fn make(...@@ -342,7 +638,7 @@ pub fn make(
342 ) catch |err| switch (err) {638 ) catch |err| switch (err) {
343 error.DirNotEmpty => {639 error.DirNotEmpty => {
344 dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err|640 dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err|
345 return step.fail(maker, "failed to remove tree {f}: {t}", .{ dst_path, del_err });641 return step.fail(maker, "failed to remove tree {qf}: {t}", .{ dst_path, del_err });
346642
347 Dir.rename(643 Dir.rename(
348 tmp_path.root_dir.handle,644 tmp_path.root_dir.handle,
...@@ -350,11 +646,11 @@ pub fn make(...@@ -350,11 +646,11 @@ pub fn make(
350 dst_path.root_dir.handle,646 dst_path.root_dir.handle,
351 dst_path.sub_path,647 dst_path.sub_path,
352 io,648 io,
353 ) catch |retry_err| return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{649 ) catch |retry_err| return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{
354 tmp_path, dst_path, retry_err,650 tmp_path, dst_path, retry_err,
355 });651 });
356 },652 },
357 else => return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{653 else => return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{
358 tmp_path, dst_path, err,654 tmp_path, dst_path, err,
359 }),655 }),
360 };656 };
...@@ -386,19 +682,30 @@ fn thirdPartyToggle(...@@ -386,19 +682,30 @@ fn thirdPartyToggle(
386 enable: ?Configuration.String,682 enable: ?Configuration.String,
387 disable: ?Configuration.String,683 disable: ?Configuration.String,
388) void {684) void {
389 if (setting) {685 const string = (if (setting) enable else disable) orelse return;
390 if (enable) |string| {686 const slice = string.slice(conf);
391 const slice = string.slice(conf);687 if (man_hash) |h| h.addBytesZ(slice);
392 if (man_hash) |h| h.addBytesZ(slice);688 argv_list.appendAssumeCapacity(slice);
393 argv_list.appendAssumeCapacity(slice);689}
394 }690
395 } else {691fn thirdPartyToggleProtocol(
396 if (disable) |string| {692 gpa: Allocator,
397 const slice = string.slice(conf);693 man_hash: *Cache.HashHelper,
398 if (man_hash) |h| h.addBytesZ(slice);694 protocol_args: *std.ArrayList(u8),
399 argv_list.appendAssumeCapacity(slice);695 conf: *const Configuration,
400 }696 setting: bool,
401 }697 enable: ?Configuration.String,
698 disable: ?Configuration.String,
699) Allocator.Error!void {
700 const string = (if (setting) enable else disable) orelse return;
701 const slice = string.slice(conf);
702
703 man_hash.addBytesZ(slice);
704
705 try protocol_args.ensureUnusedCapacity(gpa, 1 + slice.len + 1);
706 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string));
707 protocol_args.appendSliceAssumeCapacity(slice);
708 protocol_args.appendAssumeCapacity(0);
402}709}
403710
404/// Reads stdout of a Zig test process until a termination condition is reached:711/// Reads stdout of a Zig test process until a termination condition is reached:
...@@ -412,6 +719,7 @@ fn waitZigTest(...@@ -412,6 +719,7 @@ fn waitZigTest(
412 maker: *Maker,719 maker: *Maker,
413 child: *process.Child,720 child: *process.Child,
414 progress_node: std.Progress.Node,721 progress_node: std.Progress.Node,
722 protocol_args: []const u8,
415 multi_reader: *Io.File.MultiReader,723 multi_reader: *Io.File.MultiReader,
416 opt_metadata: *?TestMetadata,724 opt_metadata: *?TestMetadata,
417 results: *Step.TestResults,725 results: *Step.TestResults,
...@@ -444,6 +752,14 @@ fn waitZigTest(...@@ -444,6 +752,14 @@ fn waitZigTest(
444 .out = &stdin_writer.interface,752 .out = &stdin_writer.interface,
445 };753 };
446754
755 if (protocol_args.len > 0) {
756 try client.serveMessageHeader(.{
757 .tag = .args,
758 .bytes_len = @intCast(protocol_args.len),
759 });
760 try client.out.writeAll(protocol_args);
761 }
762
447 if (opt_metadata.*) |*md| {763 if (opt_metadata.*) |*md| {
448 // Previous unit test process died or was killed; we're continuing where it left off764 // Previous unit test process died or was killed; we're continuing where it left off
449 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };765 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
...@@ -492,6 +808,7 @@ fn waitZigTest(...@@ -492,6 +808,7 @@ fn waitZigTest(
492 var body_r: std.Io.Reader = .fixed(body);808 var body_r: std.Io.Reader = .fixed(body);
493809
494 switch (header.tag) {810 switch (header.tag) {
811 else => {}, // ignore other messages
495 .zig_version => {812 .zig_version => {
496 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(813 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
497 maker,814 maker,
...@@ -583,7 +900,6 @@ fn waitZigTest(...@@ -583,7 +900,6 @@ fn waitZigTest(
583900
584 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };901 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
585 },902 },
586 else => {}, // ignore other messages
587 }903 }
588 }904 }
589}905}
...@@ -820,6 +1136,7 @@ const FuzzTestRunner = struct {...@@ -820,6 +1136,7 @@ const FuzzTestRunner = struct {
820 }1136 }
8211137
822 switch (header.tag) {1138 switch (header.tag) {
1139 else => {}, // ignore other messages
823 .zig_version => {1140 .zig_version => {
824 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(1141 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
825 maker,1142 maker,
...@@ -882,7 +1199,6 @@ const FuzzTestRunner = struct {...@@ -882,7 +1199,6 @@ const FuzzTestRunner = struct {
882 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));1199 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));
883 }1200 }
884 },1201 },
885 else => {}, // ignore other messages
886 }1202 }
8871203
888 instance.message.clearRetainingCapacity();1204 instance.message.clearRetainingCapacity();
...@@ -1146,6 +1462,7 @@ fn evalZigTest(...@@ -1146,6 +1462,7 @@ fn evalZigTest(
1146 maker: *Maker,1462 maker: *Maker,
1147 progress_node: std.Progress.Node,1463 progress_node: std.Progress.Node,
1148 spawn_options: process.SpawnOptions,1464 spawn_options: process.SpawnOptions,
1465 protocol_args: []const u8,
1149 fuzz_context: ?FuzzContext,1466 fuzz_context: ?FuzzContext,
1150) !void {1467) !void {
1151 if (fuzz_context != null) {1468 if (fuzz_context != null) {
...@@ -1194,6 +1511,7 @@ fn evalZigTest(...@@ -1194,6 +1511,7 @@ fn evalZigTest(
1194 maker,1511 maker,
1195 &child,1512 &child,
1196 progress_node,1513 progress_node,
1514 protocol_args,
1197 &multi_reader,1515 &multi_reader,
1198 &test_metadata,1516 &test_metadata,
1199 &test_results,1517 &test_results,
...@@ -1402,7 +1720,7 @@ fn evalGeneric(...@@ -1402,7 +1720,7 @@ fn evalGeneric(
1402 var write_buffer: [1024]u8 = undefined;1720 var write_buffer: [1024]u8 = undefined;
1403 var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer);1721 var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer);
1404 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {1722 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1405 error.ReadFailed => return step.fail(maker, "failed to read from {f}: {t}", .{1723 error.ReadFailed => return step.fail(maker, "failed to read from {qf}: {t}", .{
1406 path, file_reader.err.?,1724 path, file_reader.err.?,
1407 }),1725 }),
1408 error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{1726 error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{
...@@ -1490,6 +1808,7 @@ fn evalGeneric(...@@ -1490,6 +1808,7 @@ fn evalGeneric(
14901808
1491const IndexedOutput = struct {1809const IndexedOutput = struct {
1492 index: u32,1810 index: u32,
1811 offset: u32,
1493 arg_index: Configuration.Step.Run.Arg.Index,1812 arg_index: Configuration.Step.Run.Arg.Index,
1494};1813};
14951814
...@@ -1516,13 +1835,16 @@ pub fn rerunInFuzzMode(...@@ -1516,13 +1835,16 @@ pub fn rerunInFuzzMode(
1516 var argv_list: std.ArrayList([]const u8) = .empty;1835 var argv_list: std.ArrayList([]const u8) = .empty;
1517 defer argv_list.deinit(gpa);1836 defer argv_list.deinit(gpa);
15181837
1519 for (conf_run.args.slice) |arg_index| {1838 for (switch (conf_run.flags.stdio) {
1839 .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice,
1840 .protocol => @panic("protocol fuzz unimplemented"),
1841 }) |arg_index| {
1520 const arg = arg_index.get(conf);1842 const arg = arg_index.get(conf);
1521 try argv_list.ensureUnusedCapacity(gpa, 1);1843 try argv_list.ensureUnusedCapacity(gpa, 1);
1522 switch (arg.flags.tag) {1844 switch (arg.flags.tag) {
1523 .string => {1845 .string => {
1524 const prefix = arg.prefix.value.?.slice(conf);1846 const string = arg.prefix.value.?.slice(conf);
1525 argv_list.appendAssumeCapacity(prefix);1847 argv_list.appendAssumeCapacity(string);
1526 },1848 },
1527 .path_file => {1849 .path_file => {
1528 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";1850 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
...@@ -1550,14 +1872,14 @@ pub fn rerunInFuzzMode(...@@ -1550,14 +1872,14 @@ pub fn rerunInFuzzMode(
1550 result.writer.writeAll(prefix) catch return error.OutOfMemory;1872 result.writer.writeAll(prefix) catch return error.OutOfMemory;
15511873
1552 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|1874 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
1553 return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err });1875 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
1554 defer file.close(io);1876 defer file.close(io);
15551877
1556 var file_reader = file.reader(io, &.{});1878 var file_reader = file.reader(io, &.{});
1557 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {1879 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
1558 error.ReadFailed => switch (file_reader.err.?) {1880 error.ReadFailed => switch (file_reader.err.?) {
1559 error.Canceled => |e| return e,1881 error.Canceled => |e| return e,
1560 else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }),1882 else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }),
1561 },1883 },
1562 error.WriteFailed => return error.OutOfMemory,1884 error.WriteFailed => return error.OutOfMemory,
1563 };1885 };
...@@ -1591,14 +1913,17 @@ pub fn rerunInFuzzMode(...@@ -1591,14 +1913,17 @@ pub fn rerunInFuzzMode(
1591 .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),1913 .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),
1592 }1914 }
1593 }1915 }
15941916 switch (conf_run.flags.stdio) {
1595 if (conf_run.flags.test_runner_mode) {1917 .infer_from_args, .inherit, .check => {},
1596 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);1918 .zig_test => {
15971919 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
1598 try argv_list.ensureUnusedCapacity(gpa, 3);1920
1599 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));1921 try argv_list.ensureUnusedCapacity(gpa, 3);
1600 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));1922 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
1601 argv_list.appendAssumeCapacity("--listen=-");1923 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
1924 argv_list.appendAssumeCapacity("--listen=-");
1925 },
1926 .protocol => unreachable,
1602 }1927 }
16031928
1604 step.clearFailedCommand(gpa);1929 step.clearFailedCommand(gpa);
...@@ -1607,7 +1932,7 @@ pub fn rerunInFuzzMode(...@@ -1607,7 +1932,7 @@ pub fn rerunInFuzzMode(
1607 var rand_int: u64 = undefined;1932 var rand_int: u64 = undefined;
1608 io.random(@ptrCast(&rand_int));1933 io.random(@ptrCast(&rand_int));
1609 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);1934 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
1610 try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{1935 try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, &.{}, &.{}, &.{}, has_side_effects, tmp_dir_path, .{
1611 .fuzz = fuzz,1936 .fuzz = fuzz,
1612 });1937 });
1613}1938}
...@@ -1633,7 +1958,16 @@ fn populateGeneratedPathsCreateDirs(...@@ -1633,7 +1958,16 @@ fn populateGeneratedPathsCreateDirs(
1633 maker: *Maker,1958 maker: *Maker,
1634 output_dir_path: []const u8,1959 output_dir_path: []const u8,
1635 output_placeholders: []const IndexedOutput,1960 output_placeholders: []const IndexedOutput,
1636 argv: [][]const u8,1961 populate: union(enum) {
1962 argv: [][]const u8,
1963 protocol: struct {
1964 owned_dirs: *std.bit_set.Dynamic,
1965 inherit_dirs: []Io.Dir,
1966 owned_files: *std.bit_set.Dynamic,
1967 inherit_files: []Io.File,
1968 args: []u8,
1969 },
1970 },
1637) !void {1971) !void {
1638 const step = maker.stepByIndex(run_index);1972 const step = maker.stepByIndex(run_index);
1639 const conf = &maker.scanned_config.configuration;1973 const conf = &maker.scanned_config.configuration;
...@@ -1653,16 +1987,52 @@ fn populateGeneratedPathsCreateDirs(...@@ -1653,16 +1987,52 @@ fn populateGeneratedPathsCreateDirs(
1653 const create_path: Path = .{1987 const create_path: Path = .{
1654 .root_dir = cache_root,1988 .root_dir = cache_root,
1655 .sub_path = switch (arg.flags.tag) {1989 .sub_path = switch (arg.flags.tag) {
1990 else => unreachable,
1656 .output_file => Dir.path.dirname(generated_path.sub_path).?,1991 .output_file => Dir.path.dirname(generated_path.sub_path).?,
1657 .output_directory => generated_path.sub_path,1992 .output_directory => generated_path.sub_path,
1658 else => unreachable,
1659 },1993 },
1660 };1994 };
1661 create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err|1995 create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err|
1662 return step.fail(maker, "unable to make path {f}: {t}", .{ create_path, err });1996 return step.fail(maker, "unable to make path {qf}: {t}", .{ create_path, err });
16631997
1664 const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute);1998 switch (populate) {
1665 argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix });1999 .argv => |argv| {
2000 const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute);
2001 argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix });
2002 },
2003 .protocol => |protocol| switch (arg.flags.tag) {
2004 else => unreachable,
2005 .output_file => {
2006 const file = generated_path.root_dir.handle.createFile(
2007 io,
2008 generated_path.sub_path,
2009 .{ .read = true },
2010 ) catch |err| return step.fail(maker, "unable to create output file {qf}: {t}", .{
2011 generated_path, err,
2012 });
2013 protocol.inherit_files[placeholder.index] = file;
2014 protocol.owned_files.set(placeholder.index);
2015 const file_handle: *align(1) Io.File.Handle =
2016 @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.File.Handle)]);
2017 file_handle.* = file.handle;
2018 },
2019 .output_directory => {
2020 assert(generated_path.sub_path.len > 0);
2021 const dir = generated_path.root_dir.handle.openDir(
2022 io,
2023 generated_path.sub_path,
2024 .{ .iterate = true },
2025 ) catch |err| return step.fail(maker, "unable to open output dir {qf}: {t}", .{
2026 generated_path, err,
2027 });
2028 protocol.inherit_dirs[placeholder.index] = dir;
2029 protocol.owned_dirs.set(placeholder.index);
2030 const dir_handle: *align(1) Io.Dir.Handle =
2031 @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.Dir.Handle)]);
2032 dir_handle.* = dir.handle;
2033 },
2034 },
2035 }
1666 }2036 }
1667}2037}
16682038
...@@ -1711,6 +2081,9 @@ fn runCommand(...@@ -1711,6 +2081,9 @@ fn runCommand(
1711 maker: *Maker,2081 maker: *Maker,
1712 progress_node: std.Progress.Node,2082 progress_node: std.Progress.Node,
1713 argv: []const []const u8,2083 argv: []const []const u8,
2084 inherit_dirs: []const Io.Dir,
2085 inherit_files: []const Io.File,
2086 protocol_args: []const u8,
1714 has_side_effects: bool,2087 has_side_effects: bool,
1715 output_dir_path: []const u8,2088 output_dir_path: []const u8,
1716 fuzz_context: ?FuzzContext,2089 fuzz_context: ?FuzzContext,
...@@ -1777,10 +2150,14 @@ fn runCommand(...@@ -1777,10 +2150,14 @@ fn runCommand(
1777 progress_node,2150 progress_node,
1778 argv,2151 argv,
1779 &environ_map,2152 &environ_map,
2153 inherit_dirs,
2154 inherit_files,
2155 protocol_args,
1780 has_side_effects,2156 has_side_effects,
1781 fuzz_context,2157 fuzz_context,
1782 ) catch |err| term: {2158 ) catch |err| term: {
1783 switch (err) {2159 switch (err) {
2160 else => {},
1784 error.InvalidExe, // cpu arch mismatch2161 error.InvalidExe, // cpu arch mismatch
1785 error.FileNotFound, // can happen with a wrong dynamic linker path2162 error.FileNotFound, // can happen with a wrong dynamic linker path
1786 => interpret: {2163 => interpret: {
...@@ -1928,6 +2305,9 @@ fn runCommand(...@@ -1928,6 +2305,9 @@ fn runCommand(
1928 progress_node,2305 progress_node,
1929 interp_argv.items,2306 interp_argv.items,
1930 &environ_map,2307 &environ_map,
2308 inherit_dirs,
2309 inherit_files,
2310 protocol_args,
1931 has_side_effects,2311 has_side_effects,
1932 fuzz_context,2312 fuzz_context,
1933 ) catch |e| {2313 ) catch |e| {
...@@ -1937,21 +2317,21 @@ fn runCommand(...@@ -1937,21 +2317,21 @@ fn runCommand(
1937 };2317 };
1938 },2318 },
1939 error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e,2319 error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e,
1940 else => {},
1941 }2320 }
1942 return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });2321 return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });
1943 };2322 };
19442323
1945 const generic_result = opt_generic_result orelse {2324 const generic_result = switch (conf_run.flags.stdio) {
1946 assert(conf_run.flags.stdio == .zig_test);2325 .infer_from_args, .inherit, .check => opt_generic_result.?,
1947 // Specific errors have already been reported, and test results are populated. All we need2326 .zig_test, .protocol => {
1948 // to do is report step failure if any test failed.2327 assert(opt_generic_result == null);
1949 if (!step.test_results.isSuccess()) return error.MakeFailed;2328 // Specific errors have already been reported, and test results are populated. All we need
1950 return;2329 // to do is report step failure if any test failed.
2330 if (!step.test_results.isSuccess()) return error.MakeFailed;
2331 return;
2332 },
1951 };2333 };
1952
1953 assert(fuzz_context == null);2334 assert(fuzz_context == null);
1954 assert(conf_run.flags.stdio != .zig_test);
19552335
1956 // Capture stdout and stderr to GeneratedFile objects.2336 // Capture stdout and stderr to GeneratedFile objects.
1957 const Stream = struct {2337 const Stream = struct {
...@@ -1978,7 +2358,7 @@ fn runCommand(...@@ -1978,7 +2358,7 @@ fn runCommand(
19782358
1979 const sub_path_parent = output_path.dirname().?;2359 const sub_path_parent = output_path.dirname().?;
1980 sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err|2360 sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err|
1981 return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err });2361 return step.fail(maker, "unable to make path {qf}: {t}", .{ sub_path_parent, err });
19822362
1983 const data = switch (stream.trim_whitespace) {2363 const data = switch (stream.trim_whitespace) {
1984 .none => stream.bytes.?,2364 .none => stream.bytes.?,
...@@ -1989,12 +2369,12 @@ fn runCommand(...@@ -1989,12 +2369,12 @@ fn runCommand(
1989 output_path.root_dir.handle.writeFile(io, .{2369 output_path.root_dir.handle.writeFile(io, .{
1990 .sub_path = output_path.sub_path,2370 .sub_path = output_path.sub_path,
1991 .data = data,2371 .data = data,
1992 }) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err });2372 }) catch |err| return step.fail(maker, "unable to write file {qf}: {t}", .{ output_path, err });
1993 }2373 }
1994 }2374 }
19952375
1996 switch (conf_run.flags.stdio) {2376 switch (conf_run.flags.stdio) {
1997 .zig_test => unreachable,2377 .zig_test, .protocol => unreachable,
1998 .check => {2378 .check => {
1999 if (conf_run.expect_stderr_exact.value) |bytes| {2379 if (conf_run.expect_stderr_exact.value) |bytes| {
2000 const expected_bytes = bytes.slice(conf);2380 const expected_bytes = bytes.slice(conf);
...@@ -2186,6 +2566,9 @@ fn spawnChildAndCollect(...@@ -2186,6 +2566,9 @@ fn spawnChildAndCollect(
2186 progress_node: std.Progress.Node,2566 progress_node: std.Progress.Node,
2187 argv: []const []const u8,2567 argv: []const []const u8,
2188 environ_map: *EnvMap,2568 environ_map: *EnvMap,
2569 inherit_dirs: []const Io.Dir,
2570 inherit_files: []const Io.File,
2571 protocol_args: []const u8,
2189 has_side_effects: bool,2572 has_side_effects: bool,
2190 fuzz_context: ?FuzzContext,2573 fuzz_context: ?FuzzContext,
2191) !?EvalGenericResult {2574) !?EvalGenericResult {
...@@ -2199,7 +2582,10 @@ fn spawnChildAndCollect(...@@ -2199,7 +2582,10 @@ fn spawnChildAndCollect(
21992582
2200 if (fuzz_context != null) {2583 if (fuzz_context != null) {
2201 assert(!has_side_effects);2584 assert(!has_side_effects);
2202 assert(conf_run.flags.stdio == .zig_test);2585 switch (conf_run.flags.stdio) {
2586 .infer_from_args, .inherit, .check => unreachable,
2587 .zig_test, .protocol => {},
2588 }
2203 }2589 }
22042590
2205 const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|2591 const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|
...@@ -2237,20 +2623,22 @@ fn spawnChildAndCollect(...@@ -2237,20 +2623,22 @@ fn spawnChildAndCollect(
2237 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,2623 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
2238 .inherit => .inherit,2624 .inherit => .inherit,
2239 .check => .ignore,2625 .check => .ignore,
2240 .zig_test => .pipe,2626 .zig_test, .protocol => .pipe,
2241 },2627 },
2242 .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) {2628 .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) {
2243 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,2629 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
2244 .inherit => .inherit,2630 .inherit => .inherit,
2245 .check => if (checksContainStdout(&conf_run)) .pipe else .ignore,2631 .check => if (checksContainStdout(&conf_run)) .pipe else .ignore,
2246 .zig_test => .pipe,2632 .zig_test, .protocol => .pipe,
2247 },2633 },
2248 .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) {2634 .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) {
2249 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe,2635 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe,
2250 .inherit => if (maker.protocol_server == null) .inherit else .pipe,2636 .inherit => if (maker.protocol_server == null) .inherit else .pipe,
2251 .check => .pipe,2637 .check => .pipe,
2252 .zig_test => .pipe,2638 .zig_test, .protocol => .pipe,
2253 },2639 },
2640 .inherit_dirs = inherit_dirs,
2641 .inherit_files = inherit_files,
2254 };2642 };
22552643
2256 if (maker.protocol_server != null) {2644 if (maker.protocol_server != null) {
...@@ -2263,35 +2651,38 @@ fn spawnChildAndCollect(...@@ -2263,35 +2651,38 @@ fn spawnChildAndCollect(
2263 assert(spawn_options.stderr != .inherit);2651 assert(spawn_options.stderr != .inherit);
2264 }2652 }
22652653
2266 if (conf_run.flags.stdio == .zig_test) {2654 switch (conf_run.flags.stdio) {
2267 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);2655 .infer_from_args, .inherit, .check => {
2268 const started: Io.Clock.Timestamp = .now(io, .awake);2656 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
2269 const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) {2657 if (!conf_run.flags.disable_zig_progress and !inherit) {
2270 error.Canceled => |e| return e,2658 spawn_options.progress_node = progress_node;
2271 else => |e| e,2659 }
2272 };2660 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
2273 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);2661 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2274 try result;2662 break :m stderr.terminal_mode;
2275 return null;2663 } else .no_color;
2276 } else {2664 defer if (inherit) io.unlockStderr();
2277 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;2665 try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode);
2278 if (!conf_run.flags.disable_zig_progress and !inherit) {2666
2279 spawn_options.progress_node = progress_node;2667 const started: Io.Clock.Timestamp = .now(io, .awake);
2280 }2668 const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) {
2281 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {2669 error.Canceled => |e| return e,
2282 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);2670 else => |e| e,
2283 break :m stderr.terminal_mode;2671 };
2284 } else .no_color;2672 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2285 defer if (inherit) io.unlockStderr();2673 return try result;
2286 try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode);2674 },
22872675 .zig_test, .protocol => {
2288 const started: Io.Clock.Timestamp = .now(io, .awake);2676 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);
2289 const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) {2677 const started: Io.Clock.Timestamp = .now(io, .awake);
2290 error.Canceled => |e| return e,2678 const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, protocol_args, fuzz_context) catch |err| switch (err) {
2291 else => |e| e,2679 error.Canceled => |e| return e,
2292 };2680 else => |e| e,
2293 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);2681 };
2294 return try result;2682 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2683 try result;
2684 return null;
2685 },
2295 }2686 }
2296}2687}
22972688
...@@ -2341,7 +2732,7 @@ fn setColorEnvironmentVariables(...@@ -2341,7 +2732,7 @@ fn setColorEnvironmentVariables(
2341 .auto => {2732 .auto => {
2342 const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) {2733 const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) {
2343 .check => checksContainStderr(conf_run),2734 .check => checksContainStderr(conf_run),
2344 .infer_from_args, .inherit, .zig_test => false,2735 .infer_from_args, .inherit, .zig_test, .protocol => false,
2345 };2736 };
2346 if (capture_stderr) {2737 if (capture_stderr) {
2347 continue :color .disable;2738 continue :color .disable;
lib/std/Build.zig+1-4
...@@ -768,9 +768,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {...@@ -768,9 +768,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
768768
769 const run_step = Step.Run.create(b, step_name);769 const run_step = Step.Run.create(b, step_name);
770 run_step.producer = exe;770 run_step.producer = exe;
771 run_step.addArtifactArg(exe);
771 if (exe.kind == .@"test") {772 if (exe.kind == .@"test") {
772 run_step.addArtifactArg(exe);
773
774 const test_server_mode: bool = s: {773 const test_server_mode: bool = s: {
775 if (exe.test_runner) |r| break :s r.mode == .server;774 if (exe.test_runner) |r| break :s r.mode == .server;
776 if (exe.use_llvm == false) {775 if (exe.use_llvm == false) {
...@@ -802,8 +801,6 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {...@@ -802,8 +801,6 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
802 // communicate failure via its exit code.801 // communicate failure via its exit code.
803 run_step.expectExitCode(0);802 run_step.expectExitCode(0);
804 }803 }
805 } else {
806 run_step.addArtifactArg(exe);
807 }804 }
808805
809 return run_step;806 return run_step;
lib/std/Build/Configuration.zig+1-2
...@@ -665,7 +665,7 @@ pub const Step = extern struct {...@@ -665,7 +665,7 @@ pub const Step = extern struct {
665 pub const Tag = enum(u2) { none, bytes, lazy_path };665 pub const Tag = enum(u2) { none, bytes, lazy_path };
666 };666 };
667 pub const TrimWhitespace = enum(u2) { none, all, leading, trailing };667 pub const TrimWhitespace = enum(u2) { none, all, leading, trailing };
668 pub const StdIo = enum(u2) { infer_from_args, inherit, check, zig_test };668 pub const StdIo = enum(u3) { infer_from_args, inherit, check, zig_test, protocol };
669669
670 pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown };670 pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown };
671671
...@@ -675,7 +675,6 @@ pub const Step = extern struct {...@@ -675,7 +675,6 @@ pub const Step = extern struct {
675 skip_foreign_checks: bool,675 skip_foreign_checks: bool,
676 failing_to_execute_foreign_is_an_error: bool,676 failing_to_execute_foreign_is_an_error: bool,
677 has_side_effects: bool,677 has_side_effects: bool,
678 test_runner_mode: bool,
679 color: Color,678 color: Color,
680 stdin: StdIn.Tag,679 stdin: StdIn.Tag,
681 stdio: StdIo,680 stdio: StdIo,
lib/std/Build/Serialize.zig+1-1
...@@ -462,13 +462,13 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi...@@ -462,13 +462,13 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi
462 .skip_foreign_checks = run.skip_foreign_checks,462 .skip_foreign_checks = run.skip_foreign_checks,
463 .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error,463 .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error,
464 .has_side_effects = run.has_side_effects,464 .has_side_effects = run.has_side_effects,
465 .test_runner_mode = run.test_runner_mode,
466 .color = run.color,465 .color = run.color,
467 .stdio = switch (run.stdio) {466 .stdio = switch (run.stdio) {
468 .infer_from_args => .infer_from_args,467 .infer_from_args => .infer_from_args,
469 .inherit => .inherit,468 .inherit => .inherit,
470 .check => .check,469 .check => .check,
471 .zig_test => .zig_test,470 .zig_test => .zig_test,
471 .protocol => .protocol,
472 },472 },
473 .stdin = switch (run.stdin) {473 .stdin = switch (run.stdin) {
474 .none => .none,474 .none => .none,
lib/std/Build/Step/Run.zig+16-7
...@@ -92,7 +92,6 @@ captured_stdout: ?*CapturedStdIo,...@@ -92,7 +92,6 @@ captured_stdout: ?*CapturedStdIo,
92captured_stderr: ?*CapturedStdIo,92captured_stderr: ?*CapturedStdIo,
9393
94has_side_effects: bool,94has_side_effects: bool,
95test_runner_mode: bool = false,
9695
97/// If this Run step was produced by a Compile step, it is tracked here.96/// If this Run step was produced by a Compile step, it is tracked here.
98producer: ?*Step.Compile,97producer: ?*Step.Compile,
...@@ -127,7 +126,10 @@ pub const StdIo = union(enum) {...@@ -127,7 +126,10 @@ pub const StdIo = union(enum) {
127 check: std.ArrayList(Check),126 check: std.ArrayList(Check),
128 /// This Run step is running a zig unit test binary and will communicate127 /// This Run step is running a zig unit test binary and will communicate
129 /// extra metadata over the IPC protocol.128 /// extra metadata over the IPC protocol.
129 /// Deprecated, use `.protocol`.
130 zig_test,130 zig_test,
131 /// This Run step is running a binary that will communicate over the IPC protocol.
132 protocol,
131133
132 pub const Check = union(enum) {134 pub const Check = union(enum) {
133 expect_stderr_exact: []const u8,135 expect_stderr_exact: []const u8,
...@@ -243,10 +245,13 @@ pub fn setName(run: *Run, name: []const u8) void {...@@ -243,10 +245,13 @@ pub fn setName(run: *Run, name: []const u8) void {
243 run.rename_step_with_output_arg = false;245 run.rename_step_with_output_arg = false;
244}246}
245247
248/// Deprecated, use `enableProtocolMode`.
246pub fn enableTestRunnerMode(run: *Run) void {249pub fn enableTestRunnerMode(run: *Run) void {
247 if (run.test_runner_mode) return;
248 run.stdio = .zig_test;250 run.stdio = .zig_test;
249 run.test_runner_mode = true;251}
252
253pub fn enableProtocolMode(run: *Run) void {
254 run.stdio = .protocol;
250}255}
251256
252pub const ArgOptions = struct {257pub const ArgOptions = struct {
...@@ -750,8 +755,10 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {...@@ -750,8 +755,10 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
750}755}
751756
752pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {757pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
753 assert(run.stdio != .inherit);758 switch (run.stdio) {
754 assert(run.stdio != .zig_test);759 .infer_from_args, .check => {},
760 .inherit, .zig_test, .protocol => unreachable,
761 }
755762
756 const b = run.step.owner;763 const b = run.step.owner;
757 const graph = b.graph;764 const graph = b.graph;
...@@ -771,8 +778,10 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa...@@ -771,8 +778,10 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa
771}778}
772779
773pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {780pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
774 assert(run.stdio != .inherit);781 switch (run.stdio) {
775 assert(run.stdio != .zig_test);782 .infer_from_args, .check => {},
783 .inherit, .zig_test, .protocol => unreachable,
784 }
776785
777 const b = run.step.owner;786 const b = run.step.owner;
778 const graph = b.graph;787 const graph = b.graph;
lib/std/Io/Reader/Limited.zig+2-4
...@@ -28,8 +28,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited {...@@ -28,8 +28,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited {
28fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {28fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
29 const l: *Limited = @fieldParentPtr("interface", r);29 const l: *Limited = @fieldParentPtr("interface", r);
30 if (l.remaining == .nothing) return error.EndOfStream;30 if (l.remaining == .nothing) return error.EndOfStream;
31 const combined_limit = limit.min(l.remaining);31 const n = try l.unlimited.stream(w, limit.min(l.remaining));
32 const n = try l.unlimited.stream(w, combined_limit);
33 l.remaining = l.remaining.subtract(n).?;32 l.remaining = l.remaining.subtract(n).?;
34 return n;33 return n;
35}34}
...@@ -53,8 +52,7 @@ test stream {...@@ -53,8 +52,7 @@ test stream {
53fn discard(r: *Reader, limit: Limit) Reader.Error!usize {52fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
54 const l: *Limited = @fieldParentPtr("interface", r);53 const l: *Limited = @fieldParentPtr("interface", r);
55 if (l.remaining == .nothing) return error.EndOfStream;54 if (l.remaining == .nothing) return error.EndOfStream;
56 const combined_limit = limit.min(l.remaining);55 const n = try l.unlimited.discard(limit.min(l.remaining));
57 const n = try l.unlimited.discard(combined_limit);
58 l.remaining = l.remaining.subtract(n).?;56 l.remaining = l.remaining.subtract(n).?;
59 return n;57 return n;
60}58}
lib/std/Io/Threaded.zig+36-31
...@@ -4462,7 +4462,7 @@ fn dirCreateFilePosix(...@@ -4462,7 +4462,7 @@ fn dirCreateFilePosix(
4462 var fl_flags: usize = fl: {4462 var fl_flags: usize = fl: {
4463 const syscall: Syscall = try .start();4463 const syscall: Syscall = try .start();
4464 while (true) {4464 while (true) {
4465 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));4465 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0));
4466 switch (posix.errno(rc)) {4466 switch (posix.errno(rc)) {
4467 .SUCCESS => {4467 .SUCCESS => {
4468 syscall.finish();4468 syscall.finish();
...@@ -5058,7 +5058,7 @@ fn dirOpenFilePosix(...@@ -5058,7 +5058,7 @@ fn dirOpenFilePosix(
5058 var fl_flags: usize = fl: {5058 var fl_flags: usize = fl: {
5059 const syscall: Syscall = try .start();5059 const syscall: Syscall = try .start();
5060 while (true) {5060 while (true) {
5061 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));5061 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0));
5062 switch (posix.errno(rc)) {5062 switch (posix.errno(rc)) {
5063 .SUCCESS => {5063 .SUCCESS => {
5064 syscall.finish();5064 syscall.finish();
...@@ -15230,16 +15230,12 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -15230,16 +15230,12 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
15230 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);15230 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
15231 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;15231 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
1523215232
15233 const prog_fileno = 3;
15234 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
15235
15236 const env_block = env_block: {15233 const env_block = env_block: {
15237 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
15238 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{15234 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
15239 .zig_progress_fd = prog_fd,15235 .zig_progress_fd = prog_pipe[1],
15240 });15236 });
15241 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{15237 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
15242 .zig_progress_fd = prog_fd,15238 .zig_progress_fd = prog_pipe[1],
15243 });15239 });
15244 };15240 };
1524515241
...@@ -15267,23 +15263,42 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -15267,23 +15263,42 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
15267 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;15263 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;
15268 const ep1 = err_pipe[1];15264 const ep1 = err_pipe[1];
1526915265
15270 setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);15266 // Must happen befor clobbering fds below.
15271 setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15272 setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15273
15274 switch (options.cwd) {15267 switch (options.cwd) {
15275 .inherit => {},15268 .inherit => {},
15276 .dir => |cwd| {15269 .dir => |cwd| fchdir(cwd.handle) catch |err| forkBail(ep1, err),
15277 fchdir(cwd.handle) catch |err| forkBail(ep1, err);15270 .path => |cwd| chdir(cwd) catch |err| forkBail(ep1, err),
15278 },
15279 .path => |cwd| {
15280 chdir(cwd) catch |err| forkBail(ep1, err);
15281 },
15282 }15271 }
1528315272
15284 // Must happen after fchdir above, the cwd file descriptor might be15273 for (options.inherit_dirs) |dir| switch (posix.errno(
15285 // equal to prog_fileno and be clobbered by this dup2 call.15274 posix.system.fcntl(dir.handle, posix.F.SETFD, @as(u32, 0)),
15286 if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err);15275 )) {
15276 .SUCCESS => {},
15277 else => |err| forkBail(ep1, posix.unexpectedErrno(err)),
15278 };
15279 for (options.inherit_files) |file| switch (posix.errno(
15280 posix.system.fcntl(file.handle, posix.F.SETFD, @as(u32, 0)),
15281 )) {
15282 .SUCCESS => {},
15283 else => |err| forkBail(ep1, posix.unexpectedErrno(err)),
15284 };
15285 if (prog_pipe[1] != -1) switch (posix.errno(
15286 posix.system.fcntl(prog_pipe[1], posix.F.SETFD, @as(u32, 0)),
15287 )) {
15288 .SUCCESS => {},
15289 else => |err| forkBail(ep1, posix.unexpectedErrno(err)),
15290 };
15291 for (
15292 [_]process.SpawnOptions.StdIo{ options.stdin, options.stdout, options.stderr },
15293 [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] },
15294 [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO },
15295 ) |stdio, pipe_fd, std_fd| switch (stdio) {
15296 .pipe => dup2(pipe_fd, std_fd) catch |err| forkBail(ep1, err),
15297 .close => closeFd(std_fd),
15298 .inherit => {},
15299 .ignore => dup2(dev_null_fd, std_fd) catch |err| forkBail(ep1, err),
15300 .file => |file| dup2(file.handle, std_fd) catch |err| forkBail(ep1, err),
15301 };
1528715302
15288 if (options.gid) |gid| {15303 if (options.gid) |gid| {
15289 switch (posix.errno(posix.system.setregid(gid, gid))) {15304 switch (posix.errno(posix.system.setregid(gid, gid))) {
...@@ -15746,16 +15761,6 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {...@@ -15746,16 +15761,6 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {
15746 if (pipe[0] != pipe[1]) closeFd(pipe[1]);15761 if (pipe[0] != pipe[1]) closeFd(pipe[1]);
15747}15762}
1574815763
15749fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
15750 switch (stdio) {
15751 .pipe => try dup2(pipe_fd, std_fileno),
15752 .close => closeFd(std_fileno),
15753 .inherit => {},
15754 .ignore => try dup2(dev_null_fd, std_fileno),
15755 .file => |file| try dup2(file.handle, std_fileno),
15756 }
15757}
15758
15759fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {15764fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15760 const t: *Threaded = @ptrCast(@alignCast(userdata));15765 const t: *Threaded = @ptrCast(@alignCast(userdata));
1576115766
lib/std/bit_set.zig+2-7
...@@ -723,13 +723,8 @@ pub const Dynamic = struct {...@@ -723,13 +723,8 @@ pub const Dynamic = struct {
723 // That slot holds the size of the true allocation, which723 // That slot holds the size of the true allocation, which
724 // is needed by Zig's allocator interface in case a shrink724 // is needed by Zig's allocator interface in case a shrink
725 // fails.725 // fails.
726726 const empty_masks_data = [_]MaskInt{ 0, undefined };
727 // Don't modify this value. Ideally it would go in const data so727 const empty_masks_ptr = @constCast(empty_masks_data[1..2]);
728 // modifications would cause a bus error, but the only way
729 // to discard a const qualifier is through intFromPtr, which
730 // cannot currently round trip at comptime.
731 var empty_masks_data = [_]MaskInt{ 0, undefined };
732 const empty_masks_ptr = empty_masks_data[1..2];
733728
734 /// Creates a bit set with no elements present.729 /// Creates a bit set with no elements present.
735 /// If bit_length is not zero, deinit must eventually be called.730 /// If bit_length is not zero, deinit must eventually be called.
lib/std/process.zig+2
...@@ -390,6 +390,8 @@ pub const SpawnOptions = struct {...@@ -390,6 +390,8 @@ pub const SpawnOptions = struct {
390 stdin: StdIo = .inherit,390 stdin: StdIo = .inherit,
391 stdout: StdIo = .inherit,391 stdout: StdIo = .inherit,
392 stderr: StdIo = .inherit,392 stderr: StdIo = .inherit,
393 inherit_dirs: []const Dir = &.{},
394 inherit_files: []const File = &.{},
393395
394 /// Set to true to obtain rusage information for the child process.396 /// Set to true to obtain rusage information for the child process.
395 /// Depending on the target platform and implementation status, the397 /// Depending on the target platform and implementation status, the
lib/std/zig/Client.zig+28
...@@ -60,6 +60,7 @@ pub const Message = struct {...@@ -60,6 +60,7 @@ pub const Message = struct {
60 start_fuzzing,60 start_fuzzing,
61 /// The message body has the same format as in Server.61 /// The message body has the same format as in Server.
62 new_fuzz_input,62 new_fuzz_input,
63 args,
6364
64 /// Asks the server to run a list of steps.65 /// Asks the server to run a list of steps.
65 /// Body is a `BuildSteps`.66 /// Body is a `BuildSteps`.
...@@ -69,6 +70,33 @@ pub const Message = struct {...@@ -69,6 +70,33 @@ pub const Message = struct {
69 _,70 _,
70 };71 };
7172
73 pub const Arg = enum(u8) {
74 /// Trailing:
75 /// * string: [:0]const u8,
76 string,
77 /// Trailing:
78 /// * string: [:0]const u8,
79 prefix,
80 /// Trailing:
81 /// * string: [:0]const u8,
82 suffix,
83 /// Trailing:
84 /// * handle: Handle,
85 input_file,
86 /// Trailing:
87 /// * handle: Handle,
88 input_dir,
89 /// Trailing:
90 /// * handle: Handle,
91 input_file_content,
92 /// Trailing:
93 /// * handle: Handle,
94 output_file,
95 /// Trailing:
96 /// * handle: Handle,
97 output_dir,
98 };
99
72 /// Trailing:100 /// Trailing:
73 /// * step_indices: [step_count]std.Build.Configuration.Step.Index,101 /// * step_indices: [step_count]std.Build.Configuration.Step.Index,
74 pub const BuildSteps = extern struct {102 pub const BuildSteps = extern struct {
test/incremental2/change_panic_handler_explicit/0-main.zig created+46
...@@ -0,0 +1,46 @@
1pub fn main() !u8 {
2 var a: u8 = undefined;
3 a = 255;
4 _ = a + 1;
5 return 1;
6}
7const no_panic = std.debug.no_panic;
8pub const panic = struct {
9 pub const call = myPanic;
10 pub fn integerOverflow() noreturn {
11 @panic("integer overflow");
12 }
13 pub const sentinelMismatch = no_panic.sentinelMismatch;
14 pub const unwrapError = no_panic.unwrapError;
15 pub const outOfBounds = no_panic.outOfBounds;
16 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
17 pub const inactiveUnionField = no_panic.inactiveUnionField;
18 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
19 pub const reachedUnreachable = no_panic.reachedUnreachable;
20 pub const unwrapNull = no_panic.unwrapNull;
21 pub const castToNull = no_panic.castToNull;
22 pub const incorrectAlignment = no_panic.incorrectAlignment;
23 pub const invalidErrorCode = no_panic.invalidErrorCode;
24 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
25 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
26 pub const shlOverflow = no_panic.shlOverflow;
27 pub const shrOverflow = no_panic.shrOverflow;
28 pub const divideByZero = no_panic.divideByZero;
29 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
30 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
31 pub const corruptSwitch = no_panic.corruptSwitch;
32 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
33 pub const invalidEnumValue = no_panic.invalidEnumValue;
34 pub const forLenMismatch = no_panic.forLenMismatch;
35 pub const copyLenMismatch = no_panic.copyLenMismatch;
36 pub const memcpyAlias = no_panic.memcpyAlias;
37 pub const noreturnReturned = no_panic.noreturnReturned;
38 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
39};
40fn myPanic(msg: []const u8, _: ?usize) noreturn {
41 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
42 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};
43 std.process.exit(0);
44}
45const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.io();
test/incremental2/change_panic_handler_explicit/1-main.zig created+46
...@@ -0,0 +1,46 @@
1pub fn main() !u8 {
2 var a: u8 = undefined;
3 a = 255;
4 _ = a + 1;
5 return 1;
6}
7const no_panic = std.debug.no_panic;
8pub const panic = struct {
9 pub const call = myPanic;
10 pub fn integerOverflow() noreturn {
11 @panic("integer overflow");
12 }
13 pub const sentinelMismatch = no_panic.sentinelMismatch;
14 pub const unwrapError = no_panic.unwrapError;
15 pub const outOfBounds = no_panic.outOfBounds;
16 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
17 pub const inactiveUnionField = no_panic.inactiveUnionField;
18 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
19 pub const reachedUnreachable = no_panic.reachedUnreachable;
20 pub const unwrapNull = no_panic.unwrapNull;
21 pub const castToNull = no_panic.castToNull;
22 pub const incorrectAlignment = no_panic.incorrectAlignment;
23 pub const invalidErrorCode = no_panic.invalidErrorCode;
24 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
25 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
26 pub const shlOverflow = no_panic.shlOverflow;
27 pub const shrOverflow = no_panic.shrOverflow;
28 pub const divideByZero = no_panic.divideByZero;
29 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
30 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
31 pub const corruptSwitch = no_panic.corruptSwitch;
32 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
33 pub const invalidEnumValue = no_panic.invalidEnumValue;
34 pub const forLenMismatch = no_panic.forLenMismatch;
35 pub const copyLenMismatch = no_panic.copyLenMismatch;
36 pub const memcpyAlias = no_panic.memcpyAlias;
37 pub const noreturnReturned = no_panic.noreturnReturned;
38 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
39};
40fn myPanic(msg: []const u8, _: ?usize) noreturn {
41 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
42 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};
43 std.process.exit(0);
44}
45const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.io();
test/incremental2/change_panic_handler_explicit/2-main.zig created+46
...@@ -0,0 +1,46 @@
1pub fn main() !u8 {
2 var a: u8 = undefined;
3 a = 255;
4 _ = a + 1;
5 return 1;
6}
7const no_panic = std.debug.no_panic;
8pub const panic = struct {
9 pub const call = myPanicNew;
10 pub fn integerOverflow() noreturn {
11 @panic("integer overflow");
12 }
13 pub const sentinelMismatch = no_panic.sentinelMismatch;
14 pub const unwrapError = no_panic.unwrapError;
15 pub const outOfBounds = no_panic.outOfBounds;
16 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
17 pub const inactiveUnionField = no_panic.inactiveUnionField;
18 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
19 pub const reachedUnreachable = no_panic.reachedUnreachable;
20 pub const unwrapNull = no_panic.unwrapNull;
21 pub const castToNull = no_panic.castToNull;
22 pub const incorrectAlignment = no_panic.incorrectAlignment;
23 pub const invalidErrorCode = no_panic.invalidErrorCode;
24 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
25 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
26 pub const shlOverflow = no_panic.shlOverflow;
27 pub const shrOverflow = no_panic.shrOverflow;
28 pub const divideByZero = no_panic.divideByZero;
29 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
30 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
31 pub const corruptSwitch = no_panic.corruptSwitch;
32 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
33 pub const invalidEnumValue = no_panic.invalidEnumValue;
34 pub const forLenMismatch = no_panic.forLenMismatch;
35 pub const copyLenMismatch = no_panic.copyLenMismatch;
36 pub const memcpyAlias = no_panic.memcpyAlias;
37 pub const noreturnReturned = no_panic.noreturnReturned;
38 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
39};
40fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
41 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
42 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};
43 std.process.exit(0);
44}
45const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.io();
test/incremental2/change_panic_handler_explicit/manifest created+23
...@@ -0,0 +1,23 @@
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig 0-main.zig
6#update
7#check stdout {
8panic message: integer overflow
9#}
10
11#write main.zig 1-main.zig
12#update {
13change the panic handler body#}
14#check stdout {
15new panic message: integer overflow
16#}
17
18#write main.zig 2-main.zig
19#update {
20change the panic handler function value#}
21#check stdout {
22third panic message: integer overflow
23#}
test/incremental2/hello created+28
...@@ -0,0 +1,28 @@
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
6const std = @import("std");
7const io = std.Io.Threaded.global_single_threaded.io();
8pub fn main() !void {
9 try std.Io.File.stdout().writeStreamingAll(io, "good morning\n");
10}
11#}
12#update
13#check stdout {
14good morning
15#}
16
17#write main.zig {
18const std = @import("std");
19const io = std.Io.Threaded.global_single_threaded.io();
20pub fn main() !void {
21 try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n");
22}
23#}
24#update {
25change the string#}
26#check stdout {
27おはようございます
28#}
test/src/runner.zig created+279
...@@ -0,0 +1,279 @@
1const std = @import("std");
2const Args = struct {
3 test_dir: ?std.Io.Dir,
4 manifest_file: ?std.Io.File,
5 zig_file: ?std.Io.File,
6 lib_dir: ?std.Io.Dir,
7 src_dir: ?std.Io.Dir,
8};
9pub fn main(init: std.process.Init) !void {
10 var stdin_buffer: [512]u8 = undefined;
11 var stdout_buffer: [512]u8 = undefined;
12 var stderr_buffer: [512]u8 = undefined;
13 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer);
14 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
15 var stderr: std.Io.File.Writer = .initStreaming(.stderr(), init.io, &stderr_buffer);
16 var server: std.zig.Server = .{
17 .in = &stdin.interface,
18 .out = &stdout.interface,
19 };
20 try server.serveStringMessage(.zig_version, @import("builtin").zig_version_string);
21 var args: Args = .{
22 .test_dir = null,
23 .manifest_file = null,
24 .zig_file = null,
25 .lib_dir = null,
26 .src_dir = null,
27 };
28 while (true) {
29 const hdr = try server.receiveMessage();
30 switch (hdr.tag) {
31 else => {
32 std.debug.print("unsupported message: {t}\n", .{hdr.tag});
33 std.process.exit(1);
34 },
35 .exit => {
36 std.process.exit(0);
37 },
38 .args => {
39 const args_body = try init.arena.allocator().alloc(u8, hdr.bytes_len);
40 try server.in.readSliceAll(args_body);
41 var state: enum { positional, zig, lib, src } = .positional;
42 var args_body_offset: usize = 0;
43 while (args_body.len - args_body_offset > 0) {
44 const arg: std.zig.Client.Message.Arg =
45 @fromBackingInt(args_body[args_body_offset]);
46 args_body_offset += 1;
47 switch (arg) {
48 .string => {
49 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
50 const string = args_body[args_body_offset..end];
51 args_body_offset = end + 1;
52 _ = string;
53 },
54 .prefix => {
55 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
56 const string = args_body[args_body_offset..end];
57 args_body_offset = end + 1;
58 state = if (std.mem.eql(u8, string, "--zig="))
59 .zig
60 else if (std.mem.eql(u8, string, "--lib="))
61 .lib
62 else if (std.mem.eql(u8, string, "--src="))
63 .src
64 else
65 unreachable;
66 continue;
67 },
68 .suffix => unreachable,
69 .input_dir, .output_dir => {
70 const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast(
71 args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)],
72 );
73 args_body_offset += @sizeOf(std.Io.Dir.Handle);
74 const dir: std.Io.Dir = .{
75 .handle = dir_handle.*,
76 };
77 switch (state) {
78 .positional => {
79 std.debug.assert(args.test_dir == null);
80 args.test_dir = dir;
81 std.debug.assert(args.manifest_file == null);
82 args.manifest_file = try dir.openFile(init.io, "manifest", .{});
83 },
84 .zig => unreachable,
85 .lib => {
86 std.debug.assert(args.lib_dir == null);
87 args.lib_dir = dir;
88 },
89 .src => {
90 std.debug.assert(args.src_dir == null);
91 args.src_dir = dir;
92 },
93 }
94 },
95 .input_file, .input_file_content, .output_file => {
96 const file_handle: *align(1) const std.Io.File.Handle = @ptrCast(
97 args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)],
98 );
99 args_body_offset += @sizeOf(std.Io.File.Handle);
100 const file: std.Io.File = .{
101 .handle = file_handle.*,
102 .flags = .{ .nonblocking = false },
103 };
104 switch (state) {
105 .positional => {
106 std.debug.assert(args.manifest_file == null);
107 args.manifest_file = file;
108 },
109 .zig => {
110 std.debug.assert(args.zig_file == null);
111 args.zig_file = file;
112 },
113 .lib, .src => unreachable,
114 }
115 },
116 }
117 state = .positional;
118 }
119 },
120 .query_test_metadata => try server.serveTestMetadata(.{
121 .names = &.{0},
122 .expected_panic_msgs = &.{0},
123 .string_bytes = "runner\x00",
124 }),
125 .run_test => {
126 const test_index = try server.receiveBody_u32();
127 try server.serveBodylessMessage(.test_started);
128 const test_result = runTest(init.io, &stderr.interface, &args);
129 try stderr.flush();
130 try server.serveTestResults(.{
131 .index = test_index,
132 .flags = .{
133 .status = if (test_result) .pass else |err| switch (err) {
134 else => status: {
135 if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace);
136 break :status .fail;
137 },
138 error.SkipZigTest => .skip,
139 },
140 .fuzz = false,
141 .log_err_count = 0,
142 .leak_count = 0,
143 },
144 });
145 },
146 }
147 }
148}
149fn runTest(io: std.Io, stderr: *std.Io.Writer, args: *const Args) anyerror!void {
150 const manifest_file = args.manifest_file orelse return error.MissingManifestArg;
151 const src_dir = args.src_dir orelse return error.MissingSrcDir;
152 var manifest_buffer: [512]u8 = undefined;
153 var manifest_fr = manifest_file.reader(io, &manifest_buffer);
154 var skip_delimiter = false;
155 var update_mtime = std.Io.Clock.real.now(io);
156 var update_num: usize = 0;
157 while (manifest_fr.interface.takeSentinel('\n')) |untrimmed_line| {
158 const line = std.mem.trimEnd(u8, untrimmed_line, "\r");
159 var line_it = std.mem.tokenizeScalar(u8, line, ' ');
160 const cmd_str = std.mem.cutPrefix(u8, line_it.next() orelse continue, "#").?;
161 if (skip_delimiter) {
162 std.debug.assert(std.mem.eql(u8, cmd_str, "}"));
163 skip_delimiter = false;
164 continue;
165 }
166 const cmd = std.meta.stringToEnum(
167 enum { obj, exe, lib, write, delete, update, check },
168 cmd_str,
169 ) orelse return error.UnknownCommand;
170 var maybe_arg = line_it.next();
171 var contents_file: ?std.Io.File = null;
172 defer if (contents_file) |file| file.close(io);
173 var contents_impl: union {
174 dr: DelimitedReader,
175 fr: std.Io.File.Reader,
176 } = undefined;
177 const contents_r: *std.Io.Reader = contents_r: {
178 switch (cmd) {
179 else => {},
180 .delete => break :contents_r .ending,
181 }
182 const contents_path = contents_path: {
183 const line_rest = line_it.rest();
184 if (line_rest.len > 0) break :contents_path line_rest;
185 const contents_path = maybe_arg orelse break :contents_r .ending;
186 maybe_arg = null;
187 break :contents_path contents_path;
188 };
189 if (std.mem.eql(u8, contents_path, "{")) {
190 contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &.{}) };
191 skip_delimiter = true;
192 break :contents_r &contents_impl.dr.interface;
193 }
194 const test_dir = args.test_dir orelse return error.ContentsFileInFileTest;
195 contents_file = try test_dir.openFile(io, contents_path, .{});
196 contents_impl = .{ .fr = contents_file.?.reader(io, &.{}) };
197 break :contents_r &contents_impl.fr.interface;
198 };
199 switch (cmd) {
200 else => {
201 try stderr.print("{t} {s} {{\n", .{ cmd, maybe_arg orelse "" });
202 _ = try contents_r.streamRemaining(stderr);
203 try stderr.writeAll("\n}\n");
204 },
205 .write => {
206 const file = try src_dir.createFile(io, maybe_arg orelse return error.MissingArg, .{});
207 defer file.close(io);
208 var fw = file.writer(io, &.{});
209 _ = try contents_r.streamRemaining(&fw.interface);
210 try file.setTimestamps(io, .{ .modify_timestamp = .{ .new = update_mtime } });
211 },
212 .delete => try src_dir.deleteFile(io, maybe_arg orelse return error.MissingArg),
213 .update => {
214 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
215 const name = switch (try contents_r.readSliceShort(&name_buffer)) {
216 0 => switch (update_num) {
217 0 => "initial update",
218 else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch
219 &name_buffer,
220 },
221 else => |name_len| name_buffer[0..name_len],
222 };
223 _ = try contents_r.discardRemaining();
224 try stderr.print("update({q})\n", .{name});
225 update_mtime = update_mtime.addDuration(.fromSeconds(2));
226 update_num += 1;
227 },
228 }
229 std.debug.assert(try contents_r.discardRemaining() == 0);
230 } else |err| switch (err) {
231 else => |e| return e,
232 error.EndOfStream => if (skip_delimiter) return error.MissingDelimiter,
233 }
234}
235const DelimitedReader = struct {
236 const Io = std.Io;
237
238 unlimited: *Io.Reader,
239 remaining: Io.Limit,
240 delimiter: []const u8,
241 interface: Io.Reader,
242
243 pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader {
244 return .{
245 .unlimited = reader,
246 .remaining = .nothing,
247 .delimiter = delimiter,
248 .interface = .{
249 .vtable = &.{
250 .stream = stream,
251 .discard = discard,
252 },
253 .buffer = buffer,
254 .seek = 0,
255 .end = 0,
256 },
257 };
258 }
259 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
260 const d: *DelimitedReader = @fieldParentPtr("interface", r);
261 const block = try d.unlimited.peekGreedy(d.delimiter.len);
262 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
263 block.len - d.delimiter.len + 1);
264 if (d.remaining == .nothing) return error.EndOfStream;
265 const n = try d.unlimited.stream(w, limit.min(d.remaining));
266 d.remaining = d.remaining.subtract(n).?;
267 return n;
268 }
269 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
270 const d: *DelimitedReader = @fieldParentPtr("interface", r);
271 const block = try d.unlimited.peekGreedy(d.delimiter.len);
272 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
273 block.len - d.delimiter.len + 1);
274 if (d.remaining == .nothing) return error.EndOfStream;
275 const n = try d.unlimited.discard(limit.min(d.remaining));
276 d.remaining = d.remaining.subtract(n).?;
277 return n;
278 }
279};
test/standalone/build.zig+1
...@@ -44,6 +44,7 @@ pub fn build(b: *std.Build) void {...@@ -44,6 +44,7 @@ pub fn build(b: *std.Build) void {
44 "../../tools/generate_linux_syscalls.zig",44 "../../tools/generate_linux_syscalls.zig",
45 "../../tools/process_headers.zig",45 "../../tools/process_headers.zig",
46 "../../tools/migrate_langref.zig",46 "../../tools/migrate_langref.zig",
47 "../../tools/runner-check.zig",
47 "../../tools/unskip_spirv_tests.zig",48 "../../tools/unskip_spirv_tests.zig",
48 "../../tools/update-linux-headers.zig",49 "../../tools/update-linux-headers.zig",
49 "../../tools/update_clang_options.zig",50 "../../tools/update_clang_options.zig",
test/tests.zig+86-1
...@@ -3365,7 +3365,7 @@ pub fn addIncrementalTests(...@@ -3365,7 +3365,7 @@ pub fn addIncrementalTests(
33653365
3366 var it = try dir.walk(b.graph.arena);3366 var it = try dir.walk(b.graph.arena);
3367 while (try it.next(io)) |entry| {3367 while (try it.next(io)) |entry| {
3368 if (std.mem.endsWith(u8, entry.basename, ".swp")) continue;3368 if (isEditorFileName(entry.basename)) continue;
33693369
3370 for (options.test_filters) |test_filter| {3370 for (options.test_filters) |test_filter| {
3371 if (std.mem.find(u8, entry.path, test_filter)) |_| break;3371 if (std.mem.find(u8, entry.path, test_filter)) |_| break;
...@@ -3435,6 +3435,91 @@ pub fn addIncrementalTests(...@@ -3435,6 +3435,91 @@ pub fn addIncrementalTests(
3435 }3435 }
3436}3436}
34373437
3438pub fn addNewIncrementalTests(
3439 b: *std.Build,
3440 runner: *std.Build.Step.Compile,
3441 options: IncrementalTestOptions,
3442) !*Step {
3443 const tests_step = b.step("test-new-incremental", "Run the new incremental compilation test cases");
3444
3445 const tests_path = b.path("test/incremental2");
3446 b.dependOnDirectoryContents(tests_path);
3447
3448 var tests_dir = try b.root.openDir(b.graph.io, "test/incremental2", .{ .iterate = true });
3449 defer tests_dir.close(b.graph.io);
3450 var test_it = tests_dir.iterate();
3451 while (try test_it.next(b.graph.io)) |@"test"| {
3452 const test_path = tests_path.path(b, @"test".name);
3453 switch (@"test".kind) {
3454 else => continue,
3455 .file => if (isEditorFileName(@"test".name)) continue,
3456 .directory => {},
3457 }
3458
3459 for (incremental_targets) |test_target| {
3460 const resolved_target = b.resolveTargetQuery(test_target.target);
3461
3462 if (options.skip_non_native and !isNative(&resolved_target, &b.graph.host.result))
3463 continue;
3464
3465 const target = &resolved_target.result;
3466
3467 if (options.skip_wasm and target.cpu.arch.isWasm()) continue;
3468
3469 if (options.skip_freebsd and target.os.tag == .freebsd) continue;
3470 if (options.skip_netbsd and target.os.tag == .netbsd) continue;
3471 if (options.skip_openbsd and target.os.tag == .openbsd) continue;
3472 if (options.skip_windows and target.os.tag == .windows) continue;
3473 if (options.skip_darwin and target.os.tag.isDarwin()) continue;
3474 if (options.skip_linux and target.os.tag == .linux) continue;
3475
3476 if (options.skip_llvm and test_target.backend == .llvm) continue;
3477
3478 const target_str = b.fmt("{s}-{t}", .{
3479 resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"),
3480 test_target.backend,
3481 });
3482
3483 if (options.test_target_filters.len > 0) {
3484 for (options.test_target_filters) |filter| {
3485 if (std.mem.find(u8, target_str, filter) != null) break;
3486 } else continue;
3487 }
3488
3489 const run = b.addRunArtifact(runner);
3490 run.setName(@"test".name);
3491 run.enableProtocolMode();
3492
3493 switch (@"test".kind) {
3494 else => continue,
3495 .file => run.addFileArg(test_path),
3496 .directory => run.addDirectoryArg(test_path),
3497 }
3498 run.addPrefixedFileArg("--zig=", .zig_exe);
3499 run.addPrefixedDirectoryArg("--lib=", .zig_lib);
3500 _ = run.addPrefixedOutputDirectoryArg("--src=", "src");
3501 run.addArgs(&.{ "--target", target_str });
3502
3503 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc
3504
3505 run.addThirdPartyEnabledArgDarling(.{ .enabled = "-fdarling" });
3506 run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" });
3507 run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" });
3508 run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" });
3509 run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" });
3510
3511 tests_step.dependOn(&run.step);
3512 }
3513 }
3514
3515 return tests_step;
3516}
3517
3518fn isEditorFileName(name: []const u8) bool {
3519 return (std.mem.startsWith(u8, name, "#") and std.mem.endsWith(u8, name, "#")) or
3520 std.mem.endsWith(u8, name, "~") or std.mem.endsWith(u8, name, ".swp");
3521}
3522
3438pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step {3523pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step {
3439 const step = b.step("test-llvm-ir", "Run the LLVM IR tests");3524 const step = b.step("test-llvm-ir", "Run the LLVM IR tests");
34403525
tools/runner-check.zig created+151
...@@ -0,0 +1,151 @@
1const std = @import("std");
2pub fn main(init: std.process.Init) !void {
3 const arena = init.arena.allocator();
4
5 var test_path_arg: ?[]const u8 = null;
6 var zig_path_arg: ?[]const u8 = null;
7 var lib_path_arg: ?[]const u8 = null;
8 var keep_src = false;
9
10 var arg_it = try init.minimal.args.iterateAllocator(arena);
11 std.debug.assert(arg_it.skip());
12 while (arg_it.next()) |arg| {
13 if (std.mem.eql(u8, arg, "--zig")) {
14 zig_path_arg = arg_it.next() orelse fail("missing arg after '{s}'", .{arg});
15 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
16 zig_path_arg = zig_path;
17 } else if (std.mem.eql(u8, arg, "--lib")) {
18 lib_path_arg = arg_it.next() orelse fail("missing arg after '{s}'", .{arg});
19 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
20 lib_path_arg = lib_path;
21 } else if (std.mem.eql(u8, arg, "--keep-src")) {
22 keep_src = true;
23 } else {
24 if (test_path_arg) |_| fail("unknown arg '{s}'", .{arg});
25 test_path_arg = arg;
26 }
27 }
28
29 const test_path = test_path_arg orelse fail("missing 'path/to/test'", .{});
30 const zig_path = zig_path_arg orelse fail("missing '--zig=path/to/zig'", .{});
31 const lib_path = lib_path_arg orelse fail("missing '--lib=path/to/lib'", .{});
32
33 const test_file = try std.Io.Dir.cwd().openFile(init.io, test_path, .{
34 .allow_directory = true,
35 });
36 const zig_file = try std.Io.Dir.cwd().openFile(init.io, zig_path, .{});
37 defer zig_file.close(init.io);
38 const lib_dir = try std.Io.Dir.cwd().openDir(init.io, lib_path, .{});
39 defer lib_dir.close(init.io);
40
41 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
42 var rand_int: u64 = undefined;
43 init.io.random(@ptrCast(&rand_int));
44 break :rand_int rand_int;
45 });
46 var src_dir = try std.Io.Dir.cwd().createDirPathOpen(init.io, src_dir_path, .{});
47 defer {
48 src_dir.close(init.io);
49 if (!keep_src) std.Io.Dir.cwd().deleteTree(init.io, src_dir_path) catch |err| {
50 std.log.warn("failed to delete tree '{s}': {t}", .{ src_dir_path, err });
51 };
52 }
53
54 var child = try std.process.spawn(init.io, .{
55 .argv = &.{
56 zig_path,
57 "run",
58 try std.fs.path.resolveAlloc(arena, &.{ lib_path, "..", "test", "src", "runner.zig" }),
59 },
60 .stdin = .pipe,
61 .stdout = .pipe,
62 .inherit_dirs = &.{ lib_dir, src_dir },
63 .inherit_files = &.{ zig_file, test_file },
64 });
65
66 var stdout_buffer: [512]u8 = undefined;
67 var stdout_reader = child.stdout.?.readerStreaming(init.io, &stdout_buffer);
68 var stdin_buffer: [512]u8 = undefined;
69 var stdin_writer = child.stdin.?.writerStreaming(init.io, &stdin_buffer);
70 var client: std.zig.Client = .{
71 .in = &stdout_reader.interface,
72 .out = &stdin_writer.interface,
73 };
74 {
75 const Arg = std.zig.Client.Message.Arg;
76 var args: std.ArrayList(u8) = .initBuffer(try arena.alloc(u8, 0 +
77 @sizeOf(Arg) + @sizeOf(std.Io.File.Handle) +
78 @sizeOf(Arg) + "--zig=\x00".len +
79 @sizeOf(Arg) + @sizeOf(std.Io.File.Handle) +
80 @sizeOf(Arg) + "--lib=\x00".len +
81 @sizeOf(Arg) + @sizeOf(std.Io.Dir.Handle) +
82 @sizeOf(Arg) + "--src=\x00".len +
83 @sizeOf(Arg) + @sizeOf(std.Io.Dir.Handle)));
84
85 args.appendAssumeCapacity(@backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) {
86 else => unreachable,
87 .file => .input_file,
88 .directory => .input_dir,
89 })));
90 args.appendSliceAssumeCapacity(@ptrCast(&test_file.handle));
91
92 args.appendAssumeCapacity(@backingInt(Arg.prefix));
93 args.appendSliceAssumeCapacity("--zig=");
94 args.appendAssumeCapacity(0);
95
96 args.appendAssumeCapacity(@backingInt(Arg.input_file));
97 args.appendSliceAssumeCapacity(@ptrCast(&zig_file.handle));
98
99 args.appendAssumeCapacity(@backingInt(Arg.prefix));
100 args.appendSliceAssumeCapacity("--lib=");
101 args.appendAssumeCapacity(0);
102
103 args.appendAssumeCapacity(@backingInt(Arg.input_dir));
104 args.appendSliceAssumeCapacity(@ptrCast(&lib_dir.handle));
105
106 args.appendAssumeCapacity(@backingInt(Arg.prefix));
107 args.appendSliceAssumeCapacity("--src=");
108 args.appendAssumeCapacity(0);
109
110 args.appendAssumeCapacity(@backingInt(Arg.output_dir));
111 args.appendSliceAssumeCapacity(@ptrCast(&src_dir.handle));
112
113 std.debug.assert(args.unusedCapacitySlice().len == 0);
114
115 try client.serveMessageHeader(.{
116 .tag = .args,
117 .bytes_len = @intCast(args.items.len),
118 });
119 try client.out.writeAll(args.items);
120
121 try client.serveBodylessMessage(.query_test_metadata);
122 while (true) {
123 const hdr = try client.receiveMessage();
124 switch (hdr.tag) {
125 else => try client.in.discardAll(hdr.bytes_len),
126 .zig_version => {
127 const body = try arena.alloc(u8, hdr.bytes_len);
128 try client.in.readSliceAll(body);
129 if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fail(
130 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
131 .{ @import("builtin").zig_version_string, body },
132 );
133 },
134 .test_metadata => {
135 try client.in.discardAll(hdr.bytes_len);
136 try client.serveRunTest(0);
137 },
138 .test_results => {
139 try client.in.discardAll(hdr.bytes_len);
140 break;
141 },
142 }
143 }
144 try client.serveBodylessMessage(.exit);
145 }
146 _ = try child.wait(init.io);
147}
148fn fail(comptime fmt: []const u8, args: anytype) noreturn {
149 std.log.err(fmt, args);
150 std.process.exit(1);
151}