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 @@...@@ -1,12 +1,14 @@
1#target=x86_64-linux1#target=x86_64-linux
2#update=initial version2#update=initial version
3#file=main.zig3#file=main.zig
4const std = @import("std");
4pub fn main() !void {5pub fn main() !void {
5 try std.io.getStdOut().writeAll("good morning\n");6 try std.io.getStdOut().writeAll("good morning\n");
6}7}
7#expect_stdout="good morning\n"8#expect_stdout="good morning\n"
8#update=change the string9#update=change the string
9#file=main.zig10#file=main.zig
11const std = @import("std");
10pub fn main() !void {12pub fn main() !void {
11 try std.io.getStdOut().writeAll("おはようございます\n");13 try std.io.getStdOut().writeAll("おはようございます\n");
12}14}
tools/incr-check.zig+173-19
...@@ -19,64 +19,70 @@ pub fn main() !void {...@@ -19,64 +19,70 @@ pub fn main() !void {
1919
20 const rand_int = std.crypto.random.int(u64);20 const rand_int = std.crypto.random.int(u64);
21 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);21 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";
24 const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});22 const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
2523
26 const child_prog_node = prog_node.start("zig build-exe", 0);24 const child_prog_node = prog_node.start("zig build-exe", 0);
27 defer child_prog_node.end();25 defer child_prog_node.end();
2826
29 var child = std.process.Child.init(&.{27 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),
31 "build-exe",30 "build-exe",
32 case.root_source_file,31 case.root_source_file,
33 "-fno-llvm",32 "-fno-llvm",
34 "-fno-lld",33 "-fno-lld",
35 "-fincremental",34 "-fincremental",
36 "--listen=-",
37 "-target",35 "-target",
38 case.target_query,36 case.target_query,
39 "--cache-dir",37 "--cache-dir",
40 local_cache_path,38 ".local-cache",
41 "--global-cache-dir",39 "--global-cache-dir",
42 global_cache_path,40 ".global_cache",
41 "--listen=-",
43 }, arena);42 }, arena);
4443
45 child.stdin_behavior = .Pipe;44 child.stdin_behavior = .Pipe;
46 child.stdout_behavior = .Pipe;45 child.stdout_behavior = .Pipe;
47 child.stderr_behavior = .Pipe;46 child.stderr_behavior = .Pipe;
48 child.progress_node = child_prog_node;47 child.progress_node = child_prog_node;
48 child.cwd_dir = tmp_dir;
49 child.cwd = tmp_dir_path;
4950
50 var eval: Eval = .{51 var eval: Eval = .{
52 .arena = arena,
51 .case = case,53 .case = case,
52 .tmp_dir = tmp_dir,54 .tmp_dir = tmp_dir,
53 .child = &child,55 .child = &child,
54 };56 };
5557
56 eval.write(case.updates[0]);
57
58 try child.spawn();58 try child.spawn();
5959
60 var poller = std.io.poll(arena, enum { stdout, stderr }, .{60 var poller = std.io.poll(arena, Eval.StreamEnum, .{
61 .stdout = child.stdout.?,61 .stdout = child.stdout.?,
62 .stderr = child.stderr.?,62 .stderr = child.stderr.?,
63 });63 });
64 defer poller.deinit();64 defer poller.deinit();
6565
66 try eval.check(case.updates[0]);66 for (case.updates) |update| {
67
68 for (case.updates[1..]) |update| {
69 eval.write(update);67 eval.write(update);
70 try eval.requestIncrementalUpdate();68 try eval.requestUpdate();
71 try eval.check(update);69 try eval.check(&poller, update);
72 }70 }
71
72 try eval.end(&poller);
73
74 waitChild(&child);
73}75}
7476
75const Eval = struct {77const Eval = struct {
78 arena: Allocator,
76 case: Case,79 case: Case,
77 tmp_dir: std.fs.Dir,80 tmp_dir: std.fs.Dir,
78 child: *std.process.Child,81 child: *std.process.Child,
7982
83 const StreamEnum = enum { stdout, stderr };
84 const Poller = std.io.Poller(StreamEnum);
85
80 /// Currently this function assumes the previous updates have already been written.86 /// Currently this function assumes the previous updates have already been written.
81 fn write(eval: *Eval, update: Case.Update) void {87 fn write(eval: *Eval, update: Case.Update) void {
82 for (update.changes) |full_contents| {88 for (update.changes) |full_contents| {
...@@ -94,15 +100,137 @@ const Eval = struct {...@@ -94,15 +100,137 @@ const Eval = struct {
94 }100 }
95 }101 }
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 {
98 _ = eval;176 _ = eval;
99 _ = update;177 switch (update.outcome) {
100 @panic("TODO: read messages from the compiler");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 }
101 }191 }
102192
103 fn requestIncrementalUpdate(eval: *Eval) !void {193 fn checkSuccessOutcome(eval: *Eval, update: Case.Update, binary_path: []const u8) !void {
104 _ = eval;194 _ = 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 }
106 }234 }
107};235};
108236
...@@ -213,3 +341,29 @@ const Case = struct {...@@ -213,3 +341,29 @@ const Case = struct {
213 };341 };
214 }342 }
215};343};
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}