commit ac6bf53069d3dccc3236cee05d5da026642ee4d4 (tree)
parent 0cfe8e5d6ff06eed0cde6aed0c009a58ceffc395
Author: Andrew Kelley <andrew@ziglang.org>
Date: Sat, 27 Jun 2020 21:54:11 -0400
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
Diffstat:
6 files changed, 345 insertions(+), 122 deletions(-)
diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig
@@ -33,6 +33,9 @@ bin_file_path: []const u8,
/// Decl pointers to details about them being exported.
/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.
decl_exports: std.AutoHashMap(*Decl, []*Export),
+/// We track which export is associated with the given symbol name for quick
+/// detection of symbol collisions.
+symbol_exports: std.StringHashMap(*Export),
/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
/// is performing the export of another Decl.
@@ -777,6 +780,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
.optimize_mode = options.optimize_mode,
.decl_table = DeclTable.init(gpa),
.decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa),
+ .symbol_exports = std.StringHashMap(*Export).init(gpa),
.export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa),
.failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa),
.failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa),
@@ -834,6 +838,7 @@ pub fn deinit(self: *Module) void {
}
self.export_owners.deinit();
}
+ self.symbol_exports.deinit();
self.root_scope.destroy(allocator);
self.* = undefined;
}
@@ -1732,8 +1737,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
for (decls) |src_decl, decl_i| {
if (src_decl.cast(ast.Node.FnProto)) |fn_proto| {
// We will create a Decl for it regardless of analysis status.
- const name_tok = fn_proto.name_token orelse
- @panic("TODO handle missing function name in the parser");
+ const name_tok = fn_proto.name_token orelse {
+ @panic("TODO missing function name");
+ };
+
const name_loc = tree.token_locs[name_tok];
const name = tree.tokenSliceLoc(name_loc);
const name_hash = root_scope.fullyQualifiedNameHash(name);
@@ -1743,10 +1750,16 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
// Update the AST Node index of the decl, even if its contents are unchanged, it may
// have been re-ordered.
decl.src_index = decl_i;
- deleted_decls.removeAssertDiscard(decl);
- if (!srcHashEql(decl.contents_hash, contents_hash)) {
- try self.markOutdatedDecl(decl);
- decl.contents_hash = contents_hash;
+ if (deleted_decls.remove(decl) == null) {
+ decl.analysis = .sema_failure;
+ const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name});
+ errdefer err_msg.destroy(self.allocator);
+ try self.failed_decls.putNoClobber(decl, err_msg);
+ } else {
+ if (!srcHashEql(decl.contents_hash, contents_hash)) {
+ try self.markOutdatedDecl(decl);
+ decl.contents_hash = contents_hash;
+ }
}
} else {
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 {
}
self.bin_file.deleteExport(exp.link);
+ if (self.failed_exports.remove(exp)) |entry| {
+ entry.value.destroy(self.allocator);
+ }
+ _ = self.symbol_exports.remove(exp.options.name);
self.allocator.destroy(exp);
}
self.allocator.free(kv.value);
@@ -2130,6 +2147,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
.Fn => {},
else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}),
}
+
try self.decl_exports.ensureCapacity(self.decl_exports.size + 1);
try self.export_owners.ensureCapacity(self.export_owners.size + 1);
@@ -2165,6 +2183,20 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
de_gop.kv.value[de_gop.kv.value.len - 1] = new_export;
errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1);
+ if (self.symbol_exports.get(symbol_name)) |_| {
+ try self.failed_exports.ensureCapacity(self.failed_exports.size + 1);
+ self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create(
+ self.allocator,
+ src,
+ "exported symbol collision: {}",
+ .{symbol_name},
+ ));
+ // TODO: add a note
+ new_export.status = .failed;
+ return;
+ }
+
+ try self.symbol_exports.putNoClobber(symbol_name, new_export);
self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => {
diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig
@@ -21,9 +21,10 @@ const ErrorMsg = struct {
};
pub const TestContext = struct {
- zir_cases: std.ArrayList(Case),
+ /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases)
+ cases: std.ArrayList(Case),
- pub const ZIRUpdate = struct {
+ pub const Update = struct {
/// The input to the current update. We simulate an incremental update
/// with the file's contents changed to this value each update.
///
@@ -33,35 +34,43 @@ pub const TestContext = struct {
/// effects of the incremental compilation.
src: [:0]const u8,
case: union(enum) {
- /// A transformation update transforms the input ZIR and tests against
+ /// A transformation update transforms the input and tests against
/// the expected output ZIR.
Transformation: [:0]const u8,
/// An error update attempts to compile bad code, and ensures that it
/// fails to compile, and for the expected reasons.
/// A slice containing the expected errors *in sequential order*.
Error: []const ErrorMsg,
- /// An execution update compiles and runs the input ZIR, feeding in
- /// provided input and ensuring that the stdout match what is expected.
+ /// An execution update compiles and runs the input, testing the
+ /// stdout against the expected results
+ /// This is a slice containing the expected message.
Execution: []const u8,
},
};
- /// A Case consists of a set of *updates*. A update can transform ZIR,
- /// compile it, ensure that compilation fails, and more. The same Module is
- /// used for each update, so each update's source is treated as a single file
- /// being updated by the test harness and incrementally compiled.
+ pub const TestType = enum {
+ Zig,
+ ZIR,
+ };
+
+ /// A Case consists of a set of *updates*. The same Module is used for each
+ /// update, so each update's source is treated as a single file being
+ /// updated by the test harness and incrementally compiled.
pub const Case = struct {
+ /// The name of the test case. This is shown if a test fails, and
+ /// otherwise ignored.
name: []const u8,
- /// The platform the ZIR targets. For non-native platforms, an emulator
+ /// The platform the test targets. For non-native platforms, an emulator
/// such as QEMU is required for tests to complete.
target: std.zig.CrossTarget,
- updates: std.ArrayList(ZIRUpdate),
+ /// In order to be able to run e.g. Execution updates, this must be set
+ /// to Executable.
output_mode: std.builtin.OutputMode,
- /// Either ".zir" or ".zig"
- extension: [4]u8,
+ updates: std.ArrayList(Update),
+ extension: TestType,
- /// Adds a subcase in which the module is updated with new ZIR, and the
- /// resulting ZIR is validated.
+ /// Adds a subcase in which the module is updated with `src`, and the
+ /// resulting ZIR is validated against `result`.
pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
self.updates.append(.{
.src = src,
@@ -69,6 +78,8 @@ pub const TestContext = struct {
}) catch unreachable;
}
+ /// Adds a subcase in which the module is updated with `src`, compiled,
+ /// run, and the output is tested against `result`.
pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
self.updates.append(.{
.src = src,
@@ -76,31 +87,31 @@ pub const TestContext = struct {
}) catch unreachable;
}
- /// Adds a subcase in which the module is updated with invalid ZIR, and
- /// ensures that compilation fails for the expected reasons.
- ///
- /// Errors must be specified in sequential order.
+ /// Adds a subcase in which the module is updated with `src`, which
+ /// should contain invalid input, and ensures that compilation fails
+ /// for the expected reasons, given in sequential order in `errors` in
+ /// the form `:line:column: error: message`.
pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {
var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable;
for (errors) |e, i| {
if (e[0] != ':') {
- std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});
+ @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
}
var cur = e[1..];
var line_index = std.mem.indexOf(u8, cur, ":");
if (line_index == null) {
- std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});
+ @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
}
const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number");
cur = cur[line_index.? + 1 ..];
const column_index = std.mem.indexOf(u8, cur, ":");
if (column_index == null) {
- std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});
+ @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
}
const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number");
cur = cur[column_index.? + 2 ..];
if (!std.mem.eql(u8, cur[0..7], "error: ")) {
- std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{});
+ @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
}
const msg = cur[7..];
@@ -116,123 +127,245 @@ pub const TestContext = struct {
}
self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable;
}
+
+ /// Adds a subcase in which the module is updated with `src`, and
+ /// asserts that it compiles without issue
+ pub fn compiles(self: *Case, src: [:0]const u8) void {
+ self.addError(src, &[_][]const u8{});
+ }
};
- pub fn addExeZIR(
+ pub fn addExe(
ctx: *TestContext,
name: []const u8,
target: std.zig.CrossTarget,
+ T: TestType,
) *Case {
- const case = Case{
+ ctx.cases.append(Case{
.name = name,
.target = target,
- .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),
+ .updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Exe,
- .extension = ".zir".*,
- };
- ctx.zir_cases.append(case) catch unreachable;
- return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
+ .extension = T,
+ }) catch unreachable;
+ return &ctx.cases.items[ctx.cases.items.len - 1];
}
- pub fn addObjZIR(
+ /// Adds a test case for Zig input, producing an executable
+ pub fn exe(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
+ return ctx.addExe(name, target, .Zig);
+ }
+
+ /// Adds a test case for ZIR input, producing an executable
+ pub fn exeZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
+ return ctx.addExe(name, target, .ZIR);
+ }
+
+ pub fn addObj(
ctx: *TestContext,
name: []const u8,
target: std.zig.CrossTarget,
+ T: TestType,
) *Case {
- const case = Case{
+ ctx.cases.append(Case{
.name = name,
.target = target,
- .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),
+ .updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Obj,
- .extension = ".zir".*,
- };
- ctx.zir_cases.append(case) catch unreachable;
- return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
+ .extension = T,
+ }) catch unreachable;
+ return &ctx.cases.items[ctx.cases.items.len - 1];
}
- pub fn addExe(
+ /// Adds a test case for Zig input, producing an object file
+ pub fn obj(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
+ return ctx.addObj(name, target, .Zig);
+ }
+
+ /// Adds a test case for ZIR input, producing an object file
+ pub fn objZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case {
+ return ctx.addObj(name, target, .ZIR);
+ }
+
+ pub fn addCompareOutput(
ctx: *TestContext,
name: []const u8,
- target: std.zig.CrossTarget,
- ) *Case {
- const case = Case{
- .name = name,
- .target = target,
- .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),
- .output_mode = .Exe,
- .extension = ".zig".*,
- };
- ctx.zir_cases.append(case) catch unreachable;
- return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
+ T: TestType,
+ src: [:0]const u8,
+ expected_stdout: []const u8,
+ ) void {
+ ctx.addExe(name, .{}, T).addCompareOutput(src, expected_stdout);
}
- pub fn addObj(
+ /// Adds a test case that compiles the Zig source given in `src`, executes
+ /// it, runs it, and tests the output against `expected_stdout`
+ pub fn compareOutput(
ctx: *TestContext,
name: []const u8,
- target: std.zig.CrossTarget,
- ) *Case {
- const case = Case{
- .name = name,
- .target = target,
- .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator),
- .output_mode = .Obj,
- .extension = ".zig".*,
- };
- ctx.zir_cases.append(case) catch unreachable;
- return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1];
+ src: [:0]const u8,
+ expected_stdout: []const u8,
+ ) void {
+ return ctx.addCompareOutput(name, .Zig, src, expected_stdout);
}
- pub fn addZIRCompareOutput(
+ /// Adds a test case that compiles the ZIR source given in `src`, executes
+ /// it, runs it, and tests the output against `expected_stdout`
+ pub fn compareOutputZIR(
ctx: *TestContext,
name: []const u8,
src: [:0]const u8,
expected_stdout: []const u8,
) void {
- var c = ctx.addExeZIR(name, .{});
- c.addCompareOutput(src, expected_stdout);
+ ctx.addCompareOutput(name, .ZIR, src, expected_stdout);
}
- pub fn addCompareOutput(
+ pub fn addTransform(
ctx: *TestContext,
name: []const u8,
+ target: std.zig.CrossTarget,
+ T: TestType,
src: [:0]const u8,
- expected_stdout: []const u8,
+ result: [:0]const u8,
+ ) void {
+ ctx.addObj(name, target, T).addTransform(src, result);
+ }
+
+ /// Adds a test case that compiles the Zig given in `src` to ZIR and tests
+ /// the ZIR against `result`
+ pub fn transform(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ src: [:0]const u8,
+ result: [:0]const u8,
) void {
- var c = ctx.addExe(name, .{});
- c.addCompareOutput(src, expected_stdout);
+ ctx.addTransform(name, target, .Zig, src, result);
}
- pub fn addZIRTransform(
+ /// Adds a test case that cleans up the ZIR source given in `src`, and
+ /// tests the resulting ZIR against `result`
+ pub fn transformZIR(
ctx: *TestContext,
name: []const u8,
target: std.zig.CrossTarget,
src: [:0]const u8,
result: [:0]const u8,
) void {
- var c = ctx.addObjZIR(name, target);
- c.addTransform(src, result);
+ ctx.addTransform(name, target, .ZIR, src, result);
+ }
+
+ pub fn addError(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ T: TestType,
+ src: [:0]const u8,
+ expected_errors: []const []const u8,
+ ) void {
+ ctx.addObj(name, target, T).addError(src, expected_errors);
+ }
+
+ /// Adds a test case that ensures that the Zig given in `src` fails to
+ /// compile for the expected reasons, given in sequential order in
+ /// `expected_errors` in the form `:line:column: error: message`.
+ pub fn compileError(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ src: [:0]const u8,
+ expected_errors: []const []const u8,
+ ) void {
+ ctx.addError(name, target, .Zig, src, expected_errors);
+ }
+
+ /// Adds a test case that ensures that the ZIR given in `src` fails to
+ /// compile for the expected reasons, given in sequential order in
+ /// `expected_errors` in the form `:line:column: error: message`.
+ pub fn compileErrorZIR(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ src: [:0]const u8,
+ expected_errors: []const []const u8,
+ ) void {
+ ctx.addError(name, target, .ZIR, src, expected_errors);
+ }
+
+ pub fn addCompiles(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ T: TestType,
+ src: [:0]const u8,
+ ) void {
+ ctx.addObj(name, target, T).compiles(src);
+ }
+
+ /// Adds a test case that asserts that the Zig given in `src` compiles
+ /// without any errors.
+ pub fn compiles(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ src: [:0]const u8,
+ ) void {
+ ctx.addCompiles(name, target, .Zig, src);
+ }
+
+ /// Adds a test case that asserts that the ZIR given in `src` compiles
+ /// without any errors.
+ pub fn compilesZIR(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ src: [:0]const u8,
+ ) void {
+ ctx.addCompiles(name, target, .ZIR, src);
}
- pub fn addZIRError(
+ /// Adds a test case that first ensures that the Zig given in `src` fails
+ /// to compile for the reasons given in sequential order in
+ /// `expected_errors` in the form `:line:column: error: message`, then
+ /// asserts that fixing the source (updating with `fixed_src`) isn't broken
+ /// by incremental compilation.
+ pub fn incrementalFailure(
ctx: *TestContext,
name: []const u8,
target: std.zig.CrossTarget,
src: [:0]const u8,
expected_errors: []const []const u8,
+ fixed_src: [:0]const u8,
) void {
- var c = ctx.addObjZIR(name, target);
- c.addError(src, expected_errors);
+ var case = ctx.addObj(name, target, .Zig);
+ case.addError(src, expected_errors);
+ case.compiles(fixed_src);
+ }
+
+ /// Adds a test case that first ensures that the ZIR given in `src` fails
+ /// to compile for the reasons given in sequential order in
+ /// `expected_errors` in the form `:line:column: error: message`, then
+ /// asserts that fixing the source (updating with `fixed_src`) isn't broken
+ /// by incremental compilation.
+ pub fn incrementalFailureZIR(
+ ctx: *TestContext,
+ name: []const u8,
+ target: std.zig.CrossTarget,
+ src: [:0]const u8,
+ expected_errors: []const []const u8,
+ fixed_src: [:0]const u8,
+ ) void {
+ var case = ctx.addObj(name, target, .ZIR);
+ case.addError(src, expected_errors);
+ case.compiles(fixed_src);
}
fn init() TestContext {
const allocator = std.heap.page_allocator;
- return .{
- .zir_cases = std.ArrayList(Case).init(allocator),
- };
+ return .{ .cases = std.ArrayList(Case).init(allocator) };
}
fn deinit(self: *TestContext) void {
- for (self.zir_cases.items) |c| {
+ for (self.cases.items) |c| {
for (c.updates.items) |u| {
if (u.case == .Error) {
c.updates.allocator.free(u.case.Error);
@@ -240,26 +373,28 @@ pub const TestContext = struct {
}
c.updates.deinit();
}
- self.zir_cases.deinit();
+ self.cases.deinit();
self.* = undefined;
}
fn run(self: *TestContext) !void {
var progress = std.Progress{};
- const root_node = try progress.start("zir", self.zir_cases.items.len);
+ const root_node = try progress.start("tests", self.cases.items.len);
defer root_node.end();
const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
- for (self.zir_cases.items) |case| {
+ for (self.cases.items) |case| {
std.testing.base_allocator_instance.reset();
var prg_node = root_node.start(case.name, case.updates.items.len);
prg_node.activate();
defer prg_node.end();
- // So that we can see which test case failed when the leak checker goes off.
- progress.refresh();
+ // So that we can see which test case failed when the leak checker goes off,
+ // or there's an internal error
+ progress.initial_delay_ns = 0;
+ progress.refresh_rate_ns = 0;
const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);
try self.runOneCase(std.testing.allocator, &prg_node, case, info.target);
@@ -267,17 +402,15 @@ pub const TestContext = struct {
}
}
- fn runOneCase(self: *TestContext, allocator: *Allocator, prg_node: *std.Progress.Node, case: Case, target: std.Target) !void {
+ fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case, target: std.Target) !void {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
- const root_name = "test_case";
- const tmp_src_path = try std.fmt.allocPrint(allocator, "{}{}", .{ root_name, case.extension });
- defer allocator.free(tmp_src_path);
+ const tmp_src_path = if (case.extension == .Zig) "test_case.zig" else if (case.extension == .ZIR) "test_case.zir" else unreachable;
const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
defer root_pkg.destroy();
- const bin_name = try std.zig.binNameAlloc(allocator, root_name, target, case.output_mode, null);
+ const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null);
defer allocator.free(bin_name);
var module = try Module.init(allocator, .{
@@ -299,7 +432,7 @@ pub const TestContext = struct {
defer module.deinit();
for (case.updates.items) |update, update_index| {
- var update_node = prg_node.start("update", 4);
+ var update_node = root_node.start("update", 3);
update_node.activate();
defer update_node.end();
@@ -316,6 +449,7 @@ pub const TestContext = struct {
switch (update.case) {
.Transformation => |expected_output| {
+ update_node.estimated_total_items = 5;
var emit_node = update_node.start("emit", null);
emit_node.activate();
var new_zir_module = try zir.emit(allocator, module);
@@ -329,9 +463,26 @@ pub const TestContext = struct {
try new_zir_module.writeToStream(allocator, out_zir.outStream());
write_node.end();
- std.testing.expectEqualSlices(u8, expected_output, out_zir.items);
+ var test_node = update_node.start("assert", null);
+ test_node.activate();
+ defer test_node.end();
+ if (expected_output.len != out_zir.items.len) {
+ std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
+ std.process.exit(1);
+ }
+ for (expected_output) |e, i| {
+ if (out_zir.items[i] != e) {
+ if (expected_output.len != out_zir.items.len) {
+ std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
+ std.process.exit(1);
+ }
+ }
+ }
},
.Error => |e| {
+ var test_node = update_node.start("assert", null);
+ test_node.activate();
+ defer test_node.end();
var handled_errors = try allocator.alloc(bool, e.len);
defer allocator.free(handled_errors);
for (handled_errors) |*h| {
@@ -360,6 +511,7 @@ pub const TestContext = struct {
}
},
.Execution => |expected_stdout| {
+ update_node.estimated_total_items = 4;
var exec_result = x: {
var exec_node = update_node.start("execute", null);
exec_node.activate();
@@ -376,6 +528,10 @@ pub const TestContext = struct {
.cwd_dir = tmp.dir,
});
};
+ var test_node = update_node.start("test", null);
+ test_node.activate();
+ defer test_node.end();
+
defer allocator.free(exec_result.stdout);
defer allocator.free(exec_result.stderr);
switch (exec_result.term) {
diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig
@@ -17,7 +17,7 @@ pub fn addCases(ctx: *TestContext) !void {
}
{
- var case = ctx.addExe("hello world with updates", linux_x64);
+ var case = ctx.exe("hello world with updates", linux_x64);
// Regular old hello world
case.addCompareOutput(
\\export fn _start() noreturn {
diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig
@@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{
};
pub fn addCases(ctx: *TestContext) !void {
- ctx.addZIRError("call undefined local", linux_x64,
+ ctx.compileErrorZIR("call undefined local", linux_x64,
\\@noreturn = primitive(noreturn)
\\
\\@start_fnty = fntype([], @noreturn, cc=Naked)
@@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void {
// TODO: address inconsistency in this message and the one in the next test
, &[_][]const u8{":5:13: error: unrecognized identifier: %test"});
- ctx.addZIRError("call with non-existent target", linux_x64,
+ ctx.compileErrorZIR("call with non-existent target", linux_x64,
\\@noreturn = primitive(noreturn)
\\
\\@start_fnty = fntype([], @noreturn, cc=Naked)
@@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void {
, &[_][]const u8{":5:13: error: decl 'notafunc' not found"});
// TODO: this error should occur at the call site, not the fntype decl
- ctx.addZIRError("call naked function", linux_x64,
+ ctx.compileErrorZIR("call naked function", linux_x64,
\\@noreturn = primitive(noreturn)
\\
\\@start_fnty = fntype([], @noreturn, cc=Naked)
@@ -43,56 +43,91 @@ pub fn addCases(ctx: *TestContext) !void {
\\@1 = export(@0, "start")
, &[_][]const u8{":4:9: error: unable to call function with naked calling convention"});
- // TODO: re-enable these tests.
- // https://github.com/ziglang/zig/issues/1364
- // TODO: add Zig AST -> ZIR testing pipeline
+ ctx.incrementalFailureZIR("exported symbol collision", linux_x64,
+ \\@noreturn = primitive(noreturn)
+ \\
+ \\@start_fnty = fntype([], @noreturn)
+ \\@start = fn(@start_fnty, {})
+ \\
+ \\@0 = str("_start")
+ \\@1 = export(@0, "start")
+ \\@2 = export(@0, "start")
+ , &[_][]const u8{":8:13: error: exported symbol collision: _start"},
+ \\@noreturn = primitive(noreturn)
+ \\
+ \\@start_fnty = fntype([], @noreturn)
+ \\@start = fn(@start_fnty, {})
+ \\
+ \\@0 = str("_start")
+ \\@1 = export(@0, "start")
+ );
+
+ ctx.compileError("function redefinition", linux_x64,
+ \\fn entry() void {}
+ \\fn entry() void {}
+ , &[_][]const u8{":2:4: error: redefinition of 'entry'"});
+
+ //ctx.incrementalFailure("function redefinition", linux_x64,
+ // \\fn entry() void {}
+ // \\fn entry() void {}
+ //, &[_][]const u8{":2:4: error: redefinition of 'entry'"},
+ // \\fn entry() void {}
+ //);
- //try ctx.testCompileError(
+ //// TODO: need to make sure this works with other variants of export.
+ //ctx.incrementalFailure("exported symbol collision", linux_x64,
// \\export fn entry() void {}
// \\export fn entry() void {}
- //, "1.zig", 2, 8, "exported symbol collision: 'entry'");
+ //, &[_][]const u8{":2:11: error: redefinition of 'entry'"},
+ // \\export fn entry() void {}
+ //);
+
+ // ctx.incrementalFailure("missing function name", linux_x64,
+ // \\fn() void {}
+ // , &[_][]const u8{":1:3: error: missing function name"},
+ // \\fn a() void {}
+ // );
- //try ctx.testCompileError(
- // \\fn() void {}
- //, "1.zig", 1, 1, "missing function name");
+ // TODO: re-enable these tests.
+ // https://github.com/ziglang/zig/issues/1364
- //try ctx.testCompileError(
+ //ctx.testCompileError(
// \\comptime {
// \\ return;
// \\}
//, "1.zig", 2, 5, "return expression outside function definition");
- //try ctx.testCompileError(
+ //ctx.testCompileError(
// \\export fn entry() void {
// \\ defer return;
// \\}
//, "1.zig", 2, 11, "cannot return from defer expression");
- //try ctx.testCompileError(
+ //ctx.testCompileError(
// \\export fn entry() c_int {
// \\ return 36893488147419103232;
// \\}
//, "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'");
- //try ctx.testCompileError(
+ //ctx.testCompileError(
// \\comptime {
// \\ var a: *align(4) align(4) i32 = 0;
// \\}
//, "1.zig", 2, 22, "Extra align qualifier");
- //try ctx.testCompileError(
+ //ctx.testCompileError(
// \\comptime {
// \\ var b: *const const i32 = 0;
// \\}
//, "1.zig", 2, 19, "Extra align qualifier");
- //try ctx.testCompileError(
+ //ctx.testCompileError(
// \\comptime {
// \\ var c: *volatile volatile i32 = 0;
// \\}
//, "1.zig", 2, 22, "Extra align qualifier");
- //try ctx.testCompileError(
+ //ctx.testCompileError(
// \\comptime {
// \\ var d: *allowzero allowzero i32 = 0;
// \\}
diff --git a/test/stage2/test.zig b/test/stage2/test.zig
@@ -3,5 +3,5 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
pub fn addCases(ctx: *TestContext) !void {
try @import("compile_errors.zig").addCases(ctx);
try @import("compare_output.zig").addCases(ctx);
- @import("zir.zig").addCases(ctx);
+ try @import("zir.zig").addCases(ctx);
}
diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig
@@ -8,8 +8,8 @@ const linux_x64 = std.zig.CrossTarget{
.os_tag = .linux,
};
-pub fn addCases(ctx: *TestContext) void {
- ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64,
+pub fn addCases(ctx: *TestContext) !void {
+ ctx.transformZIR("referencing decls which appear later in the file", linux_x64,
\\@void = primitive(void)
\\@fnty = fntype([], @void, cc=C)
\\
@@ -32,7 +32,7 @@ pub fn addCases(ctx: *TestContext) void {
\\})
\\
);
- ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64,
+ ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64,
\\@void = primitive(void)
\\@usize = primitive(usize)
\\@fnty = fntype([], @void, cc=C)
@@ -86,7 +86,7 @@ pub fn addCases(ctx: *TestContext) void {
);
{
- var case = ctx.addObjZIR("reference cycle with compile error in the cycle", linux_x64);
+ var case = ctx.objZIR("reference cycle with compile error in the cycle", linux_x64);
case.addTransform(
\\@void = primitive(void)
\\@fnty = fntype([], @void, cc=C)
@@ -207,7 +207,7 @@ pub fn addCases(ctx: *TestContext) void {
return;
}
- ctx.addZIRCompareOutput("hello world ZIR",
+ ctx.compareOutputZIR("hello world ZIR",
\\@noreturn = primitive(noreturn)
\\@void = primitive(void)
\\@usize = primitive(usize)
@@ -265,7 +265,7 @@ pub fn addCases(ctx: *TestContext) void {
\\
);
- ctx.addZIRCompareOutput("function call with no args no return value",
+ ctx.compareOutputZIR("function call with no args no return value",
\\@noreturn = primitive(noreturn)
\\@void = primitive(void)
\\@usize = primitive(usize)