diff --git a/build.zig b/build.zig index 68db2061bd..ab1d985b74 100644 --- a/build.zig +++ b/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); diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 12b76143ee..a9ed7b2390 100644 --- a/lib/std/child_process.zig +++ b/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); } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index a4a5a3a56a..3da3d1d9d7 100644 --- a/lib/std/fs.zig +++ b/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); } } diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 63adfc9648..aec8468bc0 100644 --- a/lib/std/fs/file.zig +++ b/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, diff --git a/lib/std/testing.zig b/lib/std/testing.zig index fed2b15bf5..5b72533057 100644 --- a/lib/std/testing.zig +++ b/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 }, diff --git a/lib/std/zig.zig b/lib/std/zig.zig index dcf7842a3c..bb4f955797 100644 --- a/lib/std/zig.zig +++ b/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()); } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 9585058c2a..7b05e3bcfb 100644 --- a/lib/std/zig/system.zig +++ b/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; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 310591e629..f7b4ca596e 100644 --- a/src-self-hosted/ir.zig +++ b/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 diff --git a/src-self-hosted/ir/text.zig b/src-self-hosted/ir/text.zig index 5d0b49b89e..7c86e0da7e 100644 --- a/src-self-hosted/ir/text.zig +++ b/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 { diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 9038b8128d..51d12da44b 100644 --- a/src-self-hosted/test.zig +++ b/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, + zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), - const tmp_dir_name = "stage2_test_tmp"; + 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(); + + var zir_module = x: { + var parse_node = prg_node.start("parse", null); + parse_node.activate(); + defer parse_node.end(); + + 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 std.fs.cwd().writeFile(file1_path, source); + var analyzed_module = x: { + var analyze_node = prg_node.start("analyze", null); + analyze_node.activate(); + defer analyze_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(); - - try self.group.call(getModuleEvent, comp, source, path, line, column, msg); - } - - 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 }); - - 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); + 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; } - try std.fs.cwd().writeFile(file1_path, source); + var link_result = x: { + var link_node = prg_node.start("link", null); + link_node.activate(); + defer link_node.end(); - 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); - } - - 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 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; } - } - 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(); + var exec_result = x: { + var exec_node = prg_node.start("execute", null); + exec_node.activate(); + defer exec_node.end(); - 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; - } - } + 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; } - 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; }, + else => return error.BinaryCrashed, } + std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout); } }; + +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 }); + } +} diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 443ed7a0ee..1f289c2762 100644 --- a/test/stage2/compare_output.zig +++ b/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); } diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 377d060056..9b8dcd91c4 100644 --- a/test/stage2/compile_errors.zig +++ b/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'"); + // TODO: re-enable these tests. + // https://github.com/ziglang/zig/issues/1364 - try ctx.testCompileError( - \\fn() void {} - , "1.zig", 1, 1, "missing function name"); + //try ctx.testCompileError( + // \\export fn entry() void {} + // \\export fn entry() void {} + //, "1.zig", 2, 8, "exported symbol collision: 'entry'"); - try ctx.testCompileError( - \\comptime { - \\ return; - \\} - , "1.zig", 2, 5, "return expression outside function definition"); + //try ctx.testCompileError( + // \\fn() void {} + //, "1.zig", 1, 1, "missing function name"); - try ctx.testCompileError( - \\export fn entry() void { - \\ defer return; - \\} - , "1.zig", 2, 11, "cannot return from defer expression"); + //try ctx.testCompileError( + // \\comptime { + // \\ return; + // \\} + //, "1.zig", 2, 5, "return expression outside function definition"); - 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( + // \\export fn entry() void { + // \\ defer return; + // \\} + //, "1.zig", 2, 11, "cannot return from defer expression"); - try ctx.testCompileError( - \\comptime { - \\ var a: *align(4) align(4) i32 = 0; - \\} - , "1.zig", 2, 22, "Extra align qualifier"); + //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 b: *const const i32 = 0; - \\} - , "1.zig", 2, 19, "Extra align qualifier"); + //try ctx.testCompileError( + // \\comptime { + // \\ var a: *align(4) align(4) i32 = 0; + // \\} + //, "1.zig", 2, 22, "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 b: *const const i32 = 0; + // \\} + //, "1.zig", 2, 19, "Extra align qualifier"); - try ctx.testCompileError( - \\comptime { - \\ var d: *allowzero allowzero i32 = 0; - \\} - , "1.zig", 2, 23, "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"); } diff --git a/test/stage2/test.zig b/test/stage2/test.zig index f4768cd39a..dc92f99506 100644 --- a/test/stage2/test.zig +++ b/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); } diff --git a/test/stage2/ir.zig b/test/stage2/zir.zig similarity index 80% rename from test/stage2/ir.zig rename to test/stage2/zir.zig index 450d8fa102..b30c443788 100644 --- a/test/stage2/ir.zig +++ b/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 {}