| ... | @@ -12,7 +12,7 @@ const enable_wasmtime: bool = build_options.enable_wasmtime; | ... | @@ -12,7 +12,7 @@ const enable_wasmtime: bool = build_options.enable_wasmtime; |
| 12 | const enable_darling: bool = build_options.enable_darling; | 12 | const enable_darling: bool = build_options.enable_darling; |
| 13 | const enable_rosetta: bool = build_options.enable_rosetta; | 13 | const enable_rosetta: bool = build_options.enable_rosetta; |
| 14 | const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; | 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 | const ThreadPool = @import("ThreadPool.zig"); | 16 | const ThreadPool = @import("ThreadPool.zig"); |
| 17 | const CrossTarget = std.zig.CrossTarget; | 17 | const CrossTarget = std.zig.CrossTarget; |
| 18 | const print = std.debug.print; | 18 | const print = std.debug.print; |
| ... | @@ -20,7 +20,6 @@ const assert = std.debug.assert; | ... | @@ -20,7 +20,6 @@ const assert = std.debug.assert; |
| 20 | | 20 | |
| 21 | const zig_h = link.File.C.zig_h; | 21 | const zig_h = link.File.C.zig_h; |
| 22 | | 22 | |
| 23 | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; | | |
| 24 | const hr = "=" ** 80; | 23 | const hr = "=" ** 80; |
| 25 | | 24 | |
| 26 | test { | 25 | test { |
| ... | @@ -28,9 +27,50 @@ test { | ... | @@ -28,9 +27,50 @@ test { |
| 28 | @import("stage1.zig").os_init(); | 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 | defer ctx.deinit(); | 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 | try @import("test_cases").addCases(&ctx); | 74 | try @import("test_cases").addCases(&ctx); |
| 35 | | 75 | |
| 36 | try ctx.run(); | 76 | try ctx.run(); |
| ... | @@ -114,6 +154,7 @@ const ErrorMsg = union(enum) { | ... | @@ -114,6 +154,7 @@ const ErrorMsg = union(enum) { |
| 114 | }; | 154 | }; |
| 115 | | 155 | |
| 116 | pub const TestContext = struct { | 156 | pub const TestContext = struct { |
| | 157 | arena: Allocator, |
| 117 | cases: std.ArrayList(Case), | 158 | cases: std.ArrayList(Case), |
| 118 | | 159 | |
| 119 | pub const Update = struct { | 160 | pub const Update = struct { |
| ... | @@ -316,7 +357,7 @@ pub const TestContext = struct { | ... | @@ -316,7 +357,7 @@ pub const TestContext = struct { |
| 316 | .target = target, | 357 | .target = target, |
| 317 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), | 358 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 318 | .output_mode = .Exe, | 359 | .output_mode = .Exe, |
| 319 | .files = std.ArrayList(File).init(ctx.cases.allocator), | 360 | .files = std.ArrayList(File).init(ctx.arena), |
| 320 | }) catch @panic("out of memory"); | 361 | }) catch @panic("out of memory"); |
| 321 | return &ctx.cases.items[ctx.cases.items.len - 1]; | 362 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 322 | } | 363 | } |
| ... | @@ -327,7 +368,7 @@ pub const TestContext = struct { | ... | @@ -327,7 +368,7 @@ pub const TestContext = struct { |
| 327 | } | 368 | } |
| 328 | | 369 | |
| 329 | pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { | 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 | @panic("out of memory"); | 372 | @panic("out of memory"); |
| 332 | ctx.cases.append(Case{ | 373 | ctx.cases.append(Case{ |
| 333 | .name = prefixed_name, | 374 | .name = prefixed_name, |
| ... | @@ -335,7 +376,7 @@ pub const TestContext = struct { | ... | @@ -335,7 +376,7 @@ pub const TestContext = struct { |
| 335 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), | 376 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 336 | .output_mode = .Exe, | 377 | .output_mode = .Exe, |
| 337 | .object_format = .c, | 378 | .object_format = .c, |
| 338 | .files = std.ArrayList(File).init(ctx.cases.allocator), | 379 | .files = std.ArrayList(File).init(ctx.arena), |
| 339 | }) catch @panic("out of memory"); | 380 | }) catch @panic("out of memory"); |
| 340 | return &ctx.cases.items[ctx.cases.items.len - 1]; | 381 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 341 | } | 382 | } |
| ... | @@ -348,7 +389,7 @@ pub const TestContext = struct { | ... | @@ -348,7 +389,7 @@ pub const TestContext = struct { |
| 348 | .target = target, | 389 | .target = target, |
| 349 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), | 390 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 350 | .output_mode = .Exe, | 391 | .output_mode = .Exe, |
| 351 | .files = std.ArrayList(File).init(ctx.cases.allocator), | 392 | .files = std.ArrayList(File).init(ctx.arena), |
| 352 | .backend = .llvm, | 393 | .backend = .llvm, |
| 353 | .link_libc = true, | 394 | .link_libc = true, |
| 354 | }) catch @panic("out of memory"); | 395 | }) catch @panic("out of memory"); |
| ... | @@ -365,7 +406,7 @@ pub const TestContext = struct { | ... | @@ -365,7 +406,7 @@ pub const TestContext = struct { |
| 365 | .target = target, | 406 | .target = target, |
| 366 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), | 407 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 367 | .output_mode = .Obj, | 408 | .output_mode = .Obj, |
| 368 | .files = std.ArrayList(File).init(ctx.cases.allocator), | 409 | .files = std.ArrayList(File).init(ctx.arena), |
| 369 | }) catch @panic("out of memory"); | 410 | }) catch @panic("out of memory"); |
| 370 | return &ctx.cases.items[ctx.cases.items.len - 1]; | 411 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 371 | } | 412 | } |
| ... | @@ -381,7 +422,7 @@ pub const TestContext = struct { | ... | @@ -381,7 +422,7 @@ pub const TestContext = struct { |
| 381 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), | 422 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 382 | .output_mode = .Exe, | 423 | .output_mode = .Exe, |
| 383 | .is_test = true, | 424 | .is_test = true, |
| 384 | .files = std.ArrayList(File).init(ctx.cases.allocator), | 425 | .files = std.ArrayList(File).init(ctx.arena), |
| 385 | }) catch @panic("out of memory"); | 426 | }) catch @panic("out of memory"); |
| 386 | return &ctx.cases.items[ctx.cases.items.len - 1]; | 427 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 387 | } | 428 | } |
| ... | @@ -404,7 +445,7 @@ pub const TestContext = struct { | ... | @@ -404,7 +445,7 @@ pub const TestContext = struct { |
| 404 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), | 445 | .updates = std.ArrayList(Update).init(ctx.cases.allocator), |
| 405 | .output_mode = .Obj, | 446 | .output_mode = .Obj, |
| 406 | .object_format = .c, | 447 | .object_format = .c, |
| 407 | .files = std.ArrayList(File).init(ctx.cases.allocator), | 448 | .files = std.ArrayList(File).init(ctx.arena), |
| 408 | }) catch @panic("out of memory"); | 449 | }) catch @panic("out of memory"); |
| 409 | return &ctx.cases.items[ctx.cases.items.len - 1]; | 450 | return &ctx.cases.items[ctx.cases.items.len - 1]; |
| 410 | } | 451 | } |
| ... | @@ -423,7 +464,7 @@ pub const TestContext = struct { | ... | @@ -423,7 +464,7 @@ pub const TestContext = struct { |
| 423 | src: [:0]const u8, | 464 | src: [:0]const u8, |
| 424 | expected_errors: []const []const u8, | 465 | expected_errors: []const []const u8, |
| 425 | ) void { | 466 | ) void { |
| 426 | if (skip_compile_errors) return; | 467 | if (skip_stage1) return; |
| 427 | | 468 | |
| 428 | const case = ctx.addObj(name, .{}); | 469 | const case = ctx.addObj(name, .{}); |
| 429 | case.backend = .stage1; | 470 | case.backend = .stage1; |
| ... | @@ -436,7 +477,7 @@ pub const TestContext = struct { | ... | @@ -436,7 +477,7 @@ pub const TestContext = struct { |
| 436 | src: [:0]const u8, | 477 | src: [:0]const u8, |
| 437 | expected_errors: []const []const u8, | 478 | expected_errors: []const []const u8, |
| 438 | ) void { | 479 | ) void { |
| 439 | if (skip_compile_errors) return; | 480 | if (skip_stage1) return; |
| 440 | | 481 | |
| 441 | const case = ctx.addTest(name, .{}); | 482 | const case = ctx.addTest(name, .{}); |
| 442 | case.backend = .stage1; | 483 | case.backend = .stage1; |
| ... | @@ -449,7 +490,7 @@ pub const TestContext = struct { | ... | @@ -449,7 +490,7 @@ pub const TestContext = struct { |
| 449 | src: [:0]const u8, | 490 | src: [:0]const u8, |
| 450 | expected_errors: []const []const u8, | 491 | expected_errors: []const []const u8, |
| 451 | ) void { | 492 | ) void { |
| 452 | if (skip_compile_errors) return; | 493 | if (skip_stage1) return; |
| 453 | | 494 | |
| 454 | const case = ctx.addExe(name, .{}); | 495 | const case = ctx.addExe(name, .{}); |
| 455 | case.backend = .stage1; | 496 | case.backend = .stage1; |
| ... | @@ -612,6 +653,8 @@ pub const TestContext = struct { | ... | @@ -612,6 +653,8 @@ pub const TestContext = struct { |
| 612 | case.compiles(fixed_src); | 653 | case.compiles(fixed_src); |
| 613 | } | 654 | } |
| 614 | | 655 | |
| | 656 | const Strategy = enum { incremental, independent }; |
| | 657 | |
| 615 | /// Adds a compile-error test for each file in the provided directory, using the | 658 | /// Adds a compile-error test for each file in the provided directory, using the |
| 616 | /// selected backend and output mode. If `one_test_case_per_file` is true, a new | 659 | /// selected backend and output mode. If `one_test_case_per_file` is true, a new |
| 617 | /// test case is created for each file. Otherwise, a single test case is used for | 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,33 +671,58 @@ pub const TestContext = struct { |
| 628 | backend: Backend, | 671 | backend: Backend, |
| 629 | output_mode: std.builtin.OutputMode, | 672 | output_mode: std.builtin.OutputMode, |
| 630 | is_test: bool, | 673 | is_test: bool, |
| 631 | one_test_case_per_file: bool, | 674 | strategy: Strategy, |
| 632 | ) !void { | 675 | ) void { |
| 633 | if (skip_compile_errors) return; | 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 | var opt_case: ?*Case = null; | 696 | var opt_case: ?*Case = null; |
| 637 | | 697 | |
| 638 | var it = dir.iterate(); | 698 | var it = dir.iterate(); |
| 639 | while (try it.next()) |entry| { | 699 | while (try it.next()) |entry| { |
| 640 | if (entry.kind != .File) continue; | 700 | if (entry.kind != .File) continue; |
| 641 | | 701 | |
| 642 | var contents = try dir.readFileAlloc(gpa, entry.name, std.math.maxInt(u32)); | 702 | // Ignore stuff such as .swp files |
| 643 | defer gpa.free(contents); | 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 | // The manifest is the last contiguous block of comments in the file | 713 | // The manifest is the last contiguous block of comments in the file |
| 646 | // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" | 714 | // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" |
| 647 | var manifest_start: ?usize = null; | 715 | var manifest_start: ?usize = null; |
| 648 | var manifest_end: usize = contents.len; | 716 | var manifest_end: usize = src.len; |
| 649 | if (contents.len > 0) { | 717 | if (src.len > 0) { |
| 650 | var cursor: usize = contents.len - 1; | 718 | var cursor: usize = src.len - 1; |
| 651 | while (true) { | 719 | while (true) { |
| 652 | // Move to beginning of line | 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 | // Check if line is non-empty and does not start with "//" | 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') { | 724 | if (cursor + 1 < src.len and src[cursor + 1] != '\n' and src[cursor + 1] != '\r') { |
| 657 | if (std.mem.startsWith(u8, contents[cursor..], "//")) { | 725 | if (std.mem.startsWith(u8, src[cursor..], "//")) { |
| 658 | manifest_start = cursor; | 726 | manifest_start = cursor; |
| 659 | } else { | 727 | } else { |
| 660 | break; | 728 | break; |
| ... | @@ -666,27 +734,23 @@ pub const TestContext = struct { | ... | @@ -666,27 +734,23 @@ pub const TestContext = struct { |
| 666 | } | 734 | } |
| 667 | } | 735 | } |
| 668 | | 736 | |
| 669 | var errors = std.ArrayList([]const u8).init(gpa); | 737 | var errors = std.ArrayList([]const u8).init(ctx.arena); |
| 670 | defer errors.deinit(); | | |
| 671 | | 738 | |
| 672 | if (manifest_start) |start| { | 739 | if (manifest_start) |start| { |
| 673 | // Due to the above processing, we know that this is a contiguous block of comments | 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 | // First line is the test case name | 743 | // First line is the test case name |
| 677 | const first_line = manifest_it.next() orelse return error.InvalidFile; | 744 | const first_line = manifest_it.next() orelse return error.MissingTestCaseName; |
| 678 | const case_name = try std.mem.concat(gpa, u8, &.{ name, ": ", std.mem.trim(u8, first_line[2..], " \t") }); | 745 | const case_name = try std.mem.concat(ctx.arena, u8, &.{ name, ": ", std.mem.trim(u8, first_line[2..], " \t") }); |
| 679 | | 746 | |
| 680 | // If the second line is present, it should be blank | 747 | // If the second line is present, it should be blank |
| 681 | if (manifest_it.next()) |second_line| { | 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 | // All following lines are expected error messages | 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"))); | 753 | while (manifest_it.next()) |line| try errors.append(try ctx.arena.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); | | |
| 690 | | 754 | |
| 691 | const case = opt_case orelse case: { | 755 | const case = opt_case orelse case: { |
| 692 | ctx.cases.append(TestContext.Case{ | 756 | ctx.cases.append(TestContext.Case{ |
| ... | @@ -702,22 +766,27 @@ pub const TestContext = struct { | ... | @@ -702,22 +766,27 @@ pub const TestContext = struct { |
| 702 | opt_case = case; | 766 | opt_case = case; |
| 703 | break :case case; | 767 | break :case case; |
| 704 | }; | 768 | }; |
| 705 | if (one_test_case_per_file) { | 769 | switch (strategy) { |
| 706 | case.name = case_name; | 770 | .independent => { |
| 707 | case.addError(src, errors.items); | 771 | case.name = case_name; |
| 708 | opt_case = null; | 772 | case.addError(src, errors.items); |
| 709 | } else { | 773 | opt_case = null; |
| 710 | case.addErrorNamed(case_name, src, errors.items); | 774 | }, |
| | 775 | .incremental => { |
| | 776 | case.addErrorNamed(case_name, src, errors.items); |
| | 777 | }, |
| 711 | } | 778 | } |
| 712 | } else { | 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 { | 785 | fn init(gpa: Allocator, arena: Allocator) TestContext { |
| 719 | const allocator = std.heap.page_allocator; | 786 | return .{ |
| 720 | return .{ .cases = std.ArrayList(Case).init(allocator) }; | 787 | .cases = std.ArrayList(Case).init(gpa), |
| | 788 | .arena = arena, |
| | 789 | }; |
| 721 | } | 790 | } |
| 722 | | 791 | |
| 723 | fn deinit(self: *TestContext) void { | 792 | fn deinit(self: *TestContext) void { |