Commit 2bae942800

Andrew Kelley <andrew@ziglang.org>
2020-04-28 00:26:59
add ZIR compare output test case to test suite
1 parent b23a879
lib/std/fs/file.zig
@@ -69,6 +69,11 @@ pub const File = struct {
         /// It allows the use of `noasync` when calling functions related to opening
         /// the file, reading, and writing.
         always_blocking: bool = false,
+
+        /// `true` means the opened directory can be passed to a child process.
+        /// `false` means the directory handle is considered to be closed when a child
+        /// process is spawned. This corresponds to the inverse of `O_CLOEXEC` on POSIX.
+        share_with_child_process: bool = false,
     };
 
     /// TODO https://github.com/ziglang/zig/issues/3802
@@ -107,6 +112,11 @@ pub const File = struct {
         /// For POSIX systems this is the file system mode the file will
         /// be created with.
         mode: Mode = default_mode,
+
+        /// `true` means the opened directory can be passed to a child process.
+        /// `false` means the directory handle is considered to be closed when a child
+        /// process is spawned. This corresponds to the inverse of `O_CLOEXEC` on POSIX.
+        share_with_child_process: bool = false,
     };
 
     /// Upon success, the stream is in an uninitialized state. To continue using it,
lib/std/zig/system.zig
@@ -415,7 +415,12 @@ pub const NativeTargetInfo = struct {
         // over our own shared objects and find a dynamic linker.
         self_exe: {
             const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
-            defer allocator.free(lib_paths);
+            defer {
+                for (lib_paths) |lib_path| {
+                    allocator.free(lib_path);
+                }
+                allocator.free(lib_paths);
+            }
 
             var found_ld_info: LdInfo = undefined;
             var found_ld_path: [:0]const u8 = undefined;
lib/std/child_process.zig
@@ -46,6 +46,12 @@ pub const ChildProcess = struct {
 
     /// Set to change the current working directory when spawning the child process.
     cwd: ?[]const u8,
+    /// Set to change the current working directory when spawning the child process.
+    /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
+    /// Once that is done, `cwd` will be deprecated in favor of this field.
+    /// The directory handle must be opened with the ability to be passed
+    /// to a child process (no `O_CLOEXEC` flag on POSIX).
+    cwd_dir: ?fs.Dir = null,
 
     err_pipe: if (builtin.os.tag == .windows) void else [2]os.fd_t,
 
@@ -183,6 +189,7 @@ pub const ChildProcess = struct {
         allocator: *mem.Allocator,
         argv: []const []const u8,
         cwd: ?[]const u8 = null,
+        cwd_dir: ?fs.Dir = null,
         env_map: ?*const BufMap = null,
         max_output_bytes: usize = 50 * 1024,
         expand_arg0: Arg0Expand = .no_expand,
@@ -194,6 +201,7 @@ pub const ChildProcess = struct {
         child.stdout_behavior = .Pipe;
         child.stderr_behavior = .Pipe;
         child.cwd = args.cwd;
+        child.cwd_dir = args.cwd_dir;
         child.env_map = args.env_map;
         child.expand_arg0 = args.expand_arg0;
 
@@ -414,7 +422,9 @@ pub const ChildProcess = struct {
                 os.close(stderr_pipe[1]);
             }
 
-            if (self.cwd) |cwd| {
+            if (self.cwd_dir) |cwd| {
+                os.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err);
+            } else if (self.cwd) |cwd| {
                 os.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err);
             }
 
lib/std/fs.zig
@@ -606,7 +606,8 @@ pub const Dir = struct {
         } else 0;
 
         const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
-        const os_flags = lock_flag | O_LARGEFILE | os.O_CLOEXEC | if (flags.write and flags.read)
+        const O_CLOEXEC: u32 = if (flags.share_with_child_process) 0 else os.O_CLOEXEC;
+        const os_flags = lock_flag | O_LARGEFILE | O_CLOEXEC | if (flags.write and flags.read)
             @as(u32, os.O_RDWR)
         else if (flags.write)
             @as(u32, os.O_WRONLY)
@@ -689,7 +690,8 @@ pub const Dir = struct {
         } else 0;
 
         const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
-        const os_flags = lock_flag | O_LARGEFILE | os.O_CREAT | os.O_CLOEXEC |
+        const O_CLOEXEC: u32 = if (flags.share_with_child_process) 0 else os.O_CLOEXEC;
+        const os_flags = lock_flag | O_LARGEFILE | os.O_CREAT | O_CLOEXEC |
             (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
             (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
             (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
@@ -787,6 +789,15 @@ pub const Dir = struct {
         }
     }
 
+    /// This function performs `makePath`, followed by `openDir`.
+    /// If supported by the OS, this operation is atomic. It is not atomic on
+    /// all operating systems.
+    pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
+        // TODO improve this implementation on Windows; we can avoid 1 call to NtClose
+        try self.makePath(sub_path);
+        return self.openDir(sub_path, open_dir_options);
+    }
+
     /// Changes the current working directory to the open directory handle.
     /// This modifies global state and can have surprising effects in multi-
     /// threaded applications. Most applications and especially libraries should
@@ -807,6 +818,11 @@ pub const Dir = struct {
         /// `true` means the opened directory can be scanned for the files and sub-directories
         /// of the result. It means the `iterate` function can be called.
         iterate: bool = false,
+
+        /// `true` means the opened directory can be passed to a child process.
+        /// `false` means the directory handle is considered to be closed when a child
+        /// process is spawned. This corresponds to the inverse of `O_CLOEXEC` on POSIX.
+        share_with_child_process: bool = false,
     };
 
     /// Opens a directory at the given path. The directory is a system resource that remains
@@ -832,9 +848,11 @@ pub const Dir = struct {
             return self.openDirW(&sub_path_w, args);
         } else if (!args.iterate) {
             const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0;
-            return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH);
+            const O_CLOEXEC: u32 = if (args.share_with_child_process) 0 else os.O_CLOEXEC;
+            return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | O_CLOEXEC | O_PATH);
         } else {
-            return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC);
+            const O_CLOEXEC: u32 = if (args.share_with_child_process) 0 else os.O_CLOEXEC;
+            return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | O_CLOEXEC);
         }
     }
 
