Commit 9f112ce868
Changed files (2)
test
incremental
tools
test/incremental/hello
@@ -1,12 +1,14 @@
#target=x86_64-linux
#update=initial version
#file=main.zig
+const std = @import("std");
pub fn main() !void {
try std.io.getStdOut().writeAll("good morning\n");
}
#expect_stdout="good morning\n"
#update=change the string
#file=main.zig
+const std = @import("std");
pub fn main() !void {
try std.io.getStdOut().writeAll("おはようございます\n");
}
tools/incr-check.zig
@@ -19,64 +19,70 @@ pub fn main() !void {
const rand_int = std.crypto.random.int(u64);
const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
- const local_cache_path = tmp_dir_path ++ std.fs.path.sep_str ++ ".local-cache";
- const global_cache_path = tmp_dir_path ++ std.fs.path.sep_str ++ ".global-cache";
const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
const child_prog_node = prog_node.start("zig build-exe", 0);
defer child_prog_node.end();
var child = std.process.Child.init(&.{
- zig_exe,
+ // Convert incr-check-relative path to subprocess-relative path.
+ try std.fs.path.relative(arena, tmp_dir_path, zig_exe),
"build-exe",
case.root_source_file,
"-fno-llvm",
"-fno-lld",
"-fincremental",
- "--listen=-",
"-target",
case.target_query,
"--cache-dir",
- local_cache_path,
+ ".local-cache",
"--global-cache-dir",
- global_cache_path,
+ ".global_cache",
+ "--listen=-",
}, arena);
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;
child.progress_node = child_prog_node;
+ child.cwd_dir = tmp_dir;
+ child.cwd = tmp_dir_path;
var eval: Eval = .{
+ .arena = arena,
.case = case,
.tmp_dir = tmp_dir,
.child = &child,
};
- eval.write(case.updates[0]);
-
try child.spawn();
- var poller = std.io.poll(arena, enum { stdout, stderr }, .{
+ var poller = std.io.poll(arena, Eval.StreamEnum, .{
.stdout = child.stdout.?,
.stderr = child.stderr.?,
});
defer poller.deinit();
- try eval.check(case.updates[0]);
-
- for (case.updates[1..]) |update| {
+ for (case.updates) |update| {
eval.write(update);
- try eval.requestIncrementalUpdate();
- try eval.check(update);
+ try eval.requestUpdate();
+ try eval.check(&poller, update);
}
+
+ try eval.end(&poller);
+
+ waitChild(&child);
}
const Eval = struct {
+ arena: Allocator,
case: Case,
tmp_dir: std.fs.Dir,
child: *std.process.Child,
+ const StreamEnum = enum { stdout, stderr };
+ const Poller = std.io.Poller(StreamEnum);
+
/// Currently this function assumes the previous updates have already been written.
fn write(eval: *Eval, update: Case.Update) void {
for (update.changes) |full_contents| {
@@ -94,15 +100,137 @@ const Eval = struct {
}
}
- fn check(eval: *Eval, update: Case.Update) !void {
+ fn check(eval: *Eval, poller: *Poller, update: Case.Update) !void {
+ const arena = eval.arena;
+ const Header = std.zig.Server.Message.Header;
+ const stdout = poller.fifo(.stdout);
+ const stderr = poller.fifo(.stderr);
+
+ poll: while (true) {
+ while (stdout.readableLength() < @sizeOf(Header)) {
+ if (!(try poller.poll())) break :poll;
+ }
+ const header = stdout.reader().readStruct(Header) catch unreachable;
+ while (stdout.readableLength() < header.bytes_len) {
+ if (!(try poller.poll())) break :poll;
+ }
+ const body = stdout.readableSliceOfLen(header.bytes_len);
+ std.log.debug("received message: {s}", .{@tagName(header.tag)});
+
+ switch (header.tag) {
+ .error_bundle => {
+ const EbHdr = std.zig.Server.Message.ErrorBundle;
+ const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
+ const extra_bytes =
+ body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
+ const string_bytes =
+ body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
+ // TODO: use @ptrCast when the compiler supports it
+ const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
+ const extra_array = try arena.alloc(u32, unaligned_extra.len);
+ @memcpy(extra_array, unaligned_extra);
+ const result_error_bundle: std.zig.ErrorBundle = .{
+ .string_bytes = try arena.dupe(u8, string_bytes),
+ .extra = extra_array,
+ };
+ if (stderr.readableLength() > 0) {
+ const stderr_data = try stderr.toOwnedSlice();
+ fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});
+ }
+ try eval.checkErrorOutcome(update, result_error_bundle);
+ // This message indicates the end of the update.
+ stdout.discard(body.len);
+ return;
+ },
+ .emit_bin_path => {
+ const EbpHdr = std.zig.Server.Message.EmitBinPath;
+ const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
+ _ = ebp_hdr;
+ const result_binary = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
+ if (stderr.readableLength() > 0) {
+ const stderr_data = try stderr.toOwnedSlice();
+ fatal("emit_bin_path included unexpected stderr:\n{s}", .{stderr_data});
+ }
+ try eval.checkSuccessOutcome(update, result_binary);
+ // This message indicates the end of the update.
+ stdout.discard(body.len);
+ return;
+ },
+ else => {
+ // Ignore other messages.
+ stdout.discard(body.len);
+ },
+ }
+ }
+
+ if (stderr.readableLength() > 0) {
+ const stderr_data = try stderr.toOwnedSlice();
+ fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data });
+ }
+
+ waitChild(eval.child);
+ fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});
+ }
+
+ fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
_ = eval;
- _ = update;
- @panic("TODO: read messages from the compiler");
+ switch (update.outcome) {
+ .unknown => return,
+ .compile_errors => |expected_errors| {
+ for (expected_errors) |expected_error| {
+ _ = expected_error;
+ @panic("TODO check if the expected error matches the compile errors");
+ }
+ },
+ .stdout, .exit_code => {
+ const color: std.zig.Color = .auto;
+ error_bundle.renderToStdErr(color.renderOptions());
+ fatal("update '{s}': unexpected compile errors", .{update.name});
+ },
+ }
}
- fn requestIncrementalUpdate(eval: *Eval) !void {
+ fn checkSuccessOutcome(eval: *Eval, update: Case.Update, binary_path: []const u8) !void {
_ = eval;
- @panic("TODO: send update request to the compiler");
+ switch (update.outcome) {
+ .unknown => return,
+ .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}),
+ .stdout, .exit_code => {},
+ }
+ fatal("TODO: run this binary: '{s}'", .{binary_path});
+ }
+
+ fn requestUpdate(eval: *Eval) !void {
+ const header: std.zig.Client.Message.Header = .{
+ .tag = .update,
+ .bytes_len = 0,
+ };
+ try eval.child.stdin.?.writeAll(std.mem.asBytes(&header));
+ }
+
+ fn end(eval: *Eval, poller: *Poller) !void {
+ requestExit(eval.child);
+
+ const Header = std.zig.Server.Message.Header;
+ const stdout = poller.fifo(.stdout);
+ const stderr = poller.fifo(.stderr);
+
+ poll: while (true) {
+ while (stdout.readableLength() < @sizeOf(Header)) {
+ if (!(try poller.poll())) break :poll;
+ }
+ const header = stdout.reader().readStruct(Header) catch unreachable;
+ while (stdout.readableLength() < header.bytes_len) {
+ if (!(try poller.poll())) break :poll;
+ }
+ const body = stdout.readableSliceOfLen(header.bytes_len);
+ stdout.discard(body.len);
+ }
+
+ if (stderr.readableLength() > 0) {
+ const stderr_data = try stderr.toOwnedSlice();
+ fatal("unexpected stderr:\n{s}", .{stderr_data});
+ }
}
};
@@ -213,3 +341,29 @@ const Case = struct {
};
}
};
+
+fn requestExit(child: *std.process.Child) void {
+ if (child.stdin == null) return;
+
+ const header: std.zig.Client.Message.Header = .{
+ .tag = .exit,
+ .bytes_len = 0,
+ };
+ child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) {
+ error.BrokenPipe => {},
+ else => fatal("failed to send exit: {s}", .{@errorName(err)}),
+ };
+
+ // Send EOF to stdin.
+ child.stdin.?.close();
+ child.stdin = null;
+}
+
+fn waitChild(child: *std.process.Child) void {
+ requestExit(child);
+ const term = child.wait() catch |err| fatal("child process failed: {s}", .{@errorName(err)});
+ switch (term) {
+ .Exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}),
+ .Signal, .Stopped, .Unknown => fatal("compiler terminated unexpectedly", .{}),
+ }
+}