| ... | ... | @@ -12,7 +12,7 @@ const enable_wasmtime: bool = build_options.enable_wasmtime; |
| 12 | 12 | const enable_darling: bool = build_options.enable_darling; |
| 13 | 13 | const enable_rosetta: bool = build_options.enable_rosetta; |
| 14 | 14 | const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; |
| 15 | | const skip_compile_errors = build_options.skip_compile_errors; |
| 15 | const skip_stage1 = build_options.skip_stage1; |
| 16 | 16 | const ThreadPool = @import("ThreadPool.zig"); |
| 17 | 17 | const CrossTarget = std.zig.CrossTarget; |
| 18 | 18 | const print = std.debug.print; |
| ... | ... | @@ -20,7 +20,6 @@ const assert = std.debug.assert; |
| 20 | 20 | |
| 21 | 21 | const zig_h = link.File.C.zig_h; |
| 22 | 22 | |
| 23 | | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; |
| 24 | 23 | const hr = "=" ** 80; |
| 25 | 24 | |
| 26 | 25 | test { |
| ... | ... | @@ -28,9 +27,50 @@ test { |
| 28 | 27 | @import("stage1.zig").os_init(); |
| 29 | 28 | } |
| 30 | 29 | |
| 31 | | var ctx = TestContext.init(); |
| 30 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 31 | defer arena_allocator.deinit(); |
| 32 | const arena = arena_allocator.allocator(); |
| 33 | |
| 34 | var ctx = TestContext.init(std.testing.allocator, arena); |
| 32 | 35 | defer ctx.deinit(); |
| 33 | 36 | |
| 37 | const compile_errors_dir_path = try std.fs.path.join(arena, &.{ |
| 38 | std.fs.path.dirname(@src().file).?, "..", "test", "compile_errors", |
| 39 | }); |
| 40 | |
| 41 | var compile_errors_dir = try std.fs.cwd().openDir(compile_errors_dir_path, .{}); |
| 42 | defer compile_errors_dir.close(); |
| 43 | |
| 44 | { |
| 45 | var stage2_dir = try compile_errors_dir.openDir("stage2", .{ .iterate = true }); |
| 46 | defer stage2_dir.close(); |
| 47 | |
| 48 | // TODO make this incremental once the bug is solved that it triggers |
| 49 | ctx.addErrorCasesFromDir("stage2", stage2_dir, .stage2, .Obj, false, .independent); |
| 50 | } |
| 51 | |
| 52 | if (!skip_stage1) { |
| 53 | var stage1_dir = try compile_errors_dir.openDir("stage1", .{}); |
| 54 | defer stage1_dir.close(); |
| 55 | |
| 56 | const Config = struct { |
| 57 | name: []const u8, |
| 58 | is_test: bool, |
| 59 | output_mode: std.builtin.OutputMode, |
| 60 | }; |
| 61 | |
| 62 | for ([_]Config{ |
| 63 | .{ .name = "obj", .is_test = false, .output_mode = .Obj }, |
| 64 | .{ .name = "exe", .is_test = false, .output_mode = .Exe }, |
| 65 | .{ .name = "test", .is_test = true, .output_mode = .Exe }, |
| 66 | }) |config| { |
| 67 | var dir = try stage1_dir.openDir(config.name, .{ .iterate = true }); |
| 68 | defer dir.close(); |
| 69 | |
| 70 | ctx.addErrorCasesFromDir("stage1", dir, .stage1, config.output_mode, config.is_test, .independent); |
| 71 | } |
| 72 | } |
| 73 | |
| 34 | 74 | try @import("test_cases").addCases(&ctx); |
| 35 | 75 | |
| 36 | 76 | try ctx.run(); |
| ... | ... | @@ -114,6 +154,7 @@ const ErrorMsg = union(enum) { |
| 114 | 154 | }; |
| 115 | 155 | |
| 116 | 156 | pub const TestContext = struct { |
| 157 | arena: Allocator, |
| 117 | 158 | cases: std.ArrayList(Case), |
| 118 | 159 | |
| 119 | 160 | pub const Update = struct { |
| ... | ... | @@ -316,7 +357,7 @@ pub const TestContext = struct { |
| 316 | 357 | .target = target, |
| 317 | 358 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 318 | 359 | .output_mode = .Exe, |
| 319 | | .files = std.ArrayList(File).init(ctx.cases.allocator), |
| 360 | .files = std.ArrayList(File).init(ctx.arena), |
| 320 | 361 | }) catch @panic("out of memory"); |
| 321 | 362 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 322 | 363 | } |
| ... | ... | @@ -327,7 +368,7 @@ pub const TestContext = struct { |
| 327 | 368 | } |
| 328 | 369 | |
| 329 | 370 | pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { |
| 330 | | const prefixed_name = std.fmt.allocPrint(ctx.cases.allocator, "CBE: {s}", .{name}) catch |
| 371 | const prefixed_name = std.fmt.allocPrint(ctx.arena, "CBE: {s}", .{name}) catch |
| 331 | 372 | @panic("out of memory"); |
| 332 | 373 | ctx.cases.append(Case{ |
| 333 | 374 | .name = prefixed_name, |
| ... | ... | @@ -335,7 +376,7 @@ pub const TestContext = struct { |
| 335 | 376 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 336 | 377 | .output_mode = .Exe, |
| 337 | 378 | .object_format = .c, |
| 338 | | .files = std.ArrayList(File).init(ctx.cases.allocator), |
| 379 | .files = std.ArrayList(File).init(ctx.arena), |
| 339 | 380 | }) catch @panic("out of memory"); |
| 340 | 381 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 341 | 382 | } |
| ... | ... | @@ -348,7 +389,7 @@ pub const TestContext = struct { |
| 348 | 389 | .target = target, |
| 349 | 390 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 350 | 391 | .output_mode = .Exe, |
| 351 | | .files = std.ArrayList(File).init(ctx.cases.allocator), |
| 392 | .files = std.ArrayList(File).init(ctx.arena), |
| 352 | 393 | .backend = .llvm, |
| 353 | 394 | .link_libc = true, |
| 354 | 395 | }) catch @panic("out of memory"); |
| ... | ... | @@ -365,7 +406,7 @@ pub const TestContext = struct { |
| 365 | 406 | .target = target, |
| 366 | 407 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 367 | 408 | .output_mode = .Obj, |
| 368 | | .files = std.ArrayList(File).init(ctx.cases.allocator), |
| 409 | .files = std.ArrayList(File).init(ctx.arena), |
| 369 | 410 | }) catch @panic("out of memory"); |
| 370 | 411 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 371 | 412 | } |
| ... | ... | @@ -381,7 +422,7 @@ pub const TestContext = struct { |
| 381 | 422 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 382 | 423 | .output_mode = .Exe, |
| 383 | 424 | .is_test = true, |
| 384 | | .files = std.ArrayList(File).init(ctx.cases.allocator), |
| 425 | .files = std.ArrayList(File).init(ctx.arena), |
| 385 | 426 | }) catch @panic("out of memory"); |
| 386 | 427 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 387 | 428 | } |
| ... | ... | @@ -404,7 +445,7 @@ pub const TestContext = struct { |
| 404 | 445 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 405 | 446 | .output_mode = .Obj, |
| 406 | 447 | .object_format = .c, |
| 407 | | .files = std.ArrayList(File).init(ctx.cases.allocator), |
| 448 | .files = std.ArrayList(File).init(ctx.arena), |
| 408 | 449 | }) catch @panic("out of memory"); |
| 409 | 450 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 410 | 451 | } |
| ... | ... | @@ -423,7 +464,7 @@ pub const TestContext = struct { |
| 423 | 464 | src: [:0]const u8, |
| 424 | 465 | expected_errors: []const []const u8, |
| 425 | 466 | ) void { |
| 426 | | if (skip_compile_errors) return; |
| 467 | if (skip_stage1) return; |
| 427 | 468 | |
| 428 | 469 | const case = ctx.addObj(name, .{}); |
| 429 | 470 | case.backend = .stage1; |
| ... | ... | @@ -436,7 +477,7 @@ pub const TestContext = struct { |
| 436 | 477 | src: [:0]const u8, |
| 437 | 478 | expected_errors: []const []const u8, |
| 438 | 479 | ) void { |
| 439 | | if (skip_compile_errors) return; |
| 480 | if (skip_stage1) return; |
| 440 | 481 | |
| 441 | 482 | const case = ctx.addTest(name, .{}); |
| 442 | 483 | case.backend = .stage1; |
| ... | ... | @@ -449,7 +490,7 @@ pub const TestContext = struct { |
| 449 | 490 | src: [:0]const u8, |
| 450 | 491 | expected_errors: []const []const u8, |
| 451 | 492 | ) void { |
| 452 | | if (skip_compile_errors) return; |
| 493 | if (skip_stage1) return; |
| 453 | 494 | |
| 454 | 495 | const case = ctx.addExe(name, .{}); |
| 455 | 496 | case.backend = .stage1; |
| ... | ... | @@ -612,6 +653,8 @@ pub const TestContext = struct { |
| 612 | 653 | case.compiles(fixed_src); |
| 613 | 654 | } |
| 614 | 655 | |
| 656 | const Strategy = enum { incremental, independent }; |
| 657 | |
| 615 | 658 | /// Adds a compile-error test for each file in the provided directory, using the |
| 616 | 659 | /// selected backend and output mode. If `one_test_case_per_file` is true, a new |
| 617 | 660 | /// test case is created for each file. Otherwise, a single test case is used for |
| ... | ... | @@ -628,33 +671,58 @@ pub const TestContext = struct { |
| 628 | 671 | backend: Backend, |
| 629 | 672 | output_mode: std.builtin.OutputMode, |
| 630 | 673 | is_test: bool, |
| 631 | | one_test_case_per_file: bool, |
| 632 | | ) !void { |
| 633 | | if (skip_compile_errors) return; |
| 674 | strategy: Strategy, |
| 675 | ) void { |
| 676 | var current_file: []const u8 = "none"; |
| 677 | addErrorCasesFromDirInner(ctx, name, dir, backend, output_mode, is_test, strategy, &current_file) catch |err| { |
| 678 | std.debug.panic("test harness failed to process file '{s}': {s}\n", .{ |
| 679 | current_file, @errorName(err), |
| 680 | }); |
| 681 | }; |
| 682 | } |
| 634 | 683 | |
| 635 | | const gpa = general_purpose_allocator.allocator(); |
| 684 | fn addErrorCasesFromDirInner( |
| 685 | ctx: *TestContext, |
| 686 | name: []const u8, |
| 687 | dir: std.fs.Dir, |
| 688 | backend: Backend, |
| 689 | output_mode: std.builtin.OutputMode, |
| 690 | is_test: bool, |
| 691 | strategy: Strategy, |
| 692 | /// This is kept up to date with the currently being processed file so |
| 693 | /// that if any errors occur the caller knows it happened during this file. |
| 694 | current_file: *[]const u8, |
| 695 | ) !void { |
| 636 | 696 | var opt_case: ?*Case = null; |
| 637 | 697 | |
| 638 | 698 | var it = dir.iterate(); |
| 639 | 699 | while (try it.next()) |entry| { |
| 640 | 700 | if (entry.kind != .File) continue; |
| 641 | 701 | |
| 642 | | var contents = try dir.readFileAlloc(gpa, entry.name, std.math.maxInt(u32)); |
| 643 | | defer gpa.free(contents); |
| 702 | // Ignore stuff such as .swp files |
| 703 | switch (Compilation.classifyFileExt(entry.name)) { |
| 704 | .unknown => continue, |
| 705 | else => {}, |
| 706 | } |
| 707 | |
| 708 | current_file.* = try ctx.arena.dupe(u8, entry.name); |
| 709 | |
| 710 | const max_file_size = 10 * 1024 * 1024; |
| 711 | const src = try dir.readFileAllocOptions(ctx.arena, entry.name, max_file_size, null, 1, 0); |
| 644 | 712 | |
| 645 | 713 | // The manifest is the last contiguous block of comments in the file |
| 646 | 714 | // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" |
| 647 | 715 | var manifest_start: ?usize = null; |
| 648 | | var manifest_end: usize = contents.len; |
| 649 | | if (contents.len > 0) { |
| 650 | | var cursor: usize = contents.len - 1; |
| 716 | var manifest_end: usize = src.len; |
| 717 | if (src.len > 0) { |
| 718 | var cursor: usize = src.len - 1; |
| 651 | 719 | while (true) { |
| 652 | 720 | // Move to beginning of line |
| 653 | | while (cursor > 0 and contents[cursor - 1] != '\n') cursor -= 1; |
| 721 | while (cursor > 0 and src[cursor - 1] != '\n') cursor -= 1; |
| 654 | 722 | |
| 655 | 723 | // Check if line is non-empty and does not start with "//" |
| 656 | | if (cursor + 1 < contents.len and contents[cursor + 1] != '\n' and contents[cursor + 1] != '\r') { |
| 657 | | if (std.mem.startsWith(u8, contents[cursor..], "//")) { |
| 724 | if (cursor + 1 < src.len and src[cursor + 1] != '\n' and src[cursor + 1] != '\r') { |
| 725 | if (std.mem.startsWith(u8, src[cursor..], "//")) { |
| 658 | 726 | manifest_start = cursor; |
| 659 | 727 | } else { |
| 660 | 728 | break; |
| ... | ... | @@ -666,27 +734,23 @@ pub const TestContext = struct { |
| 666 | 734 | } |
| 667 | 735 | } |
| 668 | 736 | |
| 669 | | var errors = std.ArrayList([]const u8).init(gpa); |
| 670 | | defer errors.deinit(); |
| 737 | var errors = std.ArrayList([]const u8).init(ctx.arena); |
| 671 | 738 | |
| 672 | 739 | if (manifest_start) |start| { |
| 673 | 740 | // Due to the above processing, we know that this is a contiguous block of comments |
| 674 | | var manifest_it = std.mem.tokenize(u8, contents[start..manifest_end], "\r\n"); |
| 741 | var manifest_it = std.mem.tokenize(u8, src[start..manifest_end], "\r\n"); |
| 675 | 742 | |
| 676 | 743 | // First line is the test case name |
| 677 | | const first_line = manifest_it.next() orelse return error.InvalidFile; |
| 678 | | const case_name = try std.mem.concat(gpa, u8, &.{ name, ": ", std.mem.trim(u8, first_line[2..], " \t") }); |
| 744 | const first_line = manifest_it.next() orelse return error.MissingTestCaseName; |
| 745 | const case_name = try std.mem.concat(ctx.arena, u8, &.{ name, ": ", std.mem.trim(u8, first_line[2..], " \t") }); |
| 679 | 746 | |
| 680 | 747 | // If the second line is present, it should be blank |
| 681 | 748 | if (manifest_it.next()) |second_line| { |
| 682 | | if (std.mem.trim(u8, second_line[2..], " \t").len != 0) return error.InvalidFile; |
| 749 | if (std.mem.trim(u8, second_line[2..], " \t").len != 0) return error.SecondLineNotBlank; |
| 683 | 750 | } |
| 684 | 751 | |
| 685 | 752 | // All following lines are expected error messages |
| 686 | | while (manifest_it.next()) |line| try errors.append(try gpa.dupe(u8, std.mem.trim(u8, line[2..], " \t"))); |
| 687 | | |
| 688 | | // The entire file contents is the source, including the manifest |
| 689 | | const src = try gpa.dupeZ(u8, contents); |
| 753 | while (manifest_it.next()) |line| try errors.append(try ctx.arena.dupe(u8, std.mem.trim(u8, line[2..], " \t"))); |
| 690 | 754 | |
| 691 | 755 | const case = opt_case orelse case: { |
| 692 | 756 | ctx.cases.append(TestContext.Case{ |
| ... | ... | @@ -702,22 +766,27 @@ pub const TestContext = struct { |
| 702 | 766 | opt_case = case; |
| 703 | 767 | break :case case; |
| 704 | 768 | }; |
| 705 | | if (one_test_case_per_file) { |
| 706 | | case.name = case_name; |
| 707 | | case.addError(src, errors.items); |
| 708 | | opt_case = null; |
| 709 | | } else { |
| 710 | | case.addErrorNamed(case_name, src, errors.items); |
| 769 | switch (strategy) { |
| 770 | .independent => { |
| 771 | case.name = case_name; |
| 772 | case.addError(src, errors.items); |
| 773 | opt_case = null; |
| 774 | }, |
| 775 | .incremental => { |
| 776 | case.addErrorNamed(case_name, src, errors.items); |
| 777 | }, |
| 711 | 778 | } |
| 712 | 779 | } else { |
| 713 | | return error.InvalidFile; // Manifests are currently mandatory |
| 780 | return error.MissingManifest; |
| 714 | 781 | } |
| 715 | 782 | } |
| 716 | 783 | } |
| 717 | 784 | |
| 718 | | fn init() TestContext { |
| 719 | | const allocator = std.heap.page_allocator; |
| 720 | | return .{ .cases = std.ArrayList(Case).init(allocator) }; |
| 785 | fn init(gpa: Allocator, arena: Allocator) TestContext { |
| 786 | return .{ |
| 787 | .cases = std.ArrayList(Case).init(gpa), |
| 788 | .arena = arena, |
| 789 | }; |
| 721 | 790 | } |
| 722 | 791 | |
| 723 | 792 | fn deinit(self: *TestContext) void { |