Commit 68cc068a3a

Noam Preil <pleasantatk@gmail.com>
2020-05-27 17:51:48
[Stage2/Testing] Make API more friendly
1 parent bebc1f4
Changed files (2)
src-self-hosted
test
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;
-                }
-                if (!std.mem.eql(u8, v1.msg, v2.msg)) {
-                    std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg });
-                    err = error.ExpectedOtherError;
+                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 (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});
+                if (!handled) {
                     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;
+                    std.debug.warn("Unexpected file error: {}\n", .{v1});
                 }
             }
-            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;
         }
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 {}