diff --git a/src/test.zig b/src/test.zig index f4374ea0bd..aa7fad2af2 100644 --- a/src/test.zig +++ b/src/test.zig @@ -11,8 +11,9 @@ const enable_wine: bool = build_options.enable_wine; const enable_wasmtime: bool = build_options.enable_wasmtime; const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_dir; const ThreadPool = @import("ThreadPool.zig"); +const CrossTarget = std.zig.CrossTarget; -const cheader = @embedFile("link/cbe.h"); +const c_header = @embedFile("link/cbe.h"); test "self-hosted" { var ctx = TestContext.init(); @@ -88,6 +89,9 @@ pub const TestContext = struct { /// A transformation update transforms the input and tests against /// the expected output ZIR. Transformation: [:0]const u8, + /// Check the main binary output file against an expected set of bytes. + /// This is most useful with, for example, `-ofmt=c`. + CompareObjectFile: []const u8, /// An error update attempts to compile bad code, and ensures that it /// fails to compile, and for the expected reasons. /// A slice containing the expected errors *in sequential order*. @@ -109,12 +113,12 @@ pub const TestContext = struct { path: []const u8, }; - pub const TestType = enum { + pub const Extension = enum { Zig, ZIR, }; - /// A Case consists of a set of *updates*. The same Compilation is used for each + /// A `Case` consists of a list of `Update`. The same `Compilation` is used for each /// update, so each update's source is treated as a single file being /// updated by the test harness and incrementally compiled. pub const Case = struct { @@ -123,13 +127,14 @@ pub const TestContext = struct { name: []const u8, /// The platform the test targets. For non-native platforms, an emulator /// such as QEMU is required for tests to complete. - target: std.zig.CrossTarget, + target: CrossTarget, /// In order to be able to run e.g. Execution updates, this must be set /// to Executable. output_mode: std.builtin.OutputMode, updates: std.ArrayList(Update), - extension: TestType, - cbe: bool = false, + extension: Extension, + object_format: ?std.builtin.ObjectFormat = null, + emit_h: bool = false, files: std.ArrayList(File), @@ -145,6 +150,7 @@ pub const TestContext = struct { /// Adds a subcase in which the module is updated with `src`, and a C /// header is generated. pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void { + self.emit_h = true; self.updates.append(.{ .src = src, .case = .{ .Header = result }, @@ -160,6 +166,15 @@ pub const TestContext = struct { }) catch unreachable; } + /// Adds a subcase in which the module is updated with `src`, compiled, + /// and the object file data is compared against `result`. + pub fn addCompareObjectFile(self: *Case, src: [:0]const u8, result: []const u8) void { + self.updates.append(.{ + .src = src, + .case = .{ .CompareObjectFile = result }, + }) catch unreachable; + } + /// Adds a subcase in which the module is updated with `src`, which /// should contain invalid input, and ensures that compilation fails /// for the expected reasons, given in sequential order in `errors` in @@ -214,86 +229,100 @@ pub const TestContext = struct { pub fn addExe( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, - T: TestType, + target: CrossTarget, + extension: Extension, ) *Case { ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, - .extension = T, + .extension = extension, .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } /// Adds a test case for Zig input, producing an executable - pub fn exe(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + pub fn exe(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { return ctx.addExe(name, target, .Zig); } /// Adds a test case for ZIR input, producing an executable - pub fn exeZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + pub fn exeZIR(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { return ctx.addExe(name, target, .ZIR); } + pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Exe, + .extension = .Zig, + .object_format = .c, + .files = std.ArrayList(File).init(ctx.cases.allocator), + }) catch unreachable; + return &ctx.cases.items[ctx.cases.items.len - 1]; + } + pub fn addObj( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, - T: TestType, + target: CrossTarget, + extension: Extension, ) *Case { ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, - .extension = T, + .extension = extension, .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } - /// Adds a test case for Zig input, producing an object file - pub fn obj(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + /// Adds a test case for Zig input, producing an object file. + pub fn obj(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { return ctx.addObj(name, target, .Zig); } - /// Adds a test case for ZIR input, producing an object file - pub fn objZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + /// Adds a test case for ZIR input, producing an object file. + pub fn objZIR(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { return ctx.addObj(name, target, .ZIR); } - pub fn addC(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, T: TestType) *Case { + /// Adds a test case for Zig or ZIR input, producing C code. + pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget, ext: Extension) *Case { ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, - .extension = T, - .cbe = true, + .extension = ext, + .object_format = .c, .files = std.ArrayList(File).init(ctx.cases.allocator), }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } - pub fn c(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out); + pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { + ctx.addC(name, target, .Zig).addCompareObjectFile(src, c_header ++ out); } - pub fn h(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addHeader(src, cheader ++ out); + pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { + ctx.addC(name, target, .Zig).addHeader(src, c_header ++ out); } pub fn addCompareOutput( ctx: *TestContext, name: []const u8, - T: TestType, + extension: Extension, src: [:0]const u8, expected_stdout: []const u8, ) void { - ctx.addExe(name, .{}, T).addCompareOutput(src, expected_stdout); + ctx.addExe(name, .{}, extension).addCompareOutput(src, expected_stdout); } /// Adds a test case that compiles the Zig source given in `src`, executes @@ -321,12 +350,12 @@ pub const TestContext = struct { pub fn addTransform( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, - T: TestType, + target: CrossTarget, + extension: Extension, src: [:0]const u8, result: [:0]const u8, ) void { - ctx.addObj(name, target, T).addTransform(src, result); + ctx.addObj(name, target, extension).addTransform(src, result); } /// Adds a test case that compiles the Zig given in `src` to ZIR and tests @@ -334,7 +363,7 @@ pub const TestContext = struct { pub fn transform( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, result: [:0]const u8, ) void { @@ -346,7 +375,7 @@ pub const TestContext = struct { pub fn transformZIR( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, result: [:0]const u8, ) void { @@ -356,12 +385,12 @@ pub const TestContext = struct { pub fn addError( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, - T: TestType, + target: CrossTarget, + extension: Extension, src: [:0]const u8, expected_errors: []const []const u8, ) void { - ctx.addObj(name, target, T).addError(src, expected_errors); + ctx.addObj(name, target, extension).addError(src, expected_errors); } /// Adds a test case that ensures that the Zig given in `src` fails to @@ -370,7 +399,7 @@ pub const TestContext = struct { pub fn compileError( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, ) void { @@ -383,7 +412,7 @@ pub const TestContext = struct { pub fn compileErrorZIR( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, ) void { @@ -393,11 +422,11 @@ pub const TestContext = struct { pub fn addCompiles( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, - T: TestType, + target: CrossTarget, + extension: Extension, src: [:0]const u8, ) void { - ctx.addObj(name, target, T).compiles(src); + ctx.addObj(name, target, extension).compiles(src); } /// Adds a test case that asserts that the Zig given in `src` compiles @@ -405,7 +434,7 @@ pub const TestContext = struct { pub fn compiles( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, ) void { ctx.addCompiles(name, target, .Zig, src); @@ -416,7 +445,7 @@ pub const TestContext = struct { pub fn compilesZIR( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, ) void { ctx.addCompiles(name, target, .ZIR, src); @@ -430,7 +459,7 @@ pub const TestContext = struct { pub fn incrementalFailure( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, fixed_src: [:0]const u8, @@ -448,7 +477,7 @@ pub const TestContext = struct { pub fn incrementalFailureZIR( ctx: *TestContext, name: []const u8, - target: std.zig.CrossTarget, + target: CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, fixed_src: [:0]const u8, @@ -548,12 +577,11 @@ pub const TestContext = struct { .root_src_path = tmp_src_path, }; - const ofmt: ?std.builtin.ObjectFormat = if (case.cbe) .c else null; const bin_name = try std.zig.binNameAlloc(arena, .{ .root_name = "test_case", .target = target, .output_mode = case.output_mode, - .object_format = ofmt, + .object_format = case.object_format, }); const emit_directory: Compilation.Directory = .{ @@ -564,7 +592,7 @@ pub const TestContext = struct { .directory = emit_directory, .basename = bin_name, }; - const emit_h: ?Compilation.EmitLoc = if (case.cbe) + const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{ .directory = emit_directory, .basename = "test_case.h", @@ -588,7 +616,7 @@ pub const TestContext = struct { .emit_h = emit_h, .root_pkg = &root_pkg, .keep_source_files_loaded = true, - .object_format = ofmt, + .object_format = case.object_format, .is_native_os = case.target.isNativeOs(), .is_native_abi = case.target.isNativeAbi(), }); @@ -631,9 +659,10 @@ pub const TestContext = struct { }, } } - if (case.cbe) { - const C = comp.bin_file.cast(link.File.C).?; - std.debug.print("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items}); + if (comp.bin_file.cast(link.File.C)) |c_file| { + std.debug.print("Generated C: \n===============\n{}\n\n===========\n\n", .{ + c_file.main.items, + }); } std.debug.print("Test failed.\n", .{}); std.process.exit(1); @@ -644,39 +673,37 @@ pub const TestContext = struct { .Header => |expected_output| { var file = try tmp.dir.openFile("test_case.h", .{ .read = true }); defer file.close(); - var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read headeroutput!"); + const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); + + std.testing.expectEqualStrings(expected_output, out); + }, + .CompareObjectFile => |expected_output| { + var file = try tmp.dir.openFile(bin_name, .{ .read = true }); + defer file.close(); + const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); std.testing.expectEqualStrings(expected_output, out); }, .Transformation => |expected_output| { - if (case.cbe) { - // The C file is always closed after an update, because we don't support - // incremental updates. - var file = try tmp.dir.openFile(bin_name, .{ .read = true }); - defer file.close(); - var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!"); - std.testing.expectEqualStrings(expected_output, out); - } else { - update_node.setEstimatedTotalItems(5); - var emit_node = update_node.start("emit", 0); - emit_node.activate(); - var new_zir_module = try zir.emit(allocator, comp.bin_file.options.module.?); - defer new_zir_module.deinit(allocator); - emit_node.end(); + update_node.setEstimatedTotalItems(5); + var emit_node = update_node.start("emit", 0); + emit_node.activate(); + var new_zir_module = try zir.emit(allocator, comp.bin_file.options.module.?); + defer new_zir_module.deinit(allocator); + emit_node.end(); - var write_node = update_node.start("write", 0); - write_node.activate(); - var out_zir = std.ArrayList(u8).init(allocator); - defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.outStream()); - write_node.end(); + var write_node = update_node.start("write", 0); + write_node.activate(); + var out_zir = std.ArrayList(u8).init(allocator); + defer out_zir.deinit(); + try new_zir_module.writeToStream(allocator, out_zir.outStream()); + write_node.end(); - var test_node = update_node.start("assert", 0); - test_node.activate(); - defer test_node.end(); + var test_node = update_node.start("assert", 0); + test_node.activate(); + defer test_node.end(); - std.testing.expectEqualStrings(expected_output, out_zir.items); - } + std.testing.expectEqualStrings(expected_output, out_zir.items); }, .Error => |e| { var test_node = update_node.start("assert", 0); @@ -734,8 +761,6 @@ pub const TestContext = struct { } }, .Execution => |expected_stdout| { - std.debug.assert(!case.cbe); - update_node.setEstimatedTotalItems(4); var exec_result = x: { var exec_node = update_node.start("execute", 0); @@ -745,9 +770,12 @@ pub const TestContext = struct { var argv = std.ArrayList([]const u8).init(allocator); defer argv.deinit(); - const exe_path = try std.fmt.allocPrint(arena, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name}); - - switch (case.target.getExternalExecutor()) { + const exe_path = try std.fmt.allocPrint(arena, "." ++ std.fs.path.sep_str ++ "{s}", .{bin_name}); + if (case.object_format != null and case.object_format.? == .c) { + try argv.appendSlice(&[_][]const u8{ + std.testing.zig_exe_path, "run", exe_path, "-lc", + }); + } else switch (case.target.getExternalExecutor()) { .native => try argv.append(exe_path), .unavailable => { try self.runInterpreterIfAvailable(allocator, &exec_node, case, tmp.dir, bin_name); @@ -809,18 +837,13 @@ pub const TestContext = struct { switch (exec_result.term) { .Exited => |code| { if (code != 0) { - std.debug.print("elf file exited with code {}\n", .{code}); + std.debug.print("execution exited with code {}\n", .{code}); return error.BinaryBadExitCode; } }, else => return error.BinaryCrashed, } - if (!std.mem.eql(u8, expected_stdout, exec_result.stdout)) { - std.debug.panic( - "update index {}, mismatched stdout\n====Expected (len={}):====\n{}\n====Actual (len={}):====\n{}\n========\n", - .{ update_index, expected_stdout.len, expected_stdout, exec_result.stdout.len, exec_result.stdout }, - ); - } + std.testing.expectEqualStrings(expected_stdout, exec_result.stdout); }, } } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 3a9bff897a..2212d48298 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -9,6 +9,29 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exeFromCompiledC("hello world with updates", .{}); + + // Regular old hello world + case.addCompareOutput( + \\extern fn puts(s: [*:0]const u8) c_int; + \\export fn main() c_int { + \\ _ = puts("hello world!"); + \\ return 0; + \\} + , "hello world!" ++ std.cstr.line_sep); + + // Now change the message only + // TODO fix C backend not supporting updates + //case.addCompareOutput( + // \\extern fn puts(s: [*:0]const u8) c_int; + // \\export fn main() c_int { + // \\ _ = puts("yo"); + // \\ return 0; + // \\} + //, "yo" ++ std.cstr.line_sep); + } + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable;