authorgravatar for noam@pixelhero.devNoam Preil <noam@pixelhero.dev> 2020-06-02 15:29:59-04:00
committergravatar for noam@pixelhero.devNoam Preil <noam@pixelhero.dev> 2020-06-15 20:32:54-04:00
logd4fd7c6a014f4ba41f2231b58d090d72179a5bdb
tree9fea16ae1f355bfa79ac7914152d593d8d198124
parent2ed07a36f8b225d4abd531e80b73cf97e51ae0bb
signature Commit is signed but in an unrecognized format.

Stage2/Testing: Staged test harness draft design


1 files changed, 170 insertions(+), 0 deletions(-)

src-self-hosted/test.zig+170
......@@ -56,16 +56,27 @@ fn findOffset(src: []const u8, line: usize, column: usize) ?usize {
5656}
5757
5858pub const TestContext = struct {
59 // TODO: remove these. They are deprecated.
5960 zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase),
61 // TODO: remove
6062 zir_transform_cases: std.ArrayList(ZIRTransformCase),
63 // TODO: remove
6164 zir_error_cases: std.ArrayList(ZIRErrorCase),
6265
66 /// TODO: find a way to treat cases as individual tests as far as
67 /// `zig test` is concerned. If we have 100 tests, they should *not* be
68 /// considered as *one*. "ZIR" isn't really a *test*, it's a *category* of
69 /// tests.
70 zir_cases: std.ArrayList(ZIRCase),
71
72 // TODO: remove
6373 pub const ZIRCompareOutputCase = struct {
6474 name: []const u8,
6575 src_list: []const []const u8,
6676 expected_stdout_list: []const []const u8,
6777 };
6878
79 // TODO: remove
6980 pub const ZIRTransformCase = struct {
7081 name: []const u8,
7182 cross_target: std.zig.CrossTarget,
......@@ -96,6 +107,7 @@ pub const TestContext = struct {
96107 }
97108 };
98109
110 // TODO: remove
99111 pub const ZIRErrorCase = struct {
100112 name: []const u8,
101113 src: [:0]const u8,
......@@ -103,6 +115,83 @@ pub const TestContext = struct {
103115 cross_target: std.zig.CrossTarget,
104116 };
105117
118 pub const ZIRStageType = enum {
119 /// A transformation stage transforms the input ZIR and tests against
120 /// the expected output
121 Transformation,
122 /// An error stage attempts to compile bad code, and ensures that it
123 /// fails to compile, and for the expected reasons
124 Error,
125 /// An execution stage compiles and runs the input ZIR, feeding in
126 /// provided input and ensuring that the outputs match what is expected
127 Execution,
128 /// A compilation stage checks that the ZIR compiles without any issues
129 Compiles,
130 };
131
132 pub const ZIRStage = struct {
133 /// The input to the current stage. We simulate an incremental update
134 /// with the file's contents changed to this value each stage.
135 ///
136 /// This value can change entirely between stages, which would be akin
137 /// to deleting the source file and creating a new one from scratch; or
138 /// you can keep it mostly consistent, with small changes, testing the
139 /// effects of the incremental compilation.
140 src: [:0]const u8,
141 case: union(ZIRStageType) {
142 /// The expected output ZIR
143 Transformation: []const u8,
144 /// A slice containing the expected errors *in sequential order*.
145 Error: []const ErrorMsg,
146
147 /// Input to feed to the program, and expected outputs.
148 ///
149 /// If stdout, stderr, and exit_code are all null, addZIRCase will
150 /// discard the test. To test for successful compilation, use a
151 /// dedicated Compile stage instead.
152 Execution: struct {
153 stdin: ?[]const u8,
154 stdout: ?[]const u8,
155 stderr: ?[]const u8,
156 exit_code: ?u8,
157 },
158 /// A Compiles test checks only that compilation of the given ZIR
159 /// succeeds. To test outputs, use an Execution test. It is good to
160 /// use a Compiles test before an Execution, as the overhead should
161 /// be low (due to incremental compilation) and TODO: provide a way
162 /// to check changed / new / etc decls in testing mode
163 /// (usingnamespace a debug info struct with a comptime flag?)
164 Compiles: void,
165 },
166 };
167
168 /// A ZIRCase consists of a set of *stages*. A stage can transform ZIR,
169 /// compile it, ensure that compilation fails, and more. The same Module is
170 /// used for each stage, so each stage's source is treated as a single file
171 /// being updated by the test harness and incrementally compiled.
172 pub const ZIRCase = struct {
173 name: []const u8,
174 /// The platform the ZIR targets. For non-native platforms, an emulator
175 /// such as QEMU is required for tests to complete.
176 ///
177 target: std.zig.CrossTarget,
178 stages: []ZIRStage,
179 };
180
181 pub fn addZIRCase(
182 ctx: *TestContext,
183 name: []const u8,
184 target: std.zig.CrossTarget,
185 stages: []ZIRStage,
186 ) !void {
187 const case = .{
188 .name = name,
189 .target = target,
190 .stages = stages,
191 };
192 try ctx.cases.append(case);
193 }
194
106195 pub fn addZIRCompareOutput(
107196 ctx: *TestContext,
108197 name: []const u8,
......@@ -196,6 +285,14 @@ pub const TestContext = struct {
196285
197286 const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
198287
288 for (self.zir_cases.items) |case| {
289 std.testing.base_allocator_instance.reset();
290 const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);
291 try self.runOneZIRCase(std.testing.allocator, root_node, case, info.target);
292 try std.testing.allocator_instance.validate();
293 }
294
295 // TODO: wipe the rest of this function
199296 for (self.zir_cmp_output_cases.items) |case| {
200297 std.testing.base_allocator_instance.reset();
201298 try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target);
......@@ -215,6 +312,75 @@ pub const TestContext = struct {
215312 }
216313 }
217314
315 fn runOneZIRCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void {
316 var tmp = std.testing.tmpDir(.{});
317 defer tmp.cleanup();
318
319 const tmp_src_path = "test_case.zir";
320 const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
321 defer root_pkg.destroy();
322
323 var prg_node = root_node.start(case.name, case.stages.len);
324 prg_node.activate();
325 defer prg_node.end();
326
327 var module = try Module.init(allocator, .{
328 .target = target,
329 // This is an Executable, as opposed to e.g. a *library*. This does
330 // not mean no ZIR is generated.
331 //
332 // TODO: support tests for object file building, and library builds
333 // and linking. This will require a rework to support multi-file
334 // tests.
335 .output_mode = .Exe,
336 // TODO: support testing optimizations
337 .optimize_mode = .Debug,
338 .bin_file_dir = tmp.dir,
339 .bin_file_path = "test_case",
340 .root_pkg = root_pkg,
341 });
342 defer module.deinit();
343
344 for (case.stages) |s| {
345 // TODO: remove before committing. This is for ZLS ;)
346 const stage: ZIRStage = s;
347
348 var stage_node = prg_node.start("stage", 4);
349 stage_node.activate();
350 defer stage_node.end();
351
352 var sync_node = stage_node.start("write", null);
353 sync_node.activate();
354 try tmp.dir.writeFile(tmp_src_path, stage.src);
355 sync_node.end();
356
357 var module_node = stage_node.start("parse/analysis/codegen", null);
358 module_node.activate();
359 try module.update();
360 module_node.end();
361
362 switch (stage.case) {
363 .Transformation => |expected_output| {
364 var emit_node = stage_node.start("emit", null);
365 emit_node.activate();
366 var new_zir_module = try zir.emit(allocator, module);
367 defer new_zir_module.deinit(allocator);
368 emit_node.end();
369
370 var write_node = stage_node.start("write", null);
371 write_node.activate();
372 var out_zir = std.ArrayList(u8).init(allocator);
373 defer out_zir.deinit();
374 try new_zir_module.writeToStream(allocator, out_zir.outStream());
375 write_node.end();
376
377 std.testing.expectEqualSlices(u8, expected_output, out_zir.items);
378 },
379 else => return error.unimplemented,
380 }
381 }
382 }
383
218384 fn runOneZIRCmpOutputCase(
219385 self: *TestContext,
220386 allocator: *Allocator,
......@@ -426,6 +592,10 @@ pub const TestContext = struct {
426592 e.* = false;
427593 }
428594
595 // TODO: check the input error list in sequential order, manually
596 // incrementing indices when needed. This would allow deduplicating the
597 // following three blocks into one, and the restriction it imposes on
598 // test writers is one that naturally flows anyways.
429599 {
430600 var i = module.failed_files.iterator();
431601 while (i.next()) |pair| {