lib/std/testing.zig
@@ -193,6 +193,44 @@ pub fn expect(ok: bool) void {
     if (!ok) @panic("test failure");
 }
 
+pub const TmpDir = struct {
+    dir: std.fs.Dir,
+    parent_dir: std.fs.Dir,
+    sub_path: [sub_path_len]u8,
+
+    const random_bytes_count = 12;
+    const sub_path_len = std.base64.Base64Encoder.calcSize(random_bytes_count);
+
+    pub fn cleanup(self: *TmpDir) void {
+        self.dir.close();
+        self.parent_dir.deleteTree(&self.sub_path) catch {};
+        self.parent_dir.close();
+        self.* = undefined;
+    }
+};
+
+pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
+    var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
+    std.crypto.randomBytes(&random_bytes) catch
+        @panic("unable to make tmp dir for testing: unable to get random bytes");
+    var sub_path: [TmpDir.sub_path_len]u8 = undefined;
+    std.fs.base64_encoder.encode(&sub_path, &random_bytes);
+
+    var cache_dir = std.fs.cwd().makeOpenPath("zig-cache", .{}) catch
+        @panic("unable to make tmp dir for testing: unable to make and open zig-cache dir");
+    defer cache_dir.close();
+    var parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch
+        @panic("unable to make tmp dir for testing: unable to make and open zig-cache/tmp dir");
+    var dir = parent_dir.makeOpenPath(&sub_path, opts) catch
+        @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
+
+    return .{
+        .dir = dir,
+        .parent_dir = parent_dir,
+        .sub_path = sub_path,
+    };
+}
+
 test "expectEqual nested array" {
     const a = [2][2]f32{
         [_]f32{ 1.0, 0.0 },
lib/std/zig.zig
@@ -9,6 +9,23 @@ pub const ast = @import("zig/ast.zig");
 pub const system = @import("zig/system.zig");
 pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;
 
+pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } {
+    var line: usize = 0;
+    var column: usize = 0;
+    for (source[0..byte_offset]) |byte| {
+        switch (byte) {
+            '\n' => {
+                line += 1;
+                column = 0;
+            },
+            else => {
+                column += 1;
+            },
+        }
+    }
+    return .{ .line = line, .column = column };
+}
+
 test "" {
     @import("std").meta.refAllDecls(@This());
 }
src-self-hosted/ir/text.zig
@@ -532,9 +532,10 @@ const Parser = struct {
             else => |byte| return self.failByte(byte),
         };
 
-        return Inst.Fn.Body{
-            .instructions = body_context.instructions.toOwnedSlice(),
-        };
+        // Move the instructions to the arena
+        const instrs = try self.arena.allocator.alloc(*Inst, body_context.instructions.items.len);
+        mem.copy(*Inst, instrs, body_context.instructions.items);
+        return Inst.Fn.Body{ .instructions = instrs };
     }
 
     fn parseStringLiteral(self: *Parser) ![]u8 {
@@ -588,26 +589,27 @@ const Parser = struct {
 
     fn parseRoot(self: *Parser) !void {
         // The IR format is designed so that it can be tokenized and parsed at the same time.
-        while (true) : (self.i += 1) switch (self.source[self.i]) {
-            ';' => _ = try skipToAndOver(self, '\n'),
-            '@' => {
-                self.i += 1;
-                const ident = try skipToAndOver(self, ' ');
-                skipSpace(self);
-                try requireEatBytes(self, "=");
-                skipSpace(self);
-                const inst = try parseInstruction(self, null);
-                const ident_index = self.decls.items.len;
-                if (try self.global_name_map.put(ident, ident_index)) |_| {
-                    return self.fail("redefinition of identifier '{}'", .{ident});
-                }
-                try self.decls.append(inst);
-                continue;
-            },
-            ' ', '\n' => continue,
-            0 => break,
-            else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
-        };
+        while (true) {
+            switch (self.source[self.i]) {
+                ';' => _ = try skipToAndOver(self, '\n'),
+                '@' => {
+                    self.i += 1;
+                    const ident = try skipToAndOver(self, ' ');
+                    skipSpace(self);
+                    try requireEatBytes(self, "=");
+                    skipSpace(self);
+                    const inst = try parseInstruction(self, null);
+                    const ident_index = self.decls.items.len;
+                    if (try self.global_name_map.put(ident, ident_index)) |_| {
+                        return self.fail("redefinition of identifier '{}'", .{ident});
+                    }
+                    try self.decls.append(inst);
+                },
+                ' ', '\n' => self.i += 1,
+                0 => break,
+                else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
+            }
+        }
     }
 
     fn eatByte(self: *Parser, byte: u8) bool {
src-self-hosted/ir.zig
@@ -4,10 +4,11 @@ const Allocator = std.mem.Allocator;
 const Value = @import("value.zig").Value;
 const Type = @import("type.zig").Type;
 const assert = std.debug.assert;
-const text = @import("ir/text.zig");
 const BigInt = std.math.big.Int;
 const Target = std.Target;
 
+pub const text = @import("ir/text.zig");
+
 /// These are in-memory, analyzed instructions. See `text.Inst` for the representation
 /// of instructions that correspond to the ZIR text format.
 /// This struct owns the `Value` and `Type` memory. When the struct is deallocated,
@@ -124,6 +125,10 @@ pub const Module = struct {
     pub fn deinit(self: *Module, allocator: *Allocator) void {
         allocator.free(self.exports);
         allocator.free(self.errors);
+        for (self.fns) |f| {
+            allocator.free(f.body);
+        }
+        allocator.free(self.fns);
         self.arena.deinit();
         self.* = undefined;
     }
@@ -795,7 +800,7 @@ pub fn main() anyerror!void {
 
     if (zir_module.errors.len != 0) {
         for (zir_module.errors) |err_msg| {
-            const loc = findLineColumn(source, err_msg.byte_offset);
+            const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
             std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
         }
         if (debug_error_trace) return error.ParseFailure;
@@ -809,10 +814,10 @@ pub fn main() anyerror!void {
 
     if (analyzed_module.errors.len != 0) {
         for (analyzed_module.errors) |err_msg| {
-            const loc = findLineColumn(source, err_msg.byte_offset);
+            const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
             std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
         }
-        if (debug_error_trace) return error.ParseFailure;
+        if (debug_error_trace) return error.AnalysisFail;
         std.process.exit(1);
     }
 
@@ -831,30 +836,13 @@ pub fn main() anyerror!void {
     defer result.deinit(allocator);
     if (result.errors.len != 0) {
         for (result.errors) |err_msg| {
-            const loc = findLineColumn(source, err_msg.byte_offset);
+            const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
             std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
         }
-        if (debug_error_trace) return error.ParseFailure;
+        if (debug_error_trace) return error.LinkFailure;
         std.process.exit(1);
     }
 }
 
-fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } {
-    var line: usize = 0;
-    var column: usize = 0;
-    for (source[0..byte_offset]) |byte| {
-        switch (byte) {
-            '\n' => {
-                line += 1;
-                column = 0;
-            },
-            else => {
-                column += 1;
-            },
-        }
-    }
-    return .{ .line = line, .column = column };
-}
-
 // Performance optimization ideas:
 // * when analyzing use a field in the Inst instead of HashMap to track corresponding instructions
src-self-hosted/test.zig
@@ -1,237 +1,168 @@
 const std = @import("std");
-const mem = std.mem;
-const Target = std.Target;
-const Compilation = @import("compilation.zig").Compilation;
-const introspect = @import("introspect.zig");
-const testing = std.testing;
-const errmsg = @import("errmsg.zig");
-const ZigCompiler = @import("compilation.zig").ZigCompiler;
+const link = @import("link.zig");
+const ir = @import("ir.zig");
+const Allocator = std.mem.Allocator;
 
-var ctx: TestContext = undefined;
+var global_ctx: TestContext = undefined;
 
-test "stage2" {
-    // TODO provide a way to run tests in evented I/O mode
-    if (!std.io.is_async) return error.SkipZigTest;
+test "self-hosted" {
+    try global_ctx.init();
+    defer global_ctx.deinit();
 
-    // TODO https://github.com/ziglang/zig/issues/1364
-    // TODO https://github.com/ziglang/zig/issues/3117
-    if (true) return error.SkipZigTest;
+    try @import("stage2_tests").addCases(&global_ctx);
 
-    try ctx.init();
-    defer ctx.deinit();
-
-    try @import("stage2_tests").addCases(&ctx);
-
-    try ctx.run();
+    try global_ctx.run();
 }
 
-const file1 = "1.zig";
-// TODO https://github.com/ziglang/zig/issues/3783
-const allocator = std.heap.page_allocator;
-
 pub const TestContext = struct {
-    zig_compiler: ZigCompiler,
-    zig_lib_dir: []u8,
-    file_index: std.atomic.Int(usize),
-    group: std.event.Group(anyerror!void),
-    any_err: anyerror!void,
-
-    const tmp_dir_name = "stage2_test_tmp";
+    zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase),
+
+    pub const ZIRCompareOutputCase = struct {
+        name: []const u8,
+        src: [:0]const u8,
+        expected_stdout: []const u8,
+    };
+
+    pub fn addZIRCompareOutput(
+        ctx: *TestContext,
+        name: []const u8,
+        src: [:0]const u8,
+        expected_stdout: []const u8,
+    ) void {
+        ctx.zir_cmp_output_cases.append(.{
+            .name = name,
+            .src = src,
+            .expected_stdout = expected_stdout,
+        }) catch unreachable;
+    }
 
     fn init(self: *TestContext) !void {
-        self.* = TestContext{
-            .any_err = {},
-            .zig_compiler = undefined,
-            .zig_lib_dir = undefined,
-            .group = undefined,
-            .file_index = std.atomic.Int(usize).init(0),
+        self.* = .{
+            .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(std.heap.page_allocator),
         };
-
-        self.zig_compiler = try ZigCompiler.init(allocator);
-        errdefer self.zig_compiler.deinit();
-
-        self.group = std.event.Group(anyerror!void).init(allocator);
-        errdefer self.group.wait() catch {};
-
-        self.zig_lib_dir = try introspect.resolveZigLibDir(allocator);
-        errdefer allocator.free(self.zig_lib_dir);
-
-        try std.fs.cwd().makePath(tmp_dir_name);
-        errdefer std.fs.cwd().deleteTree(tmp_dir_name) catch {};
     }
 
     fn deinit(self: *TestContext) void {
-        std.fs.cwd().deleteTree(tmp_dir_name) catch {};
-        allocator.free(self.zig_lib_dir);
-        self.zig_compiler.deinit();
+        self.zir_cmp_output_cases.deinit();
+        self.* = undefined;
     }
 
     fn run(self: *TestContext) !void {
-        std.event.Loop.startCpuBoundOperation();
-        self.any_err = self.group.wait();
-        return self.any_err;
+        var progress = std.Progress{};
+        const root_node = try progress.start("zir", self.zir_cmp_output_cases.items.len);
+        defer root_node.end();
+
+        const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
+
+        for (self.zir_cmp_output_cases.items) |case| {
+            std.testing.base_allocator_instance.reset();
+            try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target);
+            try std.testing.allocator_instance.validate();
+        }
     }
 
-    fn testCompileError(
+    fn runOneZIRCmpOutputCase(
         self: *TestContext,
-        source: []const u8,
-        path: []const u8,
-        line: usize,
-        column: usize,
-        msg: []const u8,
+        allocator: *Allocator,
+        root_node: *std.Progress.Node,
+        case: ZIRCompareOutputCase,
+        target: std.Target,
     ) !void {
-        var file_index_buf: [20]u8 = undefined;
-        const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", .{self.file_index.incr()});
-        const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 });
+        var tmp = std.testing.tmpDir(.{ .share_with_child_process = true });
+        defer tmp.cleanup();
 
-        if (std.fs.path.dirname(file1_path)) |dirname| {
-            try std.fs.cwd().makePath(dirname);
-        }
+        var prg_node = root_node.start(case.name, 4);
+        prg_node.activate();
+        defer prg_node.end();
 
-        try std.fs.cwd().writeFile(file1_path, source);
+        var zir_module = x: {
+            var parse_node = prg_node.start("parse", null);
+            parse_node.activate();
+            defer parse_node.end();
 
-        var comp = try Compilation.create(
-            &self.zig_compiler,
-            "test",
-            file1_path,
-            .Native,
-            .Obj,
-            .Debug,
-            true, // is_static
-            self.zig_lib_dir,
-        );
-        errdefer comp.destroy();
-
-        comp.start();
+            break :x try ir.text.parse(allocator, case.src);
+        };
+        defer zir_module.deinit(allocator);
+        if (zir_module.errors.len != 0) {
+            debugPrintErrors(case.src, zir_module.errors);
+            return error.ParseFailure;
+        }
 
-        try self.group.call(getModuleEvent, comp, source, path, line, column, msg);
-    }
+        var analyzed_module = x: {
+            var analyze_node = prg_node.start("analyze", null);
+            analyze_node.activate();
+            defer analyze_node.end();
 
-    fn testCompareOutputLibC(
-        self: *TestContext,
-        source: []const u8,
-        expected_output: []const u8,
-    ) !void {
-        var file_index_buf: [20]u8 = undefined;
-        const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", .{self.file_index.incr()});
-        const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 });
+            break :x try ir.analyze(allocator, zir_module, target);
+        };
+        defer analyzed_module.deinit(allocator);
+        if (analyzed_module.errors.len != 0) {
+            debugPrintErrors(case.src, analyzed_module.errors);
+            return error.ParseFailure;
+        }
 
-        const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", .{ file1_path, (Target{ .Native = {} }).exeFileExt() });
-        if (std.fs.path.dirname(file1_path)) |dirname| {
-            try std.fs.cwd().makePath(dirname);
+        var link_result = x: {
+            var link_node = prg_node.start("link", null);
+            link_node.activate();
+            defer link_node.end();
+
+            break :x try link.updateExecutableFilePath(
+                allocator,
+                analyzed_module,
+                tmp.dir,
+                "a.out",
+            );
+        };
+        defer link_result.deinit(allocator);
+        if (link_result.errors.len != 0) {
+            debugPrintErrors(case.src, link_result.errors);
+            return error.LinkFailure;
         }
 
-        try std.fs.cwd().writeFile(file1_path, source);
-
-        var comp = try Compilation.create(
-            &self.zig_compiler,
-            "test",
-            file1_path,
-            .Native,
-            .Exe,
-            .Debug,
-            false,
-            self.zig_lib_dir,
-        );
-        errdefer comp.destroy();
-
-        _ = try comp.addLinkLib("c", true);
-        comp.link_out_file = output_file;
-        comp.start();
-
-        try self.group.call(getModuleEventSuccess, comp, output_file, expected_output);
-    }
+        var exec_result = x: {
+            var exec_node = prg_node.start("execute", null);
+            exec_node.activate();
+            defer exec_node.end();
 
-    async fn getModuleEventSuccess(
-        comp: *Compilation,
-        exe_file: []const u8,
-        expected_output: []const u8,
-    ) anyerror!void {
-        defer comp.destroy();
-        const build_event = comp.events.get();
-
-        switch (build_event) {
-            .Ok => {
-                const argv = [_][]const u8{exe_file};
-                // TODO use event loop
-                const child = try std.ChildProcess.exec(.{
-                    .allocator = allocator,
-                    .argv = argv,
-                    .max_output_bytes = 1024 * 1024,
-                });
-                switch (child.term) {
-                    .Exited => |code| {
-                        if (code != 0) {
-                            return error.BadReturnCode;
-                        }
-                    },
-                    else => {
-                        return error.Crashed;
-                    },
-                }
-                if (!mem.eql(u8, child.stdout, expected_output)) {
-                    return error.OutputMismatch;
-                }
-            },
-            .Error => @panic("Cannot return error: https://github.com/ziglang/zig/issues/3190"), // |err| return err,
-            .Fail => |msgs| {
-                const stderr = std.io.getStdErr();
-                try stderr.write("build incorrectly failed:\n");
-                for (msgs) |msg| {
-                    defer msg.destroy();
-                    try msg.printToFile(stderr, .Auto);
+            break :x try std.ChildProcess.exec(.{
+                .allocator = allocator,
+                .argv = &[_][]const u8{"./a.out"},
+                .cwd_dir = tmp.dir,
+            });
+        };
+        defer allocator.free(exec_result.stdout);
+        defer allocator.free(exec_result.stderr);
+        switch (exec_result.term) {
+            .Exited => |code| {
+                if (code != 0) {
+                    std.debug.warn("elf file exited with code {}\n", .{code});
+                    return error.BinaryBadExitCode;
                 }
             },
+            else => return error.BinaryCrashed,
         }
+        std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout);
     }
+};
 
-    async fn getModuleEvent(
-        comp: *Compilation,
-        source: []const u8,
-        path: []const u8,
-        line: usize,
-        column: usize,
-        text: []const u8,
-    ) anyerror!void {
-        defer comp.destroy();
-        const build_event = comp.events.get();
-
-        switch (build_event) {
-            .Ok => {
-                @panic("build incorrectly succeeded");
-            },
-            .Error => |err| {
-                @panic("build incorrectly failed");
-            },
-            .Fail => |msgs| {
-                testing.expect(msgs.len != 0);
-                for (msgs) |msg| {
-                    if (mem.endsWith(u8, msg.realpath, path) and mem.eql(u8, msg.text, text)) {
-                        const span = msg.getSpan();
-                        const first_token = msg.getTree().tokens.at(span.first);
-                        const last_token = msg.getTree().tokens.at(span.first);
-                        const start_loc = msg.getTree().tokenLocationPtr(0, first_token);
-                        if (start_loc.line + 1 == line and start_loc.column + 1 == column) {
-                            return;
-                        }
-                    }
-                }
-                std.debug.warn("\n=====source:=======\n{}\n====expected:========\n{}:{}:{}: error: {}\n", .{
-                    source,
-                    path,
-                    line,
-                    column,
-                    text,
-                });
-                std.debug.warn("\n====found:========\n", .{});
-                const stderr = std.io.getStdErr();
-                for (msgs) |msg| {
-                    defer msg.destroy();
-                    try msg.printToFile(stderr, errmsg.Color.Auto);
-                }
-                std.debug.warn("============\n", .{});
-                return error.TestFailed;
-            },
+fn debugPrintErrors(src: []const u8, errors: var) void {
+    std.debug.warn("\n", .{});
+    var nl = true;
+    var line: usize = 1;
+    for (src) |byte| {
+        if (nl) {
+            std.debug.warn("{: >3}| ", .{line});
+            nl = false;
         }
+        if (byte == '\n') {
+            nl = true;
+            line += 1;
+        }
+        std.debug.warn("{c}", .{byte});
     }
-};
+    std.debug.warn("\n", .{});
+    for (errors) |err_msg| {
+        const loc = std.zig.findLineColumn(src, err_msg.byte_offset);
+        std.debug.warn("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, err_msg.msg });
+    }
+}
test/stage2/compare_output.zig
@@ -2,24 +2,27 @@ const std = @import("std");
 const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
 
 pub fn addCases(ctx: *TestContext) !void {
-    // hello world
-    try ctx.testCompareOutputLibC(
-        \\extern fn puts([*]const u8) void;
-        \\pub export fn main() c_int {
-        \\    puts("Hello, world!");
-        \\    return 0;
-        \\}
-    , "Hello, world!" ++ std.cstr.line_sep);
+    // TODO: re-enable these tests.
+    // https://github.com/ziglang/zig/issues/1364
 
-    // function calling another function
-    try ctx.testCompareOutputLibC(
-        \\extern fn puts(s: [*]const u8) void;
-        \\pub export fn main() c_int {
-        \\    return foo("OK");
-        \\}
-        \\fn foo(s: [*]const u8) c_int {
-        \\    puts(s);
-        \\    return 0;
-        \\}
-    , "OK" ++ std.cstr.line_sep);
+    //// hello world
+    //try ctx.testCompareOutputLibC(
+    //    \\extern fn puts([*]const u8) void;
+    //    \\pub export fn main() c_int {
+    //    \\    puts("Hello, world!");
+    //    \\    return 0;
+    //    \\}
+    //, "Hello, world!" ++ std.cstr.line_sep);
+
+    //// function calling another function
+    //try ctx.testCompareOutputLibC(
+    //    \\extern fn puts(s: [*]const u8) void;
+    //    \\pub export fn main() c_int {
+    //    \\    return foo("OK");
+    //    \\}
+    //    \\fn foo(s: [*]const u8) c_int {
+    //    \\    puts(s);
+    //    \\    return 0;
+    //    \\}
+    //, "OK" ++ std.cstr.line_sep);
 }
test/stage2/compile_errors.zig
@@ -1,54 +1,57 @@
 const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
 
 pub fn addCases(ctx: *TestContext) !void {
-    try ctx.testCompileError(
-        \\export fn entry() void {}
-        \\export fn entry() void {}
-    , "1.zig", 2, 8, "exported symbol collision: 'entry'");
-
-    try ctx.testCompileError(
-        \\fn() void {}
-    , "1.zig", 1, 1, "missing function name");
-
-    try ctx.testCompileError(
-        \\comptime {
-        \\    return;
-        \\}
-    , "1.zig", 2, 5, "return expression outside function definition");
-
-    try ctx.testCompileError(
-        \\export fn entry() void {
-        \\    defer return;
-        \\}
-    , "1.zig", 2, 11, "cannot return from defer expression");
-
-    try ctx.testCompileError(
-        \\export fn entry() c_int {
-        \\    return 36893488147419103232;
-        \\}
-    , "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'");
-
-    try ctx.testCompileError(
-        \\comptime {
-        \\    var a: *align(4) align(4) i32 = 0;
-        \\}
-    , "1.zig", 2, 22, "Extra align qualifier");
-
-    try ctx.testCompileError(
-        \\comptime {
-        \\    var b: *const const i32 = 0;
-        \\}
-    , "1.zig", 2, 19, "Extra align qualifier");
-
-    try ctx.testCompileError(
-        \\comptime {
-        \\    var c: *volatile volatile i32 = 0;
-        \\}
-    , "1.zig", 2, 22, "Extra align qualifier");
-
-    try ctx.testCompileError(
-        \\comptime {
-        \\    var d: *allowzero allowzero i32 = 0;
-        \\}
-    , "1.zig", 2, 23, "Extra align qualifier");
+    // TODO: re-enable these tests.
+    // https://github.com/ziglang/zig/issues/1364
+
+    //try ctx.testCompileError(
+    //    \\export fn entry() void {}
+    //    \\export fn entry() void {}
+    //, "1.zig", 2, 8, "exported symbol collision: 'entry'");
+
+    //try ctx.testCompileError(
+    //    \\fn() void {}
+    //, "1.zig", 1, 1, "missing function name");
+
+    //try ctx.testCompileError(
+    //    \\comptime {
+    //    \\    return;
+    //    \\}
+    //, "1.zig", 2, 5, "return expression outside function definition");
+
+    //try ctx.testCompileError(
+    //    \\export fn entry() void {
+    //    \\    defer return;
+    //    \\}
+    //, "1.zig", 2, 11, "cannot return from defer expression");
+
+    //try ctx.testCompileError(
+    //    \\export fn entry() c_int {
+    //    \\    return 36893488147419103232;
+    //    \\}
+    //, "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'");
+
+    //try ctx.testCompileError(
+    //    \\comptime {
+    //    \\    var a: *align(4) align(4) i32 = 0;
+    //    \\}
+    //, "1.zig", 2, 22, "Extra align qualifier");
+
+    //try ctx.testCompileError(
+    //    \\comptime {
+    //    \\    var b: *const const i32 = 0;
+    //    \\}
+    //, "1.zig", 2, 19, "Extra align qualifier");
+
+    //try ctx.testCompileError(
+    //    \\comptime {
+    //    \\    var c: *volatile volatile i32 = 0;
+    //    \\}
+    //, "1.zig", 2, 22, "Extra align qualifier");
+
+    //try ctx.testCompileError(
+    //    \\comptime {
+    //    \\    var d: *allowzero allowzero i32 = 0;
+    //    \\}
+    //, "1.zig", 2, 23, "Extra align qualifier");
 }
test/stage2/test.zig
@@ -3,4 +3,5 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
 pub fn addCases(ctx: *TestContext) !void {
     try @import("compile_errors.zig").addCases(ctx);
     try @import("compare_output.zig").addCases(ctx);
+    @import("zir.zig").addCases(ctx);
 }
test/stage2/ir.zig → test/stage2/zir.zig
@@ -1,7 +1,14 @@
-test "hello world IR" {
-    exeCmp(
+const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
+
+pub fn addCases(ctx: *TestContext) void {
+    if (@import("std").Target.current.os.tag == .windows) {
+        // TODO implement self-hosted PE (.exe file) linking
+        return;
+    }
+
+    ctx.addZIRCompareOutput("hello world ZIR",
         \\@0 = str("Hello, world!\n")
-        \\@1 = primitive(void)
+        \\@1 = primitive(noreturn)
         \\@2 = primitive(usize)
         \\@3 = fntype([], @1, cc=Naked)
         \\@4 = int(0)
@@ -50,5 +57,3 @@ test "hello world IR" {
         \\
     );
 }
-
-fn exeCmp(src: []const u8, expected_stdout: []const u8) void {}
build.zig
@@ -44,7 +44,7 @@ pub fn build(b: *Builder) !void {
         try findAndReadConfigH(b);
 
     var test_stage2 = b.addTest("src-self-hosted/test.zig");
-    test_stage2.setBuildMode(builtin.Mode.Debug);
+    test_stage2.setBuildMode(.Debug); // note this is only the mode of the test harness
     test_stage2.addPackagePath("stage2_tests", "test/stage2/test.zig");
 
     const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});
@@ -68,7 +68,6 @@ pub fn build(b: *Builder) !void {
         var ctx = parseConfigH(b, config_h_text);
         ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
 
-        try configureStage2(b, test_stage2, ctx);
         try configureStage2(b, exe, ctx);
 
         b.default_step.dependOn(&exe.step);