| ... | ... | @@ -1,29 +1,10 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const link = @import("link.zig"); |
| 3 | 3 | const Module = @import("Module.zig"); |
| 4 | | const ErrorMsg = Module.ErrorMsg; |
| 5 | 4 | const Allocator = std.mem.Allocator; |
| 6 | 5 | const zir = @import("zir.zig"); |
| 7 | 6 | const Package = @import("Package.zig"); |
| 8 | 7 | |
| 9 | | test "find-offset" { |
| 10 | | std.testing.expectEqual(findOffset("hello123", 1, 8), 7); |
| 11 | | const testmsg = |
| 12 | | \\@noreturn = primitive(noreturn) |
| 13 | | \\ |
| 14 | | \\@start_fnty = fntype([], @noreturn, cc=Naked) |
| 15 | | \\@start = fn(@start_fnty, { |
| 16 | | \\ %0 = call(@notafunc, []) |
| 17 | | \\}) |
| 18 | | ; |
| 19 | | std.testing.expectEqual(findOffset(testmsg, 2, 1), 32); |
| 20 | | std.testing.expectEqual(findOffset(testmsg, 3, 1), 33); |
| 21 | | std.testing.expectEqual(findOffset(testmsg, 3, 10), 42); |
| 22 | | std.testing.expectEqual(findOffset(testmsg, 4, 1), 79); |
| 23 | | std.testing.expectEqual(findOffset(testmsg, 5, 1), 106); |
| 24 | | std.testing.expectEqual(findOffset(testmsg, 5, 13), 118); |
| 25 | | } |
| 26 | | |
| 27 | 8 | test "self-hosted" { |
| 28 | 9 | var ctx: TestContext = undefined; |
| 29 | 10 | try ctx.init(); |
| ... | ... | @@ -34,26 +15,11 @@ test "self-hosted" { |
| 34 | 15 | try ctx.run(); |
| 35 | 16 | } |
| 36 | 17 | |
| 37 | | /// Finds the raw byte offset of line:column in src. This is not a performant implementation, |
| 38 | | /// as it should only ever be called rarely and it is better to focus on readability. |
| 39 | | fn findOffset(src: []const u8, line: usize, column: usize) ?usize { |
| 40 | | // "0000000001" |
| 41 | | // 1:10 |
| 42 | | // |
| 43 | | var current_line: usize = 1; |
| 44 | | var current_column: usize = 1; |
| 45 | | for (src) |char, index| { |
| 46 | | if (current_line == line and current_column == column) { |
| 47 | | return index; |
| 48 | | } |
| 49 | | if (char == '\n') { |
| 50 | | current_line += 1; |
| 51 | | current_column = 0; |
| 52 | | } |
| 53 | | current_column += 1; |
| 54 | | } |
| 55 | | return null; |
| 56 | | } |
| 18 | const ErrorMsg = struct { |
| 19 | msg: []const u8, |
| 20 | line: u32, |
| 21 | column: u32, |
| 22 | }; |
| 57 | 23 | |
| 58 | 24 | pub const TestContext = struct { |
| 59 | 25 | // TODO: remove these. They are deprecated. |
| ... | ... | @@ -115,7 +81,7 @@ pub const TestContext = struct { |
| 115 | 81 | cross_target: std.zig.CrossTarget, |
| 116 | 82 | }; |
| 117 | 83 | |
| 118 | | pub const ZIRStageType = enum { |
| 84 | pub const ZIRUpdateType = enum { |
| 119 | 85 | /// A transformation stage transforms the input ZIR and tests against |
| 120 | 86 | /// the expected output |
| 121 | 87 | Transformation, |
| ... | ... | @@ -129,7 +95,7 @@ pub const TestContext = struct { |
| 129 | 95 | Compiles, |
| 130 | 96 | }; |
| 131 | 97 | |
| 132 | | pub const ZIRStage = struct { |
| 98 | pub const ZIRUpdate = struct { |
| 133 | 99 | /// The input to the current stage. We simulate an incremental update |
| 134 | 100 | /// with the file's contents changed to this value each stage. |
| 135 | 101 | /// |
| ... | ... | @@ -138,7 +104,7 @@ pub const TestContext = struct { |
| 138 | 104 | /// you can keep it mostly consistent, with small changes, testing the |
| 139 | 105 | /// effects of the incremental compilation. |
| 140 | 106 | src: [:0]const u8, |
| 141 | | case: union(ZIRStageType) { |
| 107 | case: union(ZIRUpdateType) { |
| 142 | 108 | /// The expected output ZIR |
| 143 | 109 | Transformation: []const u8, |
| 144 | 110 | /// A slice containing the expected errors *in sequential order*. |
| ... | ... | @@ -175,14 +141,14 @@ pub const TestContext = struct { |
| 175 | 141 | /// such as QEMU is required for tests to complete. |
| 176 | 142 | /// |
| 177 | 143 | target: std.zig.CrossTarget, |
| 178 | | stages: []ZIRStage, |
| 144 | stages: []ZIRUpdate, |
| 179 | 145 | }; |
| 180 | 146 | |
| 181 | 147 | pub fn addZIRCase( |
| 182 | 148 | ctx: *TestContext, |
| 183 | 149 | name: []const u8, |
| 184 | 150 | target: std.zig.CrossTarget, |
| 185 | | stages: []ZIRStage, |
| 151 | stages: []ZIRUpdate, |
| 186 | 152 | ) !void { |
| 187 | 153 | const case = .{ |
| 188 | 154 | .name = name, |
| ... | ... | @@ -233,21 +199,34 @@ pub const TestContext = struct { |
| 233 | 199 | ) void { |
| 234 | 200 | var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); |
| 235 | 201 | for (expected_errors) |e| { |
| 236 | | const line_index = std.mem.indexOf(u8, e, ":"); |
| 202 | var cur = e; |
| 203 | const err = cur[0..7]; |
| 204 | if (!std.mem.eql(u8, err, "error: ")) { |
| 205 | std.debug.panic("Only error messages are currently supported, received {}\n", .{e}); |
| 206 | } |
| 207 | cur = cur[7..]; |
| 208 | var line_index = std.mem.indexOf(u8, cur, ":"); |
| 237 | 209 | if (line_index == null) { |
| 238 | | std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); |
| 210 | std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); |
| 239 | 211 | } |
| 240 | | const column_index = std.mem.indexOf(u8, e[line_index.? + 1 ..], ":"); |
| 212 | const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); |
| 213 | cur = cur[line_index.? + 1 ..]; |
| 214 | const column_index = std.mem.indexOf(u8, cur, ":"); |
| 241 | 215 | if (column_index == null) { |
| 242 | | std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); |
| 216 | std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); |
| 217 | } |
| 218 | const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); |
| 219 | std.debug.assert(cur[column_index.? + 1] == ' '); |
| 220 | const msg = cur[column_index.? + 2 ..]; |
| 221 | |
| 222 | if (line == 0 or column == 0) { |
| 223 | @panic("Invalid test: error line and column must be specified starting at one!"); |
| 243 | 224 | } |
| 244 | | const line = std.fmt.parseInt(usize, e[0..line_index.?], 10) catch @panic("Unable to parse line number"); |
| 245 | | const column = std.fmt.parseInt(usize, e[line_index.? + 1 ..][0..column_index.?], 10) catch @panic("Unable to parse column number"); |
| 246 | | const msg = e[line_index.? + 1 ..][column_index.? + 1 ..]; |
| 247 | | const offset = findOffset(src, line, column) orelse std.debug.panic("Unable to match {}:{} to byte offset!", .{ line, column }); |
| 248 | | array.append(ErrorMsg{ |
| 249 | | .byte_offset = offset, |
| 225 | |
| 226 | array.append(.{ |
| 250 | 227 | .msg = msg, |
| 228 | .line = line - 1, |
| 229 | .column = column - 1, |
| 251 | 230 | }) catch unreachable; |
| 252 | 231 | } |
| 253 | 232 | ctx.zir_error_cases.append(.{ |
| ... | ... | @@ -264,6 +243,7 @@ pub const TestContext = struct { |
| 264 | 243 | .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), |
| 265 | 244 | .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator), |
| 266 | 245 | .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), |
| 246 | .zir_cases = std.ArrayList(ZIRCase).init(allocator), |
| 267 | 247 | }; |
| 268 | 248 | } |
| 269 | 249 | |
| ... | ... | @@ -274,6 +254,7 @@ pub const TestContext = struct { |
| 274 | 254 | self.zir_error_cases.allocator.free(e.expected_errors); |
| 275 | 255 | } |
| 276 | 256 | self.zir_error_cases.deinit(); |
| 257 | self.zir_cases.deinit(); |
| 277 | 258 | self.* = undefined; |
| 278 | 259 | } |
| 279 | 260 | |
| ... | ... | @@ -343,9 +324,9 @@ pub const TestContext = struct { |
| 343 | 324 | |
| 344 | 325 | for (case.stages) |s| { |
| 345 | 326 | // TODO: remove before committing. This is for ZLS ;) |
| 346 | | const stage: ZIRStage = s; |
| 327 | const stage: ZIRUpdate = s; |
| 347 | 328 | |
| 348 | | var stage_node = prg_node.start("stage", 4); |
| 329 | var stage_node = prg_node.start("update", 4); |
| 349 | 330 | stage_node.activate(); |
| 350 | 331 | defer stage_node.end(); |
| 351 | 332 | |
| ... | ... | @@ -592,74 +573,33 @@ pub const TestContext = struct { |
| 592 | 573 | e.* = false; |
| 593 | 574 | } |
| 594 | 575 | |
| 595 | | // TODO: check the input error list in sequential order, manually |
| 596 | | // incrementing indices when needed. This would allow deduplicating the |
| 597 | | // following three blocks into one, and the restriction it imposes on |
| 598 | | // test writers is one that naturally flows anyways. |
| 599 | | { |
| 600 | | var i = module.failed_files.iterator(); |
| 601 | | while (i.next()) |pair| { |
| 602 | | const v1 = pair.value.*; |
| 603 | | var handled = false; |
| 604 | | for (case.expected_errors) |e, index| { |
| 605 | | if (!handled_errors[index]) { |
| 606 | | if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { |
| 607 | | handled_errors[index] = true; |
| 608 | | handled = true; |
| 609 | | break; |
| 610 | | } |
| 576 | var all_errors = try module.getAllErrorsAlloc(); |
| 577 | defer all_errors.deinit(allocator); |
| 578 | for (all_errors.list) |e| { |
| 579 | var handled = false; |
| 580 | for (case.expected_errors) |ex, i| { |
| 581 | if (e.line == ex.line and e.column == ex.column and std.mem.eql(u8, ex.msg, e.msg)) { |
| 582 | if (handled_errors[i]) { |
| 583 | err = error.ErrorReceivedMultipleTimes; |
| 584 | std.debug.warn("Received error multiple times: {}\n", .{e.msg}); |
| 585 | } else { |
| 586 | handled_errors[i] = true; |
| 587 | handled = true; |
| 611 | 588 | } |
| 612 | | } |
| 613 | | if (!handled) { |
| 614 | | err = error.UnexpectedError; |
| 615 | | std.debug.warn("Unexpected file error: {}\n", .{v1}); |
| 589 | break; |
| 616 | 590 | } |
| 617 | 591 | } |
| 618 | | } |
| 619 | | { |
| 620 | | var i = module.failed_decls.iterator(); |
| 621 | | while (i.next()) |pair| { |
| 622 | | const v1 = pair.value.*; |
| 623 | | var handled = false; |
| 624 | | for (case.expected_errors) |e, index| { |
| 625 | | if (!handled_errors[index]) { |
| 626 | | if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { |
| 627 | | handled_errors[index] = true; |
| 628 | | handled = true; |
| 629 | | break; |
| 630 | | } |
| 631 | | } |
| 632 | | } |
| 633 | | if (!handled) { |
| 634 | | err = error.UnexpectedError; |
| 635 | | std.debug.warn("Unexpected decl error: {}\n", .{v1}); |
| 636 | | } |
| 637 | | } |
| 638 | | } |
| 639 | | { |
| 640 | | var i = module.failed_exports.iterator(); |
| 641 | | while (i.next()) |pair| { |
| 642 | | const v1 = pair.value.*; |
| 643 | | var handled = false; |
| 644 | | for (case.expected_errors) |e, index| { |
| 645 | | if (!handled_errors[index]) { |
| 646 | | if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { |
| 647 | | handled_errors[index] = true; |
| 648 | | handled = true; |
| 649 | | break; |
| 650 | | } |
| 651 | | } |
| 652 | | } |
| 653 | | if (!handled) { |
| 654 | | err = error.UnexpectedError; |
| 655 | | std.debug.warn("Unexpected export error: {}\n", .{v1}); |
| 656 | | } |
| 592 | if (!handled) { |
| 593 | err = error.ErrorNotExpected; |
| 594 | std.debug.warn("Received an unexpected error: {}:{}: {}\n", .{ e.line, e.column, e.msg }); |
| 657 | 595 | } |
| 658 | 596 | } |
| 597 | |
| 659 | 598 | for (handled_errors) |e, i| { |
| 660 | 599 | if (!e) { |
| 661 | 600 | err = error.MissingExpectedError; |
| 662 | | std.debug.warn("Did not receive error: {}\n", .{case.expected_errors[i].msg}); |
| 601 | const er = case.expected_errors[i]; |
| 602 | std.debug.warn("Did not receive error: {}:{}: {}\n", .{ er.line, er.column, er.msg }); |
| 663 | 603 | } |
| 664 | 604 | } |
| 665 | 605 | |