From c92816fbefe9d789d3f3c13f319c949c8b97ad01 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:51:29 -0400 Subject: [PATCH 01/20] [Stage2/Testing] ZIR tests for expected errors --- src-self-hosted/test.zig | 142 ++++++++++++++++++++++++++++++--- test/stage2/compile_errors.zig | 21 +++++ 2 files changed, 154 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 605c973bb9..21d3572089 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -1,6 +1,7 @@ const std = @import("std"); const link = @import("link.zig"); const Module = @import("Module.zig"); +const ErrorMsg = Module.ErrorMsg; const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); @@ -18,6 +19,7 @@ test "self-hosted" { pub const TestContext = struct { zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), zir_transform_cases: std.ArrayList(ZIRTransformCase), + zir_error_cases: std.ArrayList(ZIRErrorCase), pub const ZIRCompareOutputCase = struct { name: []const u8, @@ -55,6 +57,15 @@ pub const TestContext = struct { } }; + pub const ZIRErrorCase = struct { + name: []const u8, + src: [:0]const u8, + expected_file_errors: []const ErrorMsg, + expected_decl_errors: []const ErrorMsg, + expected_export_errors: []const ErrorMsg, + cross_target: std.zig.CrossTarget, + }; + pub fn addZIRCompareOutput( ctx: *TestContext, name: []const u8, @@ -87,30 +98,38 @@ pub const TestContext = struct { }) catch unreachable; } - pub fn addZIRMulti( + pub fn addZIRError( ctx: *TestContext, name: []const u8, cross_target: std.zig.CrossTarget, - ) *ZIRTransformCase { - const case = ctx.zir_transform_cases.addOne() catch unreachable; - case.* = .{ + src: [:0]const u8, + expected_file_errors: []const ErrorMsg, + expected_decl_errors: []const ErrorMsg, + expected_export_errors: []const ErrorMsg, + ) void { + ctx.zir_error_cases.append(.{ .name = name, + .src = src, + .expected_file_errors = expected_file_errors, + .expected_decl_errors = expected_decl_errors, + .expected_export_errors = expected_export_errors, .cross_target = cross_target, - .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), - }; - return case; + }) catch unreachable; } fn init(self: *TestContext) !void { + const allocator = std.heap.page_allocator; self.* = .{ - .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(std.heap.page_allocator), - .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(std.heap.page_allocator), + .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), + .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator), + .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), }; } fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); self.zir_transform_cases.deinit(); + self.zir_error_cases.deinit(); self.* = undefined; } @@ -133,6 +152,12 @@ pub const TestContext = struct { try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, info.target); try std.testing.allocator_instance.validate(); } + for (self.zir_error_cases.items) |case| { + std.testing.base_allocator_instance.reset(); + const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); + try self.runOneZIRErrorCase(std.testing.allocator, root_node, case, info.target); + try std.testing.allocator_instance.validate(); + } } fn runOneZIRCmpOutputCase( @@ -300,6 +325,105 @@ pub const TestContext = struct { } } } + + fn runOneZIRErrorCase( + self: *TestContext, + allocator: *Allocator, + root_node: *std.Progress.Node, + case: ZIRErrorCase, + target: std.Target, + ) !void { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + var prg_node = root_node.start(case.name, 1); + prg_node.activate(); + defer prg_node.end(); + + const tmp_src_path = "test-case.zir"; + try tmp.dir.writeFile(tmp_src_path, case.src); + + const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); + defer root_pkg.destroy(); + + var module = try Module.init(allocator, .{ + .target = target, + .output_mode = .Obj, + .optimize_mode = .Debug, + .bin_file_dir = tmp.dir, + .bin_file_path = "test-case.o", + .root_pkg = root_pkg, + }); + defer module.deinit(); + + var module_node = prg_node.start("parse/analysis/codegen", null); + module_node.activate(); + const failed = f: { + module.update() catch break :f true; + break :f false; + }; + if (!failed) { + return error.DidNotFail; + } + module_node.end(); + { + var i = module.failed_files.iterator(); + var index: usize = 0; + while (i.next()) |pair| : (index += 1) { + if (index == case.expected_file_errors.len) { + return error.UnexpectedError; + } + const v1 = pair.value.*; + const v2 = case.expected_file_errors[index]; + if (v1.byte_offset != v2.byte_offset) { + std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); + return error.ExpectedErrorElsewhere; + } + if (!std.mem.eql(u8, v1.msg, v2.msg)) { + std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); + return error.ExpectedOtherError; + } + } + } + { + var i = module.failed_decls.iterator(); + var index: usize = 0; + while (i.next()) |pair| : (index += 1) { + if (index == case.expected_decl_errors.len) { + return error.UnexpectedError; + } + const v1 = pair.value.*; + const v2 = case.expected_decl_errors[index]; + if (v1.byte_offset != v2.byte_offset) { + std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); + return error.ExpectedErrorElsewhere; + } + if (!std.mem.eql(u8, v1.msg, v2.msg)) { + std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); + return error.ExpectedOtherError; + } + } + } + { + var i = module.failed_exports.iterator(); + var index: usize = 0; + while (i.next()) |pair| : (index += 1) { + if (index == case.expected_export_errors.len) { + return error.UnexpectedError; + } + const v1 = pair.value.*; + const v2 = case.expected_export_errors[index]; + if (v1.byte_offset != v2.byte_offset) { + std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); + return error.ExpectedErrorElsewhere; + } + if (!std.mem.eql(u8, v1.msg, v2.msg)) { + std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); + return error.ExpectedOtherError; + } + } + } + } }; fn debugPrintErrors(src: []const u8, errors: var) void { diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 9b8dcd91c4..e3c009eee6 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -1,8 +1,29 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext; +const std = @import("std"); + +const ErrorMsg = @import("../../src-self-hosted/Module.zig").ErrorMsg; + +const linux_x64 = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, +}; pub fn addCases(ctx: *TestContext) !void { // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 + ctx.addZIRError("test", linux_x64, + \\@noreturn = primitive(noreturn) + \\@void = primitive(void) + \\@usize = primitive(usize) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(%test, []) + \\}) + , &[_]ErrorMsg{.{ + .byte_offset = 168, + .msg = "unrecognized identifier: %test", + }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); //try ctx.testCompileError( // \\export fn entry() void {} From 67414be86b1e33f8eda4e3c33a60a7e59098a55f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sun, 24 May 2020 09:30:52 -0400 Subject: [PATCH 02/20] [Stage2/Testing] Print name of failed test --- src-self-hosted/test.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 21d3572089..9aea61281b 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -363,6 +363,7 @@ pub const TestContext = struct { break :f false; }; if (!failed) { + std.debug.warn("Test '{}' compilation succeded, error expected.\n", .{case.name}); return error.DidNotFail; } module_node.end(); From f2399db3ef3cc3951210f7047ddd688f17f7f59c Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sun, 24 May 2020 12:30:04 -0400 Subject: [PATCH 03/20] [Stage2/Testing] Don't rely on update erroring --- src-self-hosted/test.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 9aea61281b..102e6caeff 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -362,16 +362,13 @@ pub const TestContext = struct { module.update() catch break :f true; break :f false; }; - if (!failed) { - std.debug.warn("Test '{}' compilation succeded, error expected.\n", .{case.name}); - return error.DidNotFail; - } module_node.end(); { var i = module.failed_files.iterator(); var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_file_errors.len) { + std.debug.warn("Unexpected file error: {}\n", .{pair.value}); return error.UnexpectedError; } const v1 = pair.value.*; @@ -391,6 +388,7 @@ pub const TestContext = struct { var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_decl_errors.len) { + std.debug.warn("Unexpected decl error: {}\n", .{pair.value}); return error.UnexpectedError; } const v1 = pair.value.*; @@ -410,6 +408,7 @@ pub const TestContext = struct { var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_export_errors.len) { + std.debug.warn("Unexpected export error: {}\n", .{pair.value}); return error.UnexpectedError; } const v1 = pair.value.*; From e030414c163c6f9a3c2fc8291b2d5917e78688de Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 25 May 2020 16:15:39 -0400 Subject: [PATCH 04/20] [Stage2/Testing] Always finish case, note all errs --- src-self-hosted/test.zig | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 102e6caeff..b787d371ed 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -363,25 +363,30 @@ pub const TestContext = struct { break :f false; }; module_node.end(); + var err: ?anyerror = null; { var i = module.failed_files.iterator(); var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_file_errors.len) { std.debug.warn("Unexpected file error: {}\n", .{pair.value}); - return error.UnexpectedError; + err = error.UnexpectedError; } const v1 = pair.value.*; const v2 = case.expected_file_errors[index]; if (v1.byte_offset != v2.byte_offset) { std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - return error.ExpectedErrorElsewhere; + err = error.ExpectedErrorElsewhere; } if (!std.mem.eql(u8, v1.msg, v2.msg)) { std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - return error.ExpectedOtherError; + err = error.ExpectedOtherError; } } + if (index != case.expected_file_errors.len) { + std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_file_errors[index]}); + err = error.MissingError; + } } { var i = module.failed_decls.iterator(); @@ -389,19 +394,23 @@ pub const TestContext = struct { while (i.next()) |pair| : (index += 1) { if (index == case.expected_decl_errors.len) { std.debug.warn("Unexpected decl error: {}\n", .{pair.value}); - return error.UnexpectedError; + err = error.UnexpectedError; } const v1 = pair.value.*; const v2 = case.expected_decl_errors[index]; if (v1.byte_offset != v2.byte_offset) { std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - return error.ExpectedErrorElsewhere; + err = error.ExpectedErrorElsewhere; } if (!std.mem.eql(u8, v1.msg, v2.msg)) { std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - return error.ExpectedOtherError; + err = error.ExpectedOtherError; } } + if (index != case.expected_decl_errors.len) { + std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_decl_errors[index]}); + err = error.MissingError; + } } { var i = module.failed_exports.iterator(); @@ -409,19 +418,26 @@ pub const TestContext = struct { while (i.next()) |pair| : (index += 1) { if (index == case.expected_export_errors.len) { std.debug.warn("Unexpected export error: {}\n", .{pair.value}); - return error.UnexpectedError; + err = error.UnexpectedError; } const v1 = pair.value.*; const v2 = case.expected_export_errors[index]; if (v1.byte_offset != v2.byte_offset) { std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - return error.ExpectedErrorElsewhere; + err = error.ExpectedErrorElsewhere; } if (!std.mem.eql(u8, v1.msg, v2.msg)) { std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - return error.ExpectedOtherError; + err = error.ExpectedOtherError; } } + if (index != case.expected_export_errors.len) { + std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_export_errors[index]}); + err = error.MissingError; + } + } + if (err) |e| { + return e; } } }; From 2d1d012f1194e1e26f123891e12921d59ffe9e10 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 25 May 2020 16:26:51 -0400 Subject: [PATCH 05/20] [Stage2/Testing] Reduce test --- test/stage2/compile_errors.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index e3c009eee6..0133e55477 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -13,15 +13,13 @@ pub fn addCases(ctx: *TestContext) !void { // https://github.com/ziglang/zig/issues/1364 ctx.addZIRError("test", linux_x64, \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) , &[_]ErrorMsg{.{ - .byte_offset = 168, + .byte_offset = 118, .msg = "unrecognized identifier: %test", }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); From bebc1f49cf2e480fbd5dc2a409ee1bc0fe3de331 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 09:20:06 -0400 Subject: [PATCH 06/20] [Stage2/Testing] Add (failing) test --- test/stage2/compile_errors.zig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 0133e55477..89761736d9 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -23,6 +23,20 @@ pub fn addCases(ctx: *TestContext) !void { .msg = "unrecognized identifier: %test", }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + ctx.addZIRError("call with non-existent target", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(@notafunc, []) + \\}) + , &[_]ErrorMsg{ + .{ + .byte_offset = 118, + .msg = "unrecognized identifier: @notafunc", + }, + }, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + //try ctx.testCompileError( // \\export fn entry() void {} // \\export fn entry() void {} From 68cc068a3aa5df8bd1995d10a9133af1fa1ea1d3 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 11:51:48 -0400 Subject: [PATCH 07/20] [Stage2/Testing] Make API more friendly --- src-self-hosted/test.zig | 161 ++++++++++++++++++--------------- test/stage2/compile_errors.zig | 26 ++---- 2 files changed, 98 insertions(+), 89 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index b787d371ed..a2fcddb7e1 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -6,6 +6,24 @@ const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); +test "find-offset" { + std.testing.expectEqual(findOffset("hello123", 1, 8), 7); + const testmsg = + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(@notafunc, []) + \\}) + ; + std.testing.expectEqual(findOffset(testmsg, 2, 1), 32); + std.testing.expectEqual(findOffset(testmsg, 3, 1), 33); + std.testing.expectEqual(findOffset(testmsg, 3, 10), 42); + std.testing.expectEqual(findOffset(testmsg, 4, 1), 79); + std.testing.expectEqual(findOffset(testmsg, 5, 1), 106); + std.testing.expectEqual(findOffset(testmsg, 5, 13), 118); +} + test "self-hosted" { var ctx: TestContext = undefined; try ctx.init(); @@ -16,6 +34,27 @@ test "self-hosted" { try ctx.run(); } +/// Finds the raw byte offset of line:column in src. This is not a performant implementation, +/// as it should only ever be called rarely and it is better to focus on readability. +fn findOffset(src: []const u8, line: usize, column: usize) ?usize { + // "0000000001" + // 1:10 + // + var current_line: usize = 1; + var current_column: usize = 1; + for (src) |char, index| { + if (current_line == line and current_column == column) { + return index; + } + if (char == '\n') { + current_line += 1; + current_column = 0; + } + current_column += 1; + } + return null; +} + pub const TestContext = struct { zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), zir_transform_cases: std.ArrayList(ZIRTransformCase), @@ -60,9 +99,7 @@ pub const TestContext = struct { pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, - expected_file_errors: []const ErrorMsg, - expected_decl_errors: []const ErrorMsg, - expected_export_errors: []const ErrorMsg, + expected_errors: []const ErrorMsg, cross_target: std.zig.CrossTarget, }; @@ -103,16 +140,31 @@ pub const TestContext = struct { name: []const u8, cross_target: std.zig.CrossTarget, src: [:0]const u8, - expected_file_errors: []const ErrorMsg, - expected_decl_errors: []const ErrorMsg, - expected_export_errors: []const ErrorMsg, + expected_errors: []const []const u8, ) void { + var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); + for (expected_errors) |e| { + const line_index = std.mem.indexOf(u8, e, ":"); + if (line_index == null) { + std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); + } + const column_index = std.mem.indexOf(u8, e[line_index.? + 1 ..], ":"); + if (column_index == null) { + std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); + } + const line = std.fmt.parseInt(usize, e[0..line_index.?], 10) catch @panic("Unable to parse line number"); + const column = std.fmt.parseInt(usize, e[line_index.? + 1 ..][0..column_index.?], 10) catch @panic("Unable to parse column number"); + const msg = e[line_index.? + 1 ..][column_index.? + 1 ..]; + const offset = findOffset(src, line, column) orelse std.debug.panic("Unable to match {}:{} to byte offset!", .{ line, column }); + array.append(ErrorMsg{ + .byte_offset = offset, + .msg = msg, + }) catch unreachable; + } ctx.zir_error_cases.append(.{ .name = name, .src = src, - .expected_file_errors = expected_file_errors, - .expected_decl_errors = expected_decl_errors, - .expected_export_errors = expected_export_errors, + .expected_errors = array.toOwnedSlice(), .cross_target = cross_target, }) catch unreachable; } @@ -129,6 +181,9 @@ pub const TestContext = struct { fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); self.zir_transform_cases.deinit(); + for (self.zir_error_cases.items) |e| { + self.zir_error_cases.allocator.free(e.expected_errors); + } self.zir_error_cases.deinit(); self.* = undefined; } @@ -364,78 +419,40 @@ pub const TestContext = struct { }; module_node.end(); var err: ?anyerror = null; + + var handled_errors = allocator.alloc(bool, case.expected_errors.len) catch unreachable; + defer allocator.free(handled_errors); + for (handled_errors) |*e| { + e.* = false; + } + { var i = module.failed_files.iterator(); - var index: usize = 0; - while (i.next()) |pair| : (index += 1) { - if (index == case.expected_file_errors.len) { - std.debug.warn("Unexpected file error: {}\n", .{pair.value}); - err = error.UnexpectedError; - } + while (i.next()) |pair| { const v1 = pair.value.*; - const v2 = case.expected_file_errors[index]; - if (v1.byte_offset != v2.byte_offset) { - std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - err = error.ExpectedErrorElsewhere; + var handled = false; + for (case.expected_errors) |e, index| { + if (!handled_errors[index]) { + if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { + handled_errors[index] = true; + handled = true; + break; + } + } } - if (!std.mem.eql(u8, v1.msg, v2.msg)) { - std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - err = error.ExpectedOtherError; + if (!handled) { + err = error.UnexpectedError; + std.debug.warn("Unexpected file error: {}\n", .{v1}); } } - if (index != case.expected_file_errors.len) { - std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_file_errors[index]}); - err = error.MissingError; - } } - { - var i = module.failed_decls.iterator(); - var index: usize = 0; - while (i.next()) |pair| : (index += 1) { - if (index == case.expected_decl_errors.len) { - std.debug.warn("Unexpected decl error: {}\n", .{pair.value}); - err = error.UnexpectedError; - } - const v1 = pair.value.*; - const v2 = case.expected_decl_errors[index]; - if (v1.byte_offset != v2.byte_offset) { - std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - err = error.ExpectedErrorElsewhere; - } - if (!std.mem.eql(u8, v1.msg, v2.msg)) { - std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - err = error.ExpectedOtherError; - } - } - if (index != case.expected_decl_errors.len) { - std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_decl_errors[index]}); - err = error.MissingError; - } - } - { - var i = module.failed_exports.iterator(); - var index: usize = 0; - while (i.next()) |pair| : (index += 1) { - if (index == case.expected_export_errors.len) { - std.debug.warn("Unexpected export error: {}\n", .{pair.value}); - err = error.UnexpectedError; - } - const v1 = pair.value.*; - const v2 = case.expected_export_errors[index]; - if (v1.byte_offset != v2.byte_offset) { - std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - err = error.ExpectedErrorElsewhere; - } - if (!std.mem.eql(u8, v1.msg, v2.msg)) { - std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - err = error.ExpectedOtherError; - } - } - if (index != case.expected_export_errors.len) { - std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_export_errors[index]}); - err = error.MissingError; + for (handled_errors) |e, i| { + if (!e) { + err = error.MissingExpectedError; + std.debug.warn("Did not receive error: {}\n", .{case.expected_errors[i].msg}); } } + if (err) |e| { return e; } diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 89761736d9..50bfea5210 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,24 +18,16 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_]ErrorMsg{.{ - .byte_offset = 118, - .msg = "unrecognized identifier: %test", - }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + , &[_][]const u8{"5:13:unrecognized identifier: %test"}); - ctx.addZIRError("call with non-existent target", linux_x64, - \\@noreturn = primitive(noreturn) - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %0 = call(@notafunc, []) - \\}) - , &[_]ErrorMsg{ - .{ - .byte_offset = 118, - .msg = "unrecognized identifier: @notafunc", - }, - }, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + // ctx.addZIRError("call with non-existent target", linux_x64, + // \\@noreturn = primitive(noreturn) + // \\ + // \\@start_fnty = fntype([], @noreturn, cc=Naked) + // \\@start = fn(@start_fnty, { + // \\ %0 = call(@notafunc, []) + // \\}) + // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); //try ctx.testCompileError( // \\export fn entry() void {} From bf8b3a4394be6b1b512531cb5217147d8bead180 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 11:54:04 -0400 Subject: [PATCH 08/20] [Stage2/Testing] Handle decl and export errors --- src-self-hosted/test.zig | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index a2fcddb7e1..2c47a4743d 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -446,6 +446,46 @@ pub const TestContext = struct { } } } + { + var i = module.failed_decls.iterator(); + while (i.next()) |pair| { + const v1 = pair.value.*; + var handled = false; + for (case.expected_errors) |e, index| { + if (!handled_errors[index]) { + if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { + handled_errors[index] = true; + handled = true; + break; + } + } + } + if (!handled) { + err = error.UnexpectedError; + std.debug.warn("Unexpected decl error: {}\n", .{v1}); + } + } + } + { + var i = module.failed_exports.iterator(); + while (i.next()) |pair| { + const v1 = pair.value.*; + var handled = false; + for (case.expected_errors) |e, index| { + if (!handled_errors[index]) { + if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { + handled_errors[index] = true; + handled = true; + break; + } + } + } + if (!handled) { + err = error.UnexpectedError; + std.debug.warn("Unexpected export error: {}\n", .{v1}); + } + } + } for (handled_errors) |e, i| { if (!e) { err = error.MissingExpectedError; From 2ed07a36f8b225d4abd531e80b73cf97e51ae0bb Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 14:09:31 -0400 Subject: [PATCH 09/20] [Stage2/Testing] Attempt to call nakedcc function --- test/stage2/compile_errors.zig | 36 +++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 50bfea5210..865ececf18 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -11,7 +11,7 @@ const linux_x64 = std.zig.CrossTarget{ pub fn addCases(ctx: *TestContext) !void { // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - ctx.addZIRError("test", linux_x64, + ctx.addZIRError("call undefined local", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -20,14 +20,32 @@ pub fn addCases(ctx: *TestContext) !void { \\}) , &[_][]const u8{"5:13:unrecognized identifier: %test"}); - // ctx.addZIRError("call with non-existent target", linux_x64, - // \\@noreturn = primitive(noreturn) - // \\ - // \\@start_fnty = fntype([], @noreturn, cc=Naked) - // \\@start = fn(@start_fnty, { - // \\ %0 = call(@notafunc, []) - // \\}) - // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); + // TODO: fix this test + // ctx.addZIRError("call with non-existent target", linux_x64, + // \\@noreturn = primitive(noreturn) + // \\ + // \\@start_fnty = fntype([], @noreturn, cc=Naked) + // \\@start = fn(@start_fnty, { + // \\ %0 = call(@notafunc, []) + // \\}) + // \\@0 = str("_start") + // \\@1 = ref(@0) + // \\@2 = export(@1, @start) + // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); + + // TODO: this error should occur at the call site, not the fntype decl + ctx.addZIRError("call naked function", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@s = fn(@start_fnty, {}) + \\@start = fn(@start_fnty, { + \\ %0 = call(@s, []) + \\}) + \\@0 = str("_start") + \\@1 = ref(@0) + \\@2 = export(@1, @start) + , &[_][]const u8{"4:9:unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} From d4fd7c6a014f4ba41f2231b58d090d72179a5bdb Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 2 Jun 2020 15:29:59 -0400 Subject: [PATCH 10/20] Stage2/Testing: Staged test harness draft design --- src-self-hosted/test.zig | 170 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 2c47a4743d..bde4a804e5 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -56,16 +56,27 @@ fn findOffset(src: []const u8, line: usize, column: usize) ?usize { } pub const TestContext = struct { + // TODO: remove these. They are deprecated. zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), + // TODO: remove zir_transform_cases: std.ArrayList(ZIRTransformCase), + // TODO: remove zir_error_cases: std.ArrayList(ZIRErrorCase), + /// TODO: find a way to treat cases as individual tests as far as + /// `zig test` is concerned. If we have 100 tests, they should *not* be + /// considered as *one*. "ZIR" isn't really a *test*, it's a *category* of + /// tests. + zir_cases: std.ArrayList(ZIRCase), + + // TODO: remove pub const ZIRCompareOutputCase = struct { name: []const u8, src_list: []const []const u8, expected_stdout_list: []const []const u8, }; + // TODO: remove pub const ZIRTransformCase = struct { name: []const u8, cross_target: std.zig.CrossTarget, @@ -96,6 +107,7 @@ pub const TestContext = struct { } }; + // TODO: remove pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, @@ -103,6 +115,83 @@ pub const TestContext = struct { cross_target: std.zig.CrossTarget, }; + pub const ZIRStageType = enum { + /// A transformation stage transforms the input ZIR and tests against + /// the expected output + Transformation, + /// An error stage attempts to compile bad code, and ensures that it + /// fails to compile, and for the expected reasons + Error, + /// An execution stage compiles and runs the input ZIR, feeding in + /// provided input and ensuring that the outputs match what is expected + Execution, + /// A compilation stage checks that the ZIR compiles without any issues + Compiles, + }; + + pub const ZIRStage = struct { + /// The input to the current stage. We simulate an incremental update + /// with the file's contents changed to this value each stage. + /// + /// This value can change entirely between stages, which would be akin + /// to deleting the source file and creating a new one from scratch; or + /// you can keep it mostly consistent, with small changes, testing the + /// effects of the incremental compilation. + src: [:0]const u8, + case: union(ZIRStageType) { + /// The expected output ZIR + Transformation: []const u8, + /// A slice containing the expected errors *in sequential order*. + Error: []const ErrorMsg, + + /// Input to feed to the program, and expected outputs. + /// + /// If stdout, stderr, and exit_code are all null, addZIRCase will + /// discard the test. To test for successful compilation, use a + /// dedicated Compile stage instead. + Execution: struct { + stdin: ?[]const u8, + stdout: ?[]const u8, + stderr: ?[]const u8, + exit_code: ?u8, + }, + /// A Compiles test checks only that compilation of the given ZIR + /// succeeds. To test outputs, use an Execution test. It is good to + /// use a Compiles test before an Execution, as the overhead should + /// be low (due to incremental compilation) and TODO: provide a way + /// to check changed / new / etc decls in testing mode + /// (usingnamespace a debug info struct with a comptime flag?) + Compiles: void, + }, + }; + + /// A ZIRCase consists of a set of *stages*. A stage can transform ZIR, + /// compile it, ensure that compilation fails, and more. The same Module is + /// used for each stage, so each stage's source is treated as a single file + /// being updated by the test harness and incrementally compiled. + pub const ZIRCase = struct { + name: []const u8, + /// The platform the ZIR targets. For non-native platforms, an emulator + /// such as QEMU is required for tests to complete. + /// + target: std.zig.CrossTarget, + stages: []ZIRStage, + }; + + pub fn addZIRCase( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + stages: []ZIRStage, + ) !void { + const case = .{ + .name = name, + .target = target, + .stages = stages, + }; + try ctx.cases.append(case); + } + pub fn addZIRCompareOutput( ctx: *TestContext, name: []const u8, @@ -196,6 +285,14 @@ pub const TestContext = struct { const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); + for (self.zir_cases.items) |case| { + std.testing.base_allocator_instance.reset(); + const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); + try self.runOneZIRCase(std.testing.allocator, root_node, case, info.target); + try std.testing.allocator_instance.validate(); + } + + // TODO: wipe the rest of this function for (self.zir_cmp_output_cases.items) |case| { std.testing.base_allocator_instance.reset(); try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); @@ -215,6 +312,75 @@ pub const TestContext = struct { } } + fn runOneZIRCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const tmp_src_path = "test_case.zir"; + const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); + defer root_pkg.destroy(); + + var prg_node = root_node.start(case.name, case.stages.len); + prg_node.activate(); + defer prg_node.end(); + + var module = try Module.init(allocator, .{ + .target = target, + // This is an Executable, as opposed to e.g. a *library*. This does + // not mean no ZIR is generated. + // + // TODO: support tests for object file building, and library builds + // and linking. This will require a rework to support multi-file + // tests. + .output_mode = .Exe, + // TODO: support testing optimizations + .optimize_mode = .Debug, + .bin_file_dir = tmp.dir, + .bin_file_path = "test_case", + .root_pkg = root_pkg, + }); + defer module.deinit(); + + for (case.stages) |s| { + // TODO: remove before committing. This is for ZLS ;) + const stage: ZIRStage = s; + + var stage_node = prg_node.start("stage", 4); + stage_node.activate(); + defer stage_node.end(); + + var sync_node = stage_node.start("write", null); + sync_node.activate(); + try tmp.dir.writeFile(tmp_src_path, stage.src); + sync_node.end(); + + var module_node = stage_node.start("parse/analysis/codegen", null); + module_node.activate(); + try module.update(); + module_node.end(); + + switch (stage.case) { + .Transformation => |expected_output| { + var emit_node = stage_node.start("emit", null); + emit_node.activate(); + var new_zir_module = try zir.emit(allocator, module); + defer new_zir_module.deinit(allocator); + emit_node.end(); + + var write_node = stage_node.start("write", null); + write_node.activate(); + var out_zir = std.ArrayList(u8).init(allocator); + defer out_zir.deinit(); + try new_zir_module.writeToStream(allocator, out_zir.outStream()); + write_node.end(); + + std.testing.expectEqualSlices(u8, expected_output, out_zir.items); + }, + else => return error.unimplemented, + } + } + } + fn runOneZIRCmpOutputCase( self: *TestContext, allocator: *Allocator, @@ -426,6 +592,10 @@ pub const TestContext = struct { e.* = false; } + // TODO: check the input error list in sequential order, manually + // incrementing indices when needed. This would allow deduplicating the + // following three blocks into one, and the restriction it imposes on + // test writers is one that naturally flows anyways. { var i = module.failed_files.iterator(); while (i.next()) |pair| { From 6dce317fe39443278ee744e66118c6e9b6023615 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 4 Jun 2020 15:59:33 -0400 Subject: [PATCH 11/20] Stage2/Testing: Fix error tests --- src-self-hosted/test.zig | 172 +++++++++++---------------------- test/stage2/compile_errors.zig | 4 +- 2 files changed, 58 insertions(+), 118 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index bde4a804e5..feba455758 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -1,29 +1,10 @@ const std = @import("std"); const link = @import("link.zig"); const Module = @import("Module.zig"); -const ErrorMsg = Module.ErrorMsg; const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); -test "find-offset" { - std.testing.expectEqual(findOffset("hello123", 1, 8), 7); - const testmsg = - \\@noreturn = primitive(noreturn) - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %0 = call(@notafunc, []) - \\}) - ; - std.testing.expectEqual(findOffset(testmsg, 2, 1), 32); - std.testing.expectEqual(findOffset(testmsg, 3, 1), 33); - std.testing.expectEqual(findOffset(testmsg, 3, 10), 42); - std.testing.expectEqual(findOffset(testmsg, 4, 1), 79); - std.testing.expectEqual(findOffset(testmsg, 5, 1), 106); - std.testing.expectEqual(findOffset(testmsg, 5, 13), 118); -} - test "self-hosted" { var ctx: TestContext = undefined; try ctx.init(); @@ -34,26 +15,11 @@ test "self-hosted" { try ctx.run(); } -/// Finds the raw byte offset of line:column in src. This is not a performant implementation, -/// as it should only ever be called rarely and it is better to focus on readability. -fn findOffset(src: []const u8, line: usize, column: usize) ?usize { - // "0000000001" - // 1:10 - // - var current_line: usize = 1; - var current_column: usize = 1; - for (src) |char, index| { - if (current_line == line and current_column == column) { - return index; - } - if (char == '\n') { - current_line += 1; - current_column = 0; - } - current_column += 1; - } - return null; -} +const ErrorMsg = struct { + msg: []const u8, + line: u32, + column: u32, +}; pub const TestContext = struct { // TODO: remove these. They are deprecated. @@ -115,7 +81,7 @@ pub const TestContext = struct { cross_target: std.zig.CrossTarget, }; - pub const ZIRStageType = enum { + pub const ZIRUpdateType = enum { /// A transformation stage transforms the input ZIR and tests against /// the expected output Transformation, @@ -129,7 +95,7 @@ pub const TestContext = struct { Compiles, }; - pub const ZIRStage = struct { + pub const ZIRUpdate = struct { /// The input to the current stage. We simulate an incremental update /// with the file's contents changed to this value each stage. /// @@ -138,7 +104,7 @@ pub const TestContext = struct { /// you can keep it mostly consistent, with small changes, testing the /// effects of the incremental compilation. src: [:0]const u8, - case: union(ZIRStageType) { + case: union(ZIRUpdateType) { /// The expected output ZIR Transformation: []const u8, /// A slice containing the expected errors *in sequential order*. @@ -175,14 +141,14 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - stages: []ZIRStage, + stages: []ZIRUpdate, }; pub fn addZIRCase( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - stages: []ZIRStage, + stages: []ZIRUpdate, ) !void { const case = .{ .name = name, @@ -233,21 +199,34 @@ pub const TestContext = struct { ) void { var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); for (expected_errors) |e| { - const line_index = std.mem.indexOf(u8, e, ":"); + var cur = e; + const err = cur[0..7]; + if (!std.mem.eql(u8, err, "error: ")) { + std.debug.panic("Only error messages are currently supported, received {}\n", .{e}); + } + cur = cur[7..]; + var line_index = std.mem.indexOf(u8, cur, ":"); if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); } - const column_index = std.mem.indexOf(u8, e[line_index.? + 1 ..], ":"); + 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 'line:column:msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); } - const line = std.fmt.parseInt(usize, e[0..line_index.?], 10) catch @panic("Unable to parse line number"); - const column = std.fmt.parseInt(usize, e[line_index.? + 1 ..][0..column_index.?], 10) catch @panic("Unable to parse column number"); - const msg = e[line_index.? + 1 ..][column_index.? + 1 ..]; - const offset = findOffset(src, line, column) orelse std.debug.panic("Unable to match {}:{} to byte offset!", .{ line, column }); - array.append(ErrorMsg{ - .byte_offset = offset, + const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); + std.debug.assert(cur[column_index.? + 1] == ' '); + const msg = cur[column_index.? + 2 ..]; + + if (line == 0 or column == 0) { + @panic("Invalid test: error line and column must be specified starting at one!"); + } + + array.append(.{ .msg = msg, + .line = line - 1, + .column = column - 1, }) catch unreachable; } ctx.zir_error_cases.append(.{ @@ -264,6 +243,7 @@ pub const TestContext = struct { .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator), .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), + .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; } @@ -274,6 +254,7 @@ pub const TestContext = struct { self.zir_error_cases.allocator.free(e.expected_errors); } self.zir_error_cases.deinit(); + self.zir_cases.deinit(); self.* = undefined; } @@ -343,9 +324,9 @@ pub const TestContext = struct { for (case.stages) |s| { // TODO: remove before committing. This is for ZLS ;) - const stage: ZIRStage = s; + const stage: ZIRUpdate = s; - var stage_node = prg_node.start("stage", 4); + var stage_node = prg_node.start("update", 4); stage_node.activate(); defer stage_node.end(); @@ -592,74 +573,33 @@ pub const TestContext = struct { e.* = false; } - // TODO: check the input error list in sequential order, manually - // incrementing indices when needed. This would allow deduplicating the - // following three blocks into one, and the restriction it imposes on - // test writers is one that naturally flows anyways. - { - var i = module.failed_files.iterator(); - while (i.next()) |pair| { - const v1 = pair.value.*; - var handled = false; - for (case.expected_errors) |e, index| { - if (!handled_errors[index]) { - if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { - handled_errors[index] = true; - handled = true; - break; - } + var all_errors = try module.getAllErrorsAlloc(); + defer all_errors.deinit(allocator); + for (all_errors.list) |e| { + var handled = false; + for (case.expected_errors) |ex, i| { + if (e.line == ex.line and e.column == ex.column and std.mem.eql(u8, ex.msg, e.msg)) { + if (handled_errors[i]) { + err = error.ErrorReceivedMultipleTimes; + std.debug.warn("Received error multiple times: {}\n", .{e.msg}); + } else { + handled_errors[i] = true; + handled = true; } - } - if (!handled) { - err = error.UnexpectedError; - std.debug.warn("Unexpected file error: {}\n", .{v1}); + break; } } - } - { - var i = module.failed_decls.iterator(); - while (i.next()) |pair| { - const v1 = pair.value.*; - var handled = false; - for (case.expected_errors) |e, index| { - if (!handled_errors[index]) { - if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { - handled_errors[index] = true; - handled = true; - break; - } - } - } - if (!handled) { - err = error.UnexpectedError; - std.debug.warn("Unexpected decl error: {}\n", .{v1}); - } - } - } - { - var i = module.failed_exports.iterator(); - while (i.next()) |pair| { - const v1 = pair.value.*; - var handled = false; - for (case.expected_errors) |e, index| { - if (!handled_errors[index]) { - if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { - handled_errors[index] = true; - handled = true; - break; - } - } - } - if (!handled) { - err = error.UnexpectedError; - std.debug.warn("Unexpected export error: {}\n", .{v1}); - } + if (!handled) { + err = error.ErrorNotExpected; + std.debug.warn("Received an unexpected error: {}:{}: {}\n", .{ e.line, e.column, e.msg }); } } + for (handled_errors) |e, i| { if (!e) { err = error.MissingExpectedError; - std.debug.warn("Did not receive error: {}\n", .{case.expected_errors[i].msg}); + const er = case.expected_errors[i]; + std.debug.warn("Did not receive error: {}:{}: {}\n", .{ er.line, er.column, er.msg }); } } diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 865ececf18..5c7a9e4554 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,7 +18,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{"5:13:unrecognized identifier: %test"}); + , &[_][]const u8{"error: 5:13: unrecognized identifier: %test"}); // TODO: fix this test // ctx.addZIRError("call with non-existent target", linux_x64, @@ -45,7 +45,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@0 = str("_start") \\@1 = ref(@0) \\@2 = export(@1, @start) - , &[_][]const u8{"4:9:unable to call function with naked calling convention"}); + , &[_][]const u8{"error: 4:9: unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} From e77fc7fe7e3d6e5e4dc89bbc8fa95e65d4134b9f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 4 Jun 2020 16:12:09 -0400 Subject: [PATCH 12/20] Stage2/Testing: Fix error specification --- src-self-hosted/test.zig | 14 +++++--------- test/stage2/compile_errors.zig | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index feba455758..c0fb52a74d 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -200,24 +200,20 @@ pub const TestContext = struct { var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); for (expected_errors) |e| { var cur = e; - const err = cur[0..7]; - if (!std.mem.eql(u8, err, "error: ")) { - std.debug.panic("Only error messages are currently supported, received {}\n", .{e}); - } - cur = cur[7..]; var line_index = std.mem.indexOf(u8, cur, ":"); if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'line:column: error: msg', found '{}'", .{e}); } 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 'error: line:column: msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'line:column: error: msg', found '{}'", .{e}); } const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); - std.debug.assert(cur[column_index.? + 1] == ' '); - const msg = cur[column_index.? + 2 ..]; + cur = cur[column_index.? + 2 ..]; + std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); + const msg = cur[7..]; if (line == 0 or column == 0) { @panic("Invalid test: error line and column must be specified starting at one!"); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 5c7a9e4554..78a00840a0 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,7 +18,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{"error: 5:13: unrecognized identifier: %test"}); + , &[_][]const u8{"5:13: error: unrecognized identifier: %test"}); // TODO: fix this test // ctx.addZIRError("call with non-existent target", linux_x64, @@ -45,7 +45,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@0 = str("_start") \\@1 = ref(@0) \\@2 = export(@1, @start) - , &[_][]const u8{"error: 4:9: unable to call function with naked calling convention"}); + , &[_][]const u8{"4:9: error: unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} From b6bd51ed69f75930bbf9a80b5b8841d1517a6828 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:55:24 -0400 Subject: [PATCH 13/20] Stage2/Testing: Move Transformation case to ZIRCase --- src-self-hosted/test.zig | 10 +-- test/stage2/zir.zig | 132 +++++++++++++++------------------------ 2 files changed, 57 insertions(+), 85 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index c0fb52a74d..a9a4c4fc4b 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -141,21 +141,21 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - stages: []ZIRUpdate, + stages: []const ZIRUpdate, }; pub fn addZIRCase( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - stages: []ZIRUpdate, - ) !void { - const case = .{ + stages: []const ZIRUpdate, + ) void { + const case = ZIRCase{ .name = name, .target = target, .stages = stages, }; - try ctx.cases.append(case); + ctx.zir_cases.append(case) catch |err| std.debug.panic("Error: {}", .{err}); } pub fn addZIRCompareOutput( diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index bf5d4b8eae..a8cf4ca964 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -1,5 +1,6 @@ const std = @import("std"); const TestContext = @import("../../src-self-hosted/test.zig").TestContext; +const ZIRUpdate = TestContext.ZIRUpdate; // self-hosted does not yet support PE executable files / COFF object files // or mach-o files. So we do the ZIR transform test cases cross compiling for // x86_64-linux. @@ -9,86 +10,57 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) void { - ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64, - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\ - \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) - \\ - \\@entry = fn(@fnty, { - \\ %11 = return() - \\}) - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$6 = str("entry") - \\@unnamed$7 = ref(@unnamed$6) - \\@unnamed$8 = export(@unnamed$7, @entry) - \\@unnamed$10 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$10, { - \\ %0 = return() - \\}) - \\ - ); - ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@entry = fn(@fnty, { - \\ %a = str("\x32\x08\x01\x0a") - \\ %aref = ref(%a) - \\ %eptr0 = elemptr(%aref, @0) - \\ %eptr1 = elemptr(%aref, @1) - \\ %eptr2 = elemptr(%aref, @2) - \\ %eptr3 = elemptr(%aref, @3) - \\ %v0 = deref(%eptr0) - \\ %v1 = deref(%eptr1) - \\ %v2 = deref(%eptr2) - \\ %v3 = deref(%eptr3) - \\ %x0 = add(%v0, %v1) - \\ %x1 = add(%v2, %v3) - \\ %result = add(%x0, %x1) - \\ - \\ %expected = int(69) - \\ %ok = cmp(%result, eq, %expected) - \\ %10 = condbr(%ok, { - \\ %11 = return() - \\ }, { - \\ %12 = breakpoint() - \\ }) - \\}) - \\ - \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\@unnamed$7 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$7, { - \\ %0 = return() - \\}) - \\@a = str("2\x08\x01\n") - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$14 = str("entry") - \\@unnamed$15 = ref(@unnamed$14) - \\@unnamed$16 = export(@unnamed$15, @entry) - \\ - ); + ctx.addZIRCase("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, &[_]ZIRUpdate{ZIRUpdate{ + .src = + \\@void = primitive(void) + \\@usize = primitive(usize) + \\@fnty = fntype([], @void, cc=C) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\ + \\@entry = fn(@fnty, { + \\ %a = str("\x32\x08\x01\x0a") + \\ %aref = ref(%a) + \\ %eptr0 = elemptr(%aref, @0) + \\ %eptr1 = elemptr(%aref, @1) + \\ %eptr2 = elemptr(%aref, @2) + \\ %eptr3 = elemptr(%aref, @3) + \\ %v0 = deref(%eptr0) + \\ %v1 = deref(%eptr1) + \\ %v2 = deref(%eptr2) + \\ %v3 = deref(%eptr3) + \\ %x0 = add(%v0, %v1) + \\ %x1 = add(%v2, %v3) + \\ %result = add(%x0, %x1) + \\ + \\ %expected = int(69) + \\ %ok = cmp(%result, eq, %expected) + \\ %10 = condbr(%ok, { + \\ %11 = return() + \\ }, { + \\ %12 = breakpoint() + \\ }) + \\}) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + , + .case = .{ + .Transformation = + \\@0 = primitive(void) + \\@1 = fntype([], @0, cc=C) + \\@2 = fn(@1, { + \\ %0 = return() + \\}) + \\@3 = str("entry") + \\@4 = ref(@3) + \\@5 = export(@4, @2) + \\ + }, + }}); { var case = ctx.addZIRMulti("reference cycle with compile error in the cycle", linux_x64); From 71dca252a575a6a09e16dfc8faf8b704e847eb87 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sun, 14 Jun 2020 20:55:49 -0400 Subject: [PATCH 14/20] Stage2/Testing: Rename stage -> update --- src-self-hosted/test.zig | 50 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index a9a4c4fc4b..59960d3ed8 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -82,24 +82,24 @@ pub const TestContext = struct { }; pub const ZIRUpdateType = enum { - /// A transformation stage transforms the input ZIR and tests against + /// A transformation update transforms the input ZIR and tests against /// the expected output Transformation, - /// An error stage attempts to compile bad code, and ensures that it + /// An error update attempts to compile bad code, and ensures that it /// fails to compile, and for the expected reasons Error, - /// An execution stage compiles and runs the input ZIR, feeding in + /// An execution update compiles and runs the input ZIR, feeding in /// provided input and ensuring that the outputs match what is expected Execution, - /// A compilation stage checks that the ZIR compiles without any issues + /// A compilation update checks that the ZIR compiles without any issues Compiles, }; pub const ZIRUpdate = struct { - /// The input to the current stage. We simulate an incremental update - /// with the file's contents changed to this value each stage. + /// The input to the current update. We simulate an incremental update + /// with the file's contents changed to this value each update. /// - /// This value can change entirely between stages, which would be akin + /// This value can change entirely between updates, which would be akin /// to deleting the source file and creating a new one from scratch; or /// you can keep it mostly consistent, with small changes, testing the /// effects of the incremental compilation. @@ -114,7 +114,7 @@ pub const TestContext = struct { /// /// If stdout, stderr, and exit_code are all null, addZIRCase will /// discard the test. To test for successful compilation, use a - /// dedicated Compile stage instead. + /// dedicated Compile update instead. Execution: struct { stdin: ?[]const u8, stdout: ?[]const u8, @@ -131,9 +131,9 @@ pub const TestContext = struct { }, }; - /// A ZIRCase consists of a set of *stages*. A stage can transform ZIR, + /// A ZIRCase 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 stage, so each stage's source is treated as a single file + /// 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 ZIRCase = struct { name: []const u8, @@ -141,19 +141,19 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - stages: []const ZIRUpdate, + updates: []const ZIRUpdate, }; pub fn addZIRCase( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - stages: []const ZIRUpdate, + updates: []const ZIRUpdate, ) void { const case = ZIRCase{ .name = name, .target = target, - .stages = stages, + .updates = updates, }; ctx.zir_cases.append(case) catch |err| std.debug.panic("Error: {}", .{err}); } @@ -297,7 +297,7 @@ pub const TestContext = struct { const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - var prg_node = root_node.start(case.name, case.stages.len); + var prg_node = root_node.start(case.name, case.updates.len); prg_node.activate(); defer prg_node.end(); @@ -318,33 +318,33 @@ pub const TestContext = struct { }); defer module.deinit(); - for (case.stages) |s| { + for (case.updates) |s| { // TODO: remove before committing. This is for ZLS ;) - const stage: ZIRUpdate = s; + const update: ZIRUpdate = s; - var stage_node = prg_node.start("update", 4); - stage_node.activate(); - defer stage_node.end(); + var update_node = prg_node.start("update", 4); + update_node.activate(); + defer update_node.end(); - var sync_node = stage_node.start("write", null); + var sync_node = update_node.start("write", null); sync_node.activate(); - try tmp.dir.writeFile(tmp_src_path, stage.src); + try tmp.dir.writeFile(tmp_src_path, update.src); sync_node.end(); - var module_node = stage_node.start("parse/analysis/codegen", null); + var module_node = update_node.start("parse/analysis/codegen", null); module_node.activate(); try module.update(); module_node.end(); - switch (stage.case) { + switch (update.case) { .Transformation => |expected_output| { - var emit_node = stage_node.start("emit", null); + var emit_node = update_node.start("emit", null); emit_node.activate(); var new_zir_module = try zir.emit(allocator, module); defer new_zir_module.deinit(allocator); emit_node.end(); - var write_node = stage_node.start("write", null); + var write_node = update_node.start("write", null); write_node.activate(); var out_zir = std.ArrayList(u8).init(allocator); defer out_zir.deinit(); From 1e5945d0a926b74517b1bed3cdab00f244aa592c Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:56:28 -0400 Subject: [PATCH 15/20] Stage2/Testing: remove ZIRTransformCase --- src-self-hosted/test.zig | 159 +-------------------------------------- 1 file changed, 2 insertions(+), 157 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 59960d3ed8..569b88184d 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -25,14 +25,9 @@ pub const TestContext = struct { // TODO: remove these. They are deprecated. zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), // TODO: remove - zir_transform_cases: std.ArrayList(ZIRTransformCase), - // TODO: remove zir_error_cases: std.ArrayList(ZIRErrorCase), - /// TODO: find a way to treat cases as individual tests as far as - /// `zig test` is concerned. If we have 100 tests, they should *not* be - /// considered as *one*. "ZIR" isn't really a *test*, it's a *category* of - /// tests. + /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) zir_cases: std.ArrayList(ZIRCase), // TODO: remove @@ -43,37 +38,7 @@ pub const TestContext = struct { }; // TODO: remove - pub const ZIRTransformCase = struct { - name: []const u8, - cross_target: std.zig.CrossTarget, - updates: std.ArrayList(Update), - pub const Update = struct { - expected: Expected, - src: [:0]const u8, - }; - - pub const Expected = union(enum) { - zir: []const u8, - errors: []const []const u8, - }; - - pub fn addZIR(case: *ZIRTransformCase, src: [:0]const u8, zir_text: []const u8) void { - case.updates.append(.{ - .src = src, - .expected = .{ .zir = zir_text }, - }) catch unreachable; - } - - pub fn addError(case: *ZIRTransformCase, src: [:0]const u8, errors: []const []const u8) void { - case.updates.append(.{ - .src = src, - .expected = .{ .errors = errors }, - }) catch unreachable; - } - }; - - // TODO: remove pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, @@ -171,25 +136,6 @@ pub const TestContext = struct { }) catch unreachable; } - pub fn addZIRTransform( - ctx: *TestContext, - name: []const u8, - cross_target: std.zig.CrossTarget, - src: [:0]const u8, - expected_zir: []const u8, - ) void { - const case = ctx.zir_transform_cases.addOne() catch unreachable; - case.* = .{ - .name = name, - .cross_target = cross_target, - .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), - }; - case.updates.append(.{ - .src = src, - .expected = .{ .zir = expected_zir }, - }) catch unreachable; - } - pub fn addZIRError( ctx: *TestContext, name: []const u8, @@ -237,7 +183,6 @@ pub const TestContext = struct { const allocator = std.heap.page_allocator; self.* = .{ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), - .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator), .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; @@ -245,7 +190,6 @@ pub const TestContext = struct { fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); - self.zir_transform_cases.deinit(); for (self.zir_error_cases.items) |e| { self.zir_error_cases.allocator.free(e.expected_errors); } @@ -256,8 +200,7 @@ pub const TestContext = struct { fn run(self: *TestContext) !void { var progress = std.Progress{}; - const root_node = try progress.start("zir", self.zir_cmp_output_cases.items.len + - self.zir_transform_cases.items.len); + const root_node = try progress.start("zir", self.zir_cases.items.len); defer root_node.end(); const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); @@ -275,12 +218,6 @@ pub const TestContext = struct { try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); try std.testing.allocator_instance.validate(); } - for (self.zir_transform_cases.items) |case| { - std.testing.base_allocator_instance.reset(); - const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); - try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, info.target); - try std.testing.allocator_instance.validate(); - } for (self.zir_error_cases.items) |case| { std.testing.base_allocator_instance.reset(); const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); @@ -432,98 +369,6 @@ pub const TestContext = struct { } } - fn runOneZIRTransformCase( - self: *TestContext, - allocator: *Allocator, - root_node: *std.Progress.Node, - case: ZIRTransformCase, - target: std.Target, - ) !void { - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - var update_node = root_node.start(case.name, case.updates.items.len); - update_node.activate(); - defer update_node.end(); - - const tmp_src_path = "test-case.zir"; - const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); - defer root_pkg.destroy(); - - var module = try Module.init(allocator, .{ - .target = target, - .output_mode = .Obj, - .optimize_mode = .Debug, - .bin_file_dir = tmp.dir, - .bin_file_path = "test-case.o", - .root_pkg = root_pkg, - }); - defer module.deinit(); - - for (case.updates.items) |update| { - var prg_node = update_node.start("", 3); - prg_node.activate(); - defer prg_node.end(); - - try tmp.dir.writeFile(tmp_src_path, update.src); - - var module_node = prg_node.start("parse/analysis/codegen", null); - module_node.activate(); - try module.update(); - module_node.end(); - - switch (update.expected) { - .zir => |expected_zir| { - var emit_node = prg_node.start("emit", null); - emit_node.activate(); - var new_zir_module = try zir.emit(allocator, module); - defer new_zir_module.deinit(allocator); - emit_node.end(); - - var write_node = prg_node.start("write", null); - write_node.activate(); - var out_zir = std.ArrayList(u8).init(allocator); - defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.outStream()); - write_node.end(); - - std.testing.expectEqualSlices(u8, expected_zir, out_zir.items); - }, - .errors => |expected_errors| { - var all_errors = try module.getAllErrorsAlloc(); - defer all_errors.deinit(module.allocator); - for (expected_errors) |expected_error| { - for (all_errors.list) |full_err_msg| { - const text = try std.fmt.allocPrint(allocator, ":{}:{}: error: {}", .{ - full_err_msg.line + 1, - full_err_msg.column + 1, - full_err_msg.msg, - }); - defer allocator.free(text); - if (std.mem.eql(u8, text, expected_error)) { - break; - } - } else { - std.debug.warn( - "{}\nExpected this error:\n================\n{}\n================\nBut found these errors:\n================\n", - .{ case.name, expected_error }, - ); - for (all_errors.list) |full_err_msg| { - std.debug.warn(":{}:{}: error: {}\n", .{ - full_err_msg.line + 1, - full_err_msg.column + 1, - full_err_msg.msg, - }); - } - std.debug.warn("================\nTest failed\n", .{}); - std.process.exit(1); - } - } - }, - } - } - } - fn runOneZIRErrorCase( self: *TestContext, allocator: *Allocator, From 7ee0462f5f8b4ec5ff824b38821d8f1b79e5ba63 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:59:38 -0400 Subject: [PATCH 16/20] Stage2/Testing: Fix transformation tests --- src-self-hosted/test.zig | 42 +++++++++--- test/stage2/zir.zig | 136 +++++++++++++++++++++++---------------- 2 files changed, 114 insertions(+), 64 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 569b88184d..42af19ddd9 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -38,7 +38,6 @@ pub const TestContext = struct { }; // TODO: remove - pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, @@ -71,7 +70,7 @@ pub const TestContext = struct { src: [:0]const u8, case: union(ZIRUpdateType) { /// The expected output ZIR - Transformation: []const u8, + Transformation: [:0]const u8, /// A slice containing the expected errors *in sequential order*. Error: []const ErrorMsg, @@ -106,21 +105,30 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - updates: []const ZIRUpdate, + updates: std.ArrayList(ZIRUpdate), + + pub fn addTransform(self: *ZIRCase, src: [:0]const u8, result: [:0]const u8) void { + self.updates.append(.{ + .src = src, + .case = .{ .Transformation = result }, + }) catch unreachable; + } + + pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void {} }; - pub fn addZIRCase( + pub fn addZIRMulti( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - updates: []const ZIRUpdate, - ) void { + ) *ZIRCase { const case = ZIRCase{ .name = name, .target = target, - .updates = updates, + .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), }; - ctx.zir_cases.append(case) catch |err| std.debug.panic("Error: {}", .{err}); + ctx.zir_cases.append(case) catch unreachable; + return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; } pub fn addZIRCompareOutput( @@ -136,6 +144,17 @@ pub const TestContext = struct { }) catch unreachable; } + pub fn addZIRTransform( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + result: [:0]const u8, + ) void { + var c = ctx.addZIRMulti(name, target); + c.addTransform(src, result); + } + pub fn addZIRError( ctx: *TestContext, name: []const u8, @@ -194,6 +213,9 @@ pub const TestContext = struct { self.zir_error_cases.allocator.free(e.expected_errors); } self.zir_error_cases.deinit(); + for (self.zir_cases.items) |c| { + c.updates.deinit(); + } self.zir_cases.deinit(); self.* = undefined; } @@ -234,7 +256,7 @@ pub const TestContext = struct { const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - var prg_node = root_node.start(case.name, case.updates.len); + var prg_node = root_node.start(case.name, case.updates.items.len); prg_node.activate(); defer prg_node.end(); @@ -255,7 +277,7 @@ pub const TestContext = struct { }); defer module.deinit(); - for (case.updates) |s| { + for (case.updates.items) |s| { // TODO: remove before committing. This is for ZLS ;) const update: ZIRUpdate = s; diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index a8cf4ca964..d58b30c29d 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -1,6 +1,5 @@ const std = @import("std"); const TestContext = @import("../../src-self-hosted/test.zig").TestContext; -const ZIRUpdate = TestContext.ZIRUpdate; // self-hosted does not yet support PE executable files / COFF object files // or mach-o files. So we do the ZIR transform test cases cross compiling for // x86_64-linux. @@ -10,61 +9,90 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) void { - ctx.addZIRCase("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, &[_]ZIRUpdate{ZIRUpdate{ - .src = - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@entry = fn(@fnty, { - \\ %a = str("\x32\x08\x01\x0a") - \\ %aref = ref(%a) - \\ %eptr0 = elemptr(%aref, @0) - \\ %eptr1 = elemptr(%aref, @1) - \\ %eptr2 = elemptr(%aref, @2) - \\ %eptr3 = elemptr(%aref, @3) - \\ %v0 = deref(%eptr0) - \\ %v1 = deref(%eptr1) - \\ %v2 = deref(%eptr2) - \\ %v3 = deref(%eptr3) - \\ %x0 = add(%v0, %v1) - \\ %x1 = add(%v2, %v3) - \\ %result = add(%x0, %x1) - \\ - \\ %expected = int(69) - \\ %ok = cmp(%result, eq, %expected) - \\ %10 = condbr(%ok, { - \\ %11 = return() - \\ }, { - \\ %12 = breakpoint() - \\ }) - \\}) - \\ - \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) - , - .case = .{ - .Transformation = - \\@0 = primitive(void) - \\@1 = fntype([], @0, cc=C) - \\@2 = fn(@1, { - \\ %0 = return() - \\}) - \\@3 = str("entry") - \\@4 = ref(@3) - \\@5 = export(@4, @2) - \\ - }, - }}); + ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64, + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + \\ + \\@entry = fn(@fnty, { + \\ %11 = return() + \\}) + , + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\@9 = str("entry") + \\@10 = ref(@9) + \\@unnamed$6 = str("entry") + \\@unnamed$7 = ref(@unnamed$6) + \\@unnamed$8 = export(@unnamed$7, @entry) + \\@unnamed$10 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$10, { + \\ %0 = return() + \\}) + \\ + ); + ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, + \\@void = primitive(void) + \\@usize = primitive(usize) + \\@fnty = fntype([], @void, cc=C) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\ + \\@entry = fn(@fnty, { + \\ %a = str("\x32\x08\x01\x0a") + \\ %aref = ref(%a) + \\ %eptr0 = elemptr(%aref, @0) + \\ %eptr1 = elemptr(%aref, @1) + \\ %eptr2 = elemptr(%aref, @2) + \\ %eptr3 = elemptr(%aref, @3) + \\ %v0 = deref(%eptr0) + \\ %v1 = deref(%eptr1) + \\ %v2 = deref(%eptr2) + \\ %v3 = deref(%eptr3) + \\ %x0 = add(%v0, %v1) + \\ %x1 = add(%v2, %v3) + \\ %result = add(%x0, %x1) + \\ + \\ %expected = int(69) + \\ %ok = cmp(%result, eq, %expected) + \\ %10 = condbr(%ok, { + \\ %11 = return() + \\ }, { + \\ %12 = breakpoint() + \\ }) + \\}) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + , + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\@unnamed$7 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$7, { + \\ %0 = return() + \\}) + \\@a = str("2\x08\x01\n") + \\@9 = str("entry") + \\@10 = ref(@9) + \\@unnamed$14 = str("entry") + \\@unnamed$15 = ref(@unnamed$14) + \\@unnamed$16 = export(@unnamed$15, @entry) + \\ + ); { var case = ctx.addZIRMulti("reference cycle with compile error in the cycle", linux_x64); - case.addZIR( + case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -143,7 +171,7 @@ pub fn addCases(ctx: *TestContext) void { // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are // referencing either of them. This tests that the cycle is detected, and the error // goes away. - case.addZIR( + case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ From adb21f1cafb4cf5e2a64ff9d8262c2506d72208b Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 20:25:13 -0400 Subject: [PATCH 17/20] Stage2/Testing: Add error tests to ZIRCase --- src-self-hosted/test.zig | 112 ++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 42af19ddd9..852c000a29 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -114,7 +114,37 @@ pub const TestContext = struct { }) catch unreachable; } - pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void {} + pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { + var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; + for (errors) |e, i| { + 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 ':line:column: error: msg', found '{}'", .{e}); + } + 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 ':line:column: error: msg', found '{}'", .{e}); + } + const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); + cur = cur[column_index.? + 2 ..]; + std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); + const msg = cur[7..]; + + if (line == 0 or column == 0) { + @panic("Invalid test: error line and column must be specified starting at one!"); + } + + array[i] = .{ + .msg = msg, + .line = line - 1, + .column = column - 1, + }; + } + self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; + } }; pub fn addZIRMulti( @@ -161,42 +191,7 @@ pub const TestContext = struct { cross_target: std.zig.CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, - ) void { - var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); - for (expected_errors) |e| { - var cur = e; - var line_index = std.mem.indexOf(u8, cur, ":"); - if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as 'line:column: error: msg', found '{}'", .{e}); - } - 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 'line:column: error: msg', found '{}'", .{e}); - } - const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); - cur = cur[column_index.? + 2 ..]; - std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); - const msg = cur[7..]; - - if (line == 0 or column == 0) { - @panic("Invalid test: error line and column must be specified starting at one!"); - } - - array.append(.{ - .msg = msg, - .line = line - 1, - .column = column - 1, - }) catch unreachable; - } - ctx.zir_error_cases.append(.{ - .name = name, - .src = src, - .expected_errors = array.toOwnedSlice(), - .cross_target = cross_target, - }) catch unreachable; - } + ) void {} fn init(self: *TestContext) !void { const allocator = std.heap.page_allocator; @@ -214,6 +209,11 @@ pub const TestContext = struct { } self.zir_error_cases.deinit(); for (self.zir_cases.items) |c| { + for (c.updates.items) |u| { + if (u.case == .Error) { + c.updates.allocator.free(u.case.Error); + } + } c.updates.deinit(); } self.zir_cases.deinit(); @@ -268,11 +268,11 @@ pub const TestContext = struct { // TODO: support tests for object file building, and library builds // and linking. This will require a rework to support multi-file // tests. - .output_mode = .Exe, + .output_mode = .Obj, // TODO: support testing optimizations .optimize_mode = .Debug, .bin_file_dir = tmp.dir, - .bin_file_path = "test_case", + .bin_file_path = "test_case.o", .root_pkg = root_pkg, }); defer module.deinit(); @@ -312,6 +312,35 @@ pub const TestContext = struct { std.testing.expectEqualSlices(u8, expected_output, out_zir.items); }, + .Error => |e| { + var handled_errors = try allocator.alloc(bool, e.len); + defer allocator.free(handled_errors); + for (handled_errors) |*h| { + h.* = false; + } + var all_errors = try module.getAllErrorsAlloc(); + defer all_errors.deinit(allocator); + for (all_errors.list) |a| { + for (e) |ex, i| { + if (a.line == ex.line and a.column == ex.column and std.mem.eql(u8, ex.msg, a.msg)) { + handled_errors[i] = true; + break; + } + } else { + std.debug.warn("{}\nUnexpected error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); + std.process.exit(1); + } + } + + for (handled_errors) |h, i| { + if (!h) { + const er = e[i]; + std.debug.warn("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg }); + std.process.exit(1); + } + } + }, + else => return error.unimplemented, } } @@ -423,10 +452,7 @@ pub const TestContext = struct { var module_node = prg_node.start("parse/analysis/codegen", null); module_node.activate(); - const failed = f: { - module.update() catch break :f true; - break :f false; - }; + try module.update(); module_node.end(); var err: ?anyerror = null; From 7d1c9a69ccaf148c7264df2d197549a851478998 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 20:30:08 -0400 Subject: [PATCH 18/20] Stage2/Testing: Remove dead code --- src-self-hosted/test.zig | 130 ++------------------------------- test/stage2/compile_errors.zig | 4 +- 2 files changed, 10 insertions(+), 124 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 852c000a29..ad0bd4a640 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -24,8 +24,6 @@ const ErrorMsg = struct { pub const TestContext = struct { // TODO: remove these. They are deprecated. zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), - // TODO: remove - zir_error_cases: std.ArrayList(ZIRErrorCase), /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) zir_cases: std.ArrayList(ZIRCase), @@ -37,14 +35,6 @@ pub const TestContext = struct { expected_stdout_list: []const []const u8, }; - // TODO: remove - pub const ZIRErrorCase = struct { - name: []const u8, - src: [:0]const u8, - expected_errors: []const ErrorMsg, - cross_target: std.zig.CrossTarget, - }; - pub const ZIRUpdateType = enum { /// A transformation update transforms the input ZIR and tests against /// the expected output @@ -114,6 +104,9 @@ pub const TestContext = struct { }) catch unreachable; } + /// TODO: document + /// + /// Errors must be specified in sequential order pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { @@ -188,26 +181,24 @@ pub const TestContext = struct { pub fn addZIRError( ctx: *TestContext, name: []const u8, - cross_target: std.zig.CrossTarget, + target: std.zig.CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, - ) void {} + ) void { + var c = ctx.addZIRMulti(name, target); + c.addError(src, expected_errors); + } fn init(self: *TestContext) !void { const allocator = std.heap.page_allocator; self.* = .{ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), - .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; } fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); - for (self.zir_error_cases.items) |e| { - self.zir_error_cases.allocator.free(e.expected_errors); - } - self.zir_error_cases.deinit(); for (self.zir_cases.items) |c| { for (c.updates.items) |u| { if (u.case == .Error) { @@ -240,12 +231,6 @@ pub const TestContext = struct { try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); try std.testing.allocator_instance.validate(); } - for (self.zir_error_cases.items) |case| { - std.testing.base_allocator_instance.reset(); - const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); - try self.runOneZIRErrorCase(std.testing.allocator, root_node, case, info.target); - try std.testing.allocator_instance.validate(); - } } fn runOneZIRCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { @@ -419,103 +404,4 @@ pub const TestContext = struct { } } } - - fn runOneZIRErrorCase( - self: *TestContext, - allocator: *Allocator, - root_node: *std.Progress.Node, - case: ZIRErrorCase, - target: std.Target, - ) !void { - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - var prg_node = root_node.start(case.name, 1); - prg_node.activate(); - defer prg_node.end(); - - const tmp_src_path = "test-case.zir"; - try tmp.dir.writeFile(tmp_src_path, case.src); - - const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); - defer root_pkg.destroy(); - - var module = try Module.init(allocator, .{ - .target = target, - .output_mode = .Obj, - .optimize_mode = .Debug, - .bin_file_dir = tmp.dir, - .bin_file_path = "test-case.o", - .root_pkg = root_pkg, - }); - defer module.deinit(); - - var module_node = prg_node.start("parse/analysis/codegen", null); - module_node.activate(); - try module.update(); - module_node.end(); - var err: ?anyerror = null; - - var handled_errors = allocator.alloc(bool, case.expected_errors.len) catch unreachable; - defer allocator.free(handled_errors); - for (handled_errors) |*e| { - e.* = false; - } - - var all_errors = try module.getAllErrorsAlloc(); - defer all_errors.deinit(allocator); - for (all_errors.list) |e| { - var handled = false; - for (case.expected_errors) |ex, i| { - if (e.line == ex.line and e.column == ex.column and std.mem.eql(u8, ex.msg, e.msg)) { - if (handled_errors[i]) { - err = error.ErrorReceivedMultipleTimes; - std.debug.warn("Received error multiple times: {}\n", .{e.msg}); - } else { - handled_errors[i] = true; - handled = true; - } - break; - } - } - if (!handled) { - err = error.ErrorNotExpected; - std.debug.warn("Received an unexpected error: {}:{}: {}\n", .{ e.line, e.column, e.msg }); - } - } - - for (handled_errors) |e, i| { - if (!e) { - err = error.MissingExpectedError; - const er = case.expected_errors[i]; - std.debug.warn("Did not receive error: {}:{}: {}\n", .{ er.line, er.column, er.msg }); - } - } - - if (err) |e| { - return e; - } - } }; - -fn debugPrintErrors(src: []const u8, errors: var) void { - std.debug.warn("\n", .{}); - var nl = true; - var line: usize = 1; - for (src) |byte| { - if (nl) { - std.debug.warn("{: >3}| ", .{line}); - nl = false; - } - if (byte == '\n') { - nl = true; - line += 1; - } - std.debug.warn("{c}", .{byte}); - } - std.debug.warn("\n", .{}); - for (errors) |err_msg| { - const loc = std.zig.findLineColumn(src, err_msg.byte_offset); - std.debug.warn("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, err_msg.msg }); - } -} diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 78a00840a0..72c41f1230 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,7 +18,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{"5:13: error: unrecognized identifier: %test"}); + , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); // TODO: fix this test // ctx.addZIRError("call with non-existent target", linux_x64, @@ -45,7 +45,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@0 = str("_start") \\@1 = ref(@0) \\@2 = export(@1, @start) - , &[_][]const u8{"4:9: error: unable to call function with naked calling convention"}); + , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} From afec3e72f438fae41e493e3fd18ca62e5ef1c89b Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 20:42:22 -0400 Subject: [PATCH 19/20] Stage2/Testing: Enable another test --- src-self-hosted/test.zig | 13 +++++++++---- test/stage2/compile_errors.zig | 26 +++++++++++++------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index ad0bd4a640..78bdcddf71 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -110,20 +110,25 @@ pub const TestContext = struct { pub fn addError(self: *ZIRCase, 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", .{}); + } 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 ':line:column: error: msg', found '{}'", .{e}); + std.debug.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 ':line:column: error: msg', found '{}'", .{e}); + std.debug.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 ..]; - std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); + 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", .{}); + } const msg = cur[7..]; if (line == 0 or column == 0) { @@ -312,7 +317,7 @@ pub const TestContext = struct { break; } } else { - std.debug.warn("{}\nUnexpected error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); + std.debug.warn("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); std.process.exit(1); } } diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 72c41f1230..5484b6459a 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,20 +18,20 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); + // TODO: address inconsistency in this message and the one in the next test + , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); - // TODO: fix this test - // ctx.addZIRError("call with non-existent target", linux_x64, - // \\@noreturn = primitive(noreturn) - // \\ - // \\@start_fnty = fntype([], @noreturn, cc=Naked) - // \\@start = fn(@start_fnty, { - // \\ %0 = call(@notafunc, []) - // \\}) - // \\@0 = str("_start") - // \\@1 = ref(@0) - // \\@2 = export(@1, @start) - // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); + ctx.addZIRError("call with non-existent target", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(@notafunc, []) + \\}) + \\@0 = str("_start") + \\@1 = ref(@0) + \\@2 = export(@1, @start) + , &[_][]const u8{":5:13: error: use of undeclared identifier 'notafunc'"}); // TODO: this error should occur at the call site, not the fntype decl ctx.addZIRError("call naked function", linux_x64, From a99e61ebaa71aa74dfa95869ea8d02131ef9f696 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 21:47:42 -0400 Subject: [PATCH 20/20] Stage2/Testing: Code cleanup --- src-self-hosted/test.zig | 20 +++++++++----------- test/stage2/compile_errors.zig | 6 ++++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 78bdcddf71..4cf72ce481 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -6,8 +6,7 @@ const zir = @import("zir.zig"); const Package = @import("Package.zig"); test "self-hosted" { - var ctx: TestContext = undefined; - try ctx.init(); + var ctx = TestContext.init(); defer ctx.deinit(); try @import("stage2_tests").addCases(&ctx); @@ -93,10 +92,11 @@ pub const TestContext = struct { name: []const u8, /// The platform the ZIR targets. For non-native platforms, an emulator /// such as QEMU is required for tests to complete. - /// target: std.zig.CrossTarget, updates: std.ArrayList(ZIRUpdate), + /// Adds a subcase in which the module is updated with new ZIR, and the + /// resulting ZIR is validated. pub fn addTransform(self: *ZIRCase, src: [:0]const u8, result: [:0]const u8) void { self.updates.append(.{ .src = src, @@ -104,9 +104,10 @@ pub const TestContext = struct { }) catch unreachable; } - /// TODO: document + /// 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 + /// Errors must be specified in sequential order. pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { @@ -194,9 +195,9 @@ pub const TestContext = struct { c.addError(src, expected_errors); } - fn init(self: *TestContext) !void { + fn init() TestContext { const allocator = std.heap.page_allocator; - self.* = .{ + return .{ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; @@ -267,10 +268,7 @@ pub const TestContext = struct { }); defer module.deinit(); - for (case.updates.items) |s| { - // TODO: remove before committing. This is for ZLS ;) - const update: ZIRUpdate = s; - + for (case.updates.items) |update| { var update_node = prg_node.start("update", 4); update_node.activate(); defer update_node.end(); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 5484b6459a..43c41aa364 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -9,8 +9,6 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - // TODO: re-enable these tests. - // https://github.com/ziglang/zig/issues/1364 ctx.addZIRError("call undefined local", linux_x64, \\@noreturn = primitive(noreturn) \\ @@ -47,6 +45,10 @@ pub fn addCases(ctx: *TestContext) !void { \\@2 = export(@1, @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 + //try ctx.testCompileError( // \\export fn entry() void {} // \\export fn entry() void {}