Stage2/Testing: Fix error tests
parent
d4fd7c6a01
commit
6dce317fe3
|
@ -1,29 +1,10 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const link = @import("link.zig");
|
const link = @import("link.zig");
|
||||||
const Module = @import("Module.zig");
|
const Module = @import("Module.zig");
|
||||||
const ErrorMsg = Module.ErrorMsg;
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const zir = @import("zir.zig");
|
const zir = @import("zir.zig");
|
||||||
const Package = @import("Package.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" {
|
test "self-hosted" {
|
||||||
var ctx: TestContext = undefined;
|
var ctx: TestContext = undefined;
|
||||||
try ctx.init();
|
try ctx.init();
|
||||||
|
@ -34,26 +15,11 @@ test "self-hosted" {
|
||||||
try ctx.run();
|
try ctx.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the raw byte offset of line:column in src. This is not a performant implementation,
|
const ErrorMsg = struct {
|
||||||
/// as it should only ever be called rarely and it is better to focus on readability.
|
msg: []const u8,
|
||||||
fn findOffset(src: []const u8, line: usize, column: usize) ?usize {
|
line: u32,
|
||||||
// "0000000001"
|
column: u32,
|
||||||
// 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 {
|
pub const TestContext = struct {
|
||||||
// TODO: remove these. They are deprecated.
|
// TODO: remove these. They are deprecated.
|
||||||
|
@ -115,7 +81,7 @@ pub const TestContext = struct {
|
||||||
cross_target: std.zig.CrossTarget,
|
cross_target: std.zig.CrossTarget,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ZIRStageType = enum {
|
pub const ZIRUpdateType = enum {
|
||||||
/// A transformation stage transforms the input ZIR and tests against
|
/// A transformation stage transforms the input ZIR and tests against
|
||||||
/// the expected output
|
/// the expected output
|
||||||
Transformation,
|
Transformation,
|
||||||
|
@ -129,7 +95,7 @@ pub const TestContext = struct {
|
||||||
Compiles,
|
Compiles,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ZIRStage = struct {
|
pub const ZIRUpdate = struct {
|
||||||
/// The input to the current stage. We simulate an incremental update
|
/// The input to the current stage. We simulate an incremental update
|
||||||
/// with the file's contents changed to this value each stage.
|
/// 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
|
/// you can keep it mostly consistent, with small changes, testing the
|
||||||
/// effects of the incremental compilation.
|
/// effects of the incremental compilation.
|
||||||
src: [:0]const u8,
|
src: [:0]const u8,
|
||||||
case: union(ZIRStageType) {
|
case: union(ZIRUpdateType) {
|
||||||
/// The expected output ZIR
|
/// The expected output ZIR
|
||||||
Transformation: []const u8,
|
Transformation: []const u8,
|
||||||
/// A slice containing the expected errors *in sequential order*.
|
/// 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.
|
/// such as QEMU is required for tests to complete.
|
||||||
///
|
///
|
||||||
target: std.zig.CrossTarget,
|
target: std.zig.CrossTarget,
|
||||||
stages: []ZIRStage,
|
stages: []ZIRUpdate,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addZIRCase(
|
pub fn addZIRCase(
|
||||||
ctx: *TestContext,
|
ctx: *TestContext,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
target: std.zig.CrossTarget,
|
target: std.zig.CrossTarget,
|
||||||
stages: []ZIRStage,
|
stages: []ZIRUpdate,
|
||||||
) !void {
|
) !void {
|
||||||
const case = .{
|
const case = .{
|
||||||
.name = name,
|
.name = name,
|
||||||
|
@ -233,21 +199,34 @@ pub const TestContext = struct {
|
||||||
) void {
|
) void {
|
||||||
var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator);
|
var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator);
|
||||||
for (expected_errors) |e| {
|
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) {
|
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) {
|
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(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number");
|
||||||
const column = std.fmt.parseInt(usize, e[line_index.? + 1 ..][0..column_index.?], 10) catch @panic("Unable to parse column number");
|
std.debug.assert(cur[column_index.? + 1] == ' ');
|
||||||
const msg = e[line_index.? + 1 ..][column_index.? + 1 ..];
|
const msg = cur[column_index.? + 2 ..];
|
||||||
const offset = findOffset(src, line, column) orelse std.debug.panic("Unable to match {}:{} to byte offset!", .{ line, column });
|
|
||||||
array.append(ErrorMsg{
|
if (line == 0 or column == 0) {
|
||||||
.byte_offset = offset,
|
@panic("Invalid test: error line and column must be specified starting at one!");
|
||||||
|
}
|
||||||
|
|
||||||
|
array.append(.{
|
||||||
.msg = msg,
|
.msg = msg,
|
||||||
|
.line = line - 1,
|
||||||
|
.column = column - 1,
|
||||||
}) catch unreachable;
|
}) catch unreachable;
|
||||||
}
|
}
|
||||||
ctx.zir_error_cases.append(.{
|
ctx.zir_error_cases.append(.{
|
||||||
|
@ -264,6 +243,7 @@ pub const TestContext = struct {
|
||||||
.zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator),
|
.zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator),
|
||||||
.zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator),
|
.zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator),
|
||||||
.zir_error_cases = std.ArrayList(ZIRErrorCase).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.allocator.free(e.expected_errors);
|
||||||
}
|
}
|
||||||
self.zir_error_cases.deinit();
|
self.zir_error_cases.deinit();
|
||||||
|
self.zir_cases.deinit();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,9 +324,9 @@ pub const TestContext = struct {
|
||||||
|
|
||||||
for (case.stages) |s| {
|
for (case.stages) |s| {
|
||||||
// TODO: remove before committing. This is for ZLS ;)
|
// 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();
|
stage_node.activate();
|
||||||
defer stage_node.end();
|
defer stage_node.end();
|
||||||
|
|
||||||
|
@ -592,74 +573,33 @@ pub const TestContext = struct {
|
||||||
e.* = false;
|
e.* = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check the input error list in sequential order, manually
|
var all_errors = try module.getAllErrorsAlloc();
|
||||||
// incrementing indices when needed. This would allow deduplicating the
|
defer all_errors.deinit(allocator);
|
||||||
// following three blocks into one, and the restriction it imposes on
|
for (all_errors.list) |e| {
|
||||||
// test writers is one that naturally flows anyways.
|
var handled = false;
|
||||||
{
|
for (case.expected_errors) |ex, i| {
|
||||||
var i = module.failed_files.iterator();
|
if (e.line == ex.line and e.column == ex.column and std.mem.eql(u8, ex.msg, e.msg)) {
|
||||||
while (i.next()) |pair| {
|
if (handled_errors[i]) {
|
||||||
const v1 = pair.value.*;
|
err = error.ErrorReceivedMultipleTimes;
|
||||||
var handled = false;
|
std.debug.warn("Received error multiple times: {}\n", .{e.msg});
|
||||||
for (case.expected_errors) |e, index| {
|
} else {
|
||||||
if (!handled_errors[index]) {
|
handled_errors[i] = true;
|
||||||
if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) {
|
handled = true;
|
||||||
handled_errors[index] = true;
|
|
||||||
handled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
if (!handled) {
|
|
||||||
err = error.UnexpectedError;
|
|
||||||
std.debug.warn("Unexpected file error: {}\n", .{v1});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (!handled) {
|
||||||
{
|
err = error.ErrorNotExpected;
|
||||||
var i = module.failed_decls.iterator();
|
std.debug.warn("Received an unexpected error: {}:{}: {}\n", .{ e.line, e.column, e.msg });
|
||||||
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| {
|
for (handled_errors) |e, i| {
|
||||||
if (!e) {
|
if (!e) {
|
||||||
err = error.MissingExpectedError;
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||||
\\@start = fn(@start_fnty, {
|
\\@start = fn(@start_fnty, {
|
||||||
\\ %0 = call(%test, [])
|
\\ %0 = call(%test, [])
|
||||||
\\})
|
\\})
|
||||||
, &[_][]const u8{"5:13:unrecognized identifier: %test"});
|
, &[_][]const u8{"error: 5:13: unrecognized identifier: %test"});
|
||||||
|
|
||||||
// TODO: fix this test
|
// TODO: fix this test
|
||||||
// ctx.addZIRError("call with non-existent target", linux_x64,
|
// ctx.addZIRError("call with non-existent target", linux_x64,
|
||||||
|
@ -45,7 +45,7 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||||
\\@0 = str("_start")
|
\\@0 = str("_start")
|
||||||
\\@1 = ref(@0)
|
\\@1 = ref(@0)
|
||||||
\\@2 = export(@1, @start)
|
\\@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(
|
//try ctx.testCompileError(
|
||||||
// \\export fn entry() void {}
|
// \\export fn entry() void {}
|
||||||
|
|
Loading…
Reference in New Issue