authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-19 21:40:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-20 01:06:29-07:00
log9f112ce8689213b34f10709a337f713b3df5f581
tree0edc117107d7a1593f82e4c4e90a1d84823b8d74
parentea2c45227af0000353fe3626293bc22e1f04d1f7

incr-test: running an update


2 files changed, 175 insertions(+), 19 deletions(-)

test/incremental/hello+2
......@@ -1,12 +1,14 @@
11#target=x86_64-linux
22#update=initial version
33#file=main.zig
4const std = @import("std");
45pub fn main() !void {
56 try std.io.getStdOut().writeAll("good morning\n");
67}
78#expect_stdout="good morning\n"
89#update=change the string
910#file=main.zig
11const std = @import("std");
1012pub fn main() !void {
1113 try std.io.getStdOut().writeAll("おはようございます\n");
1214}
tools/incr-check.zig+173-19
......@@ -19,64 +19,70 @@ pub fn main() !void {
1919
2020 const rand_int = std.crypto.random.int(u64);
2121 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
22 const local_cache_path = tmp_dir_path ++ std.fs.path.sep_str ++ ".local-cache";
23 const global_cache_path = tmp_dir_path ++ std.fs.path.sep_str ++ ".global-cache";
2422 const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
2523
2624 const child_prog_node = prog_node.start("zig build-exe", 0);
2725 defer child_prog_node.end();
2826
2927 var child = std.process.Child.init(&.{
30 zig_exe,
28 // Convert incr-check-relative path to subprocess-relative path.
29 try std.fs.path.relative(arena, tmp_dir_path, zig_exe),
3130 "build-exe",
3231 case.root_source_file,
3332 "-fno-llvm",
3433 "-fno-lld",
3534 "-fincremental",
36 "--listen=-",
3735 "-target",
3836 case.target_query,
3937 "--cache-dir",
40 local_cache_path,
38 ".local-cache",
4139 "--global-cache-dir",
42 global_cache_path,
40 ".global_cache",
41 "--listen=-",
4342 }, arena);
4443
4544 child.stdin_behavior = .Pipe;
4645 child.stdout_behavior = .Pipe;
4746 child.stderr_behavior = .Pipe;
4847 child.progress_node = child_prog_node;
48 child.cwd_dir = tmp_dir;
49 child.cwd = tmp_dir_path;
4950
5051 var eval: Eval = .{
52 .arena = arena,
5153 .case = case,
5254 .tmp_dir = tmp_dir,
5355 .child = &child,
5456 };
5557
56 eval.write(case.updates[0]);
57
5858 try child.spawn();
5959
60 var poller = std.io.poll(arena, enum { stdout, stderr }, .{
60 var poller = std.io.poll(arena, Eval.StreamEnum, .{
6161 .stdout = child.stdout.?,
6262 .stderr = child.stderr.?,
6363 });
6464 defer poller.deinit();
6565
66 try eval.check(case.updates[0]);
67
68 for (case.updates[1..]) |update| {
66 for (case.updates) |update| {
6967 eval.write(update);
70 try eval.requestIncrementalUpdate();
71 try eval.check(update);
68 try eval.requestUpdate();
69 try eval.check(&poller, update);
7270 }
71
72 try eval.end(&poller);
73
74 waitChild(&child);
7375}
7476
7577const Eval = struct {
78 arena: Allocator,
7679 case: Case,
7780 tmp_dir: std.fs.Dir,
7881 child: *std.process.Child,
7982
83 const StreamEnum = enum { stdout, stderr };
84 const Poller = std.io.Poller(StreamEnum);
85
8086 /// Currently this function assumes the previous updates have already been written.
8187 fn write(eval: *Eval, update: Case.Update) void {
8288 for (update.changes) |full_contents| {
......@@ -94,15 +100,137 @@ const Eval = struct {
94100 }
95101 }
96102
97 fn check(eval: *Eval, update: Case.Update) !void {
103 fn check(eval: *Eval, poller: *Poller, update: Case.Update) !void {
104 const arena = eval.arena;
105 const Header = std.zig.Server.Message.Header;
106 const stdout = poller.fifo(.stdout);
107 const stderr = poller.fifo(.stderr);
108
109 poll: while (true) {
110 while (stdout.readableLength() < @sizeOf(Header)) {
111 if (!(try poller.poll())) break :poll;
112 }
113 const header = stdout.reader().readStruct(Header) catch unreachable;
114 while (stdout.readableLength() < header.bytes_len) {
115 if (!(try poller.poll())) break :poll;
116 }
117 const body = stdout.readableSliceOfLen(header.bytes_len);
118 std.log.debug("received message: {s}", .{@tagName(header.tag)});
119
120 switch (header.tag) {
121 .error_bundle => {
122 const EbHdr = std.zig.Server.Message.ErrorBundle;
123 const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
124 const extra_bytes =
125 body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
126 const string_bytes =
127 body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
128 // TODO: use @ptrCast when the compiler supports it
129 const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
130 const extra_array = try arena.alloc(u32, unaligned_extra.len);
131 @memcpy(extra_array, unaligned_extra);
132 const result_error_bundle: std.zig.ErrorBundle = .{
133 .string_bytes = try arena.dupe(u8, string_bytes),
134 .extra = extra_array,
135 };
136 if (stderr.readableLength() > 0) {
137 const stderr_data = try stderr.toOwnedSlice();
138 fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});
139 }
140 try eval.checkErrorOutcome(update, result_error_bundle);
141 // This message indicates the end of the update.
142 stdout.discard(body.len);
143 return;
144 },
145 .emit_bin_path => {
146 const EbpHdr = std.zig.Server.Message.EmitBinPath;
147 const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
148 _ = ebp_hdr;
149 const result_binary = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
150 if (stderr.readableLength() > 0) {
151 const stderr_data = try stderr.toOwnedSlice();
152 fatal("emit_bin_path included unexpected stderr:\n{s}", .{stderr_data});
153 }
154 try eval.checkSuccessOutcome(update, result_binary);
155 // This message indicates the end of the update.
156 stdout.discard(body.len);
157 return;
158 },
159 else => {
160 // Ignore other messages.
161 stdout.discard(body.len);
162 },
163 }
164 }
165
166 if (stderr.readableLength() > 0) {
167 const stderr_data = try stderr.toOwnedSlice();
168 fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data });
169 }
170
171 waitChild(eval.child);
172 fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});
173 }
174
175 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
98176 _ = eval;
99 _ = update;
100 @panic("TODO: read messages from the compiler");
177 switch (update.outcome) {
178 .unknown => return,
179 .compile_errors => |expected_errors| {
180 for (expected_errors) |expected_error| {
181 _ = expected_error;
182 @panic("TODO check if the expected error matches the compile errors");
183 }
184 },
185 .stdout, .exit_code => {
186 const color: std.zig.Color = .auto;
187 error_bundle.renderToStdErr(color.renderOptions());
188 fatal("update '{s}': unexpected compile errors", .{update.name});
189 },
190 }
101191 }
102192
103 fn requestIncrementalUpdate(eval: *Eval) !void {
193 fn checkSuccessOutcome(eval: *Eval, update: Case.Update, binary_path: []const u8) !void {
104194 _ = eval;
105 @panic("TODO: send update request to the compiler");
195 switch (update.outcome) {
196 .unknown => return,
197 .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}),
198 .stdout, .exit_code => {},
199 }
200 fatal("TODO: run this binary: '{s}'", .{binary_path});
201 }
202
203 fn requestUpdate(eval: *Eval) !void {
204 const header: std.zig.Client.Message.Header = .{
205 .tag = .update,
206 .bytes_len = 0,
207 };
208 try eval.child.stdin.?.writeAll(std.mem.asBytes(&header));
209 }
210
211 fn end(eval: *Eval, poller: *Poller) !void {
212 requestExit(eval.child);
213
214 const Header = std.zig.Server.Message.Header;
215 const stdout = poller.fifo(.stdout);
216 const stderr = poller.fifo(.stderr);
217
218 poll: while (true) {
219 while (stdout.readableLength() < @sizeOf(Header)) {
220 if (!(try poller.poll())) break :poll;
221 }
222 const header = stdout.reader().readStruct(Header) catch unreachable;
223 while (stdout.readableLength() < header.bytes_len) {
224 if (!(try poller.poll())) break :poll;
225 }
226 const body = stdout.readableSliceOfLen(header.bytes_len);
227 stdout.discard(body.len);
228 }
229
230 if (stderr.readableLength() > 0) {
231 const stderr_data = try stderr.toOwnedSlice();
232 fatal("unexpected stderr:\n{s}", .{stderr_data});
233 }
106234 }
107235};
108236
......@@ -213,3 +341,29 @@ const Case = struct {
213341 };
214342 }
215343};
344
345fn requestExit(child: *std.process.Child) void {
346 if (child.stdin == null) return;
347
348 const header: std.zig.Client.Message.Header = .{
349 .tag = .exit,
350 .bytes_len = 0,
351 };
352 child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) {
353 error.BrokenPipe => {},
354 else => fatal("failed to send exit: {s}", .{@errorName(err)}),
355 };
356
357 // Send EOF to stdin.
358 child.stdin.?.close();
359 child.stdin = null;
360}
361
362fn waitChild(child: *std.process.Child) void {
363 requestExit(child);
364 const term = child.wait() catch |err| fatal("child process failed: {s}", .{@errorName(err)});
365 switch (term) {
366 .Exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}),
367 .Signal, .Stopped, .Unknown => fatal("compiler terminated unexpectedly", .{}),
368 }
369}