authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-06-27 21:54:11-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-06-27 21:54:11-04:00
logac6bf53069d3dccc3236cee05d5da026642ee4d4
tree3298c620be500345bf275f3098b0c52011d18ba5
parent0cfe8e5d6ff06eed0cde6aed0c009a58ceffc395
parent80b70470c016ad0d1db47d0edc4d48f1f75af258
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

stage2: clean up test harness, implement symbol collision detection (#5708)

* Clean up test harness * Stage2/Testing: Add convenience wrappers * Add a `compiles` wrapper case * fix incremental compilation after error * exported symbol collision detection * function redefinition detection for Zig code * handle missing function names * Stage2/Testing: Simplify incremental compilation tests * Stage2/Testing: Update documentation * Stage2/TestHarness: Improve progress reporting * Disable test * Improve Tranform failure output

6 files changed, 345 insertions(+), 122 deletions(-)

src-self-hosted/Module.zig+38-6
...@@ -33,6 +33,9 @@ bin_file_path: []const u8,...@@ -33,6 +33,9 @@ bin_file_path: []const u8,
33/// Decl pointers to details about them being exported.33/// Decl pointers to details about them being exported.
34/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.34/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.
35decl_exports: std.AutoHashMap(*Decl, []*Export),35decl_exports: std.AutoHashMap(*Decl, []*Export),
36/// We track which export is associated with the given symbol name for quick
37/// detection of symbol collisions.
38symbol_exports: std.StringHashMap(*Export),
36/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl39/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
37/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that40/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
38/// is performing the export of another Decl.41/// is performing the export of another Decl.
...@@ -777,6 +780,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {...@@ -777,6 +780,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
777 .optimize_mode = options.optimize_mode,780 .optimize_mode = options.optimize_mode,
778 .decl_table = DeclTable.init(gpa),781 .decl_table = DeclTable.init(gpa),
779 .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa),782 .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa),
783 .symbol_exports = std.StringHashMap(*Export).init(gpa),
780 .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa),784 .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa),
781 .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa),785 .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa),
782 .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa),786 .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa),
...@@ -834,6 +838,7 @@ pub fn deinit(self: *Module) void {...@@ -834,6 +838,7 @@ pub fn deinit(self: *Module) void {
834 }838 }
835 self.export_owners.deinit();839 self.export_owners.deinit();
836 }840 }
841 self.symbol_exports.deinit();
837 self.root_scope.destroy(allocator);842 self.root_scope.destroy(allocator);
838 self.* = undefined;843 self.* = undefined;
839}844}
...@@ -1732,8 +1737,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {...@@ -1732,8 +1737,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1732 for (decls) |src_decl, decl_i| {1737 for (decls) |src_decl, decl_i| {
1733 if (src_decl.cast(ast.Node.FnProto)) |fn_proto| {1738 if (src_decl.cast(ast.Node.FnProto)) |fn_proto| {
1734 // We will create a Decl for it regardless of analysis status.1739 // We will create a Decl for it regardless of analysis status.
1735 const name_tok = fn_proto.name_token orelse1740 const name_tok = fn_proto.name_token orelse {
1736 @panic("TODO handle missing function name in the parser");1741 @panic("TODO missing function name");
1742 };
1743
1737 const name_loc = tree.token_locs[name_tok];1744 const name_loc = tree.token_locs[name_tok];
1738 const name = tree.tokenSliceLoc(name_loc);1745 const name = tree.tokenSliceLoc(name_loc);
1739 const name_hash = root_scope.fullyQualifiedNameHash(name);1746 const name_hash = root_scope.fullyQualifiedNameHash(name);
...@@ -1743,10 +1750,16 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {...@@ -1743,10 +1750,16 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1743 // Update the AST Node index of the decl, even if its contents are unchanged, it may1750 // Update the AST Node index of the decl, even if its contents are unchanged, it may
1744 // have been re-ordered.1751 // have been re-ordered.
1745 decl.src_index = decl_i;1752 decl.src_index = decl_i;
1746 deleted_decls.removeAssertDiscard(decl);1753 if (deleted_decls.remove(decl) == null) {
1747 if (!srcHashEql(decl.contents_hash, contents_hash)) {1754 decl.analysis = .sema_failure;
1748 try self.markOutdatedDecl(decl);1755 const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name});
1749 decl.contents_hash = contents_hash;1756 errdefer err_msg.destroy(self.allocator);
1757 try self.failed_decls.putNoClobber(decl, err_msg);
1758 } else {
1759 if (!srcHashEql(decl.contents_hash, contents_hash)) {
1760 try self.markOutdatedDecl(decl);
1761 decl.contents_hash = contents_hash;
1762 }
1750 }1763 }
1751 } else {1764 } else {
1752 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);1765 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
...@@ -1895,6 +1908,10 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {...@@ -1895,6 +1908,10 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {
1895 }1908 }
18961909
1897 self.bin_file.deleteExport(exp.link);1910 self.bin_file.deleteExport(exp.link);
1911 if (self.failed_exports.remove(exp)) |entry| {
1912 entry.value.destroy(self.allocator);
1913 }
1914 _ = self.symbol_exports.remove(exp.options.name);
1898 self.allocator.destroy(exp);1915 self.allocator.destroy(exp);
1899 }1916 }
1900 self.allocator.free(kv.value);1917 self.allocator.free(kv.value);
...@@ -2130,6 +2147,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const...@@ -2130,6 +2147,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
2130 .Fn => {},2147 .Fn => {},
2131 else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}),2148 else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}),
2132 }2149 }
2150
2133 try self.decl_exports.ensureCapacity(self.decl_exports.size + 1);2151 try self.decl_exports.ensureCapacity(self.decl_exports.size + 1);
2134 try self.export_owners.ensureCapacity(self.export_owners.size + 1);2152 try self.export_owners.ensureCapacity(self.export_owners.size + 1);
21352153
...@@ -2165,6 +2183,20 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const...@@ -2165,6 +2183,20 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
2165 de_gop.kv.value[de_gop.kv.value.len - 1] = new_export;2183 de_gop.kv.value[de_gop.kv.value.len - 1] = new_export;
2166 errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1);2184 errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1);
21672185
2186 if (self.symbol_exports.get(symbol_name)) |_| {
2187 try self.failed_exports.ensureCapacity(self.failed_exports.size + 1);
2188 self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create(
2189 self.allocator,
2190 src,
2191 "exported symbol collision: {}",
2192 .{symbol_name},
2193 ));
2194 // TODO: add a note
2195 new_export.status = .failed;
2196 return;
2197 }
2198
2199 try self.symbol_exports.putNoClobber(symbol_name, new_export);
2168 self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) {2200 self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) {
2169 error.OutOfMemory => return error.OutOfMemory,2201 error.OutOfMemory => return error.OutOfMemory,
2170 else => {2202 else => {
src-self-hosted/test.zig+246-90
...@@ -21,9 +21,10 @@ const ErrorMsg = struct {...@@ -21,9 +21,10 @@ const ErrorMsg = struct {
21};21};
2222
23pub const TestContext = struct {23pub const TestContext = struct {
24 zir_cases: std.ArrayList(Case),24 /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases)
25 cases: std.ArrayList(Case),
2526
26 pub const ZIRUpdate = struct {27 pub const Update = struct {
27 /// The input to the current update. We simulate an incremental update28 /// The input to the current update. We simulate an incremental update
28 /// with the file's contents changed to this value each update.29 /// with the file's contents changed to this value each update.
29 ///30 ///
...@@ -33,35 +34,43 @@ pub const TestContext = struct {...@@ -33,35 +34,43 @@ pub const TestContext = struct {
33 /// effects of the incremental compilation.34 /// effects of the incremental compilation.
34 src: [:0]const u8,35 src: [:0]const u8,
35 case: union(enum) {36 case: union(enum) {
36 /// A transformation update transforms the input ZIR and tests against37 /// A transformation update transforms the input and tests against
37 /// the expected output ZIR.38 /// the expected output ZIR.
38 Transformation: [:0]const u8,39 Transformation: [:0]const u8,
39 /// An error update attempts to compile bad code, and ensures that it40 /// An error update attempts to compile bad code, and ensures that it
40 /// fails to compile, and for the expected reasons.41 /// fails to compile, and for the expected reasons.
41 /// A slice containing the expected errors *in sequential order*.42 /// A slice containing the expected errors *in sequential order*.
42 Error: []const ErrorMsg,43 Error: []const ErrorMsg,
43 /// An execution update compiles and runs the input ZIR, feeding in44 /// An execution update compiles and runs the input, testing the
44 /// provided input and ensuring that the stdout match what is expected.45 /// stdout against the expected results
46 /// This is a slice containing the expected message.
45 Execution: []const u8,47 Execution: []const u8,
46 },48 },
47 };49 };
4850
49 /// A Case consists of a set of *updates*. A update can transform ZIR,51 pub const TestType = enum {
50 /// compile it, ensure that compilation fails, and more. The same Module is52 Zig,
51 /// used for each update, so each update's source is treated as a single file53 ZIR,
52 /// being updated by the test harness and incrementally compiled.54 };
55
56 /// A Case consists of a set of *updates*. The same Module is used for each
57 /// update, so each update's source is treated as a single file being
58 /// updated by the test harness and incrementally compiled.
53 pub const Case = struct {59 pub const Case = struct {
60 /// The name of the test case. This is shown if a test fails, and
61 /// otherwise ignored.
54 name: []const u8,62 name: []const u8,
55 /// The platform the ZIR targets. For non-native platforms, an emulator63 /// The platform the test targets. For non-native platforms, an emulator
56 /// such as QEMU is required for tests to complete.64 /// such as QEMU is required for tests to complete.
57 target: std.zig.CrossTarget,65 target: std.zig.CrossTarget,
58 updates: std.ArrayList(ZIRUpdate),66 /// In order to be able to run e.g. Execution updates, this must be set
67 /// to Executable.
59 output_mode: std.builtin.OutputMode,68 output_mode: std.builtin.OutputMode,
60 /// Either ".zir" or ".zig"69 updates: std.ArrayList(Update),
61 extension: [4]u8,70 extension: TestType,
6271
63 /// Adds a subcase in which the module is updated with new ZIR, and the72 /// Adds a subcase in which the module is updated with `src`, and the
64 /// resulting ZIR is validated.73 /// resulting ZIR is validated against `result`.
65 pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void {74 pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
66 self.updates.append(.{75 self.updates.append(.{
67 .src = src,76 .src = src,
...@@ -69,6 +78,8 @@ pub const TestContext = struct {...@@ -69,6 +78,8 @@ pub const TestContext = struct {
69 }) catch unreachable;78 }) catch unreachable;
70 }79 }
7180
81 /// Adds a subcase in which the module is updated with `src`, compiled,
82 /// run, and the output is tested against `result`.
72 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {83 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
73 self.updates.append(.{84 self.updates.append(.{
74 .src = src,85 .src = src,
...@@ -76,31 +87,31 @@ pub const TestContext = struct {...@@ -76,31 +87,31 @@ pub const TestContext = struct {
76 }) catch unreachable;87 }) catch unreachable;
77 }88 }
7889
79 /// Adds a subcase in which the module is updated with invalid ZIR, and90 /// Adds a subcase in which the module is updated with `src`, which
80 /// ensures that compilation fails for the expected reasons.91 /// should contain invalid input, and ensures that compilation fails
81 ///92 /// for the expected reasons, given in sequential order in `errors` in
82 /// Errors must be specified in sequential order.93 /// the form `:line:column: error: message`.
83 pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {94 pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {
84 var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable;95 var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable;
85 for (errors) |e, i| {96 for (errors) |e, i| {
86 if (e[0] != ':') {97 if (e[0] != ':') {
87 std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});98 @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
88 }99 }
89 var cur = e[1..];100 var cur = e[1..];
90 var line_index = std.mem.indexOf(u8, cur, ":");101 var line_index = std.mem.indexOf(u8, cur, ":");
91 if (line_index == null) {102 if (line_index == null) {
92 std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});103 @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
93 }104 }
94 const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number");105 const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number");
95 cur = cur[line_index.? + 1 ..];106 cur = cur[line_index.? + 1 ..];
96 const column_index = std.mem.indexOf(u8, cur, ":");107 const column_index = std.mem.indexOf(u8, cur, ":");
97 if (column_index == null) {108 if (column_index == null) {
98 std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});109 @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
99 }110 }
100 const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number");111 const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number");
101 cur = cur[column_index.? + 2 ..];112 cur = cur[column_index.? + 2 ..];
102 if (!std.mem.eql(u8, cur[0..7], "error: ")) {113 if (!std.mem.eql(u8, cur[0..7], "error: ")) {
103 std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});114 @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
104 }115 }
105 const msg = cur[7..];116 const msg = cur[7..];
106117
...@@ -116,123 +127,245 @@ pub const TestContext = struct {...@@ -116,123 +127,245 @@ pub const TestContext = struct {
116 }127 }
117 self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable;128 self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable;
118 }129 }
130
131 /// Adds a subcase in which the module is updated with `src`, and
132 /// asserts that it compiles without issue
133 pub fn compiles(self: *Case, src: [:0]const u8) void {
134 self.addError(src, &[_][]const u8{});
135 }
119 };136 };
120137
121 pub fn addExeZIR(138 pub fn addExe(
122 ctx: *TestContext,139 ctx: *TestContext,
123 name: []const u8,140 name: []const u8,
124 target: std.zig.CrossTarget,141 target: std.zig.CrossTarget,
142 T: TestType,
125 ) *Case {143 ) *Case {
126 const case = Case{144 ctx.cases.append(Case{
127 .name = name,145 .name = name,
128 .target = target,146 .target = target,
129 .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),147 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
130 .output_mode = .Exe,148 .output_mode = .Exe,
131 .extension = ".zir".*,149 .extension = T,
132 };150 }) catch unreachable;
133 ctx.zir_cases.append(case) catch unreachable;151 return &ctx.cases.items[ctx.cases.items.len - 1];
134 return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
135 }152 }
136153
137 pub fn addObjZIR(154 /// Adds a test case for Zig input, producing an executable
155 pub fn exe(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
156 return ctx.addExe(name, target, .Zig);
157 }
158
159 /// Adds a test case for ZIR input, producing an executable
160 pub fn exeZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
161 return ctx.addExe(name, target, .ZIR);
162 }
163
164 pub fn addObj(
138 ctx: *TestContext,165 ctx: *TestContext,
139 name: []const u8,166 name: []const u8,
140 target: std.zig.CrossTarget,167 target: std.zig.CrossTarget,
168 T: TestType,
141 ) *Case {169 ) *Case {
142 const case = Case{170 ctx.cases.append(Case{
143 .name = name,171 .name = name,
144 .target = target,172 .target = target,
145 .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),173 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
146 .output_mode = .Obj,174 .output_mode = .Obj,
147 .extension = ".zir".*,175 .extension = T,
148 };176 }) catch unreachable;
149 ctx.zir_cases.append(case) catch unreachable;177 return &ctx.cases.items[ctx.cases.items.len - 1];
150 return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
151 }178 }
152179
153 pub fn addExe(180 /// Adds a test case for Zig input, producing an object file
181 pub fn obj(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
182 return ctx.addObj(name, target, .Zig);
183 }
184
185 /// Adds a test case for ZIR input, producing an object file
186 pub fn objZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
187 return ctx.addObj(name, target, .ZIR);
188 }
189
190 pub fn addCompareOutput(
154 ctx: *TestContext,191 ctx: *TestContext,
155 name: []const u8,192 name: []const u8,
156 target: std.zig.CrossTarget,193 T: TestType,
157 ) *Case {194 src: [:0]const u8,
158 const case = Case{195 expected_stdout: []const u8,
159 .name = name,196 ) void {
160 .target = target,197 ctx.addExe(name, .{}, T).addCompareOutput(src, expected_stdout);
161 .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),
162 .output_mode = .Exe,
163 .extension = ".zig".*,
164 };
165 ctx.zir_cases.append(case) catch unreachable;
166 return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
167 }198 }
168199
169 pub fn addObj(200 /// Adds a test case that compiles the Zig source given in `src`, executes
201 /// it, runs it, and tests the output against `expected_stdout`
202 pub fn compareOutput(
170 ctx: *TestContext,203 ctx: *TestContext,
171 name: []const u8,204 name: []const u8,
172 target: std.zig.CrossTarget,205 src: [:0]const u8,
173 ) *Case {206 expected_stdout: []const u8,
174 const case = Case{207 ) void {
175 .name = name,208 return ctx.addCompareOutput(name, .Zig, src, expected_stdout);
176 .target = target,
177 .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),
178 .output_mode = .Obj,
179 .extension = ".zig".*,
180 };
181 ctx.zir_cases.append(case) catch unreachable;
182 return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
183 }209 }
184210
185 pub fn addZIRCompareOutput(211 /// Adds a test case that compiles the ZIR source given in `src`, executes
212 /// it, runs it, and tests the output against `expected_stdout`
213 pub fn compareOutputZIR(
186 ctx: *TestContext,214 ctx: *TestContext,
187 name: []const u8,215 name: []const u8,
188 src: [:0]const u8,216 src: [:0]const u8,
189 expected_stdout: []const u8,217 expected_stdout: []const u8,
190 ) void {218 ) void {
191 var c = ctx.addExeZIR(name, .{});219 ctx.addCompareOutput(name, .ZIR, src, expected_stdout);
192 c.addCompareOutput(src, expected_stdout);
193 }220 }
194221
195 pub fn addCompareOutput(222 pub fn addTransform(
196 ctx: *TestContext,223 ctx: *TestContext,
197 name: []const u8,224 name: []const u8,
225 target: std.zig.CrossTarget,
226 T: TestType,
198 src: [:0]const u8,227 src: [:0]const u8,
199 expected_stdout: []const u8,228 result: [:0]const u8,
229 ) void {
230 ctx.addObj(name, target, T).addTransform(src, result);
231 }
232
233 /// Adds a test case that compiles the Zig given in `src` to ZIR and tests
234 /// the ZIR against `result`
235 pub fn transform(
236 ctx: *TestContext,
237 name: []const u8,
238 target: std.zig.CrossTarget,
239 src: [:0]const u8,
240 result: [:0]const u8,
200 ) void {241 ) void {
201 var c = ctx.addExe(name, .{});242 ctx.addTransform(name, target, .Zig, src, result);
202 c.addCompareOutput(src, expected_stdout);
203 }243 }
204244
205 pub fn addZIRTransform(245 /// Adds a test case that cleans up the ZIR source given in `src`, and
246 /// tests the resulting ZIR against `result`
247 pub fn transformZIR(
206 ctx: *TestContext,248 ctx: *TestContext,
207 name: []const u8,249 name: []const u8,
208 target: std.zig.CrossTarget,250 target: std.zig.CrossTarget,
209 src: [:0]const u8,251 src: [:0]const u8,
210 result: [:0]const u8,252 result: [:0]const u8,
211 ) void {253 ) void {
212 var c = ctx.addObjZIR(name, target);254 ctx.addTransform(name, target, .ZIR, src, result);
213 c.addTransform(src, result);255 }
256
257 pub fn addError(
258 ctx: *TestContext,
259 name: []const u8,
260 target: std.zig.CrossTarget,
261 T: TestType,
262 src: [:0]const u8,
263 expected_errors: []const []const u8,
264 ) void {
265 ctx.addObj(name, target, T).addError(src, expected_errors);
266 }
267
268 /// Adds a test case that ensures that the Zig given in `src` fails to
269 /// compile for the expected reasons, given in sequential order in
270 /// `expected_errors` in the form `:line:column: error: message`.
271 pub fn compileError(
272 ctx: *TestContext,
273 name: []const u8,
274 target: std.zig.CrossTarget,
275 src: [:0]const u8,
276 expected_errors: []const []const u8,
277 ) void {
278 ctx.addError(name, target, .Zig, src, expected_errors);
279 }
280
281 /// Adds a test case that ensures that the ZIR given in `src` fails to
282 /// compile for the expected reasons, given in sequential order in
283 /// `expected_errors` in the form `:line:column: error: message`.
284 pub fn compileErrorZIR(
285 ctx: *TestContext,
286 name: []const u8,
287 target: std.zig.CrossTarget,
288 src: [:0]const u8,
289 expected_errors: []const []const u8,
290 ) void {
291 ctx.addError(name, target, .ZIR, src, expected_errors);
292 }
293
294 pub fn addCompiles(
295 ctx: *TestContext,
296 name: []const u8,
297 target: std.zig.CrossTarget,
298 T: TestType,
299 src: [:0]const u8,
300 ) void {
301 ctx.addObj(name, target, T).compiles(src);
302 }
303
304 /// Adds a test case that asserts that the Zig given in `src` compiles
305 /// without any errors.
306 pub fn compiles(
307 ctx: *TestContext,
308 name: []const u8,
309 target: std.zig.CrossTarget,
310 src: [:0]const u8,
311 ) void {
312 ctx.addCompiles(name, target, .Zig, src);
313 }
314
315 /// Adds a test case that asserts that the ZIR given in `src` compiles
316 /// without any errors.
317 pub fn compilesZIR(
318 ctx: *TestContext,
319 name: []const u8,
320 target: std.zig.CrossTarget,
321 src: [:0]const u8,
322 ) void {
323 ctx.addCompiles(name, target, .ZIR, src);
214 }324 }
215325
216 pub fn addZIRError(326 /// Adds a test case that first ensures that the Zig given in `src` fails
327 /// to compile for the reasons given in sequential order in
328 /// `expected_errors` in the form `:line:column: error: message`, then
329 /// asserts that fixing the source (updating with `fixed_src`) isn't broken
330 /// by incremental compilation.
331 pub fn incrementalFailure(
217 ctx: *TestContext,332 ctx: *TestContext,
218 name: []const u8,333 name: []const u8,
219 target: std.zig.CrossTarget,334 target: std.zig.CrossTarget,
220 src: [:0]const u8,335 src: [:0]const u8,
221 expected_errors: []const []const u8,336 expected_errors: []const []const u8,
337 fixed_src: [:0]const u8,
222 ) void {338 ) void {
223 var c = ctx.addObjZIR(name, target);339 var case = ctx.addObj(name, target, .Zig);
224 c.addError(src, expected_errors);340 case.addError(src, expected_errors);
341 case.compiles(fixed_src);
342 }
343
344 /// Adds a test case that first ensures that the ZIR given in `src` fails
345 /// to compile for the reasons given in sequential order in
346 /// `expected_errors` in the form `:line:column: error: message`, then
347 /// asserts that fixing the source (updating with `fixed_src`) isn't broken
348 /// by incremental compilation.
349 pub fn incrementalFailureZIR(
350 ctx: *TestContext,
351 name: []const u8,
352 target: std.zig.CrossTarget,
353 src: [:0]const u8,
354 expected_errors: []const []const u8,
355 fixed_src: [:0]const u8,
356 ) void {
357 var case = ctx.addObj(name, target, .ZIR);
358 case.addError(src, expected_errors);
359 case.compiles(fixed_src);
225 }360 }
226361
227 fn init() TestContext {362 fn init() TestContext {
228 const allocator = std.heap.page_allocator;363 const allocator = std.heap.page_allocator;
229 return .{364 return .{ .cases = std.ArrayList(Case).init(allocator) };
230 .zir_cases = std.ArrayList(Case).init(allocator),
231 };
232 }365 }
233366
234 fn deinit(self: *TestContext) void {367 fn deinit(self: *TestContext) void {
235 for (self.zir_cases.items) |c| {368 for (self.cases.items) |c| {
236 for (c.updates.items) |u| {369 for (c.updates.items) |u| {
237 if (u.case == .Error) {370 if (u.case == .Error) {
238 c.updates.allocator.free(u.case.Error);371 c.updates.allocator.free(u.case.Error);
...@@ -240,26 +373,28 @@ pub const TestContext = struct {...@@ -240,26 +373,28 @@ pub const TestContext = struct {
240 }373 }
241 c.updates.deinit();374 c.updates.deinit();
242 }375 }
243 self.zir_cases.deinit();376 self.cases.deinit();
244 self.* = undefined;377 self.* = undefined;
245 }378 }
246379
247 fn run(self: *TestContext) !void {380 fn run(self: *TestContext) !void {
248 var progress = std.Progress{};381 var progress = std.Progress{};
249 const root_node = try progress.start("zir", self.zir_cases.items.len);382 const root_node = try progress.start("tests", self.cases.items.len);
250 defer root_node.end();383 defer root_node.end();
251384
252 const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});385 const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
253386
254 for (self.zir_cases.items) |case| {387 for (self.cases.items) |case| {
255 std.testing.base_allocator_instance.reset();388 std.testing.base_allocator_instance.reset();
256389
257 var prg_node = root_node.start(case.name, case.updates.items.len);390 var prg_node = root_node.start(case.name, case.updates.items.len);
258 prg_node.activate();391 prg_node.activate();
259 defer prg_node.end();392 defer prg_node.end();
260393
261 // So that we can see which test case failed when the leak checker goes off.394 // So that we can see which test case failed when the leak checker goes off,
262 progress.refresh();395 // or there's an internal error
396 progress.initial_delay_ns = 0;
397 progress.refresh_rate_ns = 0;
263398
264 const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);399 const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);
265 try self.runOneCase(std.testing.allocator, &prg_node, case, info.target);400 try self.runOneCase(std.testing.allocator, &prg_node, case, info.target);
...@@ -267,17 +402,15 @@ pub const TestContext = struct {...@@ -267,17 +402,15 @@ pub const TestContext = struct {
267 }402 }
268 }403 }
269404
270 fn runOneCase(self: *TestContext, allocator: *Allocator, prg_node: *std.Progress.Node, case: Case, target: std.Target) !void {405 fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case, target: std.Target) !void {
271 var tmp = std.testing.tmpDir(.{});406 var tmp = std.testing.tmpDir(.{});
272 defer tmp.cleanup();407 defer tmp.cleanup();
273408
274 const root_name = "test_case";409 const tmp_src_path = if (case.extension == .Zig) "test_case.zig" else if (case.extension == .ZIR) "test_case.zir" else unreachable;
275 const tmp_src_path = try std.fmt.allocPrint(allocator, "{}{}", .{ root_name, case.extension });
276 defer allocator.free(tmp_src_path);
277 const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);410 const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
278 defer root_pkg.destroy();411 defer root_pkg.destroy();
279412
280 const bin_name = try std.zig.binNameAlloc(allocator, root_name, target, case.output_mode, null);413 const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null);
281 defer allocator.free(bin_name);414 defer allocator.free(bin_name);
282415
283 var module = try Module.init(allocator, .{416 var module = try Module.init(allocator, .{
...@@ -299,7 +432,7 @@ pub const TestContext = struct {...@@ -299,7 +432,7 @@ pub const TestContext = struct {
299 defer module.deinit();432 defer module.deinit();
300433
301 for (case.updates.items) |update, update_index| {434 for (case.updates.items) |update, update_index| {
302 var update_node = prg_node.start("update", 4);435 var update_node = root_node.start("update", 3);
303 update_node.activate();436 update_node.activate();
304 defer update_node.end();437 defer update_node.end();
305438
...@@ -316,6 +449,7 @@ pub const TestContext = struct {...@@ -316,6 +449,7 @@ pub const TestContext = struct {
316449
317 switch (update.case) {450 switch (update.case) {
318 .Transformation => |expected_output| {451 .Transformation => |expected_output| {
452 update_node.estimated_total_items = 5;
319 var emit_node = update_node.start("emit", null);453 var emit_node = update_node.start("emit", null);
320 emit_node.activate();454 emit_node.activate();
321 var new_zir_module = try zir.emit(allocator, module);455 var new_zir_module = try zir.emit(allocator, module);
...@@ -329,9 +463,26 @@ pub const TestContext = struct {...@@ -329,9 +463,26 @@ pub const TestContext = struct {
329 try new_zir_module.writeToStream(allocator, out_zir.outStream());463 try new_zir_module.writeToStream(allocator, out_zir.outStream());
330 write_node.end();464 write_node.end();
331465
332 std.testing.expectEqualSlices(u8, expected_output, out_zir.items);466 var test_node = update_node.start("assert", null);
467 test_node.activate();
468 defer test_node.end();
469 if (expected_output.len != out_zir.items.len) {
470 std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
471 std.process.exit(1);
472 }
473 for (expected_output) |e, i| {
474 if (out_zir.items[i] != e) {
475 if (expected_output.len != out_zir.items.len) {
476 std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
477 std.process.exit(1);
478 }
479 }
480 }
333 },481 },
334 .Error => |e| {482 .Error => |e| {
483 var test_node = update_node.start("assert", null);
484 test_node.activate();
485 defer test_node.end();
335 var handled_errors = try allocator.alloc(bool, e.len);486 var handled_errors = try allocator.alloc(bool, e.len);
336 defer allocator.free(handled_errors);487 defer allocator.free(handled_errors);
337 for (handled_errors) |*h| {488 for (handled_errors) |*h| {
...@@ -360,6 +511,7 @@ pub const TestContext = struct {...@@ -360,6 +511,7 @@ pub const TestContext = struct {
360 }511 }
361 },512 },
362 .Execution => |expected_stdout| {513 .Execution => |expected_stdout| {
514 update_node.estimated_total_items = 4;
363 var exec_result = x: {515 var exec_result = x: {
364 var exec_node = update_node.start("execute", null);516 var exec_node = update_node.start("execute", null);
365 exec_node.activate();517 exec_node.activate();
...@@ -376,6 +528,10 @@ pub const TestContext = struct {...@@ -376,6 +528,10 @@ pub const TestContext = struct {
376 .cwd_dir = tmp.dir,528 .cwd_dir = tmp.dir,
377 });529 });
378 };530 };
531 var test_node = update_node.start("test", null);
532 test_node.activate();
533 defer test_node.end();
534
379 defer allocator.free(exec_result.stdout);535 defer allocator.free(exec_result.stdout);
380 defer allocator.free(exec_result.stderr);536 defer allocator.free(exec_result.stderr);
381 switch (exec_result.term) {537 switch (exec_result.term) {
test/stage2/compare_output.zig+1-1
...@@ -17,7 +17,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -17,7 +17,7 @@ pub fn addCases(ctx: *TestContext) !void {
17 }17 }
1818
19 {19 {
20 var case = ctx.addExe("hello world with updates", linux_x64);20 var case = ctx.exe("hello world with updates", linux_x64);
21 // Regular old hello world21 // Regular old hello world
22 case.addCompareOutput(22 case.addCompareOutput(
23 \\export fn _start() noreturn {23 \\export fn _start() noreturn {
test/stage2/compile_errors.zig+53-18
...@@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{...@@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{
9};9};
1010
11pub fn addCases(ctx: *TestContext) !void {11pub fn addCases(ctx: *TestContext) !void {
12 ctx.addZIRError("call undefined local", linux_x64,12 ctx.compileErrorZIR("call undefined local", linux_x64,
13 \\@noreturn = primitive(noreturn)13 \\@noreturn = primitive(noreturn)
14 \\14 \\
15 \\@start_fnty = fntype([], @noreturn, cc=Naked)15 \\@start_fnty = fntype([], @noreturn, cc=Naked)
...@@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void {
19 // TODO: address inconsistency in this message and the one in the next test19 // TODO: address inconsistency in this message and the one in the next test
20 , &[_][]const u8{":5:13: error: unrecognized identifier: %test"});20 , &[_][]const u8{":5:13: error: unrecognized identifier: %test"});
2121
22 ctx.addZIRError("call with non-existent target", linux_x64,22 ctx.compileErrorZIR("call with non-existent target", linux_x64,
23 \\@noreturn = primitive(noreturn)23 \\@noreturn = primitive(noreturn)
24 \\24 \\
25 \\@start_fnty = fntype([], @noreturn, cc=Naked)25 \\@start_fnty = fntype([], @noreturn, cc=Naked)
...@@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void {
31 , &[_][]const u8{":5:13: error: decl 'notafunc' not found"});31 , &[_][]const u8{":5:13: error: decl 'notafunc' not found"});
3232
33 // TODO: this error should occur at the call site, not the fntype decl33 // TODO: this error should occur at the call site, not the fntype decl
34 ctx.addZIRError("call naked function", linux_x64,34 ctx.compileErrorZIR("call naked function", linux_x64,
35 \\@noreturn = primitive(noreturn)35 \\@noreturn = primitive(noreturn)
36 \\36 \\
37 \\@start_fnty = fntype([], @noreturn, cc=Naked)37 \\@start_fnty = fntype([], @noreturn, cc=Naked)
...@@ -43,56 +43,91 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -43,56 +43,91 @@ pub fn addCases(ctx: *TestContext) !void {
43 \\@1 = export(@0, "start")43 \\@1 = export(@0, "start")
44 , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"});44 , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"});
4545
46 // TODO: re-enable these tests.46 ctx.incrementalFailureZIR("exported symbol collision", linux_x64,
47 // https://github.com/ziglang/zig/issues/136447 \\@noreturn = primitive(noreturn)
48 // TODO: add Zig AST -> ZIR testing pipeline48 \\
49 \\@start_fnty = fntype([], @noreturn)
50 \\@start = fn(@start_fnty, {})
51 \\
52 \\@0 = str("_start")
53 \\@1 = export(@0, "start")
54 \\@2 = export(@0, "start")
55 , &[_][]const u8{":8:13: error: exported symbol collision: _start"},
56 \\@noreturn = primitive(noreturn)
57 \\
58 \\@start_fnty = fntype([], @noreturn)
59 \\@start = fn(@start_fnty, {})
60 \\
61 \\@0 = str("_start")
62 \\@1 = export(@0, "start")
63 );
64
65 ctx.compileError("function redefinition", linux_x64,
66 \\fn entry() void {}
67 \\fn entry() void {}
68 , &[_][]const u8{":2:4: error: redefinition of 'entry'"});
69
70 //ctx.incrementalFailure("function redefinition", linux_x64,
71 // \\fn entry() void {}
72 // \\fn entry() void {}
73 //, &[_][]const u8{":2:4: error: redefinition of 'entry'"},
74 // \\fn entry() void {}
75 //);
4976
50 //try ctx.testCompileError(77 //// TODO: need to make sure this works with other variants of export.
78 //ctx.incrementalFailure("exported symbol collision", linux_x64,
51 // \\export fn entry() void {}79 // \\export fn entry() void {}
52 // \\export fn entry() void {}80 // \\export fn entry() void {}
53 //, "1.zig", 2, 8, "exported symbol collision: 'entry'");81 //, &[_][]const u8{":2:11: error: redefinition of 'entry'"},
82 // \\export fn entry() void {}
83 //);
84
85 // ctx.incrementalFailure("missing function name", linux_x64,
86 // \\fn() void {}
87 // , &[_][]const u8{":1:3: error: missing function name"},
88 // \\fn a() void {}
89 // );
5490
55 //try ctx.testCompileError(91 // TODO: re-enable these tests.
56 // \\fn() void {}92 // https://github.com/ziglang/zig/issues/1364
57 //, "1.zig", 1, 1, "missing function name");
5893
59 //try ctx.testCompileError(94 //ctx.testCompileError(
60 // \\comptime {95 // \\comptime {
61 // \\ return;96 // \\ return;
62 // \\}97 // \\}
63 //, "1.zig", 2, 5, "return expression outside function definition");98 //, "1.zig", 2, 5, "return expression outside function definition");
6499
65 //try ctx.testCompileError(100 //ctx.testCompileError(
66 // \\export fn entry() void {101 // \\export fn entry() void {
67 // \\ defer return;102 // \\ defer return;
68 // \\}103 // \\}
69 //, "1.zig", 2, 11, "cannot return from defer expression");104 //, "1.zig", 2, 11, "cannot return from defer expression");
70105
71 //try ctx.testCompileError(106 //ctx.testCompileError(
72 // \\export fn entry() c_int {107 // \\export fn entry() c_int {
73 // \\ return 36893488147419103232;108 // \\ return 36893488147419103232;
74 // \\}109 // \\}
75 //, "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'");110 //, "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'");
76111
77 //try ctx.testCompileError(112 //ctx.testCompileError(
78 // \\comptime {113 // \\comptime {
79 // \\ var a: *align(4) align(4) i32 = 0;114 // \\ var a: *align(4) align(4) i32 = 0;
80 // \\}115 // \\}
81 //, "1.zig", 2, 22, "Extra align qualifier");116 //, "1.zig", 2, 22, "Extra align qualifier");
82117
83 //try ctx.testCompileError(118 //ctx.testCompileError(
84 // \\comptime {119 // \\comptime {
85 // \\ var b: *const const i32 = 0;120 // \\ var b: *const const i32 = 0;
86 // \\}121 // \\}
87 //, "1.zig", 2, 19, "Extra align qualifier");122 //, "1.zig", 2, 19, "Extra align qualifier");
88123
89 //try ctx.testCompileError(124 //ctx.testCompileError(
90 // \\comptime {125 // \\comptime {
91 // \\ var c: *volatile volatile i32 = 0;126 // \\ var c: *volatile volatile i32 = 0;
92 // \\}127 // \\}
93 //, "1.zig", 2, 22, "Extra align qualifier");128 //, "1.zig", 2, 22, "Extra align qualifier");
94129
95 //try ctx.testCompileError(130 //ctx.testCompileError(
96 // \\comptime {131 // \\comptime {
97 // \\ var d: *allowzero allowzero i32 = 0;132 // \\ var d: *allowzero allowzero i32 = 0;
98 // \\}133 // \\}
test/stage2/test.zig+1-1
...@@ -3,5 +3,5 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext;...@@ -3,5 +3,5 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
3pub fn addCases(ctx: *TestContext) !void {3pub fn addCases(ctx: *TestContext) !void {
4 try @import("compile_errors.zig").addCases(ctx);4 try @import("compile_errors.zig").addCases(ctx);
5 try @import("compare_output.zig").addCases(ctx);5 try @import("compare_output.zig").addCases(ctx);
6 @import("zir.zig").addCases(ctx);6 try @import("zir.zig").addCases(ctx);
7}7}
test/stage2/zir.zig+6-6
...@@ -8,8 +8,8 @@ const linux_x64 = std.zig.CrossTarget{...@@ -8,8 +8,8 @@ const linux_x64 = std.zig.CrossTarget{
8 .os_tag = .linux,8 .os_tag = .linux,
9};9};
1010
11pub fn addCases(ctx: *TestContext) void {11pub fn addCases(ctx: *TestContext) !void {
12 ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64,12 ctx.transformZIR("referencing decls which appear later in the file", linux_x64,
13 \\@void = primitive(void)13 \\@void = primitive(void)
14 \\@fnty = fntype([], @void, cc=C)14 \\@fnty = fntype([], @void, cc=C)
15 \\15 \\
...@@ -32,7 +32,7 @@ pub fn addCases(ctx: *TestContext) void {...@@ -32,7 +32,7 @@ pub fn addCases(ctx: *TestContext) void {
32 \\})32 \\})
33 \\33 \\
34 );34 );
35 ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64,35 ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64,
36 \\@void = primitive(void)36 \\@void = primitive(void)
37 \\@usize = primitive(usize)37 \\@usize = primitive(usize)
38 \\@fnty = fntype([], @void, cc=C)38 \\@fnty = fntype([], @void, cc=C)
...@@ -86,7 +86,7 @@ pub fn addCases(ctx: *TestContext) void {...@@ -86,7 +86,7 @@ pub fn addCases(ctx: *TestContext) void {
86 );86 );
8787
88 {88 {
89 var case = ctx.addObjZIR("reference cycle with compile error in the cycle", linux_x64);89 var case = ctx.objZIR("reference cycle with compile error in the cycle", linux_x64);
90 case.addTransform(90 case.addTransform(
91 \\@void = primitive(void)91 \\@void = primitive(void)
92 \\@fnty = fntype([], @void, cc=C)92 \\@fnty = fntype([], @void, cc=C)
...@@ -207,7 +207,7 @@ pub fn addCases(ctx: *TestContext) void {...@@ -207,7 +207,7 @@ pub fn addCases(ctx: *TestContext) void {
207 return;207 return;
208 }208 }
209209
210 ctx.addZIRCompareOutput("hello world ZIR",210 ctx.compareOutputZIR("hello world ZIR",
211 \\@noreturn = primitive(noreturn)211 \\@noreturn = primitive(noreturn)
212 \\@void = primitive(void)212 \\@void = primitive(void)
213 \\@usize = primitive(usize)213 \\@usize = primitive(usize)
...@@ -265,7 +265,7 @@ pub fn addCases(ctx: *TestContext) void {...@@ -265,7 +265,7 @@ pub fn addCases(ctx: *TestContext) void {
265 \\265 \\
266 );266 );
267267
268 ctx.addZIRCompareOutput("function call with no args no return value",268 ctx.compareOutputZIR("function call with no args no return value",
269 \\@noreturn = primitive(noreturn)269 \\@noreturn = primitive(noreturn)
270 \\@void = primitive(void)270 \\@void = primitive(void)
271 \\@usize = primitive(usize)271 \\@usize = primitive(usize)