From 58edefc6d1716c0731ee2fe672ec8d073651aafb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 22:56:37 -0700 Subject: zig build: many enhancements related to parallel building Rework std.Build.Step to have an `owner: *Build` field. This simplified the implementation of installation steps, as well as provided some much-needed common API for the new parallelized build system. --verbose is now defined very concretely: it prints to stderr just before spawning a child process. Child process execution is updated to conform to the new parallel-friendly make() function semantics. DRY up the failWithCacheError handling code. It now integrates properly with the step graph instead of incorrectly dumping to stderr and calling process exit. In the main CLI, fix `zig fmt` crash when there are no errors and stdin is used. Deleted steps: * EmulatableRunStep - this entire thing can be removed in favor of a flag added to std.Build.RunStep called `skip_foreign_checks`. * LogStep - this doesn't really fit with a multi-threaded build runner and is effectively superseded by the new build summary output. build runner: * add -fsummary and -fno-summary to override the default behavior, which is to print a summary if any of the build steps fail. * print the dep prefix when emitting error messages for steps. std.Build.FmtStep: * This step now supports exclude paths as well as a check flag. * The check flag decides between two modes, modify mode, and check mode. These can be used to update source files in place, or to fail the build, respectively. Zig's own build.zig: * The `test-fmt` step will do all the `zig fmt` checking that we expect to be done. Since the `test` step depends on this one, we can simply remove the explicit call to `zig fmt` in the CI. * The new `fmt` step will actually perform `zig fmt` and update source files in place. std.Build.RunStep: * expose max_stdio_size is a field (previously an unchangeable hard-coded value). * rework the API. Instead of configuring each stream independently, there is a `stdio` field where you can choose between `infer_from_args`, `inherit`, or `check`. These determine whether the RunStep is considered to have side-effects or not. The previous field, `condition` is gone. * when stdio mode is set to `check` there is a slice of any number of checks to make, which include things like exit code, stderr matching, or stdout matching. * remove the ill-defined `print` field. * when adding an output arg, it takes the opportunity to give itself a better name. * The flag `skip_foreign_checks` is added. If this is true, a RunStep which is configured to check the output of the executed binary will not fail the build if the binary cannot be executed due to being for a foreign binary to the host system which is running the build graph. Command-line arguments such as -fqemu and -fwasmtime may affect whether a binary is detected as foreign, as well as system configuration such as Rosetta (macOS) and binfmt_misc (Linux). - This makes EmulatableRunStep no longer needed. * Fix the child process handling to properly integrate with the new bulid API and to avoid deadlocks in stdout/stderr streams by polling if necessary. std.Build.RemoveDirStep now uses the open build_root directory handle instead of an absolute path. --- test/src/compare_output.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'test/src') diff --git a/test/src/compare_output.zig b/test/src/compare_output.zig index 3bda3bdacd..20bd62d8e2 100644 --- a/test/src/compare_output.zig +++ b/test/src/compare_output.zig @@ -166,9 +166,7 @@ pub const CompareOutputContext = struct { const run = exe.run(); run.addArgs(case.cli_args); - run.stderr_action = .ignore; - run.stdout_action = .ignore; - run.expected_term = .{ .Exited = 126 }; + run.expectExitCode(126); self.step.dependOn(&run.step); }, -- cgit v1.2.3 From a24af8e4005d00cf76755635eb6c661d9c260758 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 19:10:37 -0700 Subject: re-integrate stack trace tests with the new std.Build API * RunStep: ability to set stdin * RunStep: ability to capture stdout and stderr as a FileSource * RunStep: add setName method * RunStep: hash the stdio checks --- build.zig | 44 +-- lib/std/Build/RunStep.zig | 246 +++++++++++--- lib/std/Build/WriteFileStep.zig | 14 +- test/src/StackTrace.zig | 105 ++++++ test/src/Standalone.zig | 141 ++++++++ test/src/check-stack-trace.zig | 79 +++++ test/tests.zig | 735 +++++----------------------------------- 7 files changed, 637 insertions(+), 727 deletions(-) create mode 100644 test/src/StackTrace.zig create mode 100644 test/src/Standalone.zig create mode 100644 test/src/check-stack-trace.zig (limited to 'test/src') diff --git a/build.zig b/build.zig index a8b06be40c..1f1b119ed3 100644 --- a/build.zig +++ b/build.zig @@ -442,33 +442,33 @@ pub fn build(b: *std.Build) !void { .skip_stage2 = true, // TODO get all these passing })); - test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); - test_step.dependOn(tests.addStandaloneTests( - b, - test_filter, - optimization_modes, - skip_non_native, - enable_macos_sdk, - target, - skip_stage2_tests, - b.enable_darling, - b.enable_qemu, - b.enable_rosetta, - b.enable_wasmtime, - b.enable_wine, - enable_symlinks_windows, - )); - test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); - test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); + _ = enable_symlinks_windows; + _ = enable_macos_sdk; + //test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); + //test_step.dependOn(tests.addStandaloneTests( + // b, + // test_filter, + // optimization_modes, + // skip_non_native, + // enable_macos_sdk, + // target, + // skip_stage2_tests, + // b.enable_darling, + // b.enable_qemu, + // b.enable_rosetta, + // b.enable_wasmtime, + // b.enable_wine, + // enable_symlinks_windows, + //)); + //test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); + //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); - test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); - test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); + //test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); + //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); if (!skip_run_translated_c) { test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target)); } - // tests for this feature are disabled until we have the self-hosted compiler available - // test_step.dependOn(tests.addGenHTests(b, test_filter)); test_step.dependOn(tests.addModuleTests(b, .{ .test_filter = test_filter, diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index d671ff7ae8..95e37f230a 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -38,6 +38,8 @@ env_map: ?*EnvMap, /// be skipped if all output files are up-to-date and input files are /// unchanged. stdio: StdIo = .infer_from_args, +/// This field must be `null` if stdio is `inherit`. +stdin: ?[]const u8 = null, /// Additional file paths relative to build.zig that, when modified, indicate /// that the RunStep should be re-executed. @@ -65,6 +67,9 @@ skip_foreign_checks: bool = false, /// the step fails. max_stdio_size: usize = 10 * 1024 * 1024, +captured_stdout: ?*Output = null, +captured_stderr: ?*Output = null, + pub const StdIo = union(enum) { /// Whether the RunStep has side-effects will be determined by whether or not one /// of the args is an output file (added with `addOutputFileArg`). @@ -99,12 +104,12 @@ pub const Arg = union(enum) { artifact: *CompileStep, file_source: std.Build.FileSource, bytes: []u8, - output: Output, + output: *Output, +}; - pub const Output = struct { - generated_file: *std.Build.GeneratedFile, - basename: []const u8, - }; +pub const Output = struct { + generated_file: std.Build.GeneratedFile, + basename: []const u8, }; pub fn create(owner: *std.Build, name: []const u8) *RunStep { @@ -119,12 +124,15 @@ pub fn create(owner: *std.Build, name: []const u8) *RunStep { .argv = ArrayList(Arg).init(owner.allocator), .cwd = null, .env_map = null, - .rename_step_with_output_arg = true, - .max_stdio_size = 10 * 1024 * 1024, }; return self; } +pub fn setName(self: *RunStep, name: []const u8) void { + self.step.name = name; + self.rename_step_with_output_arg = false; +} + pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM"); self.step.dependOn(&artifact.step); @@ -135,19 +143,19 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { /// throughout the build system. pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { const b = rs.step.owner; - const generated_file = b.allocator.create(std.Build.GeneratedFile) catch @panic("OOM"); - generated_file.* = .{ .step = &rs.step }; - rs.argv.append(.{ .output = .{ - .generated_file = generated_file, - .basename = b.dupe(basename), - } }) catch @panic("OOM"); + + const output = b.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .basename = basename, + .generated_file = .{ .step = &rs.step }, + }; + rs.argv.append(.{ .output = output }) catch @panic("OOM"); if (rs.rename_step_with_output_arg) { - rs.rename_step_with_output_arg = false; - rs.step.name = b.fmt("{s} ({s})", .{ rs.step.name, basename }); + rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename })); } - return .{ .generated = generated_file }; + return .{ .generated = &output.generated_file }; } pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { @@ -259,6 +267,34 @@ pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { } } +pub fn captureStdErr(self: *RunStep) std.Build.FileSource { + assert(self.stdio != .inherit); + + if (self.captured_stderr) |output| return .{ .generated = &output.generated_file }; + + const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .basename = "stderr", + .generated_file = .{ .step = &self.step }, + }; + self.captured_stderr = output; + return .{ .generated = &output.generated_file }; +} + +pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile { + assert(self.stdio != .inherit); + + if (self.captured_stdout) |output| return .{ .generated = &output.generated_file }; + + const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .basename = "stdout", + .generated_file = .{ .step = &self.step }, + }; + self.captured_stdout = output; + return .{ .generated = &output.generated_file }; +} + /// Returns whether the RunStep has side effects *other than* updating the output arguments. fn hasSideEffects(self: RunStep) bool { return switch (self.stdio) { @@ -269,6 +305,8 @@ fn hasSideEffects(self: RunStep) bool { } fn hasAnyOutputArgs(self: RunStep) bool { + if (self.captured_stdout != null) return true; + if (self.captured_stderr != null) return true; for (self.argv.items) |arg| switch (arg) { .output => return true, else => continue, @@ -318,7 +356,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { var argv_list = ArrayList([]const u8).init(arena); var output_placeholders = ArrayList(struct { index: usize, - output: Arg.Output, + output: *Output, }).init(arena); var man = b.cache.obtain(); @@ -361,46 +399,68 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - if (!has_side_effects) { - for (self.extra_file_dependencies) |file_path| { - _ = try man.addFile(b.pathFromRoot(file_path), null); - } + if (self.captured_stdout) |output| { + man.hash.addBytes(output.basename); + } - if (try step.cacheHit(&man)) { - // cache hit, skip running command - const digest = man.final(); - for (output_placeholders.items) |placeholder| { - placeholder.output.generated_file.path = try b.cache_root.join( - arena, - &.{ "o", &digest, placeholder.output.basename }, - ); - } - step.result_cached = true; - return; - } + if (self.captured_stderr) |output| { + man.hash.addBytes(output.basename); + } - const digest = man.final(); + hashStdIo(&man.hash, self.stdio); + if (has_side_effects) { + try runCommand(self, argv_list.items, has_side_effects, null); + return; + } + + for (self.extra_file_dependencies) |file_path| { + _ = try man.addFile(b.pathFromRoot(file_path), null); + } + + if (try step.cacheHit(&man)) { + // cache hit, skip running command + const digest = man.final(); for (output_placeholders.items) |placeholder| { - const output_components = .{ "o", &digest, placeholder.output.basename }; - const output_sub_path = try fs.path.join(arena, &output_components); - const output_sub_dir_path = fs.path.dirname(output_sub_path).?; - b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.cache_root, output_sub_dir_path, @errorName(err), - }); - }; - const output_path = try b.cache_root.join(arena, &output_components); - placeholder.output.generated_file.path = output_path; - argv_list.items[placeholder.index] = output_path; + placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, placeholder.output.basename, + }); } + + if (self.captured_stdout) |output| { + output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, output.basename, + }); + } + + if (self.captured_stderr) |output| { + output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, output.basename, + }); + } + + step.result_cached = true; + return; } - try runCommand(self, argv_list.items, has_side_effects); + const digest = man.final(); - if (!has_side_effects) { - try man.writeManifest(); + for (output_placeholders.items) |placeholder| { + const output_components = .{ "o", &digest, placeholder.output.basename }; + const output_sub_path = try fs.path.join(arena, &output_components); + const output_sub_dir_path = fs.path.dirname(output_sub_path).?; + b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, output_sub_dir_path, @errorName(err), + }); + }; + const output_path = try b.cache_root.join(arena, &output_components); + placeholder.output.generated_file.path = output_path; + argv_list.items[placeholder.index] = output_path; } + + try runCommand(self, argv_list.items, has_side_effects, &digest); + try man.writeManifest(); } fn formatTerm( @@ -448,7 +508,12 @@ fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term }; } -fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) !void { +fn runCommand( + self: *RunStep, + argv: []const []const u8, + has_side_effects: bool, + digest: ?*const [std.Build.Cache.hex_digest_len]u8, +) !void { const step = &self.step; const b = step.owner; const arena = b.allocator; @@ -584,6 +649,46 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) step.result_duration_ns = result.elapsed_ns; step.result_peak_rss = result.peak_rss; + // Capture stdout and stderr to GeneratedFile objects. + const Stream = struct { + captured: ?*Output, + is_null: bool, + bytes: []const u8, + }; + for ([_]Stream{ + .{ + .captured = self.captured_stdout, + .is_null = result.stdout_null, + .bytes = result.stdout, + }, + .{ + .captured = self.captured_stderr, + .is_null = result.stderr_null, + .bytes = result.stderr, + }, + }) |stream| { + if (stream.captured) |output| { + assert(!stream.is_null); + + const output_components = .{ "o", digest.?, output.basename }; + const output_path = try b.cache_root.join(arena, &output_components); + output.generated_file.path = output_path; + + const sub_path = try fs.path.join(arena, &output_components); + const sub_path_dirname = fs.path.dirname(sub_path).?; + b.cache_root.handle.makePath(sub_path_dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, sub_path_dirname, @errorName(err), + }); + }; + b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.cache_root, sub_path, @errorName(err), + }); + }; + } + } + switch (self.stdio) { .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { @@ -705,7 +810,7 @@ fn spawnChildAndCollect( child.request_resource_usage_statistics = true; child.stdin_behavior = switch (self.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .infer_from_args => if (has_side_effects) .Inherit else .Close, .inherit => .Inherit, .check => .Close, }; @@ -719,12 +824,26 @@ fn spawnChildAndCollect( .inherit => .Inherit, .check => .Pipe, }; + if (self.captured_stdout != null) child.stdout_behavior = .Pipe; + if (self.captured_stderr != null) child.stderr_behavior = .Pipe; + if (self.stdin != null) { + assert(child.stdin_behavior != .Inherit); + child.stdin_behavior = .Pipe; + } child.spawn() catch |err| return self.step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err), }); var timer = try std.time.Timer.start(); + if (self.stdin) |stdin| { + child.stdin.?.writeAll(stdin) catch |err| { + return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); + }; + child.stdin.?.close(); + child.stdin = null; + } + // These are not optionals, as a workaround for // https://github.com/ziglang/zig/issues/14783 var stdout_bytes: []const u8 = undefined; @@ -761,7 +880,8 @@ fn spawnChildAndCollect( } if (!stderr_null and stderr_bytes.len > 0) { - const stderr_is_diagnostic = switch (self.stdio) { + // Treat stderr as an error message. + const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) { .check => |checks| !checksContainStderr(checks.items), else => true, }; @@ -829,3 +949,27 @@ fn failForeign( }, } } + +fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { + switch (stdio) { + .infer_from_args, .inherit => {}, + .check => |checks| for (checks.items) |check| { + hh.add(@as(std.meta.Tag(StdIo.Check), check)); + switch (check) { + .expect_stderr_exact, + .expect_stderr_match, + .expect_stdout_exact, + .expect_stdout_match, + => |s| hh.addBytes(s), + + .expect_term => |term| { + hh.add(@as(std.meta.Tag(std.process.Child.Term), term)); + switch (term) { + .Exited => |x| hh.add(x), + .Signal, .Stopped, .Unknown => |x| hh.add(x), + } + }, + } + }, + } +} diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 1109cf5426..64025ce0fe 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -189,10 +189,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (try step.cacheHit(&man)) { const digest = man.final(); for (wf.files.items) |file| { - file.generated_file.path = try b.cache_root.join( - b.allocator, - &.{ "o", &digest, file.sub_path }, - ); + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + "o", &digest, file.sub_path, + }); } return; } @@ -249,10 +248,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }, } - file.generated_file.path = try b.cache_root.join( - b.allocator, - &.{ cache_path, file.sub_path }, - ); + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + cache_path, file.sub_path, + }); } try man.writeManifest(); diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig new file mode 100644 index 0000000000..5709b9d29e --- /dev/null +++ b/test/src/StackTrace.zig @@ -0,0 +1,105 @@ +b: *std.Build, +step: *Step, +test_index: usize, +test_filter: ?[]const u8, +optimize_modes: []const OptimizeMode, +check_exe: *std.Build.CompileStep, + +const Expect = [@typeInfo(OptimizeMode).Enum.fields.len][]const u8; + +pub fn addCase(self: *StackTrace, config: anytype) void { + if (@hasField(@TypeOf(config), "exclude")) { + if (config.exclude.exclude()) return; + } + if (@hasField(@TypeOf(config), "exclude_arch")) { + const exclude_arch: []const std.Target.Cpu.Arch = &config.exclude_arch; + for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; + } + if (@hasField(@TypeOf(config), "exclude_os")) { + const exclude_os: []const std.Target.Os.Tag = &config.exclude_os; + for (exclude_os) |os| if (os == builtin.os.tag) return; + } + for (self.optimize_modes) |optimize_mode| { + switch (optimize_mode) { + .Debug => { + if (@hasField(@TypeOf(config), "Debug")) { + self.addExpect(config.name, config.source, optimize_mode, config.Debug); + } + }, + .ReleaseSafe => { + if (@hasField(@TypeOf(config), "ReleaseSafe")) { + self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSafe); + } + }, + .ReleaseFast => { + if (@hasField(@TypeOf(config), "ReleaseFast")) { + self.addExpect(config.name, config.source, optimize_mode, config.ReleaseFast); + } + }, + .ReleaseSmall => { + if (@hasField(@TypeOf(config), "ReleaseSmall")) { + self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSmall); + } + }, + } + } +} + +fn addExpect( + self: *StackTrace, + name: []const u8, + source: []const u8, + optimize_mode: OptimizeMode, + mode_config: anytype, +) void { + if (@hasField(@TypeOf(mode_config), "exclude")) { + if (mode_config.exclude.exclude()) return; + } + if (@hasField(@TypeOf(mode_config), "exclude_arch")) { + const exclude_arch: []const std.Target.Cpu.Arch = &mode_config.exclude_arch; + for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; + } + if (@hasField(@TypeOf(mode_config), "exclude_os")) { + const exclude_os: []const std.Target.Os.Tag = &mode_config.exclude_os; + for (exclude_os) |os| if (os == builtin.os.tag) return; + } + + const b = self.b; + const annotated_case_name = fmt.allocPrint(b.allocator, "check {s} ({s})", .{ + name, @tagName(optimize_mode), + }) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + const src_basename = "source.zig"; + const write_src = b.addWriteFile(src_basename, source); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = write_src.getFileSource(src_basename).?, + .optimize = optimize_mode, + .target = .{}, + }); + + const run = b.addRunArtifact(exe); + run.expectExitCode(1); + run.expectStdOutEqual(""); + + const check_run = b.addRunArtifact(self.check_exe); + check_run.setName(annotated_case_name); + check_run.addFileSourceArg(run.captureStdErr()); + check_run.addArgs(&.{ + @tagName(optimize_mode), + }); + check_run.expectStdOutEqual(mode_config.expect); + + self.step.dependOn(&check_run.step); +} + +const StackTrace = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const Step = std.Build.Step; +const OptimizeMode = std.builtin.OptimizeMode; +const fmt = std.fmt; +const mem = std.mem; diff --git a/test/src/Standalone.zig b/test/src/Standalone.zig new file mode 100644 index 0000000000..c07bb511c5 --- /dev/null +++ b/test/src/Standalone.zig @@ -0,0 +1,141 @@ +b: *std.Build, +step: *Step, +test_index: usize, +test_filter: ?[]const u8, +optimize_modes: []const OptimizeMode, +skip_non_native: bool, +enable_macos_sdk: bool, +target: std.zig.CrossTarget, +omit_stage2: bool, +enable_darling: bool = false, +enable_qemu: bool = false, +enable_rosetta: bool = false, +enable_wasmtime: bool = false, +enable_wine: bool = false, +enable_symlinks_windows: bool, + +pub fn addC(self: *Standalone, root_src: []const u8) void { + self.addAllArgs(root_src, true); +} + +pub fn add(self: *Standalone, root_src: []const u8) void { + self.addAllArgs(root_src, false); +} + +pub fn addBuildFile(self: *Standalone, build_file: []const u8, features: struct { + build_modes: bool = false, + cross_targets: bool = false, + requires_macos_sdk: bool = false, + requires_stage2: bool = false, + use_emulation: bool = false, + requires_symlinks: bool = false, + extra_argv: []const []const u8 = &.{}, +}) void { + const b = self.b; + + if (features.requires_macos_sdk and !self.enable_macos_sdk) return; + if (features.requires_stage2 and self.omit_stage2) return; + if (features.requires_symlinks and !self.enable_symlinks_windows and builtin.os.tag == .windows) return; + + const annotated_case_name = b.fmt("build {s}", .{build_file}); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + var zig_args = ArrayList([]const u8).init(b.allocator); + const rel_zig_exe = fs.path.relative(b.allocator, b.build_root.path orelse ".", b.zig_exe) catch unreachable; + zig_args.append(rel_zig_exe) catch unreachable; + zig_args.append("build") catch unreachable; + + // TODO: fix the various non-concurrency-safe issues in zig's standalone tests, + // and then remove this! + zig_args.append("-j1") catch @panic("OOM"); + + zig_args.append("--build-file") catch unreachable; + zig_args.append(b.pathFromRoot(build_file)) catch unreachable; + + zig_args.appendSlice(features.extra_argv) catch unreachable; + + zig_args.append("test") catch unreachable; + + if (b.verbose) { + zig_args.append("--verbose") catch unreachable; + } + + if (features.cross_targets and !self.target.isNative()) { + const target_triple = self.target.zigTriple(b.allocator) catch unreachable; + const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; + zig_args.append(target_arg) catch unreachable; + } + + if (features.use_emulation) { + if (self.enable_darling) { + zig_args.append("-fdarling") catch unreachable; + } + if (self.enable_qemu) { + zig_args.append("-fqemu") catch unreachable; + } + if (self.enable_rosetta) { + zig_args.append("-frosetta") catch unreachable; + } + if (self.enable_wasmtime) { + zig_args.append("-fwasmtime") catch unreachable; + } + if (self.enable_wine) { + zig_args.append("-fwine") catch unreachable; + } + } + + const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug}; + for (optimize_modes) |optimize_mode| { + const arg = switch (optimize_mode) { + .Debug => "", + .ReleaseFast => "-Doptimize=ReleaseFast", + .ReleaseSafe => "-Doptimize=ReleaseSafe", + .ReleaseSmall => "-Doptimize=ReleaseSmall", + }; + const zig_args_base_len = zig_args.items.len; + if (arg.len > 0) + zig_args.append(arg) catch unreachable; + defer zig_args.resize(zig_args_base_len) catch unreachable; + + const run_cmd = b.addSystemCommand(zig_args.items); + self.step.dependOn(&run_cmd.step); + } +} + +pub fn addAllArgs(self: *Standalone, root_src: []const u8, link_libc: bool) void { + const b = self.b; + + for (self.optimize_modes) |optimize| { + const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{ + root_src, + @tagName(optimize), + }) catch unreachable; + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; + } + + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = root_src }, + .optimize = optimize, + .target = .{}, + }); + if (link_libc) { + exe.linkSystemLibrary("c"); + } + + self.step.dependOn(&exe.step); + } +} + +const Standalone = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const Step = std.Build.Step; +const OptimizeMode = std.builtin.OptimizeMode; +const fmt = std.fmt; +const mem = std.mem; +const ArrayList = std.ArrayList; +const fs = std.fs; diff --git a/test/src/check-stack-trace.zig b/test/src/check-stack-trace.zig new file mode 100644 index 0000000000..bb1db55076 --- /dev/null +++ b/test/src/check-stack-trace.zig @@ -0,0 +1,79 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const mem = std.mem; +const fs = std.fs; + +pub fn main() !void { + var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const args = try std.process.argsAlloc(arena); + + const input_path = args[1]; + const optimize_mode_text = args[2]; + + const input_bytes = try std.fs.cwd().readFileAlloc(arena, input_path, 5 * 1024 * 1024); + const optimize_mode = std.meta.stringToEnum(std.builtin.OptimizeMode, optimize_mode_text).?; + + var stderr = input_bytes; + + // process result + // - keep only basename of source file path + // - replace address with symbolic string + // - replace function name with symbolic string when optimize_mode != .Debug + // - skip empty lines + const got: []const u8 = got_result: { + var buf = std.ArrayList(u8).init(arena); + defer buf.deinit(); + if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1]; + var it = mem.split(u8, stderr, "\n"); + process_lines: while (it.next()) |line| { + if (line.len == 0) continue; + + // offset search past `[drive]:` on windows + var pos: usize = if (builtin.os.tag == .windows) 2 else 0; + // locate delims/anchor + const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" }; + var marks = [_]usize{0} ** delims.len; + for (delims, 0..) |delim, i| { + marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { + // unexpected pattern: emit raw line and cont + try buf.appendSlice(line); + try buf.appendSlice("\n"); + continue :process_lines; + }; + pos = marks[i] + delim.len; + } + // locate source basename + pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse { + // unexpected pattern: emit raw line and cont + try buf.appendSlice(line); + try buf.appendSlice("\n"); + continue :process_lines; + }; + // end processing if source basename changes + if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break; + // emit substituted line + try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]); + try buf.appendSlice(" [address]"); + if (optimize_mode == .Debug) { + // On certain platforms (windows) or possibly depending on how we choose to link main + // the object file extension may be present so we simply strip any extension. + if (mem.indexOfScalar(u8, line[marks[4]..marks[5]], '.')) |idot| { + try buf.appendSlice(line[marks[3] .. marks[4] + idot]); + try buf.appendSlice(line[marks[5]..]); + } else { + try buf.appendSlice(line[marks[3]..]); + } + } else { + try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]); + try buf.appendSlice("[function]"); + } + try buf.appendSlice("\n"); + } + break :got_result try buf.toOwnedSlice(); + }; + + try std.io.getStdOut().writeAll(got); +} diff --git a/test/tests.zig b/test/tests.zig index 2d110b28af..1a80d7de8d 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -20,13 +20,14 @@ const stack_traces = @import("stack_traces.zig"); const assemble_and_link = @import("assemble_and_link.zig"); const translate_c = @import("translate_c.zig"); const run_translated_c = @import("run_translated_c.zig"); -const gen_h = @import("gen_h.zig"); const link = @import("link.zig"); // Implementations pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext; pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext; +pub const StackTracesContext = @import("src/StackTrace.zig"); +pub const StandaloneContext = @import("src/Standalone.zig"); const TestTarget = struct { target: CrossTarget = @as(CrossTarget, .{}), @@ -460,10 +461,71 @@ const test_targets = blk: { }; }; -const max_stdout_size = 1 * 1024 * 1024; // 1 MB +const c_abi_targets = [_]CrossTarget{ + .{}, + .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .x86, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .arm, + .os_tag = .linux, + .abi = .musleabihf, + }, + .{ + .cpu_arch = .mips, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .riscv64, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + .abi = .musl, + }, + .{ + .cpu_arch = .powerpc, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .powerpc64le, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .x86, + .os_tag = .windows, + .abi = .gnu, + }, + .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, +}; -pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { - const cases = b.allocator.create(CompareOutputContext) catch unreachable; +pub fn addCompareOutputTests( + b: *std.Build, + test_filter: ?[]const u8, + optimize_modes: []const OptimizeMode, +) *Step { + const cases = b.allocator.create(CompareOutputContext) catch @panic("OOM"); cases.* = CompareOutputContext{ .b = b, .step = b.step("test-compare-output", "Run the compare output tests"), @@ -477,14 +539,26 @@ pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_m return cases.step; } -pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { - const cases = b.allocator.create(StackTracesContext) catch unreachable; - cases.* = StackTracesContext{ +pub fn addStackTraceTests( + b: *std.Build, + test_filter: ?[]const u8, + optimize_modes: []const OptimizeMode, +) *Step { + const check_exe = b.addExecutable(.{ + .name = "check-stack-trace", + .root_source_file = .{ .path = "test/src/check-stack-trace.zig" }, + .target = .{}, + .optimize = .Debug, + }); + + const cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); + cases.* = .{ .b = b, .step = b.step("test-stack-traces", "Run the stack trace tests"), .test_index = 0, .test_filter = test_filter, .optimize_modes = optimize_modes, + .check_exe = check_exe, }; stack_traces.addCases(cases); @@ -507,7 +581,7 @@ pub fn addStandaloneTests( enable_wine: bool, enable_symlinks_windows: bool, ) *Step { - const cases = b.allocator.create(StandaloneContext) catch unreachable; + const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); cases.* = StandaloneContext{ .b = b, .step = b.step("test-standalone", "Run the standalone tests"), @@ -539,7 +613,7 @@ pub fn addLinkTests( omit_stage2: bool, enable_symlinks_windows: bool, ) *Step { - const cases = b.allocator.create(StandaloneContext) catch unreachable; + const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); cases.* = StandaloneContext{ .b = b, .step = b.step("test-link", "Run the linker tests"), @@ -569,7 +643,7 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co }); const run_cmd = exe.run(); run_cmd.addArgs(&[_][]const u8{ - fs.realpathAlloc(b.allocator, b.zig_exe) catch unreachable, + fs.realpathAlloc(b.allocator, b.zig_exe) catch @panic("OOM"), b.pathFromRoot(b.cache_root.path orelse "."), }); @@ -578,7 +652,7 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co } pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { - const cases = b.allocator.create(CompareOutputContext) catch unreachable; + const cases = b.allocator.create(CompareOutputContext) catch @panic("OOM"); cases.* = CompareOutputContext{ .b = b, .step = b.step("test-asm-link", "Run the assemble and link tests"), @@ -593,7 +667,7 @@ pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize } pub fn addTranslateCTests(b: *std.Build, test_filter: ?[]const u8) *Step { - const cases = b.allocator.create(TranslateCContext) catch unreachable; + const cases = b.allocator.create(TranslateCContext) catch @panic("OOM"); cases.* = TranslateCContext{ .b = b, .step = b.step("test-translate-c", "Run the C translation tests"), @@ -611,7 +685,7 @@ pub fn addRunTranslatedCTests( test_filter: ?[]const u8, target: std.zig.CrossTarget, ) *Step { - const cases = b.allocator.create(RunTranslatedCContext) catch unreachable; + const cases = b.allocator.create(RunTranslatedCContext) catch @panic("OOM"); cases.* = .{ .b = b, .step = b.step("test-run-translated-c", "Run the Run-Translated-C tests"), @@ -625,20 +699,6 @@ pub fn addRunTranslatedCTests( return cases.step; } -pub fn addGenHTests(b: *std.Build, test_filter: ?[]const u8) *Step { - const cases = b.allocator.create(GenHContext) catch unreachable; - cases.* = GenHContext{ - .b = b, - .step = b.step("test-gen-h", "Run the C header file generation tests"), - .test_index = 0, - .test_filter = test_filter, - }; - - gen_h.addCases(cases); - - return cases.step; -} - const ModuleTestOptions = struct { test_filter: ?[]const u8, root_src: []const u8, @@ -696,7 +756,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { else "bare"; - const triple_prefix = test_target.target.zigTriple(b.allocator) catch unreachable; + const triple_prefix = test_target.target.zigTriple(b.allocator) catch @panic("OOM"); // wasm32-wasi builds need more RAM, idk why const max_rss = if (test_target.target.getOs().tag == .wasi) @@ -750,623 +810,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { return step; } -pub const StackTracesContext = struct { - b: *std.Build, - step: *Step, - test_index: usize, - test_filter: ?[]const u8, - optimize_modes: []const OptimizeMode, - - const Expect = [@typeInfo(OptimizeMode).Enum.fields.len][]const u8; - - pub fn addCase(self: *StackTracesContext, config: anytype) void { - if (@hasField(@TypeOf(config), "exclude")) { - if (config.exclude.exclude()) return; - } - if (@hasField(@TypeOf(config), "exclude_arch")) { - const exclude_arch: []const std.Target.Cpu.Arch = &config.exclude_arch; - for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; - } - if (@hasField(@TypeOf(config), "exclude_os")) { - const exclude_os: []const std.Target.Os.Tag = &config.exclude_os; - for (exclude_os) |os| if (os == builtin.os.tag) return; - } - for (self.optimize_modes) |optimize_mode| { - switch (optimize_mode) { - .Debug => { - if (@hasField(@TypeOf(config), "Debug")) { - self.addExpect(config.name, config.source, optimize_mode, config.Debug); - } - }, - .ReleaseSafe => { - if (@hasField(@TypeOf(config), "ReleaseSafe")) { - self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSafe); - } - }, - .ReleaseFast => { - if (@hasField(@TypeOf(config), "ReleaseFast")) { - self.addExpect(config.name, config.source, optimize_mode, config.ReleaseFast); - } - }, - .ReleaseSmall => { - if (@hasField(@TypeOf(config), "ReleaseSmall")) { - self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSmall); - } - }, - } - } - } - - fn addExpect( - self: *StackTracesContext, - name: []const u8, - source: []const u8, - optimize_mode: OptimizeMode, - mode_config: anytype, - ) void { - if (@hasField(@TypeOf(mode_config), "exclude")) { - if (mode_config.exclude.exclude()) return; - } - if (@hasField(@TypeOf(mode_config), "exclude_arch")) { - const exclude_arch: []const std.Target.Cpu.Arch = &mode_config.exclude_arch; - for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; - } - if (@hasField(@TypeOf(mode_config), "exclude_os")) { - const exclude_os: []const std.Target.Os.Tag = &mode_config.exclude_os; - for (exclude_os) |os| if (os == builtin.os.tag) return; - } - - const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{ - "stack-trace", - name, - @tagName(optimize_mode), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const b = self.b; - const src_basename = "source.zig"; - const write_src = b.addWriteFile(src_basename, source); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = write_src.getFileSource(src_basename).?, - .optimize = optimize_mode, - .target = .{}, - }); - - const run_and_compare = RunAndCompareStep.create( - self, - exe, - annotated_case_name, - optimize_mode, - mode_config.expect, - ); - - self.step.dependOn(&run_and_compare.step); - } - - const RunAndCompareStep = struct { - pub const base_id = .custom; - - step: Step, - context: *StackTracesContext, - exe: *CompileStep, - name: []const u8, - optimize_mode: OptimizeMode, - expect_output: []const u8, - test_index: usize, - - pub fn create( - context: *StackTracesContext, - exe: *CompileStep, - name: []const u8, - optimize_mode: OptimizeMode, - expect_output: []const u8, - ) *RunAndCompareStep { - const allocator = context.b.allocator; - const ptr = allocator.create(RunAndCompareStep) catch unreachable; - ptr.* = RunAndCompareStep{ - .step = Step.init(.{ - .id = .custom, - .name = "StackTraceCompareOutputStep", - .makeFn = make, - .owner = context.b, - }), - .context = context, - .exe = exe, - .name = name, - .optimize_mode = optimize_mode, - .expect_output = expect_output, - .test_index = context.test_index, - }; - ptr.step.dependOn(&exe.step); - context.test_index += 1; - return ptr; - } - - fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(RunAndCompareStep, "step", step); - const b = self.context.b; - - const full_exe_path = self.exe.getOutputSource().getPath(b); - var args = ArrayList([]const u8).init(b.allocator); - defer args.deinit(); - args.append(full_exe_path) catch unreachable; - - std.debug.print("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name }); - - if (!std.process.can_spawn) { - const cmd = try std.mem.join(b.allocator, " ", args.items); - std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd }); - b.allocator.free(cmd); - return ExecError.ExecNotSupported; - } - - var child = std.ChildProcess.init(args.items, b.allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - child.env_map = b.env_map; - - if (b.verbose) { - printInvocation(args.items); - } - child.spawn() catch |err| debug.panic("Unable to spawn {s}: {s}\n", .{ full_exe_path, @errorName(err) }); - - const stdout = child.stdout.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; - defer b.allocator.free(stdout); - const stderrFull = child.stderr.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; - defer b.allocator.free(stderrFull); - var stderr = stderrFull; - - const term = child.wait() catch |err| { - debug.panic("Unable to spawn {s}: {s}\n", .{ full_exe_path, @errorName(err) }); - }; - - switch (term) { - .Exited => |code| { - const expect_code: u32 = 1; - if (code != expect_code) { - std.debug.print("Process {s} exited with error code {d} but expected code {d}\n", .{ - full_exe_path, - code, - expect_code, - }); - printInvocation(args.items); - return error.TestFailed; - } - }, - .Signal => |signum| { - std.debug.print("Process {s} terminated on signal {d}\n", .{ full_exe_path, signum }); - printInvocation(args.items); - return error.TestFailed; - }, - .Stopped => |signum| { - std.debug.print("Process {s} stopped on signal {d}\n", .{ full_exe_path, signum }); - printInvocation(args.items); - return error.TestFailed; - }, - .Unknown => |code| { - std.debug.print("Process {s} terminated unexpectedly with error code {d}\n", .{ full_exe_path, code }); - printInvocation(args.items); - return error.TestFailed; - }, - } - - // process result - // - keep only basename of source file path - // - replace address with symbolic string - // - replace function name with symbolic string when optimize_mode != .Debug - // - skip empty lines - const got: []const u8 = got_result: { - var buf = ArrayList(u8).init(b.allocator); - defer buf.deinit(); - if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1]; - var it = mem.split(u8, stderr, "\n"); - process_lines: while (it.next()) |line| { - if (line.len == 0) continue; - - // offset search past `[drive]:` on windows - var pos: usize = if (builtin.os.tag == .windows) 2 else 0; - // locate delims/anchor - const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" }; - var marks = [_]usize{0} ** delims.len; - for (delims, 0..) |delim, i| { - marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { - // unexpected pattern: emit raw line and cont - try buf.appendSlice(line); - try buf.appendSlice("\n"); - continue :process_lines; - }; - pos = marks[i] + delim.len; - } - // locate source basename - pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse { - // unexpected pattern: emit raw line and cont - try buf.appendSlice(line); - try buf.appendSlice("\n"); - continue :process_lines; - }; - // end processing if source basename changes - if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break; - // emit substituted line - try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]); - try buf.appendSlice(" [address]"); - if (self.optimize_mode == .Debug) { - // On certain platforms (windows) or possibly depending on how we choose to link main - // the object file extension may be present so we simply strip any extension. - if (mem.indexOfScalar(u8, line[marks[4]..marks[5]], '.')) |idot| { - try buf.appendSlice(line[marks[3] .. marks[4] + idot]); - try buf.appendSlice(line[marks[5]..]); - } else { - try buf.appendSlice(line[marks[3]..]); - } - } else { - try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]); - try buf.appendSlice("[function]"); - } - try buf.appendSlice("\n"); - } - break :got_result try buf.toOwnedSlice(); - }; - - if (!mem.eql(u8, self.expect_output, got)) { - std.debug.print( - \\ - \\========= Expected this output: ========= - \\{s} - \\================================================ - \\{s} - \\ - , .{ self.expect_output, got }); - return error.TestFailed; - } - std.debug.print("OK\n", .{}); - } - }; -}; - -pub const StandaloneContext = struct { - b: *std.Build, - step: *Step, - test_index: usize, - test_filter: ?[]const u8, - optimize_modes: []const OptimizeMode, - skip_non_native: bool, - enable_macos_sdk: bool, - target: std.zig.CrossTarget, - omit_stage2: bool, - enable_darling: bool = false, - enable_qemu: bool = false, - enable_rosetta: bool = false, - enable_wasmtime: bool = false, - enable_wine: bool = false, - enable_symlinks_windows: bool, - - pub fn addC(self: *StandaloneContext, root_src: []const u8) void { - self.addAllArgs(root_src, true); - } - - pub fn add(self: *StandaloneContext, root_src: []const u8) void { - self.addAllArgs(root_src, false); - } - - pub fn addBuildFile(self: *StandaloneContext, build_file: []const u8, features: struct { - build_modes: bool = false, - cross_targets: bool = false, - requires_macos_sdk: bool = false, - requires_stage2: bool = false, - use_emulation: bool = false, - requires_symlinks: bool = false, - extra_argv: []const []const u8 = &.{}, - }) void { - const b = self.b; - - if (features.requires_macos_sdk and !self.enable_macos_sdk) return; - if (features.requires_stage2 and self.omit_stage2) return; - if (features.requires_symlinks and !self.enable_symlinks_windows and builtin.os.tag == .windows) return; - - const annotated_case_name = b.fmt("build {s}", .{build_file}); - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - var zig_args = ArrayList([]const u8).init(b.allocator); - const rel_zig_exe = fs.path.relative(b.allocator, b.build_root.path orelse ".", b.zig_exe) catch unreachable; - zig_args.append(rel_zig_exe) catch unreachable; - zig_args.append("build") catch unreachable; - - // TODO: fix the various non-concurrency-safe issues in zig's standalone tests, - // and then remove this! - zig_args.append("-j1") catch @panic("OOM"); - - zig_args.append("--build-file") catch unreachable; - zig_args.append(b.pathFromRoot(build_file)) catch unreachable; - - zig_args.appendSlice(features.extra_argv) catch unreachable; - - zig_args.append("test") catch unreachable; - - if (b.verbose) { - zig_args.append("--verbose") catch unreachable; - } - - if (features.cross_targets and !self.target.isNative()) { - const target_triple = self.target.zigTriple(b.allocator) catch unreachable; - const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; - zig_args.append(target_arg) catch unreachable; - } - - if (features.use_emulation) { - if (self.enable_darling) { - zig_args.append("-fdarling") catch unreachable; - } - if (self.enable_qemu) { - zig_args.append("-fqemu") catch unreachable; - } - if (self.enable_rosetta) { - zig_args.append("-frosetta") catch unreachable; - } - if (self.enable_wasmtime) { - zig_args.append("-fwasmtime") catch unreachable; - } - if (self.enable_wine) { - zig_args.append("-fwine") catch unreachable; - } - } - - const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug}; - for (optimize_modes) |optimize_mode| { - const arg = switch (optimize_mode) { - .Debug => "", - .ReleaseFast => "-Doptimize=ReleaseFast", - .ReleaseSafe => "-Doptimize=ReleaseSafe", - .ReleaseSmall => "-Doptimize=ReleaseSmall", - }; - const zig_args_base_len = zig_args.items.len; - if (arg.len > 0) - zig_args.append(arg) catch unreachable; - defer zig_args.resize(zig_args_base_len) catch unreachable; - - const run_cmd = b.addSystemCommand(zig_args.items); - self.step.dependOn(&run_cmd.step); - } - } - - pub fn addAllArgs(self: *StandaloneContext, root_src: []const u8, link_libc: bool) void { - const b = self.b; - - for (self.optimize_modes) |optimize| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{ - root_src, - @tagName(optimize), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; - } - - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = root_src }, - .optimize = optimize, - .target = .{}, - }); - if (link_libc) { - exe.linkSystemLibrary("c"); - } - - self.step.dependOn(&exe.step); - } - } -}; - -pub const GenHContext = struct { - b: *std.Build, - step: *Step, - test_index: usize, - test_filter: ?[]const u8, - - const TestCase = struct { - name: []const u8, - sources: ArrayList(SourceFile), - expected_lines: ArrayList([]const u8), - - const SourceFile = struct { - filename: []const u8, - source: []const u8, - }; - - pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { - self.sources.append(SourceFile{ - .filename = filename, - .source = source, - }) catch unreachable; - } - - pub fn addExpectedLine(self: *TestCase, text: []const u8) void { - self.expected_lines.append(text) catch unreachable; - } - }; - - const GenHCmpOutputStep = struct { - step: Step, - context: *GenHContext, - obj: *CompileStep, - name: []const u8, - test_index: usize, - case: *const TestCase, - - pub fn create( - context: *GenHContext, - obj: *CompileStep, - name: []const u8, - case: *const TestCase, - ) *GenHCmpOutputStep { - const allocator = context.b.allocator; - const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; - ptr.* = GenHCmpOutputStep{ - .step = Step.init(.{ - .id = .custom, - .name = "ParseCCmpOutput", - .owner = context.b, - .makeFn = make, - }), - .context = context, - .obj = obj, - .name = name, - .test_index = context.test_index, - .case = case, - }; - ptr.step.dependOn(&obj.step); - context.test_index += 1; - return ptr; - } - - fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); - const b = self.context.b; - - std.debug.print("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name }); - - const full_h_path = self.obj.getOutputHPath(); - const actual_h = try io.readFileAlloc(b.allocator, full_h_path); - - for (self.case.expected_lines.items) |expected_line| { - if (mem.indexOf(u8, actual_h, expected_line) == null) { - std.debug.print( - \\ - \\========= Expected this output: ================ - \\{s} - \\========= But found: =========================== - \\{s} - \\ - , .{ expected_line, actual_h }); - return error.TestFailed; - } - } - std.debug.print("OK\n", .{}); - } - }; - - pub fn create( - self: *GenHContext, - filename: []const u8, - name: []const u8, - source: []const u8, - expected_lines: []const []const u8, - ) *TestCase { - const tc = self.b.allocator.create(TestCase) catch unreachable; - tc.* = TestCase{ - .name = name, - .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), - .expected_lines = ArrayList([]const u8).init(self.b.allocator), - }; - - tc.addSourceFile(filename, source); - var arg_i: usize = 0; - while (arg_i < expected_lines.len) : (arg_i += 1) { - tc.addExpectedLine(expected_lines[arg_i]); - } - return tc; - } - - pub fn add(self: *GenHContext, name: []const u8, source: []const u8, expected_lines: []const []const u8) void { - const tc = self.create("test.zig", name, source, expected_lines); - self.addCase(tc); - } - - pub fn addCase(self: *GenHContext, case: *const TestCase) void { - const b = self.b; - - const optimize_mode = std.builtin.OptimizeMode.Debug; - const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {s} ({s})", .{ case.name, @tagName(optimize_mode) }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const write_src = b.addWriteFiles(); - for (case.sources.items) |src_file| { - write_src.add(src_file.filename, src_file.source); - } - - const obj = b.addObjectFromWriteFileStep("test", write_src, case.sources.items[0].filename); - obj.setBuildMode(optimize_mode); - - const cmp_h = GenHCmpOutputStep.create(self, obj, annotated_case_name, case); - - self.step.dependOn(&cmp_h.step); - } -}; - -fn printInvocation(args: []const []const u8) void { - for (args) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n", .{}); -} - -const c_abi_targets = [_]CrossTarget{ - .{}, - .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .x86, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .aarch64, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .arm, - .os_tag = .linux, - .abi = .musleabihf, - }, - .{ - .cpu_arch = .mips, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .riscv64, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .wasm32, - .os_tag = .wasi, - .abi = .musl, - }, - .{ - .cpu_arch = .powerpc, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .powerpc64le, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .x86, - .os_tag = .windows, - .abi = .gnu, - }, - .{ - .cpu_arch = .x86_64, - .os_tag = .windows, - .abi = .gnu, - }, -}; - pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *Step { const step = b.step("test-c-abi", "Run the C ABI tests"); @@ -1395,7 +838,7 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S test_step.want_lto = false; } - const triple_prefix = c_abi_target.zigTriple(b.allocator) catch unreachable; + const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM"); test_step.setNamePrefix(b.fmt("{s}-{s}-{s} ", .{ "test-c-abi", triple_prefix, -- cgit v1.2.3 From e897637d8d25f5b6b118356d2355da7c9148d8cb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 19:19:48 -0700 Subject: re-enable compare-output test cases --- build.zig | 2 +- lib/build_runner.zig | 4 +- test/src/CompareOutput.zig | 176 ++++++++++++++++++++++++++++++++++++++++++++ test/src/compare_output.zig | 175 ------------------------------------------- test/tests.zig | 2 +- 5 files changed, 180 insertions(+), 179 deletions(-) create mode 100644 test/src/CompareOutput.zig delete mode 100644 test/src/compare_output.zig (limited to 'test/src') diff --git a/build.zig b/build.zig index 1f1b119ed3..40dc823eb7 100644 --- a/build.zig +++ b/build.zig @@ -444,7 +444,7 @@ pub fn build(b: *std.Build) !void { _ = enable_symlinks_windows; _ = enable_macos_sdk; - //test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); + test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); //test_step.dependOn(tests.addStandaloneTests( // b, // test_filter, diff --git a/lib/build_runner.zig b/lib/build_runner.zig index c30fbaea87..bb04b3e132 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -887,6 +887,8 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\ --verbose Print commands before executing them \\ --color [auto|off|on] Enable or disable colored error messages \\ --prominent-compile-errors Output compile errors formatted for a human to read + \\ -fsummary Print the build summary, even on success + \\ -fno-summary Omit the build summary, even on failure \\ -j Limit concurrent jobs (default is to use all CPU cores) \\ --maxrss Limit memory usage (default is to use available memory) \\ @@ -920,8 +922,6 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\Advanced Options: \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error \\ -fno-reference-trace Disable reference trace - \\ -fsummary Print the build summary, even on success - \\ -fno-summary Omit the build summary, even on failure \\ --build-file [file] Override path to build.zig \\ --cache-dir [path] Override path to local Zig cache directory \\ --global-cache-dir [path] Override path to global Zig cache directory diff --git a/test/src/CompareOutput.zig b/test/src/CompareOutput.zig new file mode 100644 index 0000000000..68cf942684 --- /dev/null +++ b/test/src/CompareOutput.zig @@ -0,0 +1,176 @@ +//! This is the implementation of the test harness. +//! For the actual test cases, see test/compare_output.zig. + +b: *std.Build, +step: *std.Build.Step, +test_index: usize, +test_filter: ?[]const u8, +optimize_modes: []const OptimizeMode, + +const Special = enum { + None, + Asm, + RuntimeSafety, +}; + +const TestCase = struct { + name: []const u8, + sources: ArrayList(SourceFile), + expected_output: []const u8, + link_libc: bool, + special: Special, + cli_args: []const []const u8, + + const SourceFile = struct { + filename: []const u8, + source: []const u8, + }; + + pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { + self.sources.append(SourceFile{ + .filename = filename, + .source = source, + }) catch @panic("OOM"); + } + + pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void { + self.cli_args = args; + } +}; + +pub fn createExtra(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase { + var tc = TestCase{ + .name = name, + .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), + .expected_output = expected_output, + .link_libc = false, + .special = special, + .cli_args = &[_][]const u8{}, + }; + const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; + tc.addSourceFile(root_src_name, source); + return tc; +} + +pub fn create(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) TestCase { + return createExtra(self, name, source, expected_output, Special.None); +} + +pub fn addC(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void { + var tc = self.create(name, source, expected_output); + tc.link_libc = true; + self.addCase(tc); +} + +pub fn add(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void { + const tc = self.create(name, source, expected_output); + self.addCase(tc); +} + +pub fn addAsm(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void { + const tc = self.createExtra(name, source, expected_output, Special.Asm); + self.addCase(tc); +} + +pub fn addRuntimeSafety(self: *CompareOutput, name: []const u8, source: []const u8) void { + const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety); + self.addCase(tc); +} + +pub fn addCase(self: *CompareOutput, case: TestCase) void { + const b = self.b; + + const write_src = b.addWriteFiles(); + for (case.sources.items) |src_file| { + write_src.add(src_file.filename, src_file.source); + } + + switch (case.special) { + Special.Asm => { + const annotated_case_name = fmt.allocPrint(self.b.allocator, "run assemble-and-link {s}", .{ + case.name, + }) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + const exe = b.addExecutable(.{ + .name = "test", + .target = .{}, + .optimize = .Debug, + }); + exe.addAssemblyFileSource(write_src.getFileSource(case.sources.items[0].filename).?); + + const run = exe.run(); + run.setName(annotated_case_name); + run.addArgs(case.cli_args); + run.expectStdErrEqual(""); + run.expectStdOutEqual(case.expected_output); + + self.step.dependOn(&run.step); + }, + Special.None => { + for (self.optimize_modes) |optimize| { + const annotated_case_name = fmt.allocPrint(self.b.allocator, "run compare-output {s} ({s})", .{ + case.name, @tagName(optimize), + }) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; + } + + const basename = case.sources.items[0].filename; + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = write_src.getFileSource(basename).?, + .optimize = optimize, + .target = .{}, + }); + if (case.link_libc) { + exe.linkSystemLibrary("c"); + } + + const run = exe.run(); + run.setName(annotated_case_name); + run.addArgs(case.cli_args); + run.expectStdErrEqual(""); + run.expectStdOutEqual(case.expected_output); + + self.step.dependOn(&run.step); + } + }, + Special.RuntimeSafety => { + // TODO iterate over self.optimize_modes and test this in both + // debug and release safe mode + const annotated_case_name = fmt.allocPrint(self.b.allocator, "run safety {s}", .{case.name}) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + const basename = case.sources.items[0].filename; + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = write_src.getFileSource(basename).?, + .target = .{}, + .optimize = .Debug, + }); + if (case.link_libc) { + exe.linkSystemLibrary("c"); + } + + const run = exe.run(); + run.setName(annotated_case_name); + run.addArgs(case.cli_args); + run.expectExitCode(126); + + self.step.dependOn(&run.step); + }, + } +} + +const CompareOutput = @This(); +const std = @import("std"); +const ArrayList = std.ArrayList; +const fmt = std.fmt; +const mem = std.mem; +const fs = std.fs; +const OptimizeMode = std.builtin.OptimizeMode; diff --git a/test/src/compare_output.zig b/test/src/compare_output.zig deleted file mode 100644 index 20bd62d8e2..0000000000 --- a/test/src/compare_output.zig +++ /dev/null @@ -1,175 +0,0 @@ -// This is the implementation of the test harness. -// For the actual test cases, see test/compare_output.zig. -const std = @import("std"); -const ArrayList = std.ArrayList; -const fmt = std.fmt; -const mem = std.mem; -const fs = std.fs; -const OptimizeMode = std.builtin.OptimizeMode; - -pub const CompareOutputContext = struct { - b: *std.Build, - step: *std.Build.Step, - test_index: usize, - test_filter: ?[]const u8, - optimize_modes: []const OptimizeMode, - - const Special = enum { - None, - Asm, - RuntimeSafety, - }; - - const TestCase = struct { - name: []const u8, - sources: ArrayList(SourceFile), - expected_output: []const u8, - link_libc: bool, - special: Special, - cli_args: []const []const u8, - - const SourceFile = struct { - filename: []const u8, - source: []const u8, - }; - - pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { - self.sources.append(SourceFile{ - .filename = filename, - .source = source, - }) catch unreachable; - } - - pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void { - self.cli_args = args; - } - }; - - pub fn createExtra(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase { - var tc = TestCase{ - .name = name, - .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), - .expected_output = expected_output, - .link_libc = false, - .special = special, - .cli_args = &[_][]const u8{}, - }; - const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; - tc.addSourceFile(root_src_name, source); - return tc; - } - - pub fn create(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) TestCase { - return createExtra(self, name, source, expected_output, Special.None); - } - - pub fn addC(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { - var tc = self.create(name, source, expected_output); - tc.link_libc = true; - self.addCase(tc); - } - - pub fn add(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { - const tc = self.create(name, source, expected_output); - self.addCase(tc); - } - - pub fn addAsm(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { - const tc = self.createExtra(name, source, expected_output, Special.Asm); - self.addCase(tc); - } - - pub fn addRuntimeSafety(self: *CompareOutputContext, name: []const u8, source: []const u8) void { - const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety); - self.addCase(tc); - } - - pub fn addCase(self: *CompareOutputContext, case: TestCase) void { - const b = self.b; - - const write_src = b.addWriteFiles(); - for (case.sources.items) |src_file| { - write_src.add(src_file.filename, src_file.source); - } - - switch (case.special) { - Special.Asm => { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {s}", .{ - case.name, - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const exe = b.addExecutable(.{ - .name = "test", - .target = .{}, - .optimize = .Debug, - }); - exe.addAssemblyFileSource(write_src.getFileSource(case.sources.items[0].filename).?); - - const run = exe.run(); - run.addArgs(case.cli_args); - run.expectStdErrEqual(""); - run.expectStdOutEqual(case.expected_output); - - self.step.dependOn(&run.step); - }, - Special.None => { - for (self.optimize_modes) |optimize| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{ - "compare-output", - case.name, - @tagName(optimize), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; - } - - const basename = case.sources.items[0].filename; - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = write_src.getFileSource(basename).?, - .optimize = optimize, - .target = .{}, - }); - if (case.link_libc) { - exe.linkSystemLibrary("c"); - } - - const run = exe.run(); - run.addArgs(case.cli_args); - run.expectStdErrEqual(""); - run.expectStdOutEqual(case.expected_output); - - self.step.dependOn(&run.step); - } - }, - Special.RuntimeSafety => { - // TODO iterate over self.optimize_modes and test this in both - // debug and release safe mode - const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {s}", .{case.name}) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const basename = case.sources.items[0].filename; - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = write_src.getFileSource(basename).?, - .target = .{}, - .optimize = .Debug, - }); - if (case.link_libc) { - exe.linkSystemLibrary("c"); - } - - const run = exe.run(); - run.addArgs(case.cli_args); - run.expectExitCode(126); - - self.step.dependOn(&run.step); - }, - } - } -}; diff --git a/test/tests.zig b/test/tests.zig index 1a80d7de8d..e21e652cba 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -25,7 +25,7 @@ const link = @import("link.zig"); // Implementations pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext; -pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext; +pub const CompareOutputContext = @import("src/CompareOutput.zig"); pub const StackTracesContext = @import("src/StackTrace.zig"); pub const StandaloneContext = @import("src/Standalone.zig"); -- cgit v1.2.3 From 263aaf0e66a1e2cdf5cebdbfc5e2761dc5a67181 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 23:00:32 -0700 Subject: re-enable asm-and-link tests These already looked pretty good. I deleted two unnecessary calls to expectStdErrEqual. --- build.zig | 2 +- test/src/CompareOutput.zig | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'test/src') diff --git a/build.zig b/build.zig index b97ea3c24c..b494a4ad7a 100644 --- a/build.zig +++ b/build.zig @@ -464,7 +464,7 @@ pub fn build(b: *std.Build) !void { //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); - //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); + test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); if (!skip_run_translated_c) { test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target)); diff --git a/test/src/CompareOutput.zig b/test/src/CompareOutput.zig index 68cf942684..854bd11f9c 100644 --- a/test/src/CompareOutput.zig +++ b/test/src/CompareOutput.zig @@ -104,7 +104,6 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void { const run = exe.run(); run.setName(annotated_case_name); run.addArgs(case.cli_args); - run.expectStdErrEqual(""); run.expectStdOutEqual(case.expected_output); self.step.dependOn(&run.step); @@ -132,7 +131,6 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void { const run = exe.run(); run.setName(annotated_case_name); run.addArgs(case.cli_args); - run.expectStdErrEqual(""); run.expectStdOutEqual(case.expected_output); self.step.dependOn(&run.step); -- cgit v1.2.3 From 15c4fae1c93d970d02a9eede96371fb7c053837f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 22:40:53 -0700 Subject: re-enable the simple standalone tests --- build.zig | 19 ++-- test/link.zig | 3 - test/src/Standalone.zig | 141 ---------------------------- test/standalone.zig | 243 +++++++++++++++++++++++++++--------------------- test/tests.zig | 40 ++++---- 5 files changed, 167 insertions(+), 279 deletions(-) delete mode 100644 test/src/Standalone.zig (limited to 'test/src') diff --git a/build.zig b/build.zig index b181eaefae..c64015fe88 100644 --- a/build.zig +++ b/build.zig @@ -443,16 +443,15 @@ pub fn build(b: *std.Build) !void { })); test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); - //test_step.dependOn(tests.addStandaloneTests( - // b, - // test_filter, - // optimization_modes, - // skip_non_native, - // enable_macos_sdk, - // target, - // skip_stage2_tests, - // enable_symlinks_windows, - //)); + test_step.dependOn(tests.addStandaloneTests( + b, + test_filter, + optimization_modes, + skip_non_native, + enable_macos_sdk, + skip_stage2_tests, + enable_symlinks_windows, + )); test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); diff --git a/test/link.zig b/test/link.zig index fb18e27c22..610cbd4a3d 100644 --- a/test/link.zig +++ b/test/link.zig @@ -174,6 +174,3 @@ pub const cases = [_]Case{ .import = @import("link/macho/weak_framework/build.zig"), }, }; - -const std = @import("std"); -const builtin = @import("builtin"); diff --git a/test/src/Standalone.zig b/test/src/Standalone.zig deleted file mode 100644 index c07bb511c5..0000000000 --- a/test/src/Standalone.zig +++ /dev/null @@ -1,141 +0,0 @@ -b: *std.Build, -step: *Step, -test_index: usize, -test_filter: ?[]const u8, -optimize_modes: []const OptimizeMode, -skip_non_native: bool, -enable_macos_sdk: bool, -target: std.zig.CrossTarget, -omit_stage2: bool, -enable_darling: bool = false, -enable_qemu: bool = false, -enable_rosetta: bool = false, -enable_wasmtime: bool = false, -enable_wine: bool = false, -enable_symlinks_windows: bool, - -pub fn addC(self: *Standalone, root_src: []const u8) void { - self.addAllArgs(root_src, true); -} - -pub fn add(self: *Standalone, root_src: []const u8) void { - self.addAllArgs(root_src, false); -} - -pub fn addBuildFile(self: *Standalone, build_file: []const u8, features: struct { - build_modes: bool = false, - cross_targets: bool = false, - requires_macos_sdk: bool = false, - requires_stage2: bool = false, - use_emulation: bool = false, - requires_symlinks: bool = false, - extra_argv: []const []const u8 = &.{}, -}) void { - const b = self.b; - - if (features.requires_macos_sdk and !self.enable_macos_sdk) return; - if (features.requires_stage2 and self.omit_stage2) return; - if (features.requires_symlinks and !self.enable_symlinks_windows and builtin.os.tag == .windows) return; - - const annotated_case_name = b.fmt("build {s}", .{build_file}); - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - var zig_args = ArrayList([]const u8).init(b.allocator); - const rel_zig_exe = fs.path.relative(b.allocator, b.build_root.path orelse ".", b.zig_exe) catch unreachable; - zig_args.append(rel_zig_exe) catch unreachable; - zig_args.append("build") catch unreachable; - - // TODO: fix the various non-concurrency-safe issues in zig's standalone tests, - // and then remove this! - zig_args.append("-j1") catch @panic("OOM"); - - zig_args.append("--build-file") catch unreachable; - zig_args.append(b.pathFromRoot(build_file)) catch unreachable; - - zig_args.appendSlice(features.extra_argv) catch unreachable; - - zig_args.append("test") catch unreachable; - - if (b.verbose) { - zig_args.append("--verbose") catch unreachable; - } - - if (features.cross_targets and !self.target.isNative()) { - const target_triple = self.target.zigTriple(b.allocator) catch unreachable; - const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; - zig_args.append(target_arg) catch unreachable; - } - - if (features.use_emulation) { - if (self.enable_darling) { - zig_args.append("-fdarling") catch unreachable; - } - if (self.enable_qemu) { - zig_args.append("-fqemu") catch unreachable; - } - if (self.enable_rosetta) { - zig_args.append("-frosetta") catch unreachable; - } - if (self.enable_wasmtime) { - zig_args.append("-fwasmtime") catch unreachable; - } - if (self.enable_wine) { - zig_args.append("-fwine") catch unreachable; - } - } - - const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug}; - for (optimize_modes) |optimize_mode| { - const arg = switch (optimize_mode) { - .Debug => "", - .ReleaseFast => "-Doptimize=ReleaseFast", - .ReleaseSafe => "-Doptimize=ReleaseSafe", - .ReleaseSmall => "-Doptimize=ReleaseSmall", - }; - const zig_args_base_len = zig_args.items.len; - if (arg.len > 0) - zig_args.append(arg) catch unreachable; - defer zig_args.resize(zig_args_base_len) catch unreachable; - - const run_cmd = b.addSystemCommand(zig_args.items); - self.step.dependOn(&run_cmd.step); - } -} - -pub fn addAllArgs(self: *Standalone, root_src: []const u8, link_libc: bool) void { - const b = self.b; - - for (self.optimize_modes) |optimize| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{ - root_src, - @tagName(optimize), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; - } - - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = root_src }, - .optimize = optimize, - .target = .{}, - }); - if (link_libc) { - exe.linkSystemLibrary("c"); - } - - self.step.dependOn(&exe.step); - } -} - -const Standalone = @This(); -const std = @import("std"); -const builtin = @import("builtin"); -const Step = std.Build.Step; -const OptimizeMode = std.builtin.OptimizeMode; -const fmt = std.fmt; -const mem = std.mem; -const ArrayList = std.ArrayList; -const fs = std.fs; diff --git a/test/standalone.zig b/test/standalone.zig index 7aa4d81f97..fa67d6a14d 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -1,117 +1,144 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const tests = @import("tests.zig"); - -pub fn addCases(cases: *tests.StandaloneContext) void { - cases.add("test/standalone/hello_world/hello.zig"); - cases.addC("test/standalone/hello_world/hello_libc.zig"); - - cases.addBuildFile("test/standalone/options/build.zig", .{ - .extra_argv = &.{ - "-Dbool_true", - "-Dbool_false=false", - "-Dint=1234", - "-De=two", - "-Dstring=hello", - }, - }); +pub const SimpleCase = struct { + src_path: []const u8, + link_libc: bool = false, + all_modes: bool = false, + target: std.zig.CrossTarget = .{}, +}; - cases.add("test/standalone/cat/main.zig"); - if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/6025 - cases.add("test/standalone/issue_9693/main.zig"); - } - cases.add("test/standalone/issue_12471/main.zig"); - cases.add("test/standalone/guess_number/main.zig"); - cases.add("test/standalone/main_return_error/error_u8.zig"); - cases.add("test/standalone/main_return_error/error_u8_non_zero.zig"); - cases.add("test/standalone/noreturn_call/inline.zig"); - cases.add("test/standalone/noreturn_call/as_arg.zig"); - cases.addBuildFile("test/standalone/test_runner_path/build.zig", .{ .requires_stage2 = true }); - cases.addBuildFile("test/standalone/issue_13970/build.zig", .{}); - cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{}); - cases.addBuildFile("test/standalone/shared_library/build.zig", .{}); - cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{}); - cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ - .build_modes = true, - .cross_targets = true, - }); - cases.addBuildFile("test/standalone/global_linkage/build.zig", .{}); - cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_339/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_8550/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_794/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_5825/build.zig", .{}); - cases.addBuildFile("test/standalone/pkg_import/build.zig", .{}); - cases.addBuildFile("test/standalone/use_alias/build.zig", .{}); - cases.addBuildFile("test/standalone/brace_expansion/build.zig", .{}); - if (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64) { - // https://github.com/ziglang/zig/issues/13685 - cases.addBuildFile("test/standalone/empty_env/build.zig", .{}); - } - cases.addBuildFile("test/standalone/issue_7030/build.zig", .{}); - cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{}); - if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/12194 - cases.addBuildFile("test/standalone/issue_9812/build.zig", .{}); - } - if (builtin.os.tag != .windows) { - // https://github.com/ziglang/zig/issues/12419 - cases.addBuildFile("test/standalone/issue_11595/build.zig", .{}); - } +pub const BuildCase = struct { + build_root: []const u8, + import: type, +}; - if (builtin.os.tag != .wasi and - // https://github.com/ziglang/zig/issues/13550 - (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and - // https://github.com/ziglang/zig/issues/13686 - (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64)) - { - cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{}); - } +pub const simple_cases = [_]SimpleCase{ + .{ + .src_path = "test/standalone/hello_world/hello.zig", + .all_modes = true, + }, + .{ + .src_path = "test/standalone/hello_world/hello_libc.zig", + .link_libc = true, + .all_modes = true, + }, + .{ + .src_path = "test/standalone/cat/main.zig", + }, + // https://github.com/ziglang/zig/issues/6025 + //.{ + // .src_path = "test/standalone/issue_9693/main.zig", + //}, - if (builtin.os.tag == .windows) { - cases.addBuildFile("test/standalone/windows_spawn/build.zig", .{}); - } + .{ .src_path = "test/standalone/issue_12471/main.zig" }, + .{ .src_path = "test/standalone/guess_number/main.zig" }, + .{ .src_path = "test/standalone/main_return_error/error_u8.zig" }, + .{ .src_path = "test/standalone/main_return_error/error_u8_non_zero.zig" }, + .{ .src_path = "test/standalone/noreturn_call/inline.zig" }, + .{ .src_path = "test/standalone/noreturn_call/as_arg.zig" }, - cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ - .build_modes = true, - .cross_targets = true, - }); - - if (builtin.os.tag == .windows) { - cases.addC("test/standalone/issue_9402/main.zig"); - } - // Try to build and run a PIE executable. - if (builtin.os.tag == .linux) { - cases.addBuildFile("test/standalone/pie/build.zig", .{}); - } - cases.addBuildFile("test/standalone/issue_12706/build.zig", .{}); - if (std.os.have_sigpipe_support) { - cases.addBuildFile("test/standalone/sigpipe/build.zig", .{}); - } + .{ + .src_path = "test/standalone/issue_9402/main.zig", + .target = .{ .os_tag = .windows }, + .link_libc = true, + }, // Ensure the development tools are buildable. Alphabetically sorted. // No need to build `tools/spirv/grammar.zig`. - cases.add("tools/extract-grammar.zig"); - cases.add("tools/gen_outline_atomics.zig"); - cases.add("tools/gen_spirv_spec.zig"); - cases.add("tools/gen_stubs.zig"); - cases.add("tools/generate_linux_syscalls.zig"); - cases.add("tools/process_headers.zig"); - cases.add("tools/update-license-headers.zig"); - cases.add("tools/update-linux-headers.zig"); - cases.add("tools/update_clang_options.zig"); - cases.add("tools/update_cpu_features.zig"); - cases.add("tools/update_glibc.zig"); - cases.add("tools/update_spirv_features.zig"); + .{ .src_path = "tools/extract-grammar.zig" }, + .{ .src_path = "tools/gen_outline_atomics.zig" }, + .{ .src_path = "tools/gen_spirv_spec.zig" }, + .{ .src_path = "tools/gen_stubs.zig" }, + .{ .src_path = "tools/generate_linux_syscalls.zig" }, + .{ .src_path = "tools/process_headers.zig" }, + .{ .src_path = "tools/update-license-headers.zig" }, + .{ .src_path = "tools/update-linux-headers.zig" }, + .{ .src_path = "tools/update_clang_options.zig" }, + .{ .src_path = "tools/update_cpu_features.zig" }, + .{ .src_path = "tools/update_glibc.zig" }, + .{ .src_path = "tools/update_spirv_features.zig" }, +}; + +pub const build_cases = [_]BuildCase{}; - cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true }); - cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_12588/build.zig", .{}); - cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{}); - cases.addBuildFile("test/standalone/extern/build.zig", .{}); +//pub fn addCases(cases: *tests.StandaloneContext) void { +// cases.addBuildFile("test/standalone/options/build.zig", .{ +// .extra_argv = &.{ +// "-Dbool_true", +// "-Dbool_false=false", +// "-Dint=1234", +// "-De=two", +// "-Dstring=hello", +// }, +// }); +// +// cases.addBuildFile("test/standalone/test_runner_path/build.zig", .{ .requires_stage2 = true }); +// cases.addBuildFile("test/standalone/issue_13970/build.zig", .{}); +// cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{}); +// cases.addBuildFile("test/standalone/shared_library/build.zig", .{}); +// cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{}); +// cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ +// .build_modes = true, +// .cross_targets = true, +// }); +// cases.addBuildFile("test/standalone/global_linkage/build.zig", .{}); +// cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_339/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_8550/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_794/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_5825/build.zig", .{}); +// cases.addBuildFile("test/standalone/pkg_import/build.zig", .{}); +// cases.addBuildFile("test/standalone/use_alias/build.zig", .{}); +// cases.addBuildFile("test/standalone/brace_expansion/build.zig", .{}); +// if (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64) { +// // https://github.com/ziglang/zig/issues/13685 +// cases.addBuildFile("test/standalone/empty_env/build.zig", .{}); +// } +// cases.addBuildFile("test/standalone/issue_7030/build.zig", .{}); +// cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{}); +// if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/12194 +// cases.addBuildFile("test/standalone/issue_9812/build.zig", .{}); +// } +// if (builtin.os.tag != .windows) { +// // https://github.com/ziglang/zig/issues/12419 +// cases.addBuildFile("test/standalone/issue_11595/build.zig", .{}); +// } +// +// if (builtin.os.tag != .wasi and +// // https://github.com/ziglang/zig/issues/13550 +// (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and +// // https://github.com/ziglang/zig/issues/13686 +// (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64)) +// { +// cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{}); +// } +// +// if (builtin.os.tag == .windows) { +// cases.addBuildFile("test/standalone/windows_spawn/build.zig", .{}); +// } +// +// cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ +// .build_modes = true, +// .cross_targets = true, +// }); +// +// // Try to build and run a PIE executable. +// if (builtin.os.tag == .linux) { +// cases.addBuildFile("test/standalone/pie/build.zig", .{}); +// } +// cases.addBuildFile("test/standalone/issue_12706/build.zig", .{}); +// if (std.os.have_sigpipe_support) { +// cases.addBuildFile("test/standalone/sigpipe/build.zig", .{}); +// } +// +// cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true }); +// cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_12588/build.zig", .{}); +// cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{}); +// +// cases.addBuildFile("test/standalone/dep_diamond/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_triangle/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_recursive/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_mutually_recursive/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_shared_builtin/build.zig", .{}); +//} - cases.addBuildFile("test/standalone/dep_diamond/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_triangle/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_recursive/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_mutually_recursive/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_shared_builtin/build.zig", .{}); -} +const std = @import("std"); diff --git a/test/tests.zig b/test/tests.zig index a1945246a3..038111136a 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -20,7 +20,6 @@ pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext; pub const CompareOutputContext = @import("src/CompareOutput.zig"); pub const StackTracesContext = @import("src/StackTrace.zig"); -pub const StandaloneContext = @import("src/Standalone.zig"); const TestTarget = struct { target: CrossTarget = @as(CrossTarget, .{}), @@ -565,27 +564,34 @@ pub fn addStandaloneTests( optimize_modes: []const OptimizeMode, skip_non_native: bool, enable_macos_sdk: bool, - target: std.zig.CrossTarget, omit_stage2: bool, enable_symlinks_windows: bool, ) *Step { - const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); - cases.* = .{ - .b = b, - .step = b.step("test-standalone", "Run the standalone tests"), - .test_index = 0, - .test_filter = test_filter, - .optimize_modes = optimize_modes, - .skip_non_native = skip_non_native, - .enable_macos_sdk = enable_macos_sdk, - .target = target, - .omit_stage2 = omit_stage2, - .enable_symlinks_windows = enable_symlinks_windows, - }; + const step = b.step("test-standalone", "Run the standalone tests"); - standalone.addCases(cases); + _ = test_filter; + _ = skip_non_native; + _ = enable_macos_sdk; + _ = omit_stage2; + _ = enable_symlinks_windows; + + for (standalone.simple_cases) |case| { + for (optimize_modes) |optimize| { + if (!case.all_modes and optimize != .Debug) continue; + + const exe = b.addExecutable(.{ + .name = std.fs.path.stem(case.src_path), + .root_source_file = .{ .path = case.src_path }, + .optimize = optimize, + .target = case.target, + }); + if (case.link_libc) exe.linkLibC(); - return cases.step; + step.dependOn(&exe.step); + } + } + + return step; } pub fn addLinkTests( -- cgit v1.2.3 From 29cfd47d6509fc6ee1a165b3bc03180f6cf351a5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 18:22:51 -0700 Subject: re-enable test-cases and get them all passing Instead of using `zig test` to build a special version of the compiler that runs all the test-cases, the zig build system is now used as much as possible - all with the basic steps found in the standard library. For incremental compilation tests (the ones that look like foo.0.zig, foo.1.zig, foo.2.zig, etc.), a special version of the compiler is compiled into a utility executable called "check-case" which checks exactly one sequence of incremental updates in an independent subprocess. Previously, all incremental and non-incremental test cases were done in the same test runner process. The compile error checking code is now simpler, but also a bit rudimentary, and so it additionally makes sure that the actual compile errors do not include *extra* messages, and it makes sure that the actual compile errors output in the same order as expected. It is also based on the "ends-with" property of each line rather than the previous logic, which frankly I didn't want to touch with a ten-meter pole. The compile error test cases have been updated to pass in light of these differences. Previously, 'error' mode with 0 compile errors was used to shoehorn in a different kind of test-case - one that only checks if a piece of code compiles without errors. Now there is a 'compile' mode of test-cases, and 'error' must be only used when there are greater than 0 errors. link test cases are updated to omit the target object format argument when calling checkObject since that is no longer needed. The test/stage2 directory is removed; the 2 files within are moved to be directly in the test/ directory. --- build.zig | 50 +- src/test.zig | 1968 -------------------- test/cases.zig | 10 +- .../access_inactive_union_field_comptime.zig | 1 + test/cases/compile_errors/bad_import.zig | 2 +- .../condition_comptime_reason_explained.zig | 2 + ...y_embedding_opaque_type_in_struct_and_union.zig | 1 + .../extern_function_with_comptime_parameter.zig | 2 +- .../function_parameter_is_opaque.zig | 1 + .../helpful_return_type_error_message.zig | 2 +- .../implicit_semicolon-block_expr.zig | 2 + .../implicit_semicolon-block_statement.zig | 2 + .../implicit_semicolon-comptime_expression.zig | 2 + .../implicit_semicolon-comptime_statement.zig | 2 + .../compile_errors/implicit_semicolon-defer.zig | 2 + .../implicit_semicolon-for_expression.zig | 3 + .../implicit_semicolon-for_statement.zig | 3 + ...plicit_semicolon-if-else-if-else_expression.zig | 2 + ...mplicit_semicolon-if-else-if-else_statement.zig | 2 + .../implicit_semicolon-if-else-if_expression.zig | 2 + .../implicit_semicolon-if-else-if_statement.zig | 2 + .../implicit_semicolon-if-else_expression.zig | 2 + .../implicit_semicolon-if-else_statement.zig | 2 + .../implicit_semicolon-if_expression.zig | 2 + .../implicit_semicolon-if_statement.zig | 2 + .../implicit_semicolon-test_expression.zig | 3 + .../implicit_semicolon-test_statement.zig | 3 + ...mplicit_semicolon-while-continue_expression.zig | 2 + ...implicit_semicolon-while-continue_statement.zig | 2 + .../implicit_semicolon-while_expression.zig | 2 + .../implicit_semicolon-while_statement.zig | 2 + .../invalid_member_of_builtin_enum.zig | 2 +- .../invalid_store_to_comptime_field.zig | 2 +- test/cases/compile_errors/invalid_struct_field.zig | 1 + .../missing_main_fn_in_executable.zig | 6 +- test/cases/compile_errors/private_main_fn.zig | 6 +- .../runtime_index_into_comptime_type_slice.zig | 5 +- .../compile_errors/struct_type_mismatch_in_arg.zig | 2 +- .../union_init_with_none_or_multiple_fields.zig | 3 +- ...r_access_chaining_pointer_to_optional_array.zig | 2 +- ...paces_pointer_access_chaining_array_pointer.zig | 2 +- ...ress_spaces_pointer_access_chaining_complex.zig | 2 +- ...aces_pointer_access_chaining_struct_pointer.zig | 2 +- ...hough_multiple_pointers_with_address_spaces.zig | 2 +- test/cases/llvm/pointer_keeps_address_space.zig | 2 +- ...ss_space_when_taking_address_of_dereference.zig | 2 +- ...c_address_space_coerces_to_implicit_pointer.zig | 2 +- test/cbe.zig | 950 ++++++++++ test/compile_errors.zig | 224 +-- test/link/macho/dead_strip/build.zig | 4 +- test/link/macho/dead_strip_dylibs/build.zig | 2 +- test/link/macho/dylib/build.zig | 4 +- test/link/macho/entry/build.zig | 2 +- test/link/macho/headerpad/build.zig | 8 +- test/link/macho/linksection/build.zig | 2 +- test/link/macho/needed_framework/build.zig | 2 +- test/link/macho/needed_library/build.zig | 2 +- test/link/macho/pagezero/build.zig | 4 +- test/link/macho/search_strategy/build.zig | 2 +- test/link/macho/stack_size/build.zig | 2 +- test/link/macho/strict_validation/build.zig | 2 +- test/link/macho/unwind_info/build.zig | 2 +- test/link/macho/weak_framework/build.zig | 2 +- test/link/macho/weak_library/build.zig | 2 +- test/link/wasm/archive/build.zig | 2 +- test/link/wasm/basic-features/build.zig | 2 +- test/link/wasm/bss/build.zig | 2 +- test/link/wasm/export-data/build.zig | 2 +- test/link/wasm/export/build.zig | 6 +- test/link/wasm/extern-mangle/build.zig | 2 +- test/link/wasm/function-table/build.zig | 6 +- test/link/wasm/infer-features/build.zig | 2 +- test/link/wasm/producers/build.zig | 2 +- test/link/wasm/segments/build.zig | 2 +- test/link/wasm/stack_pointer/build.zig | 2 +- test/link/wasm/type/build.zig | 2 +- test/nvptx.zig | 106 ++ test/src/Cases.zig | 1587 ++++++++++++++++ test/stage2/cbe.zig | 1015 ---------- test/stage2/nvptx.zig | 107 -- test/tests.zig | 27 + 81 files changed, 2840 insertions(+), 3378 deletions(-) delete mode 100644 src/test.zig create mode 100644 test/cbe.zig create mode 100644 test/nvptx.zig create mode 100644 test/src/Cases.zig delete mode 100644 test/stage2/cbe.zig delete mode 100644 test/stage2/nvptx.zig (limited to 'test/src') diff --git a/build.zig b/build.zig index ba12ae5ae0..9ba2b1acef 100644 --- a/build.zig +++ b/build.zig @@ -53,13 +53,14 @@ pub fn build(b: *std.Build) !void { const docs_step = b.step("docs", "Build documentation"); docs_step.dependOn(&docgen_cmd.step); - const test_cases = b.addTest(.{ - .root_source_file = .{ .path = "src/test.zig" }, + const check_case_exe = b.addExecutable(.{ + .name = "check-case", + .root_source_file = .{ .path = "test/src/Cases.zig" }, .optimize = optimize, }); - test_cases.main_pkg_path = "."; - test_cases.stack_size = stack_size; - test_cases.single_threaded = single_threaded; + check_case_exe.main_pkg_path = "."; + check_case_exe.stack_size = stack_size; + check_case_exe.single_threaded = single_threaded; const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false; const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; @@ -178,7 +179,7 @@ pub fn build(b: *std.Build) !void { if (target.isWindows() and target.getAbi() == .gnu) { // LTO is currently broken on mingw, this can be removed when it's fixed. exe.want_lto = false; - test_cases.want_lto = false; + check_case_exe.want_lto = false; } const exe_options = b.addOptions(); @@ -196,7 +197,7 @@ pub fn build(b: *std.Build) !void { if (link_libc) { exe.linkLibC(); - test_cases.linkLibC(); + check_case_exe.linkLibC(); } const is_debug = optimize == .Debug; @@ -282,14 +283,14 @@ pub fn build(b: *std.Build) !void { } try addCmakeCfgOptionsToExe(b, cfg, exe, use_zig_libcxx); - try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx); + try addCmakeCfgOptionsToExe(b, cfg, check_case_exe, use_zig_libcxx); } else { // Here we are -Denable-llvm but no cmake integration. try addStaticLlvmOptionsToExe(exe); - try addStaticLlvmOptionsToExe(test_cases); + try addStaticLlvmOptionsToExe(check_case_exe); } if (target.isWindows()) { - inline for (.{ exe, test_cases }) |artifact| { + inline for (.{ exe, check_case_exe }) |artifact| { artifact.linkSystemLibrary("version"); artifact.linkSystemLibrary("uuid"); artifact.linkSystemLibrary("ole32"); @@ -334,8 +335,9 @@ pub fn build(b: *std.Build) !void { const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter"); const test_cases_options = b.addOptions(); - test_cases.addOptions("build_options", test_cases_options); + check_case_exe.addOptions("build_options", test_cases_options); + test_cases_options.addOption(bool, "enable_tracy", false); test_cases_options.addOption(bool, "enable_logging", enable_logging); test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots); test_cases_options.addOption(bool, "skip_non_native", skip_non_native); @@ -358,12 +360,6 @@ pub fn build(b: *std.Build) !void { test_cases_options.addOption(std.SemanticVersion, "semver", semver); test_cases_options.addOption(?[]const u8, "test_filter", test_filter); - const test_cases_step = b.step("test-cases", "Run the main compiler test cases"); - test_cases_step.dependOn(&test_cases.step); - if (!skip_stage2_tests) { - test_step.dependOn(test_cases_step); - } - var chosen_opt_modes_buf: [4]builtin.Mode = undefined; var chosen_mode_index: usize = 0; if (!skip_debug) { @@ -386,21 +382,20 @@ pub fn build(b: *std.Build) !void { const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" }; const fmt_exclude_paths = &.{"test/cases"}; - const check_fmt = b.addFmt(.{ - .paths = fmt_include_paths, - .exclude_paths = fmt_exclude_paths, - .check = true, - }); const do_fmt = b.addFmt(.{ .paths = fmt_include_paths, .exclude_paths = fmt_exclude_paths, }); - const test_fmt_step = b.step("test-fmt", "Check whether source files have conforming formatting"); - test_fmt_step.dependOn(&check_fmt.step); + b.step("test-fmt", "Check source files having conforming formatting").dependOn(&b.addFmt(.{ + .paths = fmt_include_paths, + .exclude_paths = fmt_exclude_paths, + .check = true, + }).step); - const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting"); - do_fmt_step.dependOn(&do_fmt.step); + const test_cases_step = b.step("test-cases", "Run the main compiler test cases"); + try tests.addCases(b, test_cases_step, test_filter, check_case_exe); + if (!skip_stage2_tests) test_step.dependOn(test_cases_step); test_step.dependOn(tests.addModuleTests(b, .{ .test_filter = test_filter, @@ -475,6 +470,9 @@ pub fn build(b: *std.Build) !void { })); try addWasiUpdateStep(b, version); + + b.step("fmt", "Modify source files in place to have conforming formatting") + .dependOn(&do_fmt.step); } fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { diff --git a/src/test.zig b/src/test.zig deleted file mode 100644 index 5b73e516c6..0000000000 --- a/src/test.zig +++ /dev/null @@ -1,1968 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const Allocator = std.mem.Allocator; -const CrossTarget = std.zig.CrossTarget; -const print = std.debug.print; -const assert = std.debug.assert; -const ThreadPool = std.Thread.Pool; -const WaitGroup = std.Thread.WaitGroup; - -const link = @import("link.zig"); -const Compilation = @import("Compilation.zig"); -const Package = @import("Package.zig"); -const introspect = @import("introspect.zig"); -const build_options = @import("build_options"); -const zig_h = link.File.C.zig_h; - -const enable_qemu: bool = build_options.enable_qemu; -const enable_wine: bool = build_options.enable_wine; -const enable_wasmtime: bool = build_options.enable_wasmtime; -const enable_darling: bool = build_options.enable_darling; -const enable_rosetta: bool = build_options.enable_rosetta; -const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; -const skip_stage1 = true; - -const hr = "=" ** 80; - -test { - const use_gpa = build_options.force_gpa or !builtin.link_libc; - const gpa = gpa: { - if (use_gpa) { - break :gpa std.testing.allocator; - } - // We would prefer to use raw libc allocator here, but cannot - // use it if it won't support the alignment we need. - if (@alignOf(std.c.max_align_t) < @alignOf(i128)) { - break :gpa std.heap.c_allocator; - } - break :gpa std.heap.raw_c_allocator; - }; - - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - var ctx = TestContext.init(gpa, arena); - defer ctx.deinit(); - - { - const dir_path = try std.fs.path.join(arena, &.{ - std.fs.path.dirname(@src().file).?, "..", "test", "cases", - }); - - var dir = try std.fs.cwd().openIterableDir(dir_path, .{}); - defer dir.close(); - - ctx.addTestCasesFromDir(dir); - } - - try @import("../test/cases.zig").addCases(&ctx); - - try ctx.run(); -} - -const ErrorMsg = union(enum) { - src: struct { - src_path: []const u8, - msg: []const u8, - // maxint means match anything - // this is a workaround for stage1 compiler bug I ran into when making it ?u32 - line: u32, - // maxint means match anything - // this is a workaround for stage1 compiler bug I ran into when making it ?u32 - column: u32, - kind: Kind, - count: u32, - }, - plain: struct { - msg: []const u8, - kind: Kind, - count: u32, - }, - - const Kind = enum { - @"error", - note, - }; - - fn init(other: Compilation.AllErrors.Message, kind: Kind) ErrorMsg { - switch (other) { - .src => |src| return .{ - .src = .{ - .src_path = src.src_path, - .msg = src.msg, - .line = @intCast(u32, src.line), - .column = @intCast(u32, src.column), - .kind = kind, - .count = src.count, - }, - }, - .plain => |plain| return .{ - .plain = .{ - .msg = plain.msg, - .kind = kind, - .count = plain.count, - }, - }, - } - } - - pub fn format( - self: ErrorMsg, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = fmt; - _ = options; - switch (self) { - .src => |src| { - if (!std.mem.eql(u8, src.src_path, "?") or - src.line != std.math.maxInt(u32) or - src.column != std.math.maxInt(u32)) - { - try writer.print("{s}:", .{src.src_path}); - if (src.line != std.math.maxInt(u32)) { - try writer.print("{d}:", .{src.line + 1}); - } else { - try writer.writeAll("?:"); - } - if (src.column != std.math.maxInt(u32)) { - try writer.print("{d}: ", .{src.column + 1}); - } else { - try writer.writeAll("?: "); - } - } - try writer.print("{s}: {s}", .{ @tagName(src.kind), src.msg }); - if (src.count != 1) { - try writer.print(" ({d} times)", .{src.count}); - } - }, - .plain => |plain| { - try writer.print("{s}: {s}", .{ @tagName(plain.kind), plain.msg }); - if (plain.count != 1) { - try writer.print(" ({d} times)", .{plain.count}); - } - }, - } - } -}; - -/// Default config values for known test manifest key-value pairings. -/// Currently handled defaults are: -/// * backend -/// * target -/// * output_mode -/// * is_test -const TestManifestConfigDefaults = struct { - /// Asserts if the key doesn't exist - yep, it's an oversight alright. - fn get(@"type": TestManifest.Type, key: []const u8) []const u8 { - if (std.mem.eql(u8, key, "backend")) { - return "stage2"; - } else if (std.mem.eql(u8, key, "target")) { - comptime { - var defaults: []const u8 = ""; - // TODO should we only return "mainstream" targets by default here? - // TODO we should also specify ABIs explicitly as the backends are - // getting more and more complete - // Linux - inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| { - defaults = defaults ++ arch ++ "-linux" ++ ","; - } - // macOS - inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| { - defaults = defaults ++ arch ++ "-macos" ++ ","; - } - // Windows - defaults = defaults ++ "x86_64-windows" ++ ","; - // Wasm - defaults = defaults ++ "wasm32-wasi"; - return defaults; - } - } else if (std.mem.eql(u8, key, "output_mode")) { - return switch (@"type") { - .@"error" => "Obj", - .run => "Exe", - .cli => @panic("TODO test harness for CLI tests"), - }; - } else if (std.mem.eql(u8, key, "is_test")) { - return "0"; - } else unreachable; - } -}; - -/// Manifest syntax example: -/// (see https://github.com/ziglang/zig/issues/11288) -/// -/// error -/// backend=stage1,stage2 -/// output_mode=exe -/// -/// :3:19: error: foo -/// -/// run -/// target=x86_64-linux,aarch64-macos -/// -/// I am expected stdout! Hello! -/// -/// cli -/// -/// build test -const TestManifest = struct { - type: Type, - config_map: std.StringHashMap([]const u8), - trailing_bytes: []const u8 = "", - - const Type = enum { - @"error", - run, - cli, - }; - - const TrailingIterator = struct { - inner: std.mem.TokenIterator(u8), - - fn next(self: *TrailingIterator) ?[]const u8 { - const next_inner = self.inner.next() orelse return null; - return std.mem.trim(u8, next_inner[2..], " \t"); - } - }; - - fn ConfigValueIterator(comptime T: type) type { - return struct { - inner: std.mem.SplitIterator(u8), - - fn next(self: *@This()) !?T { - const next_raw = self.inner.next() orelse return null; - const parseFn = getDefaultParser(T); - return try parseFn(next_raw); - } - }; - } - - fn parse(arena: Allocator, bytes: []const u8) !TestManifest { - // The manifest is the last contiguous block of comments in the file - // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" - var start: ?usize = null; - var end: usize = bytes.len; - if (bytes.len > 0) { - var cursor: usize = bytes.len - 1; - while (true) { - // Move to beginning of line - while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1; - - if (std.mem.startsWith(u8, bytes[cursor..], "//")) { - start = cursor; // Contiguous comment line, include in manifest - } else { - if (start != null) break; // Encountered non-comment line, end of manifest - - // We ignore all-whitespace lines following the comment block, but anything else - // means that there is no manifest present. - if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) { - end = cursor; - } else break; // If it's not whitespace, there is no manifest - } - - // Move to previous line - if (cursor != 0) cursor -= 1 else break; - } - } - - const actual_start = start orelse return error.MissingTestManifest; - const manifest_bytes = bytes[actual_start..end]; - - var it = std.mem.tokenize(u8, manifest_bytes, "\r\n"); - - // First line is the test type - const tt: Type = blk: { - const line = it.next() orelse return error.MissingTestCaseType; - const raw = std.mem.trim(u8, line[2..], " \t"); - if (std.mem.eql(u8, raw, "error")) { - break :blk .@"error"; - } else if (std.mem.eql(u8, raw, "run")) { - break :blk .run; - } else if (std.mem.eql(u8, raw, "cli")) { - break :blk .cli; - } else { - std.log.warn("unknown test case type requested: {s}", .{raw}); - return error.UnknownTestCaseType; - } - }; - - var manifest: TestManifest = .{ - .type = tt, - .config_map = std.StringHashMap([]const u8).init(arena), - }; - - // Any subsequent line until a blank comment line is key=value(s) pair - while (it.next()) |line| { - const trimmed = std.mem.trim(u8, line[2..], " \t"); - if (trimmed.len == 0) break; - - // Parse key=value(s) - var kv_it = std.mem.split(u8, trimmed, "="); - const key = kv_it.first(); - try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig); - } - - // Finally, trailing is expected output - manifest.trailing_bytes = manifest_bytes[it.index..]; - - return manifest; - } - - fn getConfigForKey( - self: TestManifest, - key: []const u8, - comptime T: type, - ) ConfigValueIterator(T) { - const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key); - return ConfigValueIterator(T){ - .inner = std.mem.split(u8, bytes, ","), - }; - } - - fn getConfigForKeyAlloc( - self: TestManifest, - allocator: Allocator, - key: []const u8, - comptime T: type, - ) ![]const T { - var out = std.ArrayList(T).init(allocator); - defer out.deinit(); - var it = self.getConfigForKey(key, T); - while (try it.next()) |item| { - try out.append(item); - } - return try out.toOwnedSlice(); - } - - fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T { - var it = self.getConfigForKey(key, T); - const res = (try it.next()) orelse unreachable; - assert((try it.next()) == null); - return res; - } - - fn trailing(self: TestManifest) TrailingIterator { - return .{ - .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"), - }; - } - - fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 { - var out = std.ArrayList([]const u8).init(allocator); - defer out.deinit(); - var it = self.trailing(); - while (it.next()) |line| { - try out.append(line); - } - return try out.toOwnedSlice(); - } - - fn ParseFn(comptime T: type) type { - return fn ([]const u8) anyerror!T; - } - - fn getDefaultParser(comptime T: type) ParseFn(T) { - if (T == CrossTarget) return struct { - fn parse(str: []const u8) anyerror!T { - var opts = CrossTarget.ParseOptions{ - .arch_os_abi = str, - }; - return try CrossTarget.parse(opts); - } - }.parse; - - switch (@typeInfo(T)) { - .Int => return struct { - fn parse(str: []const u8) anyerror!T { - return try std.fmt.parseInt(T, str, 0); - } - }.parse, - .Bool => return struct { - fn parse(str: []const u8) anyerror!T { - const as_int = try std.fmt.parseInt(u1, str, 0); - return as_int > 0; - } - }.parse, - .Enum => return struct { - fn parse(str: []const u8) anyerror!T { - return std.meta.stringToEnum(T, str) orelse { - std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str }); - return error.UnknownEnumVariant; - }; - } - }.parse, - .Struct => @compileError("no default parser for " ++ @typeName(T)), - else => @compileError("no default parser for " ++ @typeName(T)), - } - } -}; - -const TestStrategy = enum { - /// Execute tests as independent compilations, unless they are explicitly - /// incremental ("foo.0.zig", "foo.1.zig", etc.) - independent, - /// Execute all tests as incremental updates to a single compilation. Explicitly - /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order - incremental, -}; - -/// Iterates a set of filenames extracting batches that are either incremental -/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.). -/// Assumes filenames are sorted. -const TestIterator = struct { - start: usize = 0, - end: usize = 0, - filenames: []const []const u8, - /// reset on each call to `next` - index: usize = 0, - - const Error = error{InvalidIncrementalTestIndex}; - - fn next(it: *TestIterator) Error!?[]const []const u8 { - try it.nextInner(); - if (it.start == it.end) return null; - return it.filenames[it.start..it.end]; - } - - fn nextInner(it: *TestIterator) Error!void { - it.start = it.end; - if (it.end == it.filenames.len) return; - if (it.end + 1 == it.filenames.len) { - it.end += 1; - return; - } - - const remaining = it.filenames[it.end..]; - it.index = 0; - while (it.index < remaining.len - 1) : (it.index += 1) { - // First, check if this file is part of an incremental update sequence - // Split filename into ".." - const prev_parts = getTestFileNameParts(remaining[it.index]); - const new_parts = getTestFileNameParts(remaining[it.index + 1]); - - // If base_name and file_ext match, these files are in the same test sequence - // and the new one should be the incremented version of the previous test - if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and - std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext)) - { - // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1 - if (prev_parts.test_index == null) - return error.InvalidIncrementalTestIndex; - if (new_parts.test_index == null) - return error.InvalidIncrementalTestIndex; - if (new_parts.test_index.? != prev_parts.test_index.? + 1) - return error.InvalidIncrementalTestIndex; - } else { - // This is not the same test sequence, so the new file must be the first file - // in a new sequence ("*.0.zig") or an independent test file ("*.zig") - if (new_parts.test_index != null and new_parts.test_index.? != 0) - return error.InvalidIncrementalTestIndex; - - it.end += it.index + 1; - break; - } - } else { - it.end += remaining.len; - } - } - - /// In the event of an `error.InvalidIncrementalTestIndex`, this function can - /// be used to find the current filename that was being processed. - /// Asserts the iterator hasn't reached the end. - fn currentFilename(it: TestIterator) []const u8 { - assert(it.end != it.filenames.len); - const remaining = it.filenames[it.end..]; - return remaining[it.index + 1]; - } -}; - -/// For a filename in the format ".X." or ".", returns -/// "", "" and X parsed as a decimal number. If X is not present, or -/// cannot be parsed as a decimal number, it is treated as part of -fn getTestFileNameParts(name: []const u8) struct { - base_name: []const u8, - file_ext: []const u8, - test_index: ?usize, -} { - const file_ext = std.fs.path.extension(name); - const trimmed = name[0 .. name.len - file_ext.len]; // Trim off "." - const maybe_index = std.fs.path.extension(trimmed); // Extract ".X" - - // Attempt to parse index - const index: ?usize = if (maybe_index.len > 0) - std.fmt.parseInt(usize, maybe_index[1..], 10) catch null - else - null; - - // Adjust "" extent based on parsing success - const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0; - return .{ - .base_name = name[0..base_name_end], - .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext, - .test_index = index, - }; -} - -/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig", -/// "foo.1.zig", etc.) are contiguous and appear in numerical order. -fn sortTestFilenames(filenames: [][]const u8) void { - const Context = struct { - pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool { - const a_parts = getTestFileNameParts(a); - const b_parts = getTestFileNameParts(b); - - // Sort ".X." based on "" and "" first - return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) { - .lt => true, - .gt => false, - .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) { - .lt => true, - .gt => false, - .eq => { - // a and b differ only in their ".X" part - - // Sort "." before any ".X." - if (a_parts.test_index) |a_index| { - if (b_parts.test_index) |b_index| { - // Make sure that incremental tests appear in linear order - return a_index < b_index; - } else { - return false; - } - } else { - return b_parts.test_index != null; - } - }, - }, - }; - } - }; - std.sort.sort([]const u8, filenames, Context{}, Context.lessThan); -} - -pub const TestContext = struct { - gpa: Allocator, - arena: Allocator, - cases: std.ArrayList(Case), - - pub const Update = struct { - /// The input to the current update. We simulate an incremental update - /// with the file's contents changed to this value each update. - /// - /// This value can change entirely between updates, which would be akin - /// to deleting the source file and creating a new one from scratch; or - /// you can keep it mostly consistent, with small changes, testing the - /// effects of the incremental compilation. - src: [:0]const u8, - name: []const u8, - case: union(enum) { - /// 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*. - Error: []const ErrorMsg, - /// An execution update compiles and runs the input, testing the - /// stdout against the expected results - /// This is a slice containing the expected message. - Execution: []const u8, - /// A header update compiles the input with the equivalent of - /// `-femit-h` and tests the produced header against the - /// expected result - Header: []const u8, - }, - }; - - pub const File = struct { - /// Contents of the importable file. Doesn't yet support incremental updates. - src: [:0]const u8, - path: []const u8, - }; - - pub const DepModule = struct { - name: []const u8, - path: []const u8, - }; - - pub const Backend = enum { - stage1, - stage2, - llvm, - }; - - /// 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 { - /// The name of the test case. This is shown if a test fails, and - /// otherwise ignored. - name: []const u8, - /// The platform the test targets. For non-native platforms, an emulator - /// such as QEMU is required for tests to complete. - target: CrossTarget, - /// In order to be able to run e.g. Execution updates, this must be set - /// to Executable. - output_mode: std.builtin.OutputMode, - optimize_mode: std.builtin.Mode = .Debug, - updates: std.ArrayList(Update), - emit_h: bool = false, - is_test: bool = false, - expect_exact: bool = false, - backend: Backend = .stage2, - link_libc: bool = false, - - files: std.ArrayList(File), - deps: std.ArrayList(DepModule), - - result: anyerror!void = {}, - - pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void { - case.files.append(.{ .path = name, .src = src }) catch @panic("out of memory"); - } - - pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void { - case.deps.append(.{ - .name = name, - .path = path, - }) catch @panic("out of memory"); - } - - /// 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, - .name = "update", - .case = .{ .Header = result }, - }) catch @panic("out of memory"); - } - - /// Adds a subcase in which the module is updated with `src`, compiled, - /// run, and the output is tested against `result`. - pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { - self.updates.append(.{ - .src = src, - .name = "update", - .case = .{ .Execution = result }, - }) catch @panic("out of memory"); - } - - /// 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, - .name = "update", - .case = .{ .CompareObjectFile = result }, - }) catch @panic("out of memory"); - } - - pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { - return self.addErrorNamed("update", src, errors); - } - - /// 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 - /// the form `:line:column: error: message`. - pub fn addErrorNamed( - self: *Case, - name: []const u8, - src: [:0]const u8, - errors: []const []const u8, - ) void { - var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch @panic("out of memory"); - for (errors, 0..) |err_msg_line, i| { - if (std.mem.startsWith(u8, err_msg_line, "error: ")) { - array[i] = .{ - .plain = .{ - .msg = err_msg_line["error: ".len..], - .kind = .@"error", - .count = 1, - }, - }; - continue; - } else if (std.mem.startsWith(u8, err_msg_line, "note: ")) { - array[i] = .{ - .plain = .{ - .msg = err_msg_line["note: ".len..], - .kind = .note, - .count = 1, - }, - }; - continue; - } - // example: "file.zig:1:2: error: bad thing happened" - var it = std.mem.split(u8, err_msg_line, ":"); - const src_path = it.first(); - const line_text = it.next() orelse @panic("missing line"); - const col_text = it.next() orelse @panic("missing column"); - const kind_text = it.next() orelse @panic("missing 'error'/'note'"); - var msg = it.rest()[1..]; // skip over the space at end of "error: " - - const line: ?u32 = if (std.mem.eql(u8, line_text, "?")) - null - else - std.fmt.parseInt(u32, line_text, 10) catch @panic("bad line number"); - const column: ?u32 = if (std.mem.eql(u8, line_text, "?")) - null - else - std.fmt.parseInt(u32, col_text, 10) catch @panic("bad column number"); - const kind: ErrorMsg.Kind = if (std.mem.eql(u8, kind_text, " error")) - .@"error" - else if (std.mem.eql(u8, kind_text, " note")) - .note - else - @panic("expected 'error'/'note'"); - - const line_0based: u32 = if (line) |n| blk: { - if (n == 0) { - print("{s}: line must be specified starting at one\n", .{self.name}); - return; - } - break :blk n - 1; - } else std.math.maxInt(u32); - - const column_0based: u32 = if (column) |n| blk: { - if (n == 0) { - print("{s}: line must be specified starting at one\n", .{self.name}); - return; - } - break :blk n - 1; - } else std.math.maxInt(u32); - - const suffix = " times)"; - const count = if (std.mem.endsWith(u8, msg, suffix)) count: { - const lparen = std.mem.lastIndexOfScalar(u8, msg, '(').?; - const count = std.fmt.parseInt(u32, msg[lparen + 1 .. msg.len - suffix.len], 10) catch @panic("bad error note count number"); - msg = msg[0 .. lparen - 1]; - break :count count; - } else 1; - - array[i] = .{ - .src = .{ - .src_path = src_path, - .msg = msg, - .line = line_0based, - .column = column_0based, - .kind = kind, - .count = count, - }, - }; - } - self.updates.append(.{ - .src = src, - .name = name, - .case = .{ .Error = array }, - }) catch @panic("out of memory"); - } - - /// Adds a subcase in which the module is updated with `src`, and - /// asserts that it compiles without issue - pub fn compiles(self: *Case, src: [:0]const u8) void { - self.addError(src, &[_][]const u8{}); - } - }; - - pub fn addExe( - 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, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - 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: CrossTarget) *Case { - return ctx.addExe(name, target); - } - - pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - const prefixed_name = std.fmt.allocPrint(ctx.arena, "CBE: {s}", .{name}) catch - @panic("out of memory"); - var target_adjusted = target; - target_adjusted.ofmt = std.Target.ObjectFormat.c; - ctx.cases.append(Case{ - .name = prefixed_name, - .target = target_adjusted, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Exe, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - .link_libc = true, - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - /// Adds a test case that uses the LLVM backend to emit an executable. - /// Currently this implies linking libc, because only then we can generate a testable executable. - pub fn exeUsingLlvmBackend(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, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - .backend = .llvm, - .link_libc = true, - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - pub fn addObj( - 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 = .Obj, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - pub fn addTest( - 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, - .is_test = true, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - 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: CrossTarget) *Case { - return ctx.addObj(name, target); - } - - /// 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); - } - - /// Adds a test case for Zig or ZIR input, producing C code. - pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - var target_adjusted = target; - target_adjusted.ofmt = std.Target.ObjectFormat.c; - ctx.cases.append(Case{ - .name = name, - .target = target_adjusted, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Obj, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target).addCompareObjectFile(src, zig_h ++ 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).addHeader(src, zig_h ++ out); - } - - pub fn objErrStage1( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - const case = ctx.addObj(name, .{}); - case.backend = .stage1; - case.addError(src, expected_errors); - } - - pub fn testErrStage1( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - const case = ctx.addTest(name, .{}); - case.backend = .stage1; - case.addError(src, expected_errors); - } - - pub fn exeErrStage1( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - const case = ctx.addExe(name, .{}); - case.backend = .stage1; - case.addError(src, expected_errors); - } - - pub fn addCompareOutput( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout); - } - - /// Adds a test case that compiles the Zig source given in `src`, executes - /// it, runs it, and tests the output against `expected_stdout` - pub fn compareOutput( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - return ctx.addCompareOutput(name, src, expected_stdout); - } - - /// Adds a test case that compiles the ZIR source given in `src`, executes - /// it, runs it, and tests the output against `expected_stdout` - pub fn compareOutputZIR( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - ctx.addCompareOutput(name, .ZIR, src, expected_stdout); - } - - pub fn addTransform( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - result: [:0]const u8, - ) void { - ctx.addObj(name, target).addTransform(src, result); - } - - /// Adds a test case that compiles the Zig given in `src` to ZIR and tests - /// the ZIR against `result` - pub fn transform( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - result: [:0]const u8, - ) void { - ctx.addTransform(name, target, src, result); - } - - pub fn addError( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - ctx.addObj(name, target).addError(src, expected_errors); - } - - /// Adds a test case that ensures that the Zig given in `src` fails to - /// compile for the expected reasons, given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`. - pub fn compileError( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - ctx.addError(name, target, src, expected_errors); - } - - /// Adds a test case that ensures that the ZIR given in `src` fails to - /// compile for the expected reasons, given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`. - pub fn compileErrorZIR( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - ctx.addError(name, target, .ZIR, src, expected_errors); - } - - pub fn addCompiles( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - ) void { - ctx.addObj(name, target).compiles(src); - } - - /// Adds a test case that asserts that the Zig given in `src` compiles - /// without any errors. - pub fn compiles( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - ) void { - ctx.addCompiles(name, target, src); - } - - /// Adds a test case that asserts that the ZIR given in `src` compiles - /// without any errors. - pub fn compilesZIR( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - ) void { - ctx.addCompiles(name, target, .ZIR, src); - } - - /// Adds a test case that first ensures that the Zig given in `src` fails - /// to compile for the reasons given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`, then - /// asserts that fixing the source (updating with `fixed_src`) isn't broken - /// by incremental compilation. - pub fn incrementalFailure( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - fixed_src: [:0]const u8, - ) void { - var case = ctx.addObj(name, target); - case.addError(src, expected_errors); - case.compiles(fixed_src); - } - - /// Adds a test case that first ensures that the ZIR given in `src` fails - /// to compile for the reasons given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`, then - /// asserts that fixing the source (updating with `fixed_src`) isn't broken - /// by incremental compilation. - pub fn incrementalFailureZIR( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - fixed_src: [:0]const u8, - ) void { - var case = ctx.addObj(name, target, .ZIR); - case.addError(src, expected_errors); - case.compiles(fixed_src); - } - - /// Adds a test for each file in the provided directory. - /// Testing strategy (TestStrategy) is inferred automatically from filenames. - /// Recurses nested directories. - /// - /// Each file should include a test manifest as a contiguous block of comments at - /// the end of the file. The first line should be the test type, followed by a set of - /// key-value config values, followed by a blank line, then the expected output. - pub fn addTestCasesFromDir(ctx: *TestContext, dir: std.fs.IterableDir) void { - var current_file: []const u8 = "none"; - ctx.addTestCasesFromDirInner(dir, ¤t_file) catch |err| { - std.debug.panic("test harness failed to process file '{s}': {s}\n", .{ - current_file, @errorName(err), - }); - }; - } - - fn addTestCasesFromDirInner( - ctx: *TestContext, - iterable_dir: std.fs.IterableDir, - /// This is kept up to date with the currently being processed file so - /// that if any errors occur the caller knows it happened during this file. - current_file: *[]const u8, - ) !void { - var it = try iterable_dir.walk(ctx.arena); - var filenames = std.ArrayList([]const u8).init(ctx.arena); - - while (try it.next()) |entry| { - if (entry.kind != .File) continue; - - // Ignore stuff such as .swp files - switch (Compilation.classifyFileExt(entry.basename)) { - .unknown => continue, - else => {}, - } - try filenames.append(try ctx.arena.dupe(u8, entry.path)); - } - - // Sort filenames, so that incremental tests are contiguous and in-order - sortTestFilenames(filenames.items); - - var test_it = TestIterator{ .filenames = filenames.items }; - while (test_it.next()) |maybe_batch| { - const batch = maybe_batch orelse break; - const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent; - var cases = std.ArrayList(usize).init(ctx.arena); - - for (batch) |filename| { - current_file.* = filename; - - const max_file_size = 10 * 1024 * 1024; - const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0); - - // Parse the manifest - var manifest = try TestManifest.parse(ctx.arena, src); - - if (cases.items.len == 0) { - const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend); - const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget); - const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); - const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); - - const name_prefix = blk: { - const ext_index = std.mem.lastIndexOfScalar(u8, current_file.*, '.') orelse - return error.InvalidFilename; - const index = std.mem.lastIndexOfScalar(u8, current_file.*[0..ext_index], '.') orelse ext_index; - break :blk current_file.*[0..index]; - }; - - // Cross-product to get all possible test combinations - for (backends) |backend| { - for (targets) |target| { - const name = try std.fmt.allocPrint(ctx.arena, "{s} ({s}, {s})", .{ - name_prefix, - @tagName(backend), - try target.zigTriple(ctx.arena), - }); - const next = ctx.cases.items.len; - try ctx.cases.append(.{ - .name = name, - .target = target, - .backend = backend, - .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), - .is_test = is_test, - .output_mode = output_mode, - .link_libc = backend == .llvm, - .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), - .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), - }); - try cases.append(next); - } - } - } - - for (cases.items) |case_index| { - const case = &ctx.cases.items[case_index]; - switch (manifest.type) { - .@"error" => { - const errors = try manifest.trailingAlloc(ctx.arena); - switch (strategy) { - .independent => { - case.addError(src, errors); - }, - .incremental => { - case.addErrorNamed("update", src, errors); - }, - } - }, - .run => { - var output = std.ArrayList(u8).init(ctx.arena); - var trailing_it = manifest.trailing(); - while (trailing_it.next()) |line| { - try output.appendSlice(line); - try output.append('\n'); - } - if (output.items.len > 0) { - try output.resize(output.items.len - 1); - } - case.addCompareOutput(src, try output.toOwnedSlice()); - }, - .cli => @panic("TODO cli tests"), - } - } - } - } else |err| { - // make sure the current file is set to the file that produced an error - current_file.* = test_it.currentFilename(); - return err; - } - } - - fn init(gpa: Allocator, arena: Allocator) TestContext { - return .{ - .gpa = gpa, - .cases = std.ArrayList(Case).init(gpa), - .arena = arena, - }; - } - - fn deinit(self: *TestContext) void { - for (self.cases.items) |case| { - for (case.updates.items) |u| { - if (u.case == .Error) { - case.updates.allocator.free(u.case.Error); - } - } - case.updates.deinit(); - } - self.cases.deinit(); - self.* = undefined; - } - - fn run(self: *TestContext) !void { - const host = try std.zig.system.NativeTargetInfo.detect(.{}); - const zig_exe_path = try std.process.getEnvVarOwned(self.arena, "ZIG_EXE"); - - var progress = std.Progress{}; - const root_node = progress.start("compiler", self.cases.items.len); - defer root_node.end(); - - var zig_lib_directory = try introspect.findZigLibDir(self.gpa); - defer zig_lib_directory.handle.close(); - defer self.gpa.free(zig_lib_directory.path.?); - - var aux_thread_pool: ThreadPool = undefined; - try aux_thread_pool.init(.{ .allocator = self.gpa }); - defer aux_thread_pool.deinit(); - - // Use the same global cache dir for all the tests, such that we for example don't have to - // rebuild musl libc for every case (when LLVM backend is enabled). - var global_tmp = std.testing.tmpDir(.{}); - defer global_tmp.cleanup(); - - var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{}); - defer cache_dir.close(); - const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path }); - defer self.gpa.free(tmp_dir_path); - - const global_cache_directory: Compilation.Directory = .{ - .handle = cache_dir, - .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }), - }; - defer self.gpa.free(global_cache_directory.path.?); - - { - for (self.cases.items) |*case| { - if (build_options.skip_non_native) { - if (case.target.getCpuArch() != builtin.cpu.arch) - continue; - if (case.target.getObjectFormat() != builtin.object_format) - continue; - } - - // Skip tests that require LLVM backend when it is not available - if (!build_options.have_llvm and case.backend == .llvm) - continue; - - if (skip_stage1 and case.backend == .stage1) - continue; - - if (build_options.test_filter) |test_filter| { - if (std.mem.indexOf(u8, case.name, test_filter) == null) continue; - } - - var prg_node = root_node.start(case.name, case.updates.items.len); - prg_node.activate(); - defer prg_node.end(); - - case.result = runOneCase( - self.gpa, - &prg_node, - case.*, - zig_lib_directory, - zig_exe_path, - &aux_thread_pool, - global_cache_directory, - host, - ); - } - } - - var fail_count: usize = 0; - for (self.cases.items) |*case| { - case.result catch |err| { - fail_count += 1; - print("{s} failed: {s}\n", .{ case.name, @errorName(err) }); - }; - } - - if (fail_count != 0) { - print("{d} tests failed\n", .{fail_count}); - return error.TestFailed; - } - } - - fn runOneCase( - allocator: Allocator, - root_node: *std.Progress.Node, - case: Case, - zig_lib_directory: Compilation.Directory, - zig_exe_path: []const u8, - thread_pool: *ThreadPool, - global_cache_directory: Compilation.Directory, - host: std.zig.system.NativeTargetInfo, - ) !void { - const target_info = try std.zig.system.NativeTargetInfo.detect(case.target); - const target = target_info.target; - - var arena_allocator = std.heap.ArenaAllocator.init(allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); - defer cache_dir.close(); - - const tmp_dir_path = try std.fs.path.join( - arena, - &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path }, - ); - const tmp_dir_path_plus_slash = try std.fmt.allocPrint( - arena, - "{s}" ++ std.fs.path.sep_str, - .{tmp_dir_path}, - ); - const local_cache_path = try std.fs.path.join( - arena, - &[_][]const u8{ tmp_dir_path, "zig-cache" }, - ); - - for (case.files.items) |file| { - try tmp.dir.writeFile(file.path, file.src); - } - - if (case.backend == .stage1) { - // stage1 backend has limitations: - // * leaks memory - // * calls exit() when a compile error happens - // * cannot handle updates - // because of this we must spawn a child process rather than - // using Compilation directly. - - if (!std.process.can_spawn) { - print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)}); - return; // Pass test. - } - - assert(case.updates.items.len == 1); - const update = case.updates.items[0]; - try tmp.dir.writeFile(tmp_src_path, update.src); - - var zig_args = std.ArrayList([]const u8).init(arena); - try zig_args.append(zig_exe_path); - - if (case.is_test) { - try zig_args.append("test"); - } else if (update.case == .Execution) { - try zig_args.append("run"); - } else switch (case.output_mode) { - .Obj => try zig_args.append("build-obj"), - .Exe => try zig_args.append("build-exe"), - .Lib => try zig_args.append("build-lib"), - } - - try zig_args.append(try std.fs.path.join(arena, &.{ tmp_dir_path, tmp_src_path })); - - try zig_args.append("--name"); - try zig_args.append("test"); - - try zig_args.append("--cache-dir"); - try zig_args.append(local_cache_path); - - try zig_args.append("--global-cache-dir"); - try zig_args.append(global_cache_directory.path orelse "."); - - if (!case.target.isNative()) { - try zig_args.append("-target"); - try zig_args.append(try target.zigTriple(arena)); - } - - try zig_args.append("-O"); - try zig_args.append(@tagName(case.optimize_mode)); - - // Prevent sub-process progress bar from interfering with the - // one in this parent process. - try zig_args.append("--color"); - try zig_args.append("off"); - - const result = try std.ChildProcess.exec(.{ - .allocator = arena, - .argv = zig_args.items, - }); - switch (update.case) { - .Error => |case_error_list| { - switch (result.term) { - .Exited => |code| { - if (code == 0) { - dumpArgs(zig_args.items); - return error.CompilationIncorrectlySucceeded; - } - }, - else => { - std.debug.print("{s}", .{result.stderr}); - dumpArgs(zig_args.items); - return error.CompilationCrashed; - }, - } - var ok = true; - if (case.expect_exact) { - var err_iter = std.mem.split(u8, result.stderr, "\n"); - var i: usize = 0; - ok = while (err_iter.next()) |line| : (i += 1) { - if (i >= case_error_list.len) break false; - const expected = try std.mem.replaceOwned( - u8, - arena, - try std.fmt.allocPrint(arena, "{s}", .{case_error_list[i]}), - "${DIR}", - tmp_dir_path_plus_slash, - ); - - if (std.mem.indexOf(u8, line, expected) == null) break false; - continue; - } else true; - - ok = ok and i == case_error_list.len; - - if (!ok) { - print("\n======== Expected these compile errors: ========\n", .{}); - for (case_error_list) |msg| { - const expected = try std.fmt.allocPrint(arena, "{s}", .{msg}); - print("{s}\n", .{expected}); - } - } - } else { - for (case_error_list) |msg| { - const expected = try std.mem.replaceOwned( - u8, - arena, - try std.fmt.allocPrint(arena, "{s}", .{msg}), - "${DIR}", - tmp_dir_path_plus_slash, - ); - if (std.mem.indexOf(u8, result.stderr, expected) == null) { - print( - \\ - \\=========== Expected compile error: ============ - \\{s} - \\ - , .{expected}); - ok = false; - break; - } - } - } - - if (!ok) { - print( - \\================= Full output: ================= - \\{s} - \\================================================ - \\ - , .{result.stderr}); - return error.TestFailed; - } - }, - .CompareObjectFile => @panic("TODO implement in the test harness"), - .Execution => |expected_stdout| { - switch (result.term) { - .Exited => |code| { - if (code != 0) { - std.debug.print("{s}", .{result.stderr}); - dumpArgs(zig_args.items); - return error.CompilationFailed; - } - }, - else => { - std.debug.print("{s}", .{result.stderr}); - dumpArgs(zig_args.items); - return error.CompilationCrashed; - }, - } - try std.testing.expectEqualStrings("", result.stderr); - try std.testing.expectEqualStrings(expected_stdout, result.stdout); - }, - .Header => @panic("TODO implement in the test harness"), - } - return; - } - - const zig_cache_directory: Compilation.Directory = .{ - .handle = cache_dir, - .path = local_cache_path, - }; - - var main_pkg: Package = .{ - .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir }, - .root_src_path = tmp_src_path, - }; - defer { - var it = main_pkg.table.iterator(); - while (it.next()) |kv| { - allocator.free(kv.key_ptr.*); - kv.value_ptr.*.destroy(allocator); - } - main_pkg.table.deinit(allocator); - } - - for (case.deps.items) |dep| { - var pkg = try Package.create( - allocator, - tmp_dir_path, - dep.path, - ); - errdefer pkg.destroy(allocator); - try main_pkg.add(allocator, dep.name, pkg); - } - - const bin_name = try std.zig.binNameAlloc(arena, .{ - .root_name = "test_case", - .target = target, - .output_mode = case.output_mode, - }); - - const emit_directory: Compilation.Directory = .{ - .path = tmp_dir_path, - .handle = tmp.dir, - }; - const emit_bin: Compilation.EmitLoc = .{ - .directory = emit_directory, - .basename = bin_name, - }; - const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{ - .directory = emit_directory, - .basename = "test_case.h", - } else null; - const use_llvm: bool = switch (case.backend) { - .llvm => true, - else => false, - }; - const comp = try Compilation.create(allocator, .{ - .local_cache_directory = zig_cache_directory, - .global_cache_directory = global_cache_directory, - .zig_lib_directory = zig_lib_directory, - .thread_pool = thread_pool, - .root_name = "test_case", - .target = target, - // TODO: support tests for object file building, and library builds - // and linking. This will require a rework to support multi-file - // tests. - .output_mode = case.output_mode, - .is_test = case.is_test, - .optimize_mode = case.optimize_mode, - .emit_bin = emit_bin, - .emit_h = emit_h, - .main_pkg = &main_pkg, - .keep_source_files_loaded = true, - .is_native_os = case.target.isNativeOs(), - .is_native_abi = case.target.isNativeAbi(), - .dynamic_linker = target_info.dynamic_linker.get(), - .link_libc = case.link_libc, - .use_llvm = use_llvm, - .self_exe_path = zig_exe_path, - // TODO instead of turning off color, pass in a std.Progress.Node - .color = .off, - .reference_trace = 0, - // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in - // until the auto-select mechanism deems them worthy - .use_lld = switch (case.backend) { - .stage2 => false, - else => null, - }, - }); - defer comp.destroy(); - - update: for (case.updates.items, 0..) |update, update_index| { - var update_node = root_node.start(update.name, 3); - update_node.activate(); - defer update_node.end(); - - var sync_node = update_node.start("write", 0); - sync_node.activate(); - try tmp.dir.writeFile(tmp_src_path, update.src); - sync_node.end(); - - var module_node = update_node.start("parse/analysis/codegen", 0); - module_node.activate(); - try comp.makeBinFileWritable(); - try comp.update(&module_node); - module_node.end(); - - if (update.case != .Error) { - var all_errors = try comp.getAllErrorsAlloc(); - defer all_errors.deinit(allocator); - if (all_errors.errorMessageCount() > 0) { - all_errors.renderToStdErr(std.debug.detectTTYConfig(std.io.getStdErr())); - // TODO print generated C code - return error.UnexpectedCompileErrors; - } - } - - switch (update.case) { - .Header => |expected_output| { - var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only }); - defer file.close(); - const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); - - try std.testing.expectEqualStrings(expected_output, out); - }, - .CompareObjectFile => |expected_output| { - var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only }); - defer file.close(); - const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); - - try std.testing.expectEqualStrings(expected_output, out); - }, - .Error => |case_error_list| { - var test_node = update_node.start("assert", 0); - test_node.activate(); - defer test_node.end(); - - const handled_errors = try arena.alloc(bool, case_error_list.len); - std.mem.set(bool, handled_errors, false); - - var actual_errors = try comp.getAllErrorsAlloc(); - defer actual_errors.deinit(allocator); - - var any_failed = false; - var notes_to_check = std.ArrayList(*const Compilation.AllErrors.Message).init(allocator); - defer notes_to_check.deinit(); - - for (actual_errors.list) |actual_error| { - for (case_error_list, 0..) |case_msg, i| { - if (handled_errors[i]) continue; - - const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg; - switch (actual_error) { - .src => |actual_msg| { - for (actual_msg.notes) |*note| { - try notes_to_check.append(note); - } - - if (ex_tag != .src) continue; - - const src_path_ok = case_msg.src.src_path.len == 0 or - std.mem.eql(u8, case_msg.src.src_path, actual_msg.src_path); - - const expected_msg = try std.mem.replaceOwned( - u8, - arena, - case_msg.src.msg, - "${DIR}", - tmp_dir_path_plus_slash, - ); - - var buf: [1024]u8 = undefined; - const rendered_msg = blk: { - var msg: Compilation.AllErrors.Message = actual_error; - msg.src.src_path = case_msg.src.src_path; - msg.src.notes = &.{}; - msg.src.source_line = null; - var fib = std.io.fixedBufferStream(&buf); - try msg.renderToWriter(.no_color, fib.writer(), "error", .Red, 0); - var it = std.mem.split(u8, fib.getWritten(), "error: "); - _ = it.first(); - const rendered = it.rest(); - break :blk rendered[0 .. rendered.len - 1]; // trim final newline - }; - - if (src_path_ok and - (case_msg.src.line == std.math.maxInt(u32) or - actual_msg.line == case_msg.src.line) and - (case_msg.src.column == std.math.maxInt(u32) or - actual_msg.column == case_msg.src.column) and - std.mem.eql(u8, expected_msg, rendered_msg) and - case_msg.src.kind == .@"error" and - actual_msg.count == case_msg.src.count) - { - handled_errors[i] = true; - break; - } - }, - .plain => |plain| { - if (ex_tag != .plain) continue; - - if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and - case_msg.plain.kind == .@"error" and - case_msg.plain.count == plain.count) - { - handled_errors[i] = true; - break; - } - }, - } - } else { - print( - "\nUnexpected error:\n{s}\n{}\n{s}", - .{ hr, ErrorMsg.init(actual_error, .@"error"), hr }, - ); - any_failed = true; - } - } - while (notes_to_check.popOrNull()) |note| { - for (case_error_list, 0..) |case_msg, i| { - const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg; - switch (note.*) { - .src => |actual_msg| { - for (actual_msg.notes) |*sub_note| { - try notes_to_check.append(sub_note); - } - if (ex_tag != .src) continue; - - const expected_msg = try std.mem.replaceOwned( - u8, - arena, - case_msg.src.msg, - "${DIR}", - tmp_dir_path_plus_slash, - ); - - if ((case_msg.src.line == std.math.maxInt(u32) or - actual_msg.line == case_msg.src.line) and - (case_msg.src.column == std.math.maxInt(u32) or - actual_msg.column == case_msg.src.column) and - std.mem.eql(u8, expected_msg, actual_msg.msg) and - case_msg.src.kind == .note and - actual_msg.count == case_msg.src.count) - { - handled_errors[i] = true; - break; - } - }, - .plain => |plain| { - if (ex_tag != .plain) continue; - - if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and - case_msg.plain.kind == .note and - case_msg.plain.count == plain.count) - { - handled_errors[i] = true; - break; - } - }, - } - } else { - print( - "\nUnexpected note:\n{s}\n{}\n{s}", - .{ hr, ErrorMsg.init(note.*, .note), hr }, - ); - any_failed = true; - } - } - - for (handled_errors, 0..) |handled, i| { - if (!handled) { - print( - "\nExpected error not found:\n{s}\n{}\n{s}", - .{ hr, case_error_list[i], hr }, - ); - any_failed = true; - } - } - - if (any_failed) { - print("\nupdate_index={d}\n", .{update_index}); - return error.WrongCompileErrors; - } - }, - .Execution => |expected_stdout| { - if (!std.process.can_spawn) { - print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)}); - continue :update; // Pass test. - } - - update_node.setEstimatedTotalItems(4); - - var argv = std.ArrayList([]const u8).init(allocator); - defer argv.deinit(); - - var exec_result = x: { - var exec_node = update_node.start("execute", 0); - exec_node.activate(); - defer exec_node.end(); - - // We go out of our way here to use the unique temporary directory name in - // the exe_path so that it makes its way into the cache hash, avoiding - // cache collisions from multiple threads doing `zig run` at the same time - // on the same test_case.c input filename. - const ss = std.fs.path.sep_str; - const exe_path = try std.fmt.allocPrint( - arena, - ".." ++ ss ++ "{s}" ++ ss ++ "{s}", - .{ &tmp.sub_path, bin_name }, - ); - if (case.target.ofmt != null and case.target.ofmt.? == .c) { - if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) { - // We wouldn't be able to run the compiled C code. - continue :update; // Pass test. - } - try argv.appendSlice(&[_][]const u8{ - zig_exe_path, - "run", - "-cflags", - "-std=c99", - "-pedantic", - "-Werror", - "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875 - "--", - "-lc", - exe_path, - }); - if (zig_lib_directory.path) |p| { - try argv.appendSlice(&.{ "-I", p }); - } - } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) { - .native => { - if (case.backend == .stage2 and case.target.getCpuArch() == .arm) { - // https://github.com/ziglang/zig/issues/13623 - continue :update; // Pass test. - } - try argv.append(exe_path); - }, - .bad_dl, .bad_os_or_cpu => continue :update, // Pass test. - - .rosetta => if (enable_rosetta) { - try argv.append(exe_path); - } else { - continue :update; // Rosetta not available, pass test. - }, - - .qemu => |qemu_bin_name| if (enable_qemu) { - const need_cross_glibc = target.isGnuLibC() and case.link_libc; - const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc) - glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test - else - null; - try argv.append(qemu_bin_name); - if (glibc_dir_arg) |dir| { - const linux_triple = try target.linuxTriple(arena); - const full_dir = try std.fs.path.join(arena, &[_][]const u8{ - dir, - linux_triple, - }); - - try argv.append("-L"); - try argv.append(full_dir); - } - try argv.append(exe_path); - } else { - continue :update; // QEMU not available; pass test. - }, - - .wine => |wine_bin_name| if (enable_wine) { - try argv.append(wine_bin_name); - try argv.append(exe_path); - } else { - continue :update; // Wine not available; pass test. - }, - - .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) { - try argv.append(wasmtime_bin_name); - try argv.append("--dir=."); - try argv.append(exe_path); - } else { - continue :update; // wasmtime not available; pass test. - }, - - .darling => |darling_bin_name| if (enable_darling) { - try argv.append(darling_bin_name); - // Since we use relative to cwd here, we invoke darling with - // "shell" subcommand. - try argv.append("shell"); - try argv.append(exe_path); - } else { - continue :update; // Darling not available; pass test. - }, - } - - try comp.makeBinFileExecutable(); - - while (true) { - break :x std.ChildProcess.exec(.{ - .allocator = allocator, - .argv = argv.items, - .cwd_dir = tmp.dir, - .cwd = tmp_dir_path, - }) catch |err| switch (err) { - error.FileBusy => { - // There is a fundamental design flaw in Unix systems with how - // ETXTBSY interacts with fork+exec. - // https://github.com/golang/go/issues/22315 - // https://bugs.openjdk.org/browse/JDK-8068370 - // Unfortunately, this could be a real error, but we can't - // tell the difference here. - continue; - }, - else => { - print("\n{s}.{d} The following command failed with {s}:\n", .{ - case.name, update_index, @errorName(err), - }); - dumpArgs(argv.items); - return error.ChildProcessExecution; - }, - }; - } - }; - var test_node = update_node.start("test", 0); - test_node.activate(); - defer test_node.end(); - defer allocator.free(exec_result.stdout); - defer allocator.free(exec_result.stderr); - switch (exec_result.term) { - .Exited => |code| { - if (code != 0) { - print("\n{s}\n{s}: execution exited with code {d}:\n", .{ - exec_result.stderr, case.name, code, - }); - dumpArgs(argv.items); - return error.ChildProcessExecution; - } - }, - else => { - print("\n{s}\n{s}: execution crashed:\n", .{ - exec_result.stderr, case.name, - }); - dumpArgs(argv.items); - return error.ChildProcessExecution; - }, - } - try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout); - // We allow stderr to have garbage in it because wasmtime prints a - // warning about --invoke even though we don't pass it. - //std.testing.expectEqualStrings("", exec_result.stderr); - }, - } - } - } -}; - -fn dumpArgs(argv: []const []const u8) void { - for (argv) |arg| { - print("{s} ", .{arg}); - } - print("\n", .{}); -} - -const tmp_src_path = "tmp.zig"; diff --git a/test/cases.zig b/test/cases.zig index 412b4cb5e2..ffe046c70e 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1,8 +1,8 @@ const std = @import("std"); -const TestContext = @import("../src/test.zig").TestContext; +const Cases = @import("src/Cases.zig"); -pub fn addCases(ctx: *TestContext) !void { - try @import("compile_errors.zig").addCases(ctx); - try @import("stage2/cbe.zig").addCases(ctx); - try @import("stage2/nvptx.zig").addCases(ctx); +pub fn addCases(cases: *Cases) !void { + try @import("compile_errors.zig").addCases(cases); + try @import("cbe.zig").addCases(cases); + try @import("nvptx.zig").addCases(cases); } diff --git a/test/cases/compile_errors/access_inactive_union_field_comptime.zig b/test/cases/compile_errors/access_inactive_union_field_comptime.zig index d990a85f9e..2098b19d14 100644 --- a/test/cases/compile_errors/access_inactive_union_field_comptime.zig +++ b/test/cases/compile_errors/access_inactive_union_field_comptime.zig @@ -21,3 +21,4 @@ pub export fn entry1() void { // :9:15: error: access of union field 'a' while field 'b' is active // :2:21: note: union declared here // :14:16: error: access of union field 'a' while field 'b' is active +// :2:21: note: union declared here diff --git a/test/cases/compile_errors/bad_import.zig b/test/cases/compile_errors/bad_import.zig index 49e78a4be4..e624d7104c 100644 --- a/test/cases/compile_errors/bad_import.zig +++ b/test/cases/compile_errors/bad_import.zig @@ -4,4 +4,4 @@ const bogus = @import("bogus-does-not-exist.zig",); // backend=stage2 // target=native // -// :1:23: error: unable to load '${DIR}bogus-does-not-exist.zig': FileNotFound +// bogus-does-not-exist.zig': FileNotFound diff --git a/test/cases/compile_errors/condition_comptime_reason_explained.zig b/test/cases/compile_errors/condition_comptime_reason_explained.zig index 332ae8afc8..d0193986a8 100644 --- a/test/cases/compile_errors/condition_comptime_reason_explained.zig +++ b/test/cases/compile_errors/condition_comptime_reason_explained.zig @@ -45,4 +45,6 @@ pub export fn entry2() void { // :22:13: error: unable to resolve comptime value // :22:13: note: condition in comptime switch must be comptime-known // :21:17: note: expression is evaluated at comptime because the function returns a comptime-only type 'tmp.S' +// :2:12: note: struct requires comptime because of this field +// :2:12: note: use '*const fn() void' for a function pointer type // :32:19: note: called from here diff --git a/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig b/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig index 2a64326093..ace90bccfc 100644 --- a/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig +++ b/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig @@ -32,6 +32,7 @@ export fn d() void { // :3:8: error: opaque types have unknown size and therefore cannot be directly embedded in structs // :1:11: note: opaque declared here // :7:10: error: opaque types have unknown size and therefore cannot be directly embedded in unions +// :1:11: note: opaque declared here // :19:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs // :18:22: note: opaque declared here // :24:23: error: opaque types have unknown size and therefore cannot be directly embedded in structs diff --git a/test/cases/compile_errors/extern_function_with_comptime_parameter.zig b/test/cases/compile_errors/extern_function_with_comptime_parameter.zig index de69fa409f..58f15f7fab 100644 --- a/test/cases/compile_errors/extern_function_with_comptime_parameter.zig +++ b/test/cases/compile_errors/extern_function_with_comptime_parameter.zig @@ -12,6 +12,6 @@ comptime { _ = entry2; } // backend=stage2 // target=native // -// :1:15: error: comptime parameters not allowed in function with calling convention 'C' // :5:30: error: comptime parameters not allowed in function with calling convention 'C' // :6:30: error: generic parameters not allowed in function with calling convention 'C' +// :1:15: error: comptime parameters not allowed in function with calling convention 'C' diff --git a/test/cases/compile_errors/function_parameter_is_opaque.zig b/test/cases/compile_errors/function_parameter_is_opaque.zig index 1f92274577..57c89bd7f4 100644 --- a/test/cases/compile_errors/function_parameter_is_opaque.zig +++ b/test/cases/compile_errors/function_parameter_is_opaque.zig @@ -27,4 +27,5 @@ export fn entry4() void { // :1:17: note: opaque declared here // :8:28: error: parameter of type '@TypeOf(null)' not allowed // :12:8: error: parameter of opaque type 'tmp.FooType' not allowed +// :1:17: note: opaque declared here // :17:8: error: parameter of type '@TypeOf(null)' not allowed diff --git a/test/cases/compile_errors/helpful_return_type_error_message.zig b/test/cases/compile_errors/helpful_return_type_error_message.zig index 871e948537..83342c7ec3 100644 --- a/test/cases/compile_errors/helpful_return_type_error_message.zig +++ b/test/cases/compile_errors/helpful_return_type_error_message.zig @@ -24,9 +24,9 @@ export fn quux() u32 { // :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set' // :7:17: note: function cannot return an error // :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' -// :10:17: note: function cannot return an error // :11:15: note: cannot convert error union to payload type // :11:15: note: consider using 'try', 'catch', or 'if' +// :10:17: note: function cannot return an error // :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' // :15:14: note: cannot convert error union to payload type // :15:14: note: consider using 'try', 'catch', or 'if' diff --git a/test/cases/compile_errors/implicit_semicolon-block_expr.zig b/test/cases/compile_errors/implicit_semicolon-block_expr.zig index 7dd82b897b..bab8ec29c0 100644 --- a/test/cases/compile_errors/implicit_semicolon-block_expr.zig +++ b/test/cases/compile_errors/implicit_semicolon-block_expr.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-block_statement.zig b/test/cases/compile_errors/implicit_semicolon-block_statement.zig index 189ba84d98..912ccbc790 100644 --- a/test/cases/compile_errors/implicit_semicolon-block_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-block_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig b/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig index decbc352e8..e8dc8bb534 100644 --- a/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = comptime {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig b/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig index d17db15924..afc1798669 100644 --- a/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; comptime ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-defer.zig b/test/cases/compile_errors/implicit_semicolon-defer.zig index 57fd3a2626..e91dbae7f8 100644 --- a/test/cases/compile_errors/implicit_semicolon-defer.zig +++ b/test/cases/compile_errors/implicit_semicolon-defer.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; defer ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-for_expression.zig b/test/cases/compile_errors/implicit_semicolon-for_expression.zig index c751384e11..1fbe4dd3ad 100644 --- a/test/cases/compile_errors/implicit_semicolon-for_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-for_expression.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; _ = for(foo()) |_| {} var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-for_statement.zig b/test/cases/compile_errors/implicit_semicolon-for_statement.zig index 14709cef4c..2830293b70 100644 --- a/test/cases/compile_errors/implicit_semicolon-for_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-for_statement.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; for(foo()) |_| ({}) var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig index 72a4fa7d3e..9e99421cd1 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} else if(true) {} else {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig index 95135006ba..e2e7b7e3b3 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) else if(true) ({}) else ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig index a29636bd1d..33ca6ab600 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} else if(true) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig index c62430a0a2..e3d004fee1 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) else if(true) ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig b/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig index d5bee6e52b..a23809528b 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} else {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig b/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig index 94df128626..ed01aa7df2 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) else ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if_expression.zig b/test/cases/compile_errors/implicit_semicolon-if_expression.zig index 339a5378cf..e28f8616e2 100644 --- a/test/cases/compile_errors/implicit_semicolon-if_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if_statement.zig b/test/cases/compile_errors/implicit_semicolon-if_statement.zig index b8ccb5e401..3067c07767 100644 --- a/test/cases/compile_errors/implicit_semicolon-if_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-test_expression.zig b/test/cases/compile_errors/implicit_semicolon-test_expression.zig index 2a37c0aa0e..0bb345b387 100644 --- a/test/cases/compile_errors/implicit_semicolon-test_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-test_expression.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; _ = if (foo()) |_| {} var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-test_statement.zig b/test/cases/compile_errors/implicit_semicolon-test_statement.zig index afe00eba75..3a4eb8b5ba 100644 --- a/test/cases/compile_errors/implicit_semicolon-test_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-test_statement.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; if (foo()) |_| ({}) var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig b/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig index 5587627597..f05c70bc14 100644 --- a/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = while(true):({}) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig b/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig index 9bebe3861e..2d27824f6b 100644 --- a/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; while(true):({}) ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-while_expression.zig b/test/cases/compile_errors/implicit_semicolon-while_expression.zig index df388a7c39..4b39ed7c16 100644 --- a/test/cases/compile_errors/implicit_semicolon-while_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-while_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = while(true) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-while_statement.zig b/test/cases/compile_errors/implicit_semicolon-while_statement.zig index d9ed3d1e2a..538a56faf1 100644 --- a/test/cases/compile_errors/implicit_semicolon-while_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-while_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; while(true) 1 var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/invalid_member_of_builtin_enum.zig b/test/cases/compile_errors/invalid_member_of_builtin_enum.zig index b0a176d792..f3ea66ae1c 100644 --- a/test/cases/compile_errors/invalid_member_of_builtin_enum.zig +++ b/test/cases/compile_errors/invalid_member_of_builtin_enum.zig @@ -9,4 +9,4 @@ export fn entry() void { // target=native // // :3:38: error: enum 'builtin.OptimizeMode' has no member named 'x86' -// :?:18: note: enum declared here +// : note: enum declared here diff --git a/test/cases/compile_errors/invalid_store_to_comptime_field.zig b/test/cases/compile_errors/invalid_store_to_comptime_field.zig index 0f444ba78c..fd6fff5e17 100644 --- a/test/cases/compile_errors/invalid_store_to_comptime_field.zig +++ b/test/cases/compile_errors/invalid_store_to_comptime_field.zig @@ -73,11 +73,11 @@ pub export fn entry8() void { // // :6:19: error: value stored in comptime field does not match the default value of the field // :14:19: error: value stored in comptime field does not match the default value of the field -// :53:16: error: value stored in comptime field does not match the default value of the field // :19:38: error: value stored in comptime field does not match the default value of the field // :31:19: error: value stored in comptime field does not match the default value of the field // :25:29: note: default value set here // :41:16: error: value stored in comptime field does not match the default value of the field // :45:12: error: value stored in comptime field does not match the default value of the field +// :53:16: error: value stored in comptime field does not match the default value of the field // :66:43: error: value stored in comptime field does not match the default value of the field // :59:35: error: value stored in comptime field does not match the default value of the field diff --git a/test/cases/compile_errors/invalid_struct_field.zig b/test/cases/compile_errors/invalid_struct_field.zig index 4450375cb8..ff8c96a0b6 100644 --- a/test/cases/compile_errors/invalid_struct_field.zig +++ b/test/cases/compile_errors/invalid_struct_field.zig @@ -25,5 +25,6 @@ export fn e() void { // :4:7: error: no field named 'foo' in struct 'tmp.A' // :1:11: note: struct declared here // :10:17: error: no field named 'bar' in struct 'tmp.A' +// :1:11: note: struct declared here // :18:45: error: no field named 'f' in struct 'tmp.e.B' // :14:15: note: struct declared here diff --git a/test/cases/compile_errors/missing_main_fn_in_executable.zig b/test/cases/compile_errors/missing_main_fn_in_executable.zig index 2d608ad2b8..3c1ae631ac 100644 --- a/test/cases/compile_errors/missing_main_fn_in_executable.zig +++ b/test/cases/compile_errors/missing_main_fn_in_executable.zig @@ -5,5 +5,7 @@ // target=x86_64-linux // output_mode=Exe // -// :?:?: error: root struct of file 'tmp' has no member named 'main' -// :?:?: note: called from here +// : error: root struct of file 'tmp' has no member named 'main' +// : note: called from here +// : note: called from here +// : note: called from here diff --git a/test/cases/compile_errors/private_main_fn.zig b/test/cases/compile_errors/private_main_fn.zig index 26ad3d22db..6e53fbdce2 100644 --- a/test/cases/compile_errors/private_main_fn.zig +++ b/test/cases/compile_errors/private_main_fn.zig @@ -5,6 +5,8 @@ fn main() void {} // target=x86_64-linux // output_mode=Exe // -// :?:?: error: 'main' is not marked 'pub' +// : error: 'main' is not marked 'pub' // :1:1: note: declared here -// :?:?: note: called from here +// : note: called from here +// : note: called from here +// : note: called from here diff --git a/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig b/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig index 4c235ed94b..9cd0fe1798 100644 --- a/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig +++ b/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig @@ -15,5 +15,6 @@ export fn entry() void { // target=native // // :9:51: error: values of type '[]const builtin.Type.StructField' must be comptime-known, but index value is runtime-known -// :?:21: note: struct requires comptime because of this field -// :?:21: note: types are not available at runtime +// : note: struct requires comptime because of this field +// : note: types are not available at runtime +// : struct requires comptime because of this field diff --git a/test/cases/compile_errors/struct_type_mismatch_in_arg.zig b/test/cases/compile_errors/struct_type_mismatch_in_arg.zig index a52bdfab6c..d051966c52 100644 --- a/test/cases/compile_errors/struct_type_mismatch_in_arg.zig +++ b/test/cases/compile_errors/struct_type_mismatch_in_arg.zig @@ -13,6 +13,6 @@ comptime { // target=native // // :7:16: error: expected type 'tmp.Foo', found 'tmp.Bar' -// :1:13: note: struct declared here // :2:13: note: struct declared here +// :1:13: note: struct declared here // :4:18: note: parameter type declared here diff --git a/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig b/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig index 5f486bf2b7..a700f0d0f2 100644 --- a/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig +++ b/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig @@ -28,10 +28,11 @@ export fn u2m() void { // target=native // // :9:1: error: union initializer must initialize one field +// :1:12: note: union declared here // :14:20: error: cannot initialize multiple union fields at once, unions can only have one active field // :14:31: note: additional initializer here +// :1:12: note: union declared here // :18:21: error: union initializer must initialize one field // :22:20: error: cannot initialize multiple union fields at once, unions can only have one active field // :22:31: note: additional initializer here -// :1:12: note: union declared here // :5:12: note: union declared here diff --git a/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig b/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig index cf43513159..00d4a7ecc9 100644 --- a/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig +++ b/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig b/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig index 5907c1dad5..f23498e955 100644 --- a/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig +++ b/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig b/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig index ece0614f73..4f54f38e6b 100644 --- a/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig +++ b/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig @@ -6,7 +6,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig b/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig index 9175bcbc0e..84695cb35b 100644 --- a/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig +++ b/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig @@ -6,7 +6,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig b/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig index 8f36700757..badab821d3 100644 --- a/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig +++ b/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/pointer_keeps_address_space.zig b/test/cases/llvm/pointer_keeps_address_space.zig index bfd40566f8..f894c96d7b 100644 --- a/test/cases/llvm/pointer_keeps_address_space.zig +++ b/test/cases/llvm/pointer_keeps_address_space.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig b/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig index 8114e86c5d..b5803a3076 100644 --- a/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig +++ b/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig b/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig index 78bc3e4bd6..b3c0116983 100644 --- a/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig +++ b/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cbe.zig b/test/cbe.zig new file mode 100644 index 0000000000..25ac3cb137 --- /dev/null +++ b/test/cbe.zig @@ -0,0 +1,950 @@ +const std = @import("std"); +const Cases = @import("src/Cases.zig"); + +// These tests should work with all platforms, but we're using linux_x64 for +// now for consistency. Will be expanded eventually. +const linux_x64 = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, +}; + +pub fn addCases(ctx: *Cases) !void { + { + var case = ctx.exeFromCompiledC("hello world with updates", .{}); + + // Regular old hello world + case.addCompareOutput( + \\extern fn puts(s: [*:0]const u8) c_int; + \\pub export fn main() c_int { + \\ _ = puts("hello world!"); + \\ return 0; + \\} + , "hello world!" ++ std.cstr.line_sep); + + // Now change the message only + case.addCompareOutput( + \\extern fn puts(s: [*:0]const u8) c_int; + \\pub export fn main() c_int { + \\ _ = puts("yo"); + \\ return 0; + \\} + , "yo" ++ std.cstr.line_sep); + + // Add an unused Decl + case.addCompareOutput( + \\extern fn puts(s: [*:0]const u8) c_int; + \\pub export fn main() c_int { + \\ _ = puts("yo!"); + \\ return 0; + \\} + \\fn unused() void {} + , "yo!" ++ std.cstr.line_sep); + + // Comptime return type and calling convention expected. + case.addError( + \\var x: i32 = 1234; + \\pub export fn main() x { + \\ return 0; + \\} + \\export fn foo() callconv(y) c_int { + \\ return 0; + \\} + \\var y: @import("std").builtin.CallingConvention = .C; + , &.{ + ":2:22: error: expected type 'type', found 'i32'", + ":5:26: error: unable to resolve comptime value", + ":5:26: note: calling convention must be comptime-known", + }); + } + + { + var case = ctx.exeFromCompiledC("var args", .{}); + + case.addCompareOutput( + \\extern fn printf(format: [*:0]const u8, ...) c_int; + \\ + \\pub export fn main() c_int { + \\ _ = printf("Hello, %s!\n", "world"); + \\ return 0; + \\} + , "Hello, world!" ++ std.cstr.line_sep); + } + + { + var case = ctx.exeFromCompiledC("intToError", .{}); + + case.addCompareOutput( + \\pub export fn main() c_int { + \\ // comptime checks + \\ const a = error.A; + \\ const b = error.B; + \\ const c = @intToError(2); + \\ const d = @intToError(1); + \\ if (!(c == b)) unreachable; + \\ if (!(a == d)) unreachable; + \\ // runtime checks + \\ var x = error.A; + \\ var y = error.B; + \\ var z = @intToError(2); + \\ var f = @intToError(1); + \\ if (!(y == z)) unreachable; + \\ if (!(x == f)) unreachable; + \\ return 0; + \\} + , ""); + case.addError( + \\pub export fn main() c_int { + \\ _ = @intToError(0); + \\ return 0; + \\} + , &.{":2:21: error: integer value '0' represents no error"}); + case.addError( + \\pub export fn main() c_int { + \\ _ = @intToError(3); + \\ return 0; + \\} + , &.{":2:21: error: integer value '3' represents no error"}); + } + + { + var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64); + + // Exit with 0 + case.addCompareOutput( + \\fn exitGood() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ ); + \\ unreachable; + \\} + \\ + \\pub export fn main() c_int { + \\ exitGood(); + \\} + , ""); + + // Pass a usize parameter to exit + case.addCompareOutput( + \\pub export fn main() c_int { + \\ exit(0); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); + + // Change the parameter to u8 + case.addCompareOutput( + \\pub export fn main() c_int { + \\ exit(0); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); + + // Do some arithmetic at the exit callsite + case.addCompareOutput( + \\pub export fn main() c_int { + \\ exitMath(1); + \\} + \\ + \\fn exitMath(a: u8) noreturn { + \\ exit(0 + a - a); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + \\ + , ""); + + // Invert the arithmetic + case.addCompareOutput( + \\pub export fn main() c_int { + \\ exitMath(1); + \\} + \\ + \\fn exitMath(a: u8) noreturn { + \\ exit(a + 0 - a); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + \\ + , ""); + } + + { + var case = ctx.exeFromCompiledC("alloc and retptr", .{}); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\fn addIndirect(a: i32, b: i32) i32 { + \\ return add(a, b); + \\} + \\ + \\pub export fn main() c_int { + \\ return addIndirect(1, 2) - 3; + \\} + , ""); + } + + { + var case = ctx.exeFromCompiledC("inferred local const and var", .{}); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\pub export fn main() c_int { + \\ const x = add(1, 2); + \\ var y = add(3, 0); + \\ y -= x; + \\ return y; + \\} + , ""); + } + { + var case = ctx.exeFromCompiledC("control flow", .{}); + + // Simple while loop + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var a: c_int = 0; + \\ while (a < 5) : (a+=1) {} + \\ return a - 5; + \\} + , ""); + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var a = true; + \\ while (!a) {} + \\ return 0; + \\} + , ""); + + // If expression + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var cond: c_int = 0; + \\ var a: c_int = @as(c_int, if (cond == 0) + \\ 2 + \\ else + \\ 3) + 9; + \\ return a - 11; + \\} + , ""); + + // If expression with breakpoint that does not get hit + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var x: i32 = 1; + \\ if (x != 1) @breakpoint(); + \\ return 0; + \\} + , ""); + + // Switch expression + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var cond: c_int = 0; + \\ var a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 99...300, 12 => 3, + \\ 0 => 4, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , ""); + + // Switch expression missing else case. + case.addError( + \\pub export fn main() c_int { + \\ var cond: c_int = 0; + \\ const a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 3 => 3, + \\ 4 => 4, + \\ }; + \\ return a - 4; + \\} + , &.{":3:22: error: switch must handle all possibilities"}); + + // Switch expression, has an unreachable prong. + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var cond: c_int = 0; + \\ const a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 99...300, 12 => 3, + \\ 0 => 4, + \\ 13 => unreachable, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , ""); + + // Switch expression, has an unreachable prong and prongs write + // to result locations. + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var cond: c_int = 0; + \\ var a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 99...300, 12 => 3, + \\ 0 => 4, + \\ 13 => unreachable, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , ""); + + // Integer switch expression has duplicate case value. + case.addError( + \\pub export fn main() c_int { + \\ var cond: c_int = 0; + \\ const a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 96, 11...13, 97 => 3, + \\ 0 => 4, + \\ 90, 12 => 100, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , &.{ + ":8:13: error: duplicate switch value", + ":6:15: note: previous value here", + }); + + // Boolean switch expression has duplicate case value. + case.addError( + \\pub export fn main() c_int { + \\ var a: bool = false; + \\ const b: c_int = switch (a) { + \\ false => 1, + \\ true => 2, + \\ false => 3, + \\ }; + \\ _ = b; + \\} + , &.{ + ":6:9: error: duplicate switch value", + }); + + // Sparse (no range capable) switch expression has duplicate case value. + case.addError( + \\pub export fn main() c_int { + \\ const A: type = i32; + \\ const b: c_int = switch (A) { + \\ i32 => 1, + \\ bool => 2, + \\ f64, i32 => 3, + \\ else => 4, + \\ }; + \\ _ = b; + \\} + , &.{ + ":6:14: error: duplicate switch value", + ":4:9: note: previous value here", + }); + + // Ranges not allowed for some kinds of switches. + case.addError( + \\pub export fn main() c_int { + \\ const A: type = i32; + \\ const b: c_int = switch (A) { + \\ i32 => 1, + \\ bool => 2, + \\ f16...f64 => 3, + \\ else => 4, + \\ }; + \\ _ = b; + \\} + , &.{ + ":3:30: error: ranges not allowed when switching on type 'type'", + ":6:12: note: range here", + }); + + // Switch expression has unreachable else prong. + case.addError( + \\pub export fn main() c_int { + \\ var a: u2 = 0; + \\ const b: i32 = switch (a) { + \\ 0 => 10, + \\ 1 => 20, + \\ 2 => 30, + \\ 3 => 40, + \\ else => 50, + \\ }; + \\ _ = b; + \\} + , &.{ + ":8:14: error: unreachable else prong; all cases already handled", + }); + } + //{ + // var case = ctx.exeFromCompiledC("optionals", .{}); + + // // Simple while loop + // case.addCompareOutput( + // \\pub export fn main() c_int { + // \\ var count: c_int = 0; + // \\ var opt_ptr: ?*c_int = &count; + // \\ while (opt_ptr) |_| : (count += 1) { + // \\ if (count == 4) opt_ptr = null; + // \\ } + // \\ return count - 5; + // \\} + // , ""); + + // // Same with non pointer optionals + // case.addCompareOutput( + // \\pub export fn main() c_int { + // \\ var count: c_int = 0; + // \\ var opt_ptr: ?c_int = count; + // \\ while (opt_ptr) |_| : (count += 1) { + // \\ if (count == 4) opt_ptr = null; + // \\ } + // \\ return count - 5; + // \\} + // , ""); + //} + + { + var case = ctx.exeFromCompiledC("errors", .{}); + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var e1 = error.Foo; + \\ var e2 = error.Bar; + \\ assert(e1 != e2); + \\ assert(e1 == error.Foo); + \\ assert(e2 == error.Bar); + \\ return 0; + \\} + \\fn assert(b: bool) void { + \\ if (!b) unreachable; + \\} + , ""); + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var e: anyerror!c_int = 0; + \\ const i = e catch 69; + \\ return i; + \\} + , ""); + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var e: anyerror!c_int = error.Foo; + \\ const i = e catch 69; + \\ return 69 - i; + \\} + , ""); + case.addCompareOutput( + \\const E = error{e}; + \\const S = struct { x: u32 }; + \\fn f() E!u32 { + \\ const x = (try @as(E!S, S{ .x = 1 })).x; + \\ return x; + \\} + \\pub export fn main() c_int { + \\ const x = f() catch @as(u32, 0); + \\ if (x != 1) unreachable; + \\ return 0; + \\} + , ""); + } + + { + var case = ctx.exeFromCompiledC("structs", .{}); + case.addError( + \\const Point = struct { x: i32, y: i32 }; + \\pub export fn main() c_int { + \\ var p: Point = .{ + \\ .y = 24, + \\ .x = 12, + \\ .y = 24, + \\ }; + \\ return p.y - p.x - p.x; + \\} + , &.{ + ":6:10: error: duplicate field", + ":4:10: note: other field here", + }); + case.addError( + \\const Point = struct { x: i32, y: i32 }; + \\pub export fn main() c_int { + \\ var p: Point = .{ + \\ .y = 24, + \\ }; + \\ return p.y - p.x - p.x; + \\} + , &.{ + ":3:21: error: missing struct field: x", + ":1:15: note: struct 'tmp.Point' declared here", + }); + case.addError( + \\const Point = struct { x: i32, y: i32 }; + \\pub export fn main() c_int { + \\ var p: Point = .{ + \\ .x = 12, + \\ .y = 24, + \\ .z = 48, + \\ }; + \\ return p.y - p.x - p.x; + \\} + , &.{ + ":6:10: error: no field named 'z' in struct 'tmp.Point'", + ":1:15: note: struct declared here", + }); + case.addCompareOutput( + \\const Point = struct { x: i32, y: i32 }; + \\pub export fn main() c_int { + \\ var p: Point = .{ + \\ .x = 12, + \\ .y = 24, + \\ }; + \\ return p.y - p.x - p.x; + \\} + , ""); + case.addCompareOutput( + \\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 }; + \\pub export fn main() c_int { + \\ var p: Point = .{ + \\ .x = 18, + \\ .y = 24, + \\ .z = 1, + \\ .a = 2, + \\ .b = 3, + \\ }; + \\ return p.y - p.x - p.z - p.a - p.b; + \\} + , ""); + } + + { + var case = ctx.exeFromCompiledC("unions", .{}); + + case.addError( + \\const U = union { + \\ a: u32, + \\ b + \\}; + , &.{ + ":3:5: error: union field missing type", + }); + + case.addError( + \\const E = enum { a, b }; + \\const U = union(E) { + \\ a: u32 = 1, + \\ b: f32 = 2, + \\}; + , &.{ + ":2:11: error: explicitly valued tagged union requires inferred enum tag type", + ":3:14: note: tag value specified here", + }); + + case.addError( + \\const U = union(enum) { + \\ a: u32 = 1, + \\ b: f32 = 2, + \\}; + , &.{ + ":1:11: error: explicitly valued tagged union missing integer tag type", + ":2:14: note: tag value specified here", + }); + } + + { + var case = ctx.exeFromCompiledC("enums", .{}); + + case.addError( + \\const E1 = packed enum { a, b, c }; + \\const E2 = extern enum { a, b, c }; + \\export fn foo() void { + \\ _ = E1.a; + \\} + \\export fn bar() void { + \\ _ = E2.a; + \\} + , &.{ + ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", + ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", + }); + + // comptime and types are caught in AstGen. + case.addError( + \\const E1 = enum { + \\ a, + \\ comptime b, + \\ c, + \\}; + \\const E2 = enum { + \\ a, + \\ b: i32, + \\ c, + \\}; + \\export fn foo() void { + \\ _ = E1.a; + \\} + \\export fn bar() void { + \\ _ = E2.a; + \\} + , &.{ + ":3:5: error: enum fields cannot be marked comptime", + ":8:8: error: enum fields do not have types", + ":6:12: note: consider 'union(enum)' here to make it a tagged union", + }); + + // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch + case.addCompareOutput( + \\const Number = enum { One, Two, Three }; + \\ + \\pub export fn main() c_int { + \\ var number1 = Number.One; + \\ var number2: Number = .Two; + \\ const number3 = @intToEnum(Number, 2); + \\ if (number1 == number2) return 1; + \\ if (number2 == number3) return 1; + \\ if (@enumToInt(number1) != 0) return 1; + \\ if (@enumToInt(number2) != 1) return 1; + \\ if (@enumToInt(number3) != 2) return 1; + \\ var x: Number = .Two; + \\ if (number2 != x) return 1; + \\ switch (x) { + \\ .One => return 1, + \\ .Two => return 0, + \\ number3 => return 2, + \\ } + \\} + , ""); + + // Specifying alignment is a parse error. + // This also tests going from a successful build to a parse error. + case.addError( + \\const E1 = enum { + \\ a, + \\ b align(4), + \\ c, + \\}; + \\export fn foo() void { + \\ _ = E1.a; + \\} + , &.{ + ":3:13: error: enum fields cannot be aligned", + }); + + // Redundant non-exhaustive enum mark. + // This also tests going from a parse error to an AstGen error. + case.addError( + \\const E1 = enum { + \\ a, + \\ _, + \\ b, + \\ c, + \\ _, + \\}; + \\export fn foo() void { + \\ _ = E1.a; + \\} + , &.{ + ":6:5: error: redundant non-exhaustive enum mark", + ":3:5: note: other mark here", + }); + + case.addError( + \\const E1 = enum { + \\ a, + \\ b, + \\ c, + \\ _ = 10, + \\}; + \\export fn foo() void { + \\ _ = E1.a; + \\} + , &.{ + ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value", + }); + + case.addError( + \\const E1 = enum { a, b, _ }; + \\export fn foo() void { + \\ _ = E1.a; + \\} + , &.{ + ":1:12: error: non-exhaustive enum missing integer tag type", + ":1:25: note: marked non-exhaustive here", + }); + + case.addError( + \\const E1 = enum { a, b, c, b, d }; + \\pub export fn main() c_int { + \\ _ = E1.a; + \\} + , &.{ + ":1:28: error: duplicate enum field 'b'", + ":1:22: note: other field here", + }); + + case.addError( + \\pub export fn main() c_int { + \\ const a = true; + \\ _ = @enumToInt(a); + \\} + , &.{ + ":3:20: error: expected enum or tagged union, found 'bool'", + }); + + case.addError( + \\pub export fn main() c_int { + \\ const a = 1; + \\ _ = @intToEnum(bool, a); + \\} + , &.{ + ":3:20: error: expected enum, found 'bool'", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\pub export fn main() c_int { + \\ _ = @intToEnum(E, 3); + \\} + , &.{ + ":3:9: error: enum 'tmp.E' has no tag with value '3'", + ":1:11: note: enum declared here", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\pub export fn main() c_int { + \\ var x: E = .a; + \\ switch (x) { + \\ .a => {}, + \\ .c => {}, + \\ } + \\} + , &.{ + ":4:5: error: switch must handle all possibilities", + ":1:21: note: unhandled enumeration value: 'b'", + ":1:11: note: enum 'tmp.E' declared here", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\pub export fn main() c_int { + \\ var x: E = .a; + \\ switch (x) { + \\ .a => {}, + \\ .b => {}, + \\ .b => {}, + \\ .c => {}, + \\ } + \\} + , &.{ + ":7:10: error: duplicate switch value", + ":6:10: note: previous value here", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\pub export fn main() c_int { + \\ var x: E = .a; + \\ switch (x) { + \\ .a => {}, + \\ .b => {}, + \\ .c => {}, + \\ else => {}, + \\ } + \\} + , &.{ + ":8:14: error: unreachable else prong; all cases already handled", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\pub export fn main() c_int { + \\ var x: E = .a; + \\ switch (x) { + \\ .a => {}, + \\ .b => {}, + \\ _ => {}, + \\ } + \\} + , &.{ + ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums", + ":7:11: note: '_' prong here", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\pub export fn main() c_int { + \\ _ = E.d; + \\} + , &.{ + ":3:11: error: enum 'tmp.E' has no member named 'd'", + ":1:11: note: enum declared here", + }); + + case.addError( + \\const E = enum { a, b, c }; + \\pub export fn main() c_int { + \\ var x: E = .d; + \\ _ = x; + \\} + , &.{ + ":3:17: error: no field named 'd' in enum 'tmp.E'", + ":1:11: note: enum declared here", + }); + } + + { + var case = ctx.exeFromCompiledC("shift right and left", .{}); + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var i: u32 = 16; + \\ assert(i >> 1, 8); + \\ return 0; + \\} + \\fn assert(a: u32, b: u32) void { + \\ if (a != b) unreachable; + \\} + , ""); + + case.addCompareOutput( + \\pub export fn main() c_int { + \\ var i: u32 = 16; + \\ assert(i << 1, 32); + \\ return 0; + \\} + \\fn assert(a: u32, b: u32) void { + \\ if (a != b) unreachable; + \\} + , ""); + } + + { + var case = ctx.exeFromCompiledC("inferred error sets", .{}); + + case.addCompareOutput( + \\pub export fn main() c_int { + \\ if (foo()) |_| { + \\ @panic("test fail"); + \\ } else |err| { + \\ if (err != error.ItBroke) { + \\ @panic("test fail"); + \\ } + \\ } + \\ return 0; + \\} + \\fn foo() !void { + \\ return error.ItBroke; + \\} + , ""); + } + + { + // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64) + var case = ctx.exeFromCompiledC("add and sub wrapping operations", .{}); + case.addCompareOutput( + \\pub export fn main() c_int { + \\ // Addition + \\ if (!add_u3(1, 1, 2)) return 1; + \\ if (!add_u3(7, 1, 0)) return 1; + \\ if (!add_i3(1, 1, 2)) return 1; + \\ if (!add_i3(3, 2, -3)) return 1; + \\ if (!add_i3(-3, -2, 3)) return 1; + \\ if (!add_c_int(1, 1, 2)) return 1; + \\ // TODO enable these when stage2 supports std.math.maxInt + \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1; + \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1; + \\ + \\ // Subtraction + \\ if (!sub_u3(2, 1, 1)) return 1; + \\ if (!sub_u3(0, 1, 7)) return 1; + \\ if (!sub_i3(2, 1, 1)) return 1; + \\ if (!sub_i3(3, -2, -3)) return 1; + \\ if (!sub_i3(-3, 2, 3)) return 1; + \\ if (!sub_c_int(2, 1, 1)) return 1; + \\ // TODO enable these when stage2 supports std.math.maxInt + \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1; + \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1; + \\ + \\ return 0; + \\} + \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool { + \\ return expected == lhs +% rhs; + \\} + \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool { + \\ return expected == lhs +% rhs; + \\} + \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool { + \\ return expected == lhs +% rhs; + \\} + \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool { + \\ return expected == lhs -% rhs; + \\} + \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool { + \\ return expected == lhs -% rhs; + \\} + \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool { + \\ return expected == lhs -% rhs; + \\} + , ""); + } + + { + var case = ctx.exeFromCompiledC("rem", linux_x64); + case.addCompareOutput( + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\fn rem(lhs: i32, rhs: i32, expected: i32) bool { + \\ return @rem(lhs, rhs) == expected; + \\} + \\pub export fn main() c_int { + \\ assert(rem(-5, 3, -2)); + \\ assert(rem(5, 3, 2)); + \\ return 0; + \\} + , ""); + } +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index e0b78b3000..2d796b9463 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,146 +1,10 @@ const std = @import("std"); const builtin = @import("builtin"); -const TestContext = @import("../src/test.zig").TestContext; - -pub fn addCases(ctx: *TestContext) !void { - { - const case = ctx.obj("wrong same named struct", .{}); - case.backend = .stage1; - - case.addSourceFile("a.zig", - \\pub const Foo = struct { - \\ x: i32, - \\}; - ); - - case.addSourceFile("b.zig", - \\pub const Foo = struct { - \\ z: f64, - \\}; - ); - - case.addError( - \\const a = @import("a.zig"); - \\const b = @import("b.zig"); - \\ - \\export fn entry() void { - \\ var a1: a.Foo = undefined; - \\ bar(&a1); - \\} - \\ - \\fn bar(x: *b.Foo) void {_ = x;} - , &[_][]const u8{ - "tmp.zig:6:10: error: expected type '*b.Foo', found '*a.Foo'", - "tmp.zig:6:10: note: pointer type child 'a.Foo' cannot cast into pointer type child 'b.Foo'", - "a.zig:1:17: note: a.Foo declared here", - "b.zig:1:17: note: b.Foo declared here", - }); - } - - { - const case = ctx.obj("multiple files with private function error", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\fn privateFunction() void { } - ); - - case.addError( - \\const foo = @import("foo.zig",); - \\ - \\export fn callPrivFunction() void { - \\ foo.privateFunction(); - \\} - , &[_][]const u8{ - "tmp.zig:4:8: error: 'privateFunction' is private", - "foo.zig:1:1: note: declared here", - }); - } - - { - const case = ctx.obj("multiple files with private member instance function (canonical invocation) error", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\pub const Foo = struct { - \\ fn privateFunction(self: *Foo) void { _ = self; } - \\}; - ); - - case.addError( - \\const Foo = @import("foo.zig",).Foo; - \\ - \\export fn callPrivFunction() void { - \\ var foo = Foo{}; - \\ Foo.privateFunction(foo); - \\} - , &[_][]const u8{ - "tmp.zig:5:8: error: 'privateFunction' is private", - "foo.zig:2:5: note: declared here", - }); - } - - { - const case = ctx.obj("multiple files with private member instance function error", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\pub const Foo = struct { - \\ fn privateFunction(self: *Foo) void { _ = self; } - \\}; - ); - - case.addError( - \\const Foo = @import("foo.zig",).Foo; - \\ - \\export fn callPrivFunction() void { - \\ var foo = Foo{}; - \\ foo.privateFunction(); - \\} - , &[_][]const u8{ - "tmp.zig:5:8: error: 'privateFunction' is private", - "foo.zig:2:5: note: declared here", - }); - } - - { - const case = ctx.obj("export collision", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\export fn bar() void {} - \\pub const baz = 1234; - ); - - case.addError( - \\const foo = @import("foo.zig",); - \\ - \\export fn bar() usize { - \\ return foo.baz; - \\} - , &[_][]const u8{ - "foo.zig:1:1: error: exported symbol collision: 'bar'", - "tmp.zig:3:1: note: other symbol here", - }); - } - - ctx.objErrStage1("non-printable invalid character", "\xff\xfe" ++ - "fn foo() bool {\r\n" ++ - " return true;\r\n" ++ - "}\r\n", &[_][]const u8{ - "tmp.zig:1:1: error: expected test, comptime, var decl, or container field, found 'invalid bytes'", - "tmp.zig:1:1: note: invalid byte: '\\xff'", - }); - - ctx.objErrStage1("non-printable invalid character with escape alternative", "fn foo() bool {\n" ++ - "\treturn true;\n" ++ - "}\n", &[_][]const u8{ - "tmp.zig:2:1: error: invalid character: '\\t'", - }); +const Cases = @import("src/Cases.zig"); +pub fn addCases(ctx: *Cases) !void { { const case = ctx.obj("multiline error messages", .{}); - case.backend = .stage2; case.addError( \\comptime { @@ -176,7 +40,6 @@ pub fn addCases(ctx: *TestContext) !void { { const case = ctx.obj("isolated carriage return in multiline string literal", .{}); - case.backend = .stage2; case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{ ":1:19: error: expected ';' after declaration", @@ -195,16 +58,6 @@ pub fn addCases(ctx: *TestContext) !void { { const case = ctx.obj("argument causes error", .{}); - case.backend = .stage2; - - case.addSourceFile("b.zig", - \\pub const ElfDynLib = struct { - \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T { - \\ _ = self; - \\ return undefined; - \\ } - \\}; - ); case.addError( \\pub export fn entry() void { @@ -216,15 +69,18 @@ pub fn addCases(ctx: *TestContext) !void { ":3:12: note: argument to function being called at comptime must be comptime-known", ":2:55: note: expression is evaluated at comptime because the generic function was instantiated with a comptime-only return type", }); + case.addSourceFile("b.zig", + \\pub const ElfDynLib = struct { + \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T { + \\ _ = self; + \\ return undefined; + \\ } + \\}; + ); } { const case = ctx.obj("astgen failure in file struct", .{}); - case.backend = .stage2; - - case.addSourceFile("b.zig", - \\+ - ); case.addError( \\pub export fn entry() void { @@ -233,21 +89,13 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":1:1: error: expected type expression, found '+'", }); + case.addSourceFile("b.zig", + \\+ + ); } { const case = ctx.obj("invalid store to comptime field", .{}); - case.backend = .stage2; - - case.addSourceFile("a.zig", - \\pub const S = struct { - \\ comptime foo: u32 = 1, - \\ bar: u32, - \\ pub fn foo(x: @This()) void { - \\ _ = x; - \\ } - \\}; - ); case.addError( \\const a = @import("a.zig"); @@ -259,44 +107,19 @@ pub fn addCases(ctx: *TestContext) !void { ":4:23: error: value stored in comptime field does not match the default value of the field", ":2:25: note: default value set here", }); + case.addSourceFile("a.zig", + \\pub const S = struct { + \\ comptime foo: u32 = 1, + \\ bar: u32, + \\ pub fn foo(x: @This()) void { + \\ _ = x; + \\ } + \\}; + ); } - // TODO test this in stage2, but we won't even try in stage1 - //ctx.objErrStage1("inline fn calls itself indirectly", - // \\export fn foo() void { - // \\ bar(); - // \\} - // \\fn bar() callconv(.Inline) void { - // \\ baz(); - // \\ quux(); - // \\} - // \\fn baz() callconv(.Inline) void { - // \\ bar(); - // \\ quux(); - // \\} - // \\extern fn quux() void; - //, &[_][]const u8{ - // "tmp.zig:4:1: error: unable to inline function", - //}); - - //ctx.objErrStage1("save reference to inline function", - // \\export fn foo() void { - // \\ quux(@ptrToInt(bar)); - // \\} - // \\fn bar() callconv(.Inline) void { } - // \\extern fn quux(usize) void; - //, &[_][]const u8{ - // "tmp.zig:4:1: error: unable to inline function", - //}); - { const case = ctx.obj("file in multiple modules", .{}); - case.backend = .stage2; - - case.addSourceFile("foo.zig", - \\const dummy = 0; - ); - case.addDepModule("foo", "foo.zig"); case.addError( @@ -309,5 +132,8 @@ pub fn addCases(ctx: *TestContext) !void { ":1:1: note: root of module root.foo", ":3:17: note: imported from module root", }); + case.addSourceFile("foo.zig", + \\const dummy = 0; + ); } } diff --git a/test/link/macho/dead_strip/build.zig b/test/link/macho/dead_strip/build.zig index 4131d7baf9..4c739b3d8c 100644 --- a/test/link/macho/dead_strip/build.zig +++ b/test/link/macho/dead_strip/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void { // Without -dead_strip, we expect `iAmUnused` symbol present const exe = createScenario(b, optimize, target, "no-gc"); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkInSymtab(); check.checkNext("{*} (__TEXT,__text) external _iAmUnused"); @@ -27,7 +27,7 @@ pub fn build(b: *std.Build) void { const exe = createScenario(b, optimize, target, "yes-gc"); exe.link_gc_sections = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkInSymtab(); check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused"); diff --git a/test/link/macho/dead_strip_dylibs/build.zig b/test/link/macho/dead_strip_dylibs/build.zig index 4a4dda7b7f..b9b97949c1 100644 --- a/test/link/macho/dead_strip_dylibs/build.zig +++ b/test/link/macho/dead_strip_dylibs/build.zig @@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable const exe = createScenario(b, optimize, "no-dead-strip"); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name {*}Cocoa"); diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index bf4a49f50b..2d775aa23f 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); - const check_dylib = dylib.checkObject(.macho); + const check_dylib = dylib.checkObject(); check_dylib.checkStart("cmd ID_DYLIB"); check_dylib.checkNext("name @rpath/liba.dylib"); check_dylib.checkNext("timestamp 2"); @@ -44,7 +44,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); exe.linkLibC(); - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("cmd LOAD_DYLIB"); check_exe.checkNext("name @rpath/liba.dylib"); check_exe.checkNext("timestamp 2"); diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index b2c41ef6dc..e983bc9391 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkLibC(); exe.entry_symbol_name = "_non_main"; - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("segname __TEXT"); check_exe.checkNext("vmaddr {vmaddr}"); diff --git a/test/link/macho/headerpad/build.zig b/test/link/macho/headerpad/build.zig index 07d30ee6f2..97161e8e60 100644 --- a/test/link/macho/headerpad/build.zig +++ b/test/link/macho/headerpad/build.zig @@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const exe = simpleExe(b, optimize); exe.headerpad_max_install_names = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); @@ -45,7 +45,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const exe = simpleExe(b, optimize); exe.headerpad_size = 0x10000; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } }); @@ -62,7 +62,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.headerpad_max_install_names = true; exe.headerpad_size = 0x10000; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } }); @@ -79,7 +79,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.headerpad_size = 0x1000; exe.headerpad_max_install_names = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); diff --git a/test/link/macho/linksection/build.zig b/test/link/macho/linksection/build.zig index 37e0b7cbef..b8b3a59f35 100644 --- a/test/link/macho/linksection/build.zig +++ b/test/link/macho/linksection/build.zig @@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize .target = target, }); - const check = obj.checkObject(.macho); + const check = obj.checkObject(); check.checkInSymtab(); check.checkNext("{*} (__DATA,__TestGlobal) external _test_global"); diff --git a/test/link/macho/needed_framework/build.zig b/test/link/macho/needed_framework/build.zig index cd53a5d212..3de96efbc7 100644 --- a/test/link/macho/needed_framework/build.zig +++ b/test/link/macho/needed_framework/build.zig @@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkFrameworkNeeded("Cocoa"); exe.dead_strip_dylibs = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name {*}Cocoa"); test_step.dependOn(&check.step); diff --git a/test/link/macho/needed_library/build.zig b/test/link/macho/needed_library/build.zig index 6e24a12c65..cb9ea38d4b 100644 --- a/test/link/macho/needed_library/build.zig +++ b/test/link/macho/needed_library/build.zig @@ -38,7 +38,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); exe.dead_strip_dylibs = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name @rpath/liba.dylib"); diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index e7b002a389..b467df2b20 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { exe.linkLibC(); exe.pagezero_size = 0x4000; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("LC 0"); check.checkNext("segname __PAGEZERO"); check.checkNext("vmaddr 0"); @@ -41,7 +41,7 @@ pub fn build(b: *std.Build) void { exe.linkLibC(); exe.pagezero_size = 0; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("LC 0"); check.checkNext("segname __TEXT"); check.checkNext("vmaddr 0"); diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index ae1037c2d7..4777629c8b 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const exe = createScenario(b, optimize, target, "search_dylibs_first"); exe.search_strategy = .dylibs_first; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name @rpath/libsearch_dylibs_first.dylib"); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 51efed4c34..c7d308d004 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkLibC(); exe.stack_size = 0x100000000; - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("cmd MAIN"); check_exe.checkNext("stacksize 100000000"); diff --git a/test/link/macho/strict_validation/build.zig b/test/link/macho/strict_validation/build.zig index a95793234b..34a0cd73fc 100644 --- a/test/link/macho/strict_validation/build.zig +++ b/test/link/macho/strict_validation/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize }); exe.linkLibC(); - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("cmd SEGMENT_64"); check_exe.checkNext("segname __LINKEDIT"); diff --git a/test/link/macho/unwind_info/build.zig b/test/link/macho/unwind_info/build.zig index b9bcea8358..4ace2a4e96 100644 --- a/test/link/macho/unwind_info/build.zig +++ b/test/link/macho/unwind_info/build.zig @@ -31,7 +31,7 @@ fn testUnwindInfo( const exe = createScenario(b, optimize, target, name); exe.link_gc_sections = dead_strip; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("segname __TEXT"); check.checkNext("sectname __gcc_except_tab"); check.checkNext("sectname __unwind_info"); diff --git a/test/link/macho/weak_framework/build.zig b/test/link/macho/weak_framework/build.zig index a4e1d6e5d9..7cc08f5b9d 100644 --- a/test/link/macho/weak_framework/build.zig +++ b/test/link/macho/weak_framework/build.zig @@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkLibC(); exe.linkFrameworkWeak("Cocoa"); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_WEAK_DYLIB"); check.checkNext("name {*}Cocoa"); test_step.dependOn(&check.step); diff --git a/test/link/macho/weak_library/build.zig b/test/link/macho/weak_library/build.zig index ac265689e3..b12ec087f5 100644 --- a/test/link/macho/weak_library/build.zig +++ b/test/link/macho/weak_library/build.zig @@ -36,7 +36,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource()); exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_WEAK_DYLIB"); check.checkNext("name @rpath/liba.dylib"); diff --git a/test/link/wasm/archive/build.zig b/test/link/wasm/archive/build.zig index f586187105..6c242a6bf1 100644 --- a/test/link/wasm/archive/build.zig +++ b/test/link/wasm/archive/build.zig @@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.use_lld = false; lib.strip = false; - const check = lib.checkObject(.wasm); + const check = lib.checkObject(); check.checkStart("Section custom"); check.checkNext("name __truncsfhf2"); // Ensure it was imported and resolved diff --git a/test/link/wasm/basic-features/build.zig b/test/link/wasm/basic-features/build.zig index f3f5320e54..be709a698f 100644 --- a/test/link/wasm/basic-features/build.zig +++ b/test/link/wasm/basic-features/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { lib.use_lld = false; // Verify the result contains the features explicitly set on the target for the library. - const check = lib.checkObject(.wasm); + const check = lib.checkObject(); check.checkStart("name target_features"); check.checkNext("features 1"); check.checkNext("+ atomics"); diff --git a/test/link/wasm/bss/build.zig b/test/link/wasm/bss/build.zig index 8e6b19c7be..bba2e7c602 100644 --- a/test/link/wasm/bss/build.zig +++ b/test/link/wasm/bss/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { lib.import_memory = true; lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); // since we import memory, make sure it exists with the correct naming check_lib.checkStart("Section import"); diff --git a/test/link/wasm/export-data/build.zig b/test/link/wasm/export-data/build.zig index 0bd10921a2..38b8c3e19e 100644 --- a/test/link/wasm/export-data/build.zig +++ b/test/link/wasm/export-data/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { lib.export_symbol_names = &.{ "foo", "bar" }; lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section global"); check_lib.checkNext("entries 3"); diff --git a/test/link/wasm/export/build.zig b/test/link/wasm/export/build.zig index 03c4baabe3..794201dbf6 100644 --- a/test/link/wasm/export/build.zig +++ b/test/link/wasm/export/build.zig @@ -42,19 +42,19 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize force_export.use_llvm = false; force_export.use_lld = false; - const check_no_export = no_export.checkObject(.wasm); + const check_no_export = no_export.checkObject(); check_no_export.checkStart("Section export"); check_no_export.checkNext("entries 1"); check_no_export.checkNext("name memory"); check_no_export.checkNext("kind memory"); - const check_dynamic_export = dynamic_export.checkObject(.wasm); + const check_dynamic_export = dynamic_export.checkObject(); check_dynamic_export.checkStart("Section export"); check_dynamic_export.checkNext("entries 2"); check_dynamic_export.checkNext("name foo"); check_dynamic_export.checkNext("kind function"); - const check_force_export = force_export.checkObject(.wasm); + const check_force_export = force_export.checkObject(); check_force_export.checkStart("Section export"); check_force_export.checkNext("entries 2"); check_force_export.checkNext("name foo"); diff --git a/test/link/wasm/extern-mangle/build.zig b/test/link/wasm/extern-mangle/build.zig index b0655cbc1f..6c292acbab 100644 --- a/test/link/wasm/extern-mangle/build.zig +++ b/test/link/wasm/extern-mangle/build.zig @@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.import_symbols = true; // import `a` and `b` lib.rdynamic = true; // export `foo` - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section import"); check_lib.checkNext("entries 2"); // a.hello & b.hello check_lib.checkNext("module a"); diff --git a/test/link/wasm/function-table/build.zig b/test/link/wasm/function-table/build.zig index e1921caee3..4ce6294727 100644 --- a/test/link/wasm/function-table/build.zig +++ b/test/link/wasm/function-table/build.zig @@ -42,9 +42,9 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize regular_table.use_llvm = false; regular_table.use_lld = false; - const check_import = import_table.checkObject(.wasm); - const check_export = export_table.checkObject(.wasm); - const check_regular = regular_table.checkObject(.wasm); + const check_import = import_table.checkObject(); + const check_export = export_table.checkObject(); + const check_regular = regular_table.checkObject(); check_import.checkStart("Section import"); check_import.checkNext("entries 1"); diff --git a/test/link/wasm/infer-features/build.zig b/test/link/wasm/infer-features/build.zig index a7cfa985d5..00fb48651b 100644 --- a/test/link/wasm/infer-features/build.zig +++ b/test/link/wasm/infer-features/build.zig @@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void { lib.addObject(c_obj); // Verify the result contains the features from the C Object file. - const check = lib.checkObject(.wasm); + const check = lib.checkObject(); check.checkStart("name target_features"); check.checkNext("features 7"); check.checkNext("+ atomics"); diff --git a/test/link/wasm/producers/build.zig b/test/link/wasm/producers/build.zig index 41952b0adb..7b7cefd7e0 100644 --- a/test/link/wasm/producers/build.zig +++ b/test/link/wasm/producers/build.zig @@ -27,7 +27,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const version_fmt = "version " ++ builtin.zig_version_string; - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("name producers"); check_lib.checkNext("fields 2"); check_lib.checkNext("field_name language"); diff --git a/test/link/wasm/segments/build.zig b/test/link/wasm/segments/build.zig index f8e16eee24..281d8ae32b 100644 --- a/test/link/wasm/segments/build.zig +++ b/test/link/wasm/segments/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.strip = false; lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section data"); check_lib.checkNext("entries 2"); // rodata & data, no bss because we're exporting memory diff --git a/test/link/wasm/stack_pointer/build.zig b/test/link/wasm/stack_pointer/build.zig index 41bdd828f2..794b7d27fb 100644 --- a/test/link/wasm/stack_pointer/build.zig +++ b/test/link/wasm/stack_pointer/build.zig @@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); // ensure global exists and its initial value is equal to explitic stack size check_lib.checkStart("Section global"); diff --git a/test/link/wasm/type/build.zig b/test/link/wasm/type/build.zig index df8cbad021..4a8395645f 100644 --- a/test/link/wasm/type/build.zig +++ b/test/link/wasm/type/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.strip = false; lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section type"); // only 2 entries, although we have 3 functions. // This is to test functions with the same function signature diff --git a/test/nvptx.zig b/test/nvptx.zig new file mode 100644 index 0000000000..57853a657d --- /dev/null +++ b/test/nvptx.zig @@ -0,0 +1,106 @@ +const std = @import("std"); +const Cases = @import("src/Cases.zig"); + +pub fn addCases(ctx: *Cases) !void { + { + var case = addPtx(ctx, "simple addition and subtraction"); + + case.addCompile( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void { + \\ const x = add(a, 7); + \\ var y = add(2, 0); + \\ y -= x; + \\ out.* = y; + \\} + ); + } + + { + var case = addPtx(ctx, "read special registers"); + + case.addCompile( + \\fn threadIdX() u32 { + \\ return asm ("mov.u32 \t%[r], %tid.x;" + \\ : [r] "=r" (-> u32), + \\ ); + \\} + \\ + \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void { + \\ const i = threadIdX(); + \\ out[i] = a[i] + 7; + \\} + ); + } + + { + var case = addPtx(ctx, "address spaces"); + + case.addCompile( + \\var x: i32 addrspace(.global) = 0; + \\ + \\pub export fn increment(out: *i32) callconv(.PtxKernel) void { + \\ x += 1; + \\ out.* = x; + \\} + ); + } + + { + var case = addPtx(ctx, "reduce in shared mem"); + case.addCompile( + \\fn threadIdX() u32 { + \\ return asm ("mov.u32 \t%[r], %tid.x;" + \\ : [r] "=r" (-> u32), + \\ ); + \\} + \\ + \\ var _sdata: [1024]f32 addrspace(.shared) = undefined; + \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void { + \\ var sdata = @addrSpaceCast(.generic, &_sdata); + \\ const tid: u32 = threadIdX(); + \\ var sum = d_x[tid]; + \\ sdata[tid] = sum; + \\ asm volatile ("bar.sync \t0;"); + \\ var s: u32 = 512; + \\ while (s > 0) : (s = s >> 1) { + \\ if (tid < s) { + \\ sum += sdata[tid + s]; + \\ sdata[tid] = sum; + \\ } + \\ asm volatile ("bar.sync \t0;"); + \\ } + \\ + \\ if (tid == 0) { + \\ out.* = sum; + \\ } + \\ } + ); + } +} + +const nvptx_target = std.zig.CrossTarget{ + .cpu_arch = .nvptx64, + .os_tag = .cuda, +}; + +pub fn addPtx( + ctx: *Cases, + name: []const u8, +) *Cases.Case { + ctx.cases.append(.{ + .name = name, + .target = nvptx_target, + .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), + .output_mode = .Obj, + .deps = std.ArrayList(Cases.DepModule).init(ctx.cases.allocator), + .link_libc = false, + .backend = .llvm, + // Bug in Debug mode + .optimize_mode = .ReleaseSafe, + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} diff --git a/test/src/Cases.zig b/test/src/Cases.zig new file mode 100644 index 0000000000..27b0cf6b21 --- /dev/null +++ b/test/src/Cases.zig @@ -0,0 +1,1587 @@ +gpa: Allocator, +arena: Allocator, +cases: std.ArrayList(Case), +incremental_cases: std.ArrayList(IncrementalCase), + +pub const IncrementalCase = struct { + base_path: []const u8, +}; + +pub const Update = struct { + /// The input to the current update. We simulate an incremental update + /// with the file's contents changed to this value each update. + /// + /// This value can change entirely between updates, which would be akin + /// to deleting the source file and creating a new one from scratch; or + /// you can keep it mostly consistent, with small changes, testing the + /// effects of the incremental compilation. + files: std.ArrayList(File), + /// This is a description of what happens with the update, for debugging + /// purposes. + name: []const u8, + case: union(enum) { + /// Check that it compiles with no errors. + Compile: void, + /// 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 stderr template, which + /// gets some values substituted. + Error: []const []const u8, + /// An execution update compiles and runs the input, testing the + /// stdout against the expected results + /// This is a slice containing the expected message. + Execution: []const u8, + /// A header update compiles the input with the equivalent of + /// `-femit-h` and tests the produced header against the + /// expected result + Header: []const u8, + }, + + pub fn addSourceFile(update: *Update, name: []const u8, src: [:0]const u8) void { + update.files.append(.{ .path = name, .src = src }) catch @panic("out of memory"); + } +}; + +pub const File = struct { + src: [:0]const u8, + path: []const u8, +}; + +pub const DepModule = struct { + name: []const u8, + path: []const u8, +}; + +pub const Backend = enum { + stage1, + stage2, + llvm, +}; + +/// 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 { + /// The name of the test case. This is shown if a test fails, and + /// otherwise ignored. + name: []const u8, + /// The platform the test targets. For non-native platforms, an emulator + /// such as QEMU is required for tests to complete. + target: CrossTarget, + /// In order to be able to run e.g. Execution updates, this must be set + /// to Executable. + output_mode: std.builtin.OutputMode, + optimize_mode: std.builtin.Mode = .Debug, + updates: std.ArrayList(Update), + emit_h: bool = false, + is_test: bool = false, + expect_exact: bool = false, + backend: Backend = .stage2, + link_libc: bool = false, + + deps: std.ArrayList(DepModule), + + pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void { + const update = &case.updates.items[case.updates.items.len - 1]; + update.files.append(.{ .path = name, .src = src }) catch @panic("OOM"); + } + + pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void { + case.deps.append(.{ + .name = name, + .path = path, + }) catch @panic("out of memory"); + } + + /// Adds a subcase in which the module is updated with `src`, compiled, + /// run, and the output is tested against `result`. + pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { + self.updates.append(.{ + .files = std.ArrayList(File).init(self.updates.allocator), + .name = "update", + .case = .{ .Execution = result }, + }) catch @panic("out of memory"); + addSourceFile(self, "tmp.zig", src); + } + + pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { + return self.addErrorNamed("update", src, errors); + } + + /// 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 + /// the form `:line:column: error: message`. + pub fn addErrorNamed( + self: *Case, + name: []const u8, + src: [:0]const u8, + errors: []const []const u8, + ) void { + assert(errors.len != 0); + self.updates.append(.{ + .files = std.ArrayList(File).init(self.updates.allocator), + .name = name, + .case = .{ .Error = errors }, + }) catch @panic("out of memory"); + addSourceFile(self, "tmp.zig", src); + } + + /// Adds a subcase in which the module is updated with `src`, and + /// asserts that it compiles without issue + pub fn addCompile(self: *Case, src: [:0]const u8) void { + self.updates.append(.{ + .files = std.ArrayList(File).init(self.updates.allocator), + .name = "compile", + .case = .{ .Compile = {} }, + }) catch @panic("out of memory"); + addSourceFile(self, "tmp.zig", src); + } +}; + +pub fn addExe( + ctx: *Cases, + 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, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +/// Adds a test case for Zig input, producing an executable +pub fn exe(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + return ctx.addExe(name, target); +} + +pub fn exeFromCompiledC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + var target_adjusted = target; + target_adjusted.ofmt = .c; + ctx.cases.append(Case{ + .name = name, + .target = target_adjusted, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Exe, + .deps = std.ArrayList(DepModule).init(ctx.arena), + .link_libc = true, + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +/// Adds a test case that uses the LLVM backend to emit an executable. +/// Currently this implies linking libc, because only then we can generate a testable executable. +pub fn exeUsingLlvmBackend(ctx: *Cases, 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, + .deps = std.ArrayList(DepModule).init(ctx.arena), + .backend = .llvm, + .link_libc = true, + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +pub fn addObj( + ctx: *Cases, + name: []const u8, + target: CrossTarget, +) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Obj, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +pub fn addTest( + ctx: *Cases, + 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, + .is_test = true, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +/// Adds a test case for Zig input, producing an object file. +pub fn obj(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + return ctx.addObj(name, target); +} + +/// Adds a test case for ZIR input, producing an object file. +pub fn objZIR(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + return ctx.addObj(name, target, .ZIR); +} + +/// Adds a test case for Zig or ZIR input, producing C code. +pub fn addC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + var target_adjusted = target; + target_adjusted.ofmt = std.Target.ObjectFormat.c; + ctx.cases.append(Case{ + .name = name, + .target = target_adjusted, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Obj, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +pub fn addCompareOutput( + ctx: *Cases, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, +) void { + ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout); +} + +/// Adds a test case that compiles the Zig source given in `src`, executes +/// it, runs it, and tests the output against `expected_stdout` +pub fn compareOutput( + ctx: *Cases, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, +) void { + return ctx.addCompareOutput(name, src, expected_stdout); +} + +pub fn addTransform( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + result: [:0]const u8, +) void { + ctx.addObj(name, target).addTransform(src, result); +} + +/// Adds a test case that compiles the Zig given in `src` to ZIR and tests +/// the ZIR against `result` +pub fn transform( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + result: [:0]const u8, +) void { + ctx.addTransform(name, target, src, result); +} + +pub fn addError( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, +) void { + ctx.addObj(name, target).addError(src, expected_errors); +} + +/// Adds a test case that ensures that the Zig given in `src` fails to +/// compile for the expected reasons, given in sequential order in +/// `expected_errors` in the form `:line:column: error: message`. +pub fn compileError( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, +) void { + ctx.addError(name, target, src, expected_errors); +} + +/// Adds a test case that asserts that the Zig given in `src` compiles +/// without any errors. +pub fn addCompile( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, +) void { + ctx.addObj(name, target).addCompile(src); +} + +/// Adds a test for each file in the provided directory. +/// Testing strategy (TestStrategy) is inferred automatically from filenames. +/// Recurses nested directories. +/// +/// Each file should include a test manifest as a contiguous block of comments at +/// the end of the file. The first line should be the test type, followed by a set of +/// key-value config values, followed by a blank line, then the expected output. +pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void { + var current_file: []const u8 = "none"; + ctx.addFromDirInner(dir, ¤t_file) catch |err| { + std.debug.panic("test harness failed to process file '{s}': {s}\n", .{ + current_file, @errorName(err), + }); + }; +} + +fn addFromDirInner( + ctx: *Cases, + iterable_dir: std.fs.IterableDir, + /// This is kept up to date with the currently being processed file so + /// that if any errors occur the caller knows it happened during this file. + current_file: *[]const u8, +) !void { + var it = try iterable_dir.walk(ctx.arena); + var filenames = std.ArrayList([]const u8).init(ctx.arena); + + while (try it.next()) |entry| { + if (entry.kind != .File) continue; + + // Ignore stuff such as .swp files + switch (Compilation.classifyFileExt(entry.basename)) { + .unknown => continue, + else => {}, + } + try filenames.append(try ctx.arena.dupe(u8, entry.path)); + } + + // Sort filenames, so that incremental tests are contiguous and in-order + sortTestFilenames(filenames.items); + + var test_it = TestIterator{ .filenames = filenames.items }; + while (test_it.next()) |maybe_batch| { + const batch = maybe_batch orelse break; + const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent; + const filename = batch[0]; + current_file.* = filename; + if (strategy == .incremental) { + try ctx.incremental_cases.append(.{ .base_path = filename }); + continue; + } + + const max_file_size = 10 * 1024 * 1024; + const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0); + + // Parse the manifest + var manifest = try TestManifest.parse(ctx.arena, src); + + const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend); + const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget); + const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); + const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); + + var cases = std.ArrayList(usize).init(ctx.arena); + + // Cross-product to get all possible test combinations + for (backends) |backend| { + for (targets) |target| { + const next = ctx.cases.items.len; + try ctx.cases.append(.{ + .name = std.fs.path.stem(filename), + .target = target, + .backend = backend, + .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), + .is_test = is_test, + .output_mode = output_mode, + .link_libc = backend == .llvm, + .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + }); + try cases.append(next); + } + } + + for (cases.items) |case_index| { + const case = &ctx.cases.items[case_index]; + switch (manifest.type) { + .compile => { + case.addCompile(src); + }, + .@"error" => { + const errors = try manifest.trailingAlloc(ctx.arena); + case.addError(src, errors); + }, + .run => { + var output = std.ArrayList(u8).init(ctx.arena); + var trailing_it = manifest.trailing(); + while (trailing_it.next()) |line| { + try output.appendSlice(line); + try output.append('\n'); + } + if (output.items.len > 0) { + try output.resize(output.items.len - 1); + } + case.addCompareOutput(src, try output.toOwnedSlice()); + }, + .cli => @panic("TODO cli tests"), + } + } + } else |err| { + // make sure the current file is set to the file that produced an error + current_file.* = test_it.currentFilename(); + return err; + } +} + +pub fn init(gpa: Allocator, arena: Allocator) Cases { + return .{ + .gpa = gpa, + .cases = std.ArrayList(Case).init(gpa), + .incremental_cases = std.ArrayList(IncrementalCase).init(gpa), + .arena = arena, + }; +} + +pub fn lowerToBuildSteps( + self: *Cases, + b: *std.Build, + parent_step: *std.Build.Step, + opt_test_filter: ?[]const u8, + cases_dir_path: []const u8, + incremental_exe: *std.Build.CompileStep, +) void { + for (self.incremental_cases.items) |incr_case| { + if (opt_test_filter) |test_filter| { + if (std.mem.indexOf(u8, incr_case.base_path, test_filter) == null) continue; + } + const case_base_path_with_dir = std.fs.path.join(b.allocator, &.{ + cases_dir_path, incr_case.base_path, + }) catch @panic("OOM"); + const run = b.addRunArtifact(incremental_exe); + run.setName(incr_case.base_path); + run.addArgs(&.{ + case_base_path_with_dir, + b.zig_exe, + }); + run.expectStdOutEqual(""); + parent_step.dependOn(&run.step); + } + + for (self.cases.items) |case| { + if (case.updates.items.len != 1) continue; // handled with incremental_cases above + assert(case.updates.items.len == 1); + const update = case.updates.items[0]; + + if (opt_test_filter) |test_filter| { + if (std.mem.indexOf(u8, case.name, test_filter) == null) continue; + } + + const writefiles = b.addWriteFiles(); + for (update.files.items) |file| { + writefiles.add(file.path, file.src); + } + const root_source_file = writefiles.getFileSource(update.files.items[0].path).?; + + const artifact = switch (case.output_mode) { + .Obj => b.addObject(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }), + .Lib => b.addStaticLibrary(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }), + .Exe => if (case.is_test) b.addTest(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }) else b.addExecutable(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }), + }; + + if (case.link_libc) artifact.linkLibC(); + + switch (case.backend) { + .stage1 => continue, + .stage2 => { + artifact.use_llvm = false; + artifact.use_lld = false; + }, + .llvm => { + artifact.use_llvm = true; + }, + } + + for (case.deps.items) |dep| { + artifact.addAnonymousModule(dep.name, .{ + .source_file = writefiles.getFileSource(dep.path).?, + }); + } + + switch (update.case) { + .Compile => { + parent_step.dependOn(&artifact.step); + }, + .CompareObjectFile => |expected_output| { + const check = b.addCheckFile(artifact.getOutputSource(), .{ + .expected_exact = expected_output, + }); + + parent_step.dependOn(&check.step); + }, + .Error => |expected_msgs| { + assert(expected_msgs.len != 0); + artifact.expect_errors = expected_msgs; + parent_step.dependOn(&artifact.step); + }, + .Execution => |expected_stdout| { + if (case.is_test) { + parent_step.dependOn(&artifact.step); + } else { + const run = b.addRunArtifact(artifact); + run.skip_foreign_checks = true; + run.expectStdOutEqual(expected_stdout); + + parent_step.dependOn(&run.step); + } + }, + .Header => @panic("TODO"), + } + } +} + +/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig", +/// "foo.1.zig", etc.) are contiguous and appear in numerical order. +fn sortTestFilenames(filenames: [][]const u8) void { + const Context = struct { + pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool { + const a_parts = getTestFileNameParts(a); + const b_parts = getTestFileNameParts(b); + + // Sort ".X." based on "" and "" first + return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) { + .lt => true, + .gt => false, + .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) { + .lt => true, + .gt => false, + .eq => { + // a and b differ only in their ".X" part + + // Sort "." before any ".X." + if (a_parts.test_index) |a_index| { + if (b_parts.test_index) |b_index| { + // Make sure that incremental tests appear in linear order + return a_index < b_index; + } else { + return false; + } + } else { + return b_parts.test_index != null; + } + }, + }, + }; + } + }; + std.sort.sort([]const u8, filenames, Context{}, Context.lessThan); +} + +/// Iterates a set of filenames extracting batches that are either incremental +/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.). +/// Assumes filenames are sorted. +const TestIterator = struct { + start: usize = 0, + end: usize = 0, + filenames: []const []const u8, + /// reset on each call to `next` + index: usize = 0, + + const Error = error{InvalidIncrementalTestIndex}; + + fn next(it: *TestIterator) Error!?[]const []const u8 { + try it.nextInner(); + if (it.start == it.end) return null; + return it.filenames[it.start..it.end]; + } + + fn nextInner(it: *TestIterator) Error!void { + it.start = it.end; + if (it.end == it.filenames.len) return; + if (it.end + 1 == it.filenames.len) { + it.end += 1; + return; + } + + const remaining = it.filenames[it.end..]; + it.index = 0; + while (it.index < remaining.len - 1) : (it.index += 1) { + // First, check if this file is part of an incremental update sequence + // Split filename into ".." + const prev_parts = getTestFileNameParts(remaining[it.index]); + const new_parts = getTestFileNameParts(remaining[it.index + 1]); + + // If base_name and file_ext match, these files are in the same test sequence + // and the new one should be the incremented version of the previous test + if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and + std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext)) + { + // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1 + if (prev_parts.test_index == null) + return error.InvalidIncrementalTestIndex; + if (new_parts.test_index == null) + return error.InvalidIncrementalTestIndex; + if (new_parts.test_index.? != prev_parts.test_index.? + 1) + return error.InvalidIncrementalTestIndex; + } else { + // This is not the same test sequence, so the new file must be the first file + // in a new sequence ("*.0.zig") or an independent test file ("*.zig") + if (new_parts.test_index != null and new_parts.test_index.? != 0) + return error.InvalidIncrementalTestIndex; + + it.end += it.index + 1; + break; + } + } else { + it.end += remaining.len; + } + } + + /// In the event of an `error.InvalidIncrementalTestIndex`, this function can + /// be used to find the current filename that was being processed. + /// Asserts the iterator hasn't reached the end. + fn currentFilename(it: TestIterator) []const u8 { + assert(it.end != it.filenames.len); + const remaining = it.filenames[it.end..]; + return remaining[it.index + 1]; + } +}; + +/// For a filename in the format ".X." or ".", returns +/// "", "" and X parsed as a decimal number. If X is not present, or +/// cannot be parsed as a decimal number, it is treated as part of +fn getTestFileNameParts(name: []const u8) struct { + base_name: []const u8, + file_ext: []const u8, + test_index: ?usize, +} { + const file_ext = std.fs.path.extension(name); + const trimmed = name[0 .. name.len - file_ext.len]; // Trim off "." + const maybe_index = std.fs.path.extension(trimmed); // Extract ".X" + + // Attempt to parse index + const index: ?usize = if (maybe_index.len > 0) + std.fmt.parseInt(usize, maybe_index[1..], 10) catch null + else + null; + + // Adjust "" extent based on parsing success + const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0; + return .{ + .base_name = name[0..base_name_end], + .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext, + .test_index = index, + }; +} + +const TestStrategy = enum { + /// Execute tests as independent compilations, unless they are explicitly + /// incremental ("foo.0.zig", "foo.1.zig", etc.) + independent, + /// Execute all tests as incremental updates to a single compilation. Explicitly + /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order + incremental, +}; + +/// Default config values for known test manifest key-value pairings. +/// Currently handled defaults are: +/// * backend +/// * target +/// * output_mode +/// * is_test +const TestManifestConfigDefaults = struct { + /// Asserts if the key doesn't exist - yep, it's an oversight alright. + fn get(@"type": TestManifest.Type, key: []const u8) []const u8 { + if (std.mem.eql(u8, key, "backend")) { + return "stage2"; + } else if (std.mem.eql(u8, key, "target")) { + if (@"type" == .@"error") { + return "native"; + } + comptime { + var defaults: []const u8 = ""; + // TODO should we only return "mainstream" targets by default here? + // TODO we should also specify ABIs explicitly as the backends are + // getting more and more complete + // Linux + inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| { + defaults = defaults ++ arch ++ "-linux" ++ ","; + } + // macOS + inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| { + defaults = defaults ++ arch ++ "-macos" ++ ","; + } + // Windows + defaults = defaults ++ "x86_64-windows" ++ ","; + // Wasm + defaults = defaults ++ "wasm32-wasi"; + return defaults; + } + } else if (std.mem.eql(u8, key, "output_mode")) { + return switch (@"type") { + .@"error" => "Obj", + .run => "Exe", + .compile => "Obj", + .cli => @panic("TODO test harness for CLI tests"), + }; + } else if (std.mem.eql(u8, key, "is_test")) { + return "0"; + } else unreachable; + } +}; + +/// Manifest syntax example: +/// (see https://github.com/ziglang/zig/issues/11288) +/// +/// error +/// backend=stage1,stage2 +/// output_mode=exe +/// +/// :3:19: error: foo +/// +/// run +/// target=x86_64-linux,aarch64-macos +/// +/// I am expected stdout! Hello! +/// +/// cli +/// +/// build test +const TestManifest = struct { + type: Type, + config_map: std.StringHashMap([]const u8), + trailing_bytes: []const u8 = "", + + const Type = enum { + @"error", + run, + cli, + compile, + }; + + const TrailingIterator = struct { + inner: std.mem.TokenIterator(u8), + + fn next(self: *TrailingIterator) ?[]const u8 { + const next_inner = self.inner.next() orelse return null; + return std.mem.trim(u8, next_inner[2..], " \t"); + } + }; + + fn ConfigValueIterator(comptime T: type) type { + return struct { + inner: std.mem.SplitIterator(u8), + + fn next(self: *@This()) !?T { + const next_raw = self.inner.next() orelse return null; + const parseFn = getDefaultParser(T); + return try parseFn(next_raw); + } + }; + } + + fn parse(arena: Allocator, bytes: []const u8) !TestManifest { + // The manifest is the last contiguous block of comments in the file + // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" + var start: ?usize = null; + var end: usize = bytes.len; + if (bytes.len > 0) { + var cursor: usize = bytes.len - 1; + while (true) { + // Move to beginning of line + while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1; + + if (std.mem.startsWith(u8, bytes[cursor..], "//")) { + start = cursor; // Contiguous comment line, include in manifest + } else { + if (start != null) break; // Encountered non-comment line, end of manifest + + // We ignore all-whitespace lines following the comment block, but anything else + // means that there is no manifest present. + if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) { + end = cursor; + } else break; // If it's not whitespace, there is no manifest + } + + // Move to previous line + if (cursor != 0) cursor -= 1 else break; + } + } + + const actual_start = start orelse return error.MissingTestManifest; + const manifest_bytes = bytes[actual_start..end]; + + var it = std.mem.tokenize(u8, manifest_bytes, "\r\n"); + + // First line is the test type + const tt: Type = blk: { + const line = it.next() orelse return error.MissingTestCaseType; + const raw = std.mem.trim(u8, line[2..], " \t"); + if (std.mem.eql(u8, raw, "error")) { + break :blk .@"error"; + } else if (std.mem.eql(u8, raw, "run")) { + break :blk .run; + } else if (std.mem.eql(u8, raw, "cli")) { + break :blk .cli; + } else if (std.mem.eql(u8, raw, "compile")) { + break :blk .compile; + } else { + std.log.warn("unknown test case type requested: {s}", .{raw}); + return error.UnknownTestCaseType; + } + }; + + var manifest: TestManifest = .{ + .type = tt, + .config_map = std.StringHashMap([]const u8).init(arena), + }; + + // Any subsequent line until a blank comment line is key=value(s) pair + while (it.next()) |line| { + const trimmed = std.mem.trim(u8, line[2..], " \t"); + if (trimmed.len == 0) break; + + // Parse key=value(s) + var kv_it = std.mem.split(u8, trimmed, "="); + const key = kv_it.first(); + try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig); + } + + // Finally, trailing is expected output + manifest.trailing_bytes = manifest_bytes[it.index..]; + + return manifest; + } + + fn getConfigForKey( + self: TestManifest, + key: []const u8, + comptime T: type, + ) ConfigValueIterator(T) { + const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key); + return ConfigValueIterator(T){ + .inner = std.mem.split(u8, bytes, ","), + }; + } + + fn getConfigForKeyAlloc( + self: TestManifest, + allocator: Allocator, + key: []const u8, + comptime T: type, + ) ![]const T { + var out = std.ArrayList(T).init(allocator); + defer out.deinit(); + var it = self.getConfigForKey(key, T); + while (try it.next()) |item| { + try out.append(item); + } + return try out.toOwnedSlice(); + } + + fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T { + var it = self.getConfigForKey(key, T); + const res = (try it.next()) orelse unreachable; + assert((try it.next()) == null); + return res; + } + + fn trailing(self: TestManifest) TrailingIterator { + return .{ + .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"), + }; + } + + fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 { + var out = std.ArrayList([]const u8).init(allocator); + defer out.deinit(); + var it = self.trailing(); + while (it.next()) |line| { + try out.append(line); + } + return try out.toOwnedSlice(); + } + + fn ParseFn(comptime T: type) type { + return fn ([]const u8) anyerror!T; + } + + fn getDefaultParser(comptime T: type) ParseFn(T) { + if (T == CrossTarget) return struct { + fn parse(str: []const u8) anyerror!T { + var opts = CrossTarget.ParseOptions{ + .arch_os_abi = str, + }; + return try CrossTarget.parse(opts); + } + }.parse; + + switch (@typeInfo(T)) { + .Int => return struct { + fn parse(str: []const u8) anyerror!T { + return try std.fmt.parseInt(T, str, 0); + } + }.parse, + .Bool => return struct { + fn parse(str: []const u8) anyerror!T { + const as_int = try std.fmt.parseInt(u1, str, 0); + return as_int > 0; + } + }.parse, + .Enum => return struct { + fn parse(str: []const u8) anyerror!T { + return std.meta.stringToEnum(T, str) orelse { + std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str }); + return error.UnknownEnumVariant; + }; + } + }.parse, + .Struct => @compileError("no default parser for " ++ @typeName(T)), + else => @compileError("no default parser for " ++ @typeName(T)), + } + } +}; + +const Cases = @This(); +const builtin = @import("builtin"); +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const CrossTarget = std.zig.CrossTarget; +const Compilation = @import("../../src/Compilation.zig"); +const zig_h = @import("../../src/link.zig").File.C.zig_h; +const introspect = @import("../../src/introspect.zig"); +const ThreadPool = std.Thread.Pool; +const WaitGroup = std.Thread.WaitGroup; +const build_options = @import("build_options"); +const Package = @import("../../src/Package.zig"); + +pub const std_options = struct { + pub const log_level: std.log.Level = .err; +}; + +var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{ + .stack_trace_frames = build_options.mem_leak_frames, +}){}; + +// TODO: instead of embedding the compiler in this process, spawn the compiler +// as a sub-process and communicate the updates using the compiler protocol. +pub fn main() !void { + const use_gpa = build_options.force_gpa or !builtin.link_libc; + const gpa = gpa: { + if (use_gpa) { + break :gpa general_purpose_allocator.allocator(); + } + // We would prefer to use raw libc allocator here, but cannot + // use it if it won't support the alignment we need. + if (@alignOf(std.c.max_align_t) < @alignOf(i128)) { + break :gpa std.heap.c_allocator; + } + break :gpa std.heap.raw_c_allocator; + }; + + var single_threaded_arena = std.heap.ArenaAllocator.init(gpa); + defer single_threaded_arena.deinit(); + + var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ + .child_allocator = single_threaded_arena.allocator(), + }; + const arena = thread_safe_arena.allocator(); + + const args = try std.process.argsAlloc(arena); + const case_file_path = args[1]; + const zig_exe_path = args[2]; + + var filenames = std.ArrayList([]const u8).init(arena); + + const case_dirname = std.fs.path.dirname(case_file_path).?; + var iterable_dir = try std.fs.cwd().openIterableDir(case_dirname, .{}); + defer iterable_dir.close(); + + if (std.mem.endsWith(u8, case_file_path, ".0.zig")) { + const stem = case_file_path[case_dirname.len + 1 .. case_file_path.len - "0.zig".len]; + var it = iterable_dir.iterate(); + while (try it.next()) |entry| { + if (entry.kind != .File) continue; + if (!std.mem.startsWith(u8, entry.name, stem)) continue; + try filenames.append(try std.fs.path.join(arena, &.{ case_dirname, entry.name })); + } + } else { + try filenames.append(case_file_path); + } + + if (filenames.items.len == 0) { + std.debug.print("failed to find the input source file(s) from '{s}'\n", .{ + case_file_path, + }); + std.process.exit(1); + } + + // Sort filenames, so that incremental tests are contiguous and in-order + sortTestFilenames(filenames.items); + + var ctx = Cases.init(gpa, arena); + + var test_it = TestIterator{ .filenames = filenames.items }; + while (test_it.next()) |maybe_batch| { + const batch = maybe_batch orelse break; + const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent; + var cases = std.ArrayList(usize).init(arena); + + for (batch) |filename| { + const max_file_size = 10 * 1024 * 1024; + const src = try iterable_dir.dir.readFileAllocOptions(arena, filename, max_file_size, null, 1, 0); + + // Parse the manifest + var manifest = try TestManifest.parse(arena, src); + + if (cases.items.len == 0) { + const backends = try manifest.getConfigForKeyAlloc(arena, "backend", Backend); + const targets = try manifest.getConfigForKeyAlloc(arena, "target", CrossTarget); + const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); + const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); + + // Cross-product to get all possible test combinations + for (backends) |backend| { + for (targets) |target| { + const next = ctx.cases.items.len; + try ctx.cases.append(.{ + .name = std.fs.path.stem(filename), + .target = target, + .backend = backend, + .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), + .is_test = is_test, + .output_mode = output_mode, + .link_libc = backend == .llvm, + .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + }); + try cases.append(next); + } + } + } + + for (cases.items) |case_index| { + const case = &ctx.cases.items[case_index]; + switch (manifest.type) { + .compile => { + case.addCompile(src); + }, + .@"error" => { + const errors = try manifest.trailingAlloc(arena); + switch (strategy) { + .independent => { + case.addError(src, errors); + }, + .incremental => { + case.addErrorNamed("update", src, errors); + }, + } + }, + .run => { + var output = std.ArrayList(u8).init(arena); + var trailing_it = manifest.trailing(); + while (trailing_it.next()) |line| { + try output.appendSlice(line); + try output.append('\n'); + } + if (output.items.len > 0) { + try output.resize(output.items.len - 1); + } + case.addCompareOutput(src, try output.toOwnedSlice()); + }, + .cli => @panic("TODO cli tests"), + } + } + } + } else |err| { + return err; + } + + return runCases(&ctx, zig_exe_path); +} + +fn runCases(self: *Cases, zig_exe_path: []const u8) !void { + const host = try std.zig.system.NativeTargetInfo.detect(.{}); + + var progress = std.Progress{}; + const root_node = progress.start("compiler", self.cases.items.len); + progress.terminal = null; + defer root_node.end(); + + var zig_lib_directory = try introspect.findZigLibDir(self.gpa); + defer zig_lib_directory.handle.close(); + defer self.gpa.free(zig_lib_directory.path.?); + + var aux_thread_pool: ThreadPool = undefined; + try aux_thread_pool.init(.{ .allocator = self.gpa }); + defer aux_thread_pool.deinit(); + + // Use the same global cache dir for all the tests, such that we for example don't have to + // rebuild musl libc for every case (when LLVM backend is enabled). + var global_tmp = std.testing.tmpDir(.{}); + defer global_tmp.cleanup(); + + var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{}); + defer cache_dir.close(); + const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path }); + defer self.gpa.free(tmp_dir_path); + + const global_cache_directory: Compilation.Directory = .{ + .handle = cache_dir, + .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }), + }; + defer self.gpa.free(global_cache_directory.path.?); + + { + for (self.cases.items) |*case| { + if (build_options.skip_non_native) { + if (case.target.getCpuArch() != builtin.cpu.arch) + continue; + if (case.target.getObjectFormat() != builtin.object_format) + continue; + } + + // Skip tests that require LLVM backend when it is not available + if (!build_options.have_llvm and case.backend == .llvm) + continue; + + assert(case.backend != .stage1); + + if (build_options.test_filter) |test_filter| { + if (std.mem.indexOf(u8, case.name, test_filter) == null) continue; + } + + var prg_node = root_node.start(case.name, case.updates.items.len); + prg_node.activate(); + defer prg_node.end(); + + try runOneCase( + self.gpa, + &prg_node, + case.*, + zig_lib_directory, + zig_exe_path, + &aux_thread_pool, + global_cache_directory, + host, + ); + } + } +} + +fn runOneCase( + allocator: Allocator, + root_node: *std.Progress.Node, + case: Case, + zig_lib_directory: Compilation.Directory, + zig_exe_path: []const u8, + thread_pool: *ThreadPool, + global_cache_directory: Compilation.Directory, + host: std.zig.system.NativeTargetInfo, +) !void { + const tmp_src_path = "tmp.zig"; + const enable_rosetta = build_options.enable_rosetta; + const enable_qemu = build_options.enable_qemu; + const enable_wine = build_options.enable_wine; + const enable_wasmtime = build_options.enable_wasmtime; + const enable_darling = build_options.enable_darling; + const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; + + const target_info = try std.zig.system.NativeTargetInfo.detect(case.target); + const target = target_info.target; + + var arena_allocator = std.heap.ArenaAllocator.init(allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); + defer cache_dir.close(); + + const tmp_dir_path = try std.fs.path.join( + arena, + &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path }, + ); + const local_cache_path = try std.fs.path.join( + arena, + &[_][]const u8{ tmp_dir_path, "zig-cache" }, + ); + + const zig_cache_directory: Compilation.Directory = .{ + .handle = cache_dir, + .path = local_cache_path, + }; + + var main_pkg: Package = .{ + .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir }, + .root_src_path = tmp_src_path, + }; + defer { + var it = main_pkg.table.iterator(); + while (it.next()) |kv| { + allocator.free(kv.key_ptr.*); + kv.value_ptr.*.destroy(allocator); + } + main_pkg.table.deinit(allocator); + } + + for (case.deps.items) |dep| { + var pkg = try Package.create( + allocator, + tmp_dir_path, + dep.path, + ); + errdefer pkg.destroy(allocator); + try main_pkg.add(allocator, dep.name, pkg); + } + + const bin_name = try std.zig.binNameAlloc(arena, .{ + .root_name = "test_case", + .target = target, + .output_mode = case.output_mode, + }); + + const emit_directory: Compilation.Directory = .{ + .path = tmp_dir_path, + .handle = tmp.dir, + }; + const emit_bin: Compilation.EmitLoc = .{ + .directory = emit_directory, + .basename = bin_name, + }; + const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{ + .directory = emit_directory, + .basename = "test_case.h", + } else null; + const use_llvm: bool = switch (case.backend) { + .llvm => true, + else => false, + }; + const comp = try Compilation.create(allocator, .{ + .local_cache_directory = zig_cache_directory, + .global_cache_directory = global_cache_directory, + .zig_lib_directory = zig_lib_directory, + .thread_pool = thread_pool, + .root_name = "test_case", + .target = target, + // TODO: support tests for object file building, and library builds + // and linking. This will require a rework to support multi-file + // tests. + .output_mode = case.output_mode, + .is_test = case.is_test, + .optimize_mode = case.optimize_mode, + .emit_bin = emit_bin, + .emit_h = emit_h, + .main_pkg = &main_pkg, + .keep_source_files_loaded = true, + .is_native_os = case.target.isNativeOs(), + .is_native_abi = case.target.isNativeAbi(), + .dynamic_linker = target_info.dynamic_linker.get(), + .link_libc = case.link_libc, + .use_llvm = use_llvm, + .self_exe_path = zig_exe_path, + // TODO instead of turning off color, pass in a std.Progress.Node + .color = .off, + .reference_trace = 0, + // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in + // until the auto-select mechanism deems them worthy + .use_lld = switch (case.backend) { + .stage2 => false, + else => null, + }, + }); + defer comp.destroy(); + + update: for (case.updates.items, 0..) |update, update_index| { + var update_node = root_node.start(update.name, 3); + update_node.activate(); + defer update_node.end(); + + var sync_node = update_node.start("write", 0); + sync_node.activate(); + for (update.files.items) |file| { + try tmp.dir.writeFile(file.path, file.src); + } + sync_node.end(); + + var module_node = update_node.start("parse/analysis/codegen", 0); + module_node.activate(); + try comp.makeBinFileWritable(); + try comp.update(&module_node); + module_node.end(); + + if (update.case != .Error) { + var all_errors = try comp.getAllErrorsAlloc(); + defer all_errors.deinit(allocator); + if (all_errors.errorMessageCount() > 0) { + all_errors.renderToStdErr(.{ + .ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()), + }); + // TODO print generated C code + return error.UnexpectedCompileErrors; + } + } + + switch (update.case) { + .Header => |expected_output| { + var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only }); + defer file.close(); + const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); + + try std.testing.expectEqualStrings(expected_output, out); + }, + .CompareObjectFile => |expected_output| { + var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only }); + defer file.close(); + const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); + + try std.testing.expectEqualStrings(expected_output, out); + }, + .Compile => {}, + .Error => |expected_errors| { + var test_node = update_node.start("assert", 0); + test_node.activate(); + defer test_node.end(); + + var error_bundle = try comp.getAllErrorsAlloc(); + defer error_bundle.deinit(allocator); + + if (error_bundle.errorMessageCount() == 0) { + return error.ExpectedCompilationErrors; + } + + var actual_stderr = std.ArrayList(u8).init(arena); + try error_bundle.renderToWriter(.{ + .ttyconf = .no_color, + .include_reference_trace = false, + .include_source_line = false, + }, actual_stderr.writer()); + + // Render the expected lines into a string that we can compare verbatim. + var expected_generated = std.ArrayList(u8).init(arena); + + var actual_line_it = std.mem.split(u8, actual_stderr.items, "\n"); + for (expected_errors) |expect_line| { + const actual_line = actual_line_it.next() orelse { + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + continue; + }; + if (std.mem.endsWith(u8, actual_line, expect_line)) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + if (std.mem.startsWith(u8, expect_line, ":?:?: ")) { + if (std.mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + } + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + } + + try std.testing.expectEqualStrings(expected_generated.items, actual_stderr.items); + }, + .Execution => |expected_stdout| { + if (!std.process.can_spawn) { + std.debug.print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)}); + continue :update; // Pass test. + } + + update_node.setEstimatedTotalItems(4); + + var argv = std.ArrayList([]const u8).init(allocator); + defer argv.deinit(); + + var exec_result = x: { + var exec_node = update_node.start("execute", 0); + exec_node.activate(); + defer exec_node.end(); + + // We go out of our way here to use the unique temporary directory name in + // the exe_path so that it makes its way into the cache hash, avoiding + // cache collisions from multiple threads doing `zig run` at the same time + // on the same test_case.c input filename. + const ss = std.fs.path.sep_str; + const exe_path = try std.fmt.allocPrint( + arena, + ".." ++ ss ++ "{s}" ++ ss ++ "{s}", + .{ &tmp.sub_path, bin_name }, + ); + if (case.target.ofmt != null and case.target.ofmt.? == .c) { + if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) { + // We wouldn't be able to run the compiled C code. + continue :update; // Pass test. + } + try argv.appendSlice(&[_][]const u8{ + zig_exe_path, + "run", + "-cflags", + "-std=c99", + "-pedantic", + "-Werror", + "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875 + "--", + "-lc", + exe_path, + }); + if (zig_lib_directory.path) |p| { + try argv.appendSlice(&.{ "-I", p }); + } + } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) { + .native => { + if (case.backend == .stage2 and case.target.getCpuArch() == .arm) { + // https://github.com/ziglang/zig/issues/13623 + continue :update; // Pass test. + } + try argv.append(exe_path); + }, + .bad_dl, .bad_os_or_cpu => continue :update, // Pass test. + + .rosetta => if (enable_rosetta) { + try argv.append(exe_path); + } else { + continue :update; // Rosetta not available, pass test. + }, + + .qemu => |qemu_bin_name| if (enable_qemu) { + const need_cross_glibc = target.isGnuLibC() and case.link_libc; + const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc) + glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test + else + null; + try argv.append(qemu_bin_name); + if (glibc_dir_arg) |dir| { + const linux_triple = try target.linuxTriple(arena); + const full_dir = try std.fs.path.join(arena, &[_][]const u8{ + dir, + linux_triple, + }); + + try argv.append("-L"); + try argv.append(full_dir); + } + try argv.append(exe_path); + } else { + continue :update; // QEMU not available; pass test. + }, + + .wine => |wine_bin_name| if (enable_wine) { + try argv.append(wine_bin_name); + try argv.append(exe_path); + } else { + continue :update; // Wine not available; pass test. + }, + + .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) { + try argv.append(wasmtime_bin_name); + try argv.append("--dir=."); + try argv.append(exe_path); + } else { + continue :update; // wasmtime not available; pass test. + }, + + .darling => |darling_bin_name| if (enable_darling) { + try argv.append(darling_bin_name); + // Since we use relative to cwd here, we invoke darling with + // "shell" subcommand. + try argv.append("shell"); + try argv.append(exe_path); + } else { + continue :update; // Darling not available; pass test. + }, + } + + try comp.makeBinFileExecutable(); + + while (true) { + break :x std.ChildProcess.exec(.{ + .allocator = allocator, + .argv = argv.items, + .cwd_dir = tmp.dir, + .cwd = tmp_dir_path, + }) catch |err| switch (err) { + error.FileBusy => { + // There is a fundamental design flaw in Unix systems with how + // ETXTBSY interacts with fork+exec. + // https://github.com/golang/go/issues/22315 + // https://bugs.openjdk.org/browse/JDK-8068370 + // Unfortunately, this could be a real error, but we can't + // tell the difference here. + continue; + }, + else => { + std.debug.print("\n{s}.{d} The following command failed with {s}:\n", .{ + case.name, update_index, @errorName(err), + }); + dumpArgs(argv.items); + return error.ChildProcessExecution; + }, + }; + } + }; + var test_node = update_node.start("test", 0); + test_node.activate(); + defer test_node.end(); + defer allocator.free(exec_result.stdout); + defer allocator.free(exec_result.stderr); + switch (exec_result.term) { + .Exited => |code| { + if (code != 0) { + std.debug.print("\n{s}\n{s}: execution exited with code {d}:\n", .{ + exec_result.stderr, case.name, code, + }); + dumpArgs(argv.items); + return error.ChildProcessExecution; + } + }, + else => { + std.debug.print("\n{s}\n{s}: execution crashed:\n", .{ + exec_result.stderr, case.name, + }); + dumpArgs(argv.items); + return error.ChildProcessExecution; + }, + } + try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout); + // We allow stderr to have garbage in it because wasmtime prints a + // warning about --invoke even though we don't pass it. + //std.testing.expectEqualStrings("", exec_result.stderr); + }, + } + } +} + +fn dumpArgs(argv: []const []const u8) void { + for (argv) |arg| { + std.debug.print("{s} ", .{arg}); + } + std.debug.print("\n", .{}); +} diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig deleted file mode 100644 index e9750853a6..0000000000 --- a/test/stage2/cbe.zig +++ /dev/null @@ -1,1015 +0,0 @@ -const std = @import("std"); -const TestContext = @import("../../src/test.zig").TestContext; - -// These tests should work with all platforms, but we're using linux_x64 for -// now for consistency. Will be expanded eventually. -const linux_x64 = std.zig.CrossTarget{ - .cpu_arch = .x86_64, - .os_tag = .linux, -}; - -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; - \\pub export fn main() c_int { - \\ _ = puts("hello world!"); - \\ return 0; - \\} - , "hello world!" ++ std.cstr.line_sep); - - // Now change the message only - case.addCompareOutput( - \\extern fn puts(s: [*:0]const u8) c_int; - \\pub export fn main() c_int { - \\ _ = puts("yo"); - \\ return 0; - \\} - , "yo" ++ std.cstr.line_sep); - - // Add an unused Decl - case.addCompareOutput( - \\extern fn puts(s: [*:0]const u8) c_int; - \\pub export fn main() c_int { - \\ _ = puts("yo!"); - \\ return 0; - \\} - \\fn unused() void {} - , "yo!" ++ std.cstr.line_sep); - - // Comptime return type and calling convention expected. - case.addError( - \\var x: i32 = 1234; - \\pub export fn main() x { - \\ return 0; - \\} - \\export fn foo() callconv(y) c_int { - \\ return 0; - \\} - \\var y: @import("std").builtin.CallingConvention = .C; - , &.{ - ":2:22: error: expected type 'type', found 'i32'", - ":5:26: error: unable to resolve comptime value", - ":5:26: note: calling convention must be comptime-known", - }); - } - - { - var case = ctx.exeFromCompiledC("var args", .{}); - - case.addCompareOutput( - \\extern fn printf(format: [*:0]const u8, ...) c_int; - \\ - \\pub export fn main() c_int { - \\ _ = printf("Hello, %s!\n", "world"); - \\ return 0; - \\} - , "Hello, world!" ++ std.cstr.line_sep); - } - - { - var case = ctx.exeFromCompiledC("@intToError", .{}); - - case.addCompareOutput( - \\pub export fn main() c_int { - \\ // comptime checks - \\ const a = error.A; - \\ const b = error.B; - \\ const c = @intToError(2); - \\ const d = @intToError(1); - \\ if (!(c == b)) unreachable; - \\ if (!(a == d)) unreachable; - \\ // runtime checks - \\ var x = error.A; - \\ var y = error.B; - \\ var z = @intToError(2); - \\ var f = @intToError(1); - \\ if (!(y == z)) unreachable; - \\ if (!(x == f)) unreachable; - \\ return 0; - \\} - , ""); - case.addError( - \\pub export fn main() c_int { - \\ _ = @intToError(0); - \\ return 0; - \\} - , &.{":2:21: error: integer value '0' represents no error"}); - case.addError( - \\pub export fn main() c_int { - \\ _ = @intToError(3); - \\ return 0; - \\} - , &.{":2:21: error: integer value '3' represents no error"}); - } - - { - var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64); - - // Exit with 0 - case.addCompareOutput( - \\fn exitGood() noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ ); - \\ unreachable; - \\} - \\ - \\pub export fn main() c_int { - \\ exitGood(); - \\} - , ""); - - // Pass a usize parameter to exit - case.addCompareOutput( - \\pub export fn main() c_int { - \\ exit(0); - \\} - \\ - \\fn exit(code: usize) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - , ""); - - // Change the parameter to u8 - case.addCompareOutput( - \\pub export fn main() c_int { - \\ exit(0); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - , ""); - - // Do some arithmetic at the exit callsite - case.addCompareOutput( - \\pub export fn main() c_int { - \\ exitMath(1); - \\} - \\ - \\fn exitMath(a: u8) noreturn { - \\ exit(0 + a - a); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , ""); - - // Invert the arithmetic - case.addCompareOutput( - \\pub export fn main() c_int { - \\ exitMath(1); - \\} - \\ - \\fn exitMath(a: u8) noreturn { - \\ exit(a + 0 - a); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , ""); - } - - { - var case = ctx.exeFromCompiledC("alloc and retptr", .{}); - - case.addCompareOutput( - \\fn add(a: i32, b: i32) i32 { - \\ return a + b; - \\} - \\ - \\fn addIndirect(a: i32, b: i32) i32 { - \\ return add(a, b); - \\} - \\ - \\pub export fn main() c_int { - \\ return addIndirect(1, 2) - 3; - \\} - , ""); - } - - { - var case = ctx.exeFromCompiledC("inferred local const and var", .{}); - - case.addCompareOutput( - \\fn add(a: i32, b: i32) i32 { - \\ return a + b; - \\} - \\ - \\pub export fn main() c_int { - \\ const x = add(1, 2); - \\ var y = add(3, 0); - \\ y -= x; - \\ return y; - \\} - , ""); - } - { - var case = ctx.exeFromCompiledC("control flow", .{}); - - // Simple while loop - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var a: c_int = 0; - \\ while (a < 5) : (a+=1) {} - \\ return a - 5; - \\} - , ""); - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var a = true; - \\ while (!a) {} - \\ return 0; - \\} - , ""); - - // If expression - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var cond: c_int = 0; - \\ var a: c_int = @as(c_int, if (cond == 0) - \\ 2 - \\ else - \\ 3) + 9; - \\ return a - 11; - \\} - , ""); - - // If expression with breakpoint that does not get hit - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var x: i32 = 1; - \\ if (x != 1) @breakpoint(); - \\ return 0; - \\} - , ""); - - // Switch expression - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var cond: c_int = 0; - \\ var a: c_int = switch (cond) { - \\ 1 => 1, - \\ 2 => 2, - \\ 99...300, 12 => 3, - \\ 0 => 4, - \\ else => 5, - \\ }; - \\ return a - 4; - \\} - , ""); - - // Switch expression missing else case. - case.addError( - \\pub export fn main() c_int { - \\ var cond: c_int = 0; - \\ const a: c_int = switch (cond) { - \\ 1 => 1, - \\ 2 => 2, - \\ 3 => 3, - \\ 4 => 4, - \\ }; - \\ return a - 4; - \\} - , &.{":3:22: error: switch must handle all possibilities"}); - - // Switch expression, has an unreachable prong. - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var cond: c_int = 0; - \\ const a: c_int = switch (cond) { - \\ 1 => 1, - \\ 2 => 2, - \\ 99...300, 12 => 3, - \\ 0 => 4, - \\ 13 => unreachable, - \\ else => 5, - \\ }; - \\ return a - 4; - \\} - , ""); - - // Switch expression, has an unreachable prong and prongs write - // to result locations. - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var cond: c_int = 0; - \\ var a: c_int = switch (cond) { - \\ 1 => 1, - \\ 2 => 2, - \\ 99...300, 12 => 3, - \\ 0 => 4, - \\ 13 => unreachable, - \\ else => 5, - \\ }; - \\ return a - 4; - \\} - , ""); - - // Integer switch expression has duplicate case value. - case.addError( - \\pub export fn main() c_int { - \\ var cond: c_int = 0; - \\ const a: c_int = switch (cond) { - \\ 1 => 1, - \\ 2 => 2, - \\ 96, 11...13, 97 => 3, - \\ 0 => 4, - \\ 90, 12 => 100, - \\ else => 5, - \\ }; - \\ return a - 4; - \\} - , &.{ - ":8:13: error: duplicate switch value", - ":6:15: note: previous value here", - }); - - // Boolean switch expression has duplicate case value. - case.addError( - \\pub export fn main() c_int { - \\ var a: bool = false; - \\ const b: c_int = switch (a) { - \\ false => 1, - \\ true => 2, - \\ false => 3, - \\ }; - \\ _ = b; - \\} - , &.{ - ":6:9: error: duplicate switch value", - }); - - // Sparse (no range capable) switch expression has duplicate case value. - case.addError( - \\pub export fn main() c_int { - \\ const A: type = i32; - \\ const b: c_int = switch (A) { - \\ i32 => 1, - \\ bool => 2, - \\ f64, i32 => 3, - \\ else => 4, - \\ }; - \\ _ = b; - \\} - , &.{ - ":6:14: error: duplicate switch value", - ":4:9: note: previous value here", - }); - - // Ranges not allowed for some kinds of switches. - case.addError( - \\pub export fn main() c_int { - \\ const A: type = i32; - \\ const b: c_int = switch (A) { - \\ i32 => 1, - \\ bool => 2, - \\ f16...f64 => 3, - \\ else => 4, - \\ }; - \\ _ = b; - \\} - , &.{ - ":3:30: error: ranges not allowed when switching on type 'type'", - ":6:12: note: range here", - }); - - // Switch expression has unreachable else prong. - case.addError( - \\pub export fn main() c_int { - \\ var a: u2 = 0; - \\ const b: i32 = switch (a) { - \\ 0 => 10, - \\ 1 => 20, - \\ 2 => 30, - \\ 3 => 40, - \\ else => 50, - \\ }; - \\ _ = b; - \\} - , &.{ - ":8:14: error: unreachable else prong; all cases already handled", - }); - } - //{ - // var case = ctx.exeFromCompiledC("optionals", .{}); - - // // Simple while loop - // case.addCompareOutput( - // \\pub export fn main() c_int { - // \\ var count: c_int = 0; - // \\ var opt_ptr: ?*c_int = &count; - // \\ while (opt_ptr) |_| : (count += 1) { - // \\ if (count == 4) opt_ptr = null; - // \\ } - // \\ return count - 5; - // \\} - // , ""); - - // // Same with non pointer optionals - // case.addCompareOutput( - // \\pub export fn main() c_int { - // \\ var count: c_int = 0; - // \\ var opt_ptr: ?c_int = count; - // \\ while (opt_ptr) |_| : (count += 1) { - // \\ if (count == 4) opt_ptr = null; - // \\ } - // \\ return count - 5; - // \\} - // , ""); - //} - - { - var case = ctx.exeFromCompiledC("errors", .{}); - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var e1 = error.Foo; - \\ var e2 = error.Bar; - \\ assert(e1 != e2); - \\ assert(e1 == error.Foo); - \\ assert(e2 == error.Bar); - \\ return 0; - \\} - \\fn assert(b: bool) void { - \\ if (!b) unreachable; - \\} - , ""); - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var e: anyerror!c_int = 0; - \\ const i = e catch 69; - \\ return i; - \\} - , ""); - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var e: anyerror!c_int = error.Foo; - \\ const i = e catch 69; - \\ return 69 - i; - \\} - , ""); - case.addCompareOutput( - \\const E = error{e}; - \\const S = struct { x: u32 }; - \\fn f() E!u32 { - \\ const x = (try @as(E!S, S{ .x = 1 })).x; - \\ return x; - \\} - \\pub export fn main() c_int { - \\ const x = f() catch @as(u32, 0); - \\ if (x != 1) unreachable; - \\ return 0; - \\} - , ""); - } - - { - var case = ctx.exeFromCompiledC("structs", .{}); - case.addError( - \\const Point = struct { x: i32, y: i32 }; - \\pub export fn main() c_int { - \\ var p: Point = .{ - \\ .y = 24, - \\ .x = 12, - \\ .y = 24, - \\ }; - \\ return p.y - p.x - p.x; - \\} - , &.{ - ":6:10: error: duplicate field", - ":4:10: note: other field here", - }); - case.addError( - \\const Point = struct { x: i32, y: i32 }; - \\pub export fn main() c_int { - \\ var p: Point = .{ - \\ .y = 24, - \\ }; - \\ return p.y - p.x - p.x; - \\} - , &.{ - ":3:21: error: missing struct field: x", - ":1:15: note: struct 'tmp.Point' declared here", - }); - case.addError( - \\const Point = struct { x: i32, y: i32 }; - \\pub export fn main() c_int { - \\ var p: Point = .{ - \\ .x = 12, - \\ .y = 24, - \\ .z = 48, - \\ }; - \\ return p.y - p.x - p.x; - \\} - , &.{ - ":6:10: error: no field named 'z' in struct 'tmp.Point'", - ":1:15: note: struct declared here", - }); - case.addCompareOutput( - \\const Point = struct { x: i32, y: i32 }; - \\pub export fn main() c_int { - \\ var p: Point = .{ - \\ .x = 12, - \\ .y = 24, - \\ }; - \\ return p.y - p.x - p.x; - \\} - , ""); - case.addCompareOutput( - \\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 }; - \\pub export fn main() c_int { - \\ var p: Point = .{ - \\ .x = 18, - \\ .y = 24, - \\ .z = 1, - \\ .a = 2, - \\ .b = 3, - \\ }; - \\ return p.y - p.x - p.z - p.a - p.b; - \\} - , ""); - } - - { - var case = ctx.exeFromCompiledC("unions", .{}); - - case.addError( - \\const U = union { - \\ a: u32, - \\ b - \\}; - , &.{ - ":3:5: error: union field missing type", - }); - - case.addError( - \\const E = enum { a, b }; - \\const U = union(E) { - \\ a: u32 = 1, - \\ b: f32 = 2, - \\}; - , &.{ - ":2:11: error: explicitly valued tagged union requires inferred enum tag type", - ":3:14: note: tag value specified here", - }); - - case.addError( - \\const U = union(enum) { - \\ a: u32 = 1, - \\ b: f32 = 2, - \\}; - , &.{ - ":1:11: error: explicitly valued tagged union missing integer tag type", - ":2:14: note: tag value specified here", - }); - } - - { - var case = ctx.exeFromCompiledC("enums", .{}); - - case.addError( - \\const E1 = packed enum { a, b, c }; - \\const E2 = extern enum { a, b, c }; - \\export fn foo() void { - \\ _ = E1.a; - \\} - \\export fn bar() void { - \\ _ = E2.a; - \\} - , &.{ - ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", - ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", - }); - - // comptime and types are caught in AstGen. - case.addError( - \\const E1 = enum { - \\ a, - \\ comptime b, - \\ c, - \\}; - \\const E2 = enum { - \\ a, - \\ b: i32, - \\ c, - \\}; - \\export fn foo() void { - \\ _ = E1.a; - \\} - \\export fn bar() void { - \\ _ = E2.a; - \\} - , &.{ - ":3:5: error: enum fields cannot be marked comptime", - ":8:8: error: enum fields do not have types", - ":6:12: note: consider 'union(enum)' here to make it a tagged union", - }); - - // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch - case.addCompareOutput( - \\const Number = enum { One, Two, Three }; - \\ - \\pub export fn main() c_int { - \\ var number1 = Number.One; - \\ var number2: Number = .Two; - \\ const number3 = @intToEnum(Number, 2); - \\ if (number1 == number2) return 1; - \\ if (number2 == number3) return 1; - \\ if (@enumToInt(number1) != 0) return 1; - \\ if (@enumToInt(number2) != 1) return 1; - \\ if (@enumToInt(number3) != 2) return 1; - \\ var x: Number = .Two; - \\ if (number2 != x) return 1; - \\ switch (x) { - \\ .One => return 1, - \\ .Two => return 0, - \\ number3 => return 2, - \\ } - \\} - , ""); - - // Specifying alignment is a parse error. - // This also tests going from a successful build to a parse error. - case.addError( - \\const E1 = enum { - \\ a, - \\ b align(4), - \\ c, - \\}; - \\export fn foo() void { - \\ _ = E1.a; - \\} - , &.{ - ":3:13: error: enum fields cannot be aligned", - }); - - // Redundant non-exhaustive enum mark. - // This also tests going from a parse error to an AstGen error. - case.addError( - \\const E1 = enum { - \\ a, - \\ _, - \\ b, - \\ c, - \\ _, - \\}; - \\export fn foo() void { - \\ _ = E1.a; - \\} - , &.{ - ":6:5: error: redundant non-exhaustive enum mark", - ":3:5: note: other mark here", - }); - - case.addError( - \\const E1 = enum { - \\ a, - \\ b, - \\ c, - \\ _ = 10, - \\}; - \\export fn foo() void { - \\ _ = E1.a; - \\} - , &.{ - ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value", - }); - - case.addError( - \\const E1 = enum { a, b, _ }; - \\export fn foo() void { - \\ _ = E1.a; - \\} - , &.{ - ":1:12: error: non-exhaustive enum missing integer tag type", - ":1:25: note: marked non-exhaustive here", - }); - - case.addError( - \\const E1 = enum { a, b, c, b, d }; - \\pub export fn main() c_int { - \\ _ = E1.a; - \\} - , &.{ - ":1:28: error: duplicate enum field 'b'", - ":1:22: note: other field here", - }); - - case.addError( - \\pub export fn main() c_int { - \\ const a = true; - \\ _ = @enumToInt(a); - \\} - , &.{ - ":3:20: error: expected enum or tagged union, found 'bool'", - }); - - case.addError( - \\pub export fn main() c_int { - \\ const a = 1; - \\ _ = @intToEnum(bool, a); - \\} - , &.{ - ":3:20: error: expected enum, found 'bool'", - }); - - case.addError( - \\const E = enum { a, b, c }; - \\pub export fn main() c_int { - \\ _ = @intToEnum(E, 3); - \\} - , &.{ - ":3:9: error: enum 'tmp.E' has no tag with value '3'", - ":1:11: note: enum declared here", - }); - - case.addError( - \\const E = enum { a, b, c }; - \\pub export fn main() c_int { - \\ var x: E = .a; - \\ switch (x) { - \\ .a => {}, - \\ .c => {}, - \\ } - \\} - , &.{ - ":4:5: error: switch must handle all possibilities", - ":1:21: note: unhandled enumeration value: 'b'", - ":1:11: note: enum 'tmp.E' declared here", - }); - - case.addError( - \\const E = enum { a, b, c }; - \\pub export fn main() c_int { - \\ var x: E = .a; - \\ switch (x) { - \\ .a => {}, - \\ .b => {}, - \\ .b => {}, - \\ .c => {}, - \\ } - \\} - , &.{ - ":7:10: error: duplicate switch value", - ":6:10: note: previous value here", - }); - - case.addError( - \\const E = enum { a, b, c }; - \\pub export fn main() c_int { - \\ var x: E = .a; - \\ switch (x) { - \\ .a => {}, - \\ .b => {}, - \\ .c => {}, - \\ else => {}, - \\ } - \\} - , &.{ - ":8:14: error: unreachable else prong; all cases already handled", - }); - - case.addError( - \\const E = enum { a, b, c }; - \\pub export fn main() c_int { - \\ var x: E = .a; - \\ switch (x) { - \\ .a => {}, - \\ .b => {}, - \\ _ => {}, - \\ } - \\} - , &.{ - ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums", - ":7:11: note: '_' prong here", - }); - - case.addError( - \\const E = enum { a, b, c }; - \\pub export fn main() c_int { - \\ _ = E.d; - \\} - , &.{ - ":3:11: error: enum 'tmp.E' has no member named 'd'", - ":1:11: note: enum declared here", - }); - - case.addError( - \\const E = enum { a, b, c }; - \\pub export fn main() c_int { - \\ var x: E = .d; - \\ _ = x; - \\} - , &.{ - ":3:17: error: no field named 'd' in enum 'tmp.E'", - ":1:11: note: enum declared here", - }); - } - - { - var case = ctx.exeFromCompiledC("shift right + left", .{}); - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var i: u32 = 16; - \\ assert(i >> 1, 8); - \\ return 0; - \\} - \\fn assert(a: u32, b: u32) void { - \\ if (a != b) unreachable; - \\} - , ""); - - case.addCompareOutput( - \\pub export fn main() c_int { - \\ var i: u32 = 16; - \\ assert(i << 1, 32); - \\ return 0; - \\} - \\fn assert(a: u32, b: u32) void { - \\ if (a != b) unreachable; - \\} - , ""); - } - - { - var case = ctx.exeFromCompiledC("inferred error sets", .{}); - - case.addCompareOutput( - \\pub export fn main() c_int { - \\ if (foo()) |_| { - \\ @panic("test fail"); - \\ } else |err| { - \\ if (err != error.ItBroke) { - \\ @panic("test fail"); - \\ } - \\ } - \\ return 0; - \\} - \\fn foo() !void { - \\ return error.ItBroke; - \\} - , ""); - } - - { - // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64) - var case = ctx.exeFromCompiledC("add/sub wrapping operations", .{}); - case.addCompareOutput( - \\pub export fn main() c_int { - \\ // Addition - \\ if (!add_u3(1, 1, 2)) return 1; - \\ if (!add_u3(7, 1, 0)) return 1; - \\ if (!add_i3(1, 1, 2)) return 1; - \\ if (!add_i3(3, 2, -3)) return 1; - \\ if (!add_i3(-3, -2, 3)) return 1; - \\ if (!add_c_int(1, 1, 2)) return 1; - \\ // TODO enable these when stage2 supports std.math.maxInt - \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1; - \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1; - \\ - \\ // Subtraction - \\ if (!sub_u3(2, 1, 1)) return 1; - \\ if (!sub_u3(0, 1, 7)) return 1; - \\ if (!sub_i3(2, 1, 1)) return 1; - \\ if (!sub_i3(3, -2, -3)) return 1; - \\ if (!sub_i3(-3, 2, 3)) return 1; - \\ if (!sub_c_int(2, 1, 1)) return 1; - \\ // TODO enable these when stage2 supports std.math.maxInt - \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1; - \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1; - \\ - \\ return 0; - \\} - \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool { - \\ return expected == lhs +% rhs; - \\} - \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool { - \\ return expected == lhs +% rhs; - \\} - \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool { - \\ return expected == lhs +% rhs; - \\} - \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool { - \\ return expected == lhs -% rhs; - \\} - \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool { - \\ return expected == lhs -% rhs; - \\} - \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool { - \\ return expected == lhs -% rhs; - \\} - , ""); - } - - { - var case = ctx.exeFromCompiledC("@rem", linux_x64); - case.addCompareOutput( - \\fn assert(ok: bool) void { - \\ if (!ok) unreachable; - \\} - \\fn rem(lhs: i32, rhs: i32, expected: i32) bool { - \\ return @rem(lhs, rhs) == expected; - \\} - \\pub export fn main() c_int { - \\ assert(rem(-5, 3, -2)); - \\ assert(rem(5, 3, 2)); - \\ return 0; - \\} - , ""); - } - - ctx.h("simple header", linux_x64, - \\export fn start() void{} - , - \\zig_extern void start(void); - \\ - ); - ctx.h("header with single param function", linux_x64, - \\export fn start(a: u8) void{ - \\ _ = a; - \\} - , - \\zig_extern void start(uint8_t const a0); - \\ - ); - ctx.h("header with multiple param function", linux_x64, - \\export fn start(a: u8, b: u8, c: u8) void{ - \\ _ = a; _ = b; _ = c; - \\} - , - \\zig_extern void start(uint8_t const a0, uint8_t const a1, uint8_t const a2); - \\ - ); - ctx.h("header with u32 param function", linux_x64, - \\export fn start(a: u32) void{ _ = a; } - , - \\zig_extern void start(uint32_t const a0); - \\ - ); - ctx.h("header with usize param function", linux_x64, - \\export fn start(a: usize) void{ _ = a; } - , - \\zig_extern void start(uintptr_t const a0); - \\ - ); - ctx.h("header with bool param function", linux_x64, - \\export fn start(a: bool) void{_ = a;} - , - \\zig_extern void start(bool const a0); - \\ - ); - ctx.h("header with noreturn function", linux_x64, - \\export fn start() noreturn { - \\ unreachable; - \\} - , - \\zig_extern zig_noreturn void start(void); - \\ - ); - ctx.h("header with multiple functions", linux_x64, - \\export fn a() void{} - \\export fn b() void{} - \\export fn c() void{} - , - \\zig_extern void a(void); - \\zig_extern void b(void); - \\zig_extern void c(void); - \\ - ); - ctx.h("header with multiple includes", linux_x64, - \\export fn start(a: u32, b: usize) void{ _ = a; _ = b; } - , - \\zig_extern void start(uint32_t const a0, uintptr_t const a1); - \\ - ); -} diff --git a/test/stage2/nvptx.zig b/test/stage2/nvptx.zig deleted file mode 100644 index f08aa9fca4..0000000000 --- a/test/stage2/nvptx.zig +++ /dev/null @@ -1,107 +0,0 @@ -const std = @import("std"); -const TestContext = @import("../../src/test.zig").TestContext; - -pub fn addCases(ctx: *TestContext) !void { - { - var case = addPtx(ctx, "nvptx: simple addition and subtraction"); - - case.compiles( - \\fn add(a: i32, b: i32) i32 { - \\ return a + b; - \\} - \\ - \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void { - \\ const x = add(a, 7); - \\ var y = add(2, 0); - \\ y -= x; - \\ out.* = y; - \\} - ); - } - - { - var case = addPtx(ctx, "nvptx: read special registers"); - - case.compiles( - \\fn threadIdX() u32 { - \\ return asm ("mov.u32 \t%[r], %tid.x;" - \\ : [r] "=r" (-> u32), - \\ ); - \\} - \\ - \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void { - \\ const i = threadIdX(); - \\ out[i] = a[i] + 7; - \\} - ); - } - - { - var case = addPtx(ctx, "nvptx: address spaces"); - - case.compiles( - \\var x: i32 addrspace(.global) = 0; - \\ - \\pub export fn increment(out: *i32) callconv(.PtxKernel) void { - \\ x += 1; - \\ out.* = x; - \\} - ); - } - - { - var case = addPtx(ctx, "nvptx: reduce in shared mem"); - case.compiles( - \\fn threadIdX() u32 { - \\ return asm ("mov.u32 \t%[r], %tid.x;" - \\ : [r] "=r" (-> u32), - \\ ); - \\} - \\ - \\ var _sdata: [1024]f32 addrspace(.shared) = undefined; - \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void { - \\ var sdata = @addrSpaceCast(.generic, &_sdata); - \\ const tid: u32 = threadIdX(); - \\ var sum = d_x[tid]; - \\ sdata[tid] = sum; - \\ asm volatile ("bar.sync \t0;"); - \\ var s: u32 = 512; - \\ while (s > 0) : (s = s >> 1) { - \\ if (tid < s) { - \\ sum += sdata[tid + s]; - \\ sdata[tid] = sum; - \\ } - \\ asm volatile ("bar.sync \t0;"); - \\ } - \\ - \\ if (tid == 0) { - \\ out.* = sum; - \\ } - \\ } - ); - } -} - -const nvptx_target = std.zig.CrossTarget{ - .cpu_arch = .nvptx64, - .os_tag = .cuda, -}; - -pub fn addPtx( - ctx: *TestContext, - name: []const u8, -) *TestContext.Case { - ctx.cases.append(TestContext.Case{ - .name = name, - .target = nvptx_target, - .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), - .output_mode = .Obj, - .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), - .deps = std.ArrayList(TestContext.DepModule).init(ctx.cases.allocator), - .link_libc = false, - .backend = .llvm, - // Bug in Debug mode - .optimize_mode = .ReleaseSafe, - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; -} diff --git a/test/tests.zig b/test/tests.zig index 2cd06b18b9..c07b669376 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1055,3 +1055,30 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S } return step; } + +pub fn addCases( + b: *std.Build, + parent_step: *Step, + opt_test_filter: ?[]const u8, + check_case_exe: *std.Build.CompileStep, +) !void { + const arena = b.allocator; + const gpa = b.allocator; + + var cases = @import("src/Cases.zig").init(gpa, arena); + + var dir = try b.build_root.handle.openIterableDir("test/cases", .{}); + defer dir.close(); + + cases.addFromDir(dir); + try @import("cases.zig").addCases(&cases); + + const cases_dir_path = try b.build_root.join(b.allocator, &.{ "test", "cases" }); + cases.lowerToBuildSteps( + b, + parent_step, + opt_test_filter, + cases_dir_path, + check_case_exe, + ); +} -- cgit v1.2.3 From 097bcca069c5c7abdf77f321182eb4a0c54b3a93 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:54:32 -0800 Subject: build.zig: fix how test-cases marked is_test=1 are handled --- test/src/Cases.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'test/src') diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 27b0cf6b21..5007939b14 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -485,7 +485,12 @@ pub fn lowerToBuildSteps( } const root_source_file = writefiles.getFileSource(update.files.items[0].path).?; - const artifact = switch (case.output_mode) { + const artifact = if (case.is_test) b.addTest(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }) else switch (case.output_mode) { .Obj => b.addObject(.{ .root_source_file = root_source_file, .name = case.name, @@ -498,12 +503,7 @@ pub fn lowerToBuildSteps( .target = case.target, .optimize = case.optimize_mode, }), - .Exe => if (case.is_test) b.addTest(.{ - .root_source_file = root_source_file, - .name = case.name, - .target = case.target, - .optimize = case.optimize_mode, - }) else b.addExecutable(.{ + .Exe => b.addExecutable(.{ .root_source_file = root_source_file, .name = case.name, .target = case.target, -- cgit v1.2.3 From ede5dcffea5a3a5fc9fd14e4e180464633402fae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Mar 2023 00:39:21 -0700 Subject: make the build runner and test runner talk to each other std.Build.addTest creates a CompileStep as before, however, this kind of step no longer actually runs the unit tests. Instead it only compiles it, and one must additionally create a RunStep from the CompileStep in order to actually run the tests. RunStep gains integration with the default test runner, which now supports the standard --listen=- argument in order to communicate over stdin and stdout. It also reports test statistics; how many passed, failed, and leaked, as well as directly associating the relevant stderr with the particular test name that failed. This separation of CompileStep and RunStep means that `CompileStep.Kind.test_exe` is no longer needed, and therefore has been removed in this commit. * build runner: show unit test statistics in build summary * added Step.writeManifest since many steps want to treat it as a warning and emit the same message if it fails. * RunStep: fixed error message that prints the failed command printing the original argv and not the adjusted argv in case an interpreter was used. * RunStep: fixed not passing the command line arguments to the interpreter. * move src/Server.zig to std.zig.Server so that the default test runner can use it. * the simpler test runner function which is used by work-in-progress backends now no longer prints to stderr, which is necessary in order for the build runner to not print the stderr as a warning message. --- CMakeLists.txt | 2 +- lib/build_runner.zig | 59 +++- lib/std/Build.zig | 10 +- lib/std/Build/CompileStep.zig | 93 +----- lib/std/Build/InstallArtifactStep.zig | 5 +- lib/std/Build/RunStep.zig | 355 +++++++++++++++++---- lib/std/Build/Step.zig | 33 +- lib/std/Build/WriteFileStep.zig | 2 +- lib/std/zig/Client.zig | 7 + lib/std/zig/Server.zig | 200 ++++++++++++ lib/test_runner.zig | 172 +++++++--- src/Server.zig | 113 ------- src/main.zig | 21 +- src/objcopy.zig | 9 +- test/link/common_symbols/build.zig | 2 +- test/link/common_symbols_alignment/build.zig | 2 +- test/link/interdependent_static_c_libs/build.zig | 2 +- test/link/macho/tls/build.zig | 5 +- test/src/Cases.zig | 11 +- test/standalone/emit_asm_and_bin/build.zig | 2 +- test/standalone/global_linkage/build.zig | 2 +- test/standalone/issue_13970/build.zig | 6 +- test/standalone/main_pkg_path/build.zig | 2 +- test/standalone/options/build.zig | 2 +- test/standalone/pie/build.zig | 2 +- test/standalone/static_c_lib/build.zig | 2 +- .../test_runner_module_imports/build.zig | 2 +- test/standalone/test_runner_path/build.zig | 1 - test/standalone/use_alias/build.zig | 2 +- test/tests.zig | 27 +- 30 files changed, 780 insertions(+), 373 deletions(-) delete mode 100644 src/Server.zig (limited to 'test/src') diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ff8249f4d..b0867c220b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -518,6 +518,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig" + "${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig" @@ -623,7 +624,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/print_targets.zig" "${CMAKE_SOURCE_DIR}/src/print_zir.zig" "${CMAKE_SOURCE_DIR}/src/register_manager.zig" - "${CMAKE_SOURCE_DIR}/src/Server.zig" "${CMAKE_SOURCE_DIR}/src/target.zig" "${CMAKE_SOURCE_DIR}/src/tracy.zig" "${CMAKE_SOURCE_DIR}/src/translate_c.zig" diff --git a/lib/build_runner.zig b/lib/build_runner.zig index e28be8274d..bb341574a7 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -416,6 +416,12 @@ fn runStepNames( } assert(run.memory_blocked_steps.items.len == 0); + var test_skip_count: usize = 0; + var test_fail_count: usize = 0; + var test_pass_count: usize = 0; + var test_leak_count: usize = 0; + var test_count: usize = 0; + var success_count: usize = 0; var skipped_count: usize = 0; var failure_count: usize = 0; @@ -425,6 +431,12 @@ fn runStepNames( defer compile_error_steps.deinit(gpa); for (step_stack.keys()) |s| { + test_fail_count += s.test_results.fail_count; + test_skip_count += s.test_results.skip_count; + test_leak_count += s.test_results.leak_count; + test_pass_count += s.test_results.passCount(); + test_count += s.test_results.test_count; + switch (s.state) { .precheck_unstarted => unreachable, .precheck_started => unreachable, @@ -468,6 +480,11 @@ fn runStepNames( if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {}; if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {}; + if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; + if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {}; + if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {}; + if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {}; + if (run.enable_summary == null) { ttyconf.setColor(stderr, .Dim) catch {}; stderr.writeAll(" (disable with -fno-summary)") catch {}; @@ -566,6 +583,13 @@ fn printTreeStep( try ttyconf.setColor(stderr, .Green); if (s.result_cached) { try stderr.writeAll(" cached"); + } else if (s.test_results.test_count > 0) { + const pass_count = s.test_results.passCount(); + try stderr.writer().print(" {d} passed", .{pass_count}); + if (s.test_results.skip_count > 0) { + try ttyconf.setColor(stderr, .Yellow); + try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count}); + } } else { try stderr.writeAll(" success"); } @@ -609,15 +633,46 @@ fn printTreeStep( }, .failure => { - try ttyconf.setColor(stderr, .Red); if (s.result_error_bundle.errorMessageCount() > 0) { + try ttyconf.setColor(stderr, .Red); try stderr.writer().print(" {d} errors\n", .{ s.result_error_bundle.errorMessageCount(), }); + try ttyconf.setColor(stderr, .Reset); + } else if (!s.test_results.isSuccess()) { + try stderr.writer().print(" {d}/{d} passed", .{ + s.test_results.passCount(), s.test_results.test_count, + }); + if (s.test_results.fail_count > 0) { + try stderr.writeAll(", "); + try ttyconf.setColor(stderr, .Red); + try stderr.writer().print("{d} failed", .{ + s.test_results.fail_count, + }); + try ttyconf.setColor(stderr, .Reset); + } + if (s.test_results.skip_count > 0) { + try stderr.writeAll(", "); + try ttyconf.setColor(stderr, .Yellow); + try stderr.writer().print("{d} skipped", .{ + s.test_results.skip_count, + }); + try ttyconf.setColor(stderr, .Reset); + } + if (s.test_results.leak_count > 0) { + try stderr.writeAll(", "); + try ttyconf.setColor(stderr, .Red); + try stderr.writer().print("{d} leaked", .{ + s.test_results.leak_count, + }); + try ttyconf.setColor(stderr, .Reset); + } + try stderr.writeAll("\n"); } else { + try ttyconf.setColor(stderr, .Red); try stderr.writeAll(" failure\n"); + try ttyconf.setColor(stderr, .Reset); } - try ttyconf.setColor(stderr, .Reset); }, } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 5f74af3db6..279dd765b5 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -531,7 +531,6 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep { pub const TestOptions = struct { name: []const u8 = "test", - kind: CompileStep.Kind = .@"test", root_source_file: FileSource, target: CrossTarget = .{}, optimize: std.builtin.Mode = .Debug, @@ -542,7 +541,7 @@ pub const TestOptions = struct { pub fn addTest(b: *Build, options: TestOptions) *CompileStep { return CompileStep.create(b, .{ .name = options.name, - .kind = options.kind, + .kind = .@"test", .root_source_file = options.root_source_file, .target = options.target, .optimize = options.optimize, @@ -626,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep { /// Creates a `RunStep` with an executable built with `addExecutable`. /// Add command line arguments with methods of `RunStep`. pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep { - assert(exe.kind == .exe or exe.kind == .test_exe); - // It doesn't have to be native. We catch that if you actually try to run it. // Consider that this is declarative; the run step may not be run unless a user // option is supplied. const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name})); run_step.addArtifactArg(exe); - if (exe.kind == .test_exe) { - run_step.addArg(b.zig_exe); + if (exe.kind == .@"test") { + run_step.stdio = .zig_test; + run_step.addArgs(&.{"--listen=-"}); } if (exe.vcpkg_bin_path) |path| { diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 5c44ab82d3..5753641966 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -289,7 +289,6 @@ pub const Kind = enum { lib, obj, @"test", - test_exe, }; pub const Linkage = enum { dynamic, static }; @@ -328,7 +327,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { .exe => "zig build-exe", .lib => "zig build-lib", .obj => "zig build-obj", - .test_exe, .@"test" => "zig test", + .@"test" => "zig test", }, name_adjusted, @tagName(options.optimize), @@ -410,7 +409,7 @@ fn computeOutFileNames(self: *CompileStep) void { .output_mode = switch (self.kind) { .lib => .Lib, .obj => .Obj, - .exe, .@"test", .test_exe => .Exe, + .exe, .@"test" => .Exe, }, .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) { .dynamic => .Dynamic, @@ -621,7 +620,7 @@ pub fn producesPdbFile(self: *CompileStep) bool { if (!self.target.isWindows() and !self.target.isUefi()) return false; if (self.target.getObjectFormat() == .c) return false; if (self.strip == true) return false; - return self.isDynamicLibrary() or self.kind == .exe or self.kind == .test_exe; + return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test"; } pub fn linkLibC(self: *CompileStep) void { @@ -850,19 +849,19 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { pub fn setNamePrefix(self: *CompileStep, text: []const u8) void { const b = self.step.owner; - assert(self.kind == .@"test" or self.kind == .test_exe); + assert(self.kind == .@"test"); self.name_prefix = b.dupe(text); } pub fn setFilter(self: *CompileStep, text: ?[]const u8) void { const b = self.step.owner; - assert(self.kind == .@"test" or self.kind == .test_exe); + assert(self.kind == .@"test"); self.filter = if (text) |t| b.dupe(t) else null; } pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void { const b = self.step.owner; - assert(self.kind == .@"test" or self.kind == .test_exe); + assert(self.kind == .@"test"); self.test_runner = if (path) |p| b.dupePath(p) else null; } @@ -938,7 +937,7 @@ pub fn getOutputLibSource(self: *CompileStep) FileSource { /// Returns the generated header file. /// This function can only be called for libraries or object files which have `emit_h` set. pub fn getOutputHSource(self: *CompileStep) FileSource { - assert(self.kind != .exe and self.kind != .test_exe and self.kind != .@"test"); + assert(self.kind != .exe and self.kind != .@"test"); assert(self.emit_h); return .{ .generated = &self.output_h_path_source }; } @@ -1243,7 +1242,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .exe => "build-exe", .obj => "build-obj", .@"test" => "test", - .test_exe => "test", }; try zig_args.append(cmd); @@ -1293,7 +1291,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .other_step => |other| switch (other.kind) { .exe => @panic("Cannot link with an executable build artifact"), - .test_exe => @panic("Cannot link with an executable build artifact"), .@"test" => @panic("Cannot link with a test"), .obj => { try zig_args.append(other.getOutputSource().getPath(b)); @@ -1661,83 +1658,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append("--test-cmd-bin"); } } - } else { - const need_cross_glibc = self.target.isGnuLibC() and transitive_deps.is_linking_libc; - - switch (b.host.getExternalExecutor(self.target_info, .{ - .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, - .link_libc = transitive_deps.is_linking_libc, - })) { - .native => {}, - .bad_dl, .bad_os_or_cpu => { - try zig_args.append("--test-no-exec"); - }, - .rosetta => if (b.enable_rosetta) { - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - .qemu => |bin_name| ok: { - if (b.enable_qemu) qemu: { - const glibc_dir_arg = if (need_cross_glibc) - b.glibc_runtimes_dir orelse break :qemu - else - null; - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - if (glibc_dir_arg) |dir| { - // TODO look into making this a call to `linuxTriple`. This - // needs the directory to be called "i686" rather than - // "x86" which is why we do it manually here. - const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; - const cpu_arch = self.target.getCpuArch(); - const os_tag = self.target.getOsTag(); - const abi = self.target.getAbi(); - const cpu_arch_name: []const u8 = if (cpu_arch == .x86) - "i686" - else - @tagName(cpu_arch); - const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ - dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), - }); - - try zig_args.append("--test-cmd"); - try zig_args.append("-L"); - try zig_args.append("--test-cmd"); - try zig_args.append(full_dir); - } - try zig_args.append("--test-cmd-bin"); - break :ok; - } - try zig_args.append("--test-no-exec"); - }, - .wine => |bin_name| if (b.enable_wine) { - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - .wasmtime => |bin_name| if (b.enable_wasmtime) { - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - try zig_args.append("--test-cmd"); - try zig_args.append("--dir=."); - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - .darling => |bin_name| if (b.enable_darling) { - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - } } - } else if (self.kind == .test_exe) { - try zig_args.append("--test-no-exec"); } try self.appendModuleArgs(&zig_args); diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index 803998a619..445f1e8ea8 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -32,12 +32,11 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { .artifact = artifact, .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { .obj => @panic("Cannot install a .obj build artifact."), - .@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."), - .exe, .test_exe => InstallDir{ .bin = {} }, + .exe, .@"test" => InstallDir{ .bin = {} }, .lib => InstallDir{ .lib = {} }, }, .pdb_dir = if (artifact.producesPdbFile()) blk: { - if (artifact.kind == .exe or artifact.kind == .test_exe) { + if (artifact.kind == .exe or artifact.kind == .@"test") { break :blk InstallDir{ .bin = {} }; } else { break :blk InstallDir{ .lib = {} }; diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 9a1c887d7d..484600bf9b 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -92,6 +92,9 @@ pub const StdIo = union(enum) { /// Note that an explicit check for exit code 0 needs to be added to this /// list if such a check is desireable. check: std.ArrayList(Check), + /// This RunStep is running a zig unit test binary and will communicate + /// extra metadata over the IPC protocol. + zig_test, pub const Check = union(enum) { expect_stderr_exact: []const u8, @@ -324,6 +327,7 @@ fn hasSideEffects(self: RunStep) bool { .infer_from_args => !self.hasAnyOutputArgs(), .inherit => true, .check => false, + .zig_test => false, }; } @@ -366,11 +370,6 @@ fn checksContainStderr(checks: []const StdIo.Check) bool { } fn make(step: *Step, prog_node: *std.Progress.Node) !void { - // Unfortunately we have no way to collect progress from arbitrary programs. - // Perhaps in the future Zig could offer some kind of opt-in IPC mechanism that - // processes could use to supply progress updates. - _ = prog_node; - const b = step.owner; const arena = b.allocator; const self = @fieldParentPtr(RunStep, "step", step); @@ -439,7 +438,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { hashStdIo(&man.hash, self.stdio); if (has_side_effects) { - try runCommand(self, argv_list.items, has_side_effects, null); + try runCommand(self, argv_list.items, has_side_effects, null, prog_node); return; } @@ -492,8 +491,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { argv_list.items[placeholder.index] = cli_arg; } - try runCommand(self, argv_list.items, has_side_effects, &digest); - try man.writeManifest(); + try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node); + + try step.writeManifest(&man); } fn formatTerm( @@ -546,6 +546,7 @@ fn runCommand( argv: []const []const u8, has_side_effects: bool, digest: ?*const [std.Build.Cache.hex_digest_len]u8, + prog_node: *std.Progress.Node, ) !void { const step = &self.step; const b = step.owner; @@ -554,7 +555,15 @@ fn runCommand( try step.handleChildProcUnsupported(self.cwd, argv); try Step.handleVerbose(step.owner, self.cwd, argv); - const result = spawnChildAndCollect(self, argv, has_side_effects) catch |err| term: { + const allow_skip = switch (self.stdio) { + .check, .zig_test => self.skip_foreign_checks, + else => false, + }; + + var interp_argv = std.ArrayList([]const u8).init(b.allocator); + defer interp_argv.deinit(); + + const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: { // InvalidExe: cpu arch mismatch // FileNotFound: can happen with a wrong dynamic linker path if (err == error.InvalidExe or err == error.FileNotFound) interpret: { @@ -566,10 +575,10 @@ fn runCommand( .artifact => |exe| exe, else => break :interpret, }; - if (exe.kind != .exe) break :interpret; - - var interp_argv = std.ArrayList([]const u8).init(b.allocator); - defer interp_argv.deinit(); + switch (exe.kind) { + .exe, .@"test" => {}, + else => break :interpret, + } const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; switch (b.host.getExternalExecutor(exe.target_info, .{ @@ -577,14 +586,13 @@ fn runCommand( .link_libc = exe.is_linking_libc, })) { .native, .rosetta => { - if (self.stdio == .check and self.skip_foreign_checks) - return error.MakeSkipped; - + if (allow_skip) return error.MakeSkipped; break :interpret; }, .wine => |bin_name| { if (b.enable_wine) { try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); } else { return failForeign(self, "-fwine", argv[0], exe); } @@ -617,6 +625,8 @@ fn runCommand( try interp_argv.append("-L"); try interp_argv.append(full_dir); } + + try interp_argv.appendSlice(argv); } else { return failForeign(self, "-fqemu", argv[0], exe); } @@ -624,6 +634,7 @@ fn runCommand( .darling => |bin_name| { if (b.enable_darling) { try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); } else { return failForeign(self, "-fdarling", argv[0], exe); } @@ -632,13 +643,15 @@ fn runCommand( if (b.enable_wasmtime) { try interp_argv.append(bin_name); try interp_argv.append("--dir=."); + try interp_argv.append(argv[0]); + try interp_argv.append("--"); + try interp_argv.appendSlice(argv[1..]); } else { return failForeign(self, "-fwasmtime", argv[0], exe); } }, .bad_dl => |foreign_dl| { - if (self.stdio == .check and self.skip_foreign_checks) - return error.MakeSkipped; + if (allow_skip) return error.MakeSkipped; const host_dl = b.host.dynamic_linker.get() orelse "(none)"; @@ -650,8 +663,7 @@ fn runCommand( , .{ host_dl, foreign_dl }); }, .bad_os_or_cpu => { - if (self.stdio == .check and self.skip_foreign_checks) - return error.MakeSkipped; + if (allow_skip) return error.MakeSkipped; const host_name = try b.host.target.zigTriple(b.allocator); const foreign_name = try exe.target.zigTriple(b.allocator); @@ -667,11 +679,9 @@ fn runCommand( RunStep.addPathForDynLibsInternal(&self.step, b, exe); } - try interp_argv.append(argv[0]); - try Step.handleVerbose(step.owner, self.cwd, interp_argv.items); - break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects) catch |e| { + break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { return step.fail("unable to spawn {s}: {s}", .{ interp_argv.items[0], @errorName(e), }); @@ -683,6 +693,7 @@ fn runCommand( step.result_duration_ns = result.elapsed_ns; step.result_peak_rss = result.peak_rss; + step.test_results = result.stdio.test_results; // Capture stdout and stderr to GeneratedFile objects. const Stream = struct { @@ -693,13 +704,13 @@ fn runCommand( for ([_]Stream{ .{ .captured = self.captured_stdout, - .is_null = result.stdout_null, - .bytes = result.stdout, + .is_null = result.stdio.stdout_null, + .bytes = result.stdio.stdout, }, .{ .captured = self.captured_stderr, - .is_null = result.stderr_null, - .bytes = result.stderr, + .is_null = result.stdio.stderr_null, + .bytes = result.stdio.stderr, }, }) |stream| { if (stream.captured) |output| { @@ -724,11 +735,13 @@ fn runCommand( } } + const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; + switch (self.stdio) { .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { - assert(!result.stderr_null); - if (!mem.eql(u8, expected_bytes, result.stderr)) { + assert(!result.stdio.stderr_null); + if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) { return step.fail( \\ \\========= expected this stderr: ========= @@ -739,14 +752,14 @@ fn runCommand( \\{s} , .{ expected_bytes, - result.stderr, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stderr, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, .expect_stderr_match => |match| { - assert(!result.stderr_null); - if (mem.indexOf(u8, result.stderr, match) == null) { + assert(!result.stdio.stderr_null); + if (mem.indexOf(u8, result.stdio.stderr, match) == null) { return step.fail( \\ \\========= expected to find in stderr: ========= @@ -757,14 +770,14 @@ fn runCommand( \\{s} , .{ match, - result.stderr, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stderr, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, .expect_stdout_exact => |expected_bytes| { - assert(!result.stdout_null); - if (!mem.eql(u8, expected_bytes, result.stdout)) { + assert(!result.stdio.stdout_null); + if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) { return step.fail( \\ \\========= expected this stdout: ========= @@ -775,14 +788,14 @@ fn runCommand( \\{s} , .{ expected_bytes, - result.stdout, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stdout, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, .expect_stdout_match => |match| { - assert(!result.stdout_null); - if (mem.indexOf(u8, result.stdout, match) == null) { + assert(!result.stdio.stdout_null); + if (mem.indexOf(u8, result.stdio.stdout, match) == null) { return step.fail( \\ \\========= expected to find in stdout: ========= @@ -793,8 +806,8 @@ fn runCommand( \\{s} , .{ match, - result.stdout, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stdout, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, @@ -803,33 +816,46 @@ fn runCommand( return step.fail("the following command {} (expected {}):\n{s}", .{ fmtTerm(result.term), fmtTerm(expected_term), - try Step.allocPrintCmd(arena, self.cwd, argv), + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, }, + .zig_test => { + const expected_term: std.process.Child.Term = .{ .Exited = 0 }; + if (!termMatches(expected_term, result.term)) { + return step.fail("the following command {} (expected {}):\n{s}", .{ + fmtTerm(result.term), + fmtTerm(expected_term), + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + if (!result.stdio.test_results.isSuccess()) { + return step.fail( + "the following test command failed:\n{s}", + .{try Step.allocPrintCmd(arena, self.cwd, final_argv)}, + ); + } + }, else => { - try step.handleChildProcessTerm(result.term, self.cwd, argv); + try step.handleChildProcessTerm(result.term, self.cwd, final_argv); }, } } const ChildProcResult = struct { - // These use boolean flags instead of optionals as a workaround for - // https://github.com/ziglang/zig/issues/14783 - stdout: []const u8, - stderr: []const u8, - stdout_null: bool, - stderr_null: bool, term: std.process.Child.Term, elapsed_ns: u64, peak_rss: usize, + + stdio: StdIoResult, }; fn spawnChildAndCollect( self: *RunStep, argv: []const []const u8, has_side_effects: bool, + prog_node: *std.Progress.Node, ) !ChildProcResult { const b = self.step.owner; const arena = b.allocator; @@ -848,16 +874,19 @@ fn spawnChildAndCollect( .infer_from_args => if (has_side_effects) .Inherit else .Close, .inherit => .Inherit, .check => .Close, + .zig_test => .Pipe, }; child.stdout_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Ignore, .inherit => .Inherit, .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, + .zig_test => .Pipe, }; child.stderr_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Pipe, .inherit => .Inherit, .check => .Pipe, + .zig_test => .Pipe, }; if (self.captured_stdout != null) child.stdout_behavior = .Pipe; if (self.captured_stderr != null) child.stderr_behavior = .Pipe; @@ -871,6 +900,219 @@ fn spawnChildAndCollect( }); var timer = try std.time.Timer.start(); + const result = if (self.stdio == .zig_test) + evalZigTest(self, &child, prog_node) + else + evalGeneric(self, &child); + + const term = try child.wait(); + const elapsed_ns = timer.read(); + + return .{ + .stdio = try result, + .term = term, + .elapsed_ns = elapsed_ns, + .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, + }; +} + +const StdIoResult = struct { + // These use boolean flags instead of optionals as a workaround for + // https://github.com/ziglang/zig/issues/14783 + stdout: []const u8, + stderr: []const u8, + stdout_null: bool, + stderr_null: bool, + test_results: Step.TestResults, +}; + +fn evalZigTest( + self: *RunStep, + child: *std.process.Child, + prog_node: *std.Progress.Node, +) !StdIoResult { + const gpa = self.step.owner.allocator; + const arena = self.step.owner.allocator; + + var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ + .stdout = child.stdout.?, + .stderr = child.stderr.?, + }); + defer poller.deinit(); + + try sendMessage(child.stdin.?, .query_test_metadata); + + const Header = std.zig.Server.Message.Header; + + const stdout = poller.fifo(.stdout); + const stderr = poller.fifo(.stderr); + + var fail_count: u32 = 0; + var skip_count: u32 = 0; + var leak_count: u32 = 0; + var test_count: u32 = 0; + + var metadata: ?TestMetadata = null; + + var sub_prog_node: ?std.Progress.Node = null; + defer if (sub_prog_node) |*n| n.end(); + + poll: while (try poller.poll()) { + while (true) { + const buf = stdout.readableSlice(0); + assert(stdout.readableLength() == buf.len); + if (buf.len < @sizeOf(Header)) continue :poll; + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + const header_and_msg_len = header.bytes_len + @sizeOf(Header); + if (buf.len < header_and_msg_len) continue :poll; + const body = buf[@sizeOf(Header)..][0..header.bytes_len]; + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return self.step.fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ builtin.zig_version_string, body }, + ); + } + }, + .test_metadata => { + const TmHdr = std.zig.Server.Message.TestMetadata; + const tm_hdr = @ptrCast(*align(1) const TmHdr, body); + test_count = tm_hdr.tests_len; + + const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; + const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; + + const names = std.mem.bytesAsSlice(u32, names_bytes); + const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); + const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); + const names_aligned = try arena.alloc(u32, names.len); + for (names_aligned, names) |*dest, src| dest.* = src; + + const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); + for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; + + const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); + for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; + + prog_node.setEstimatedTotalItems(names.len); + metadata = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .names = names_aligned, + .async_frame_lens = async_frame_lens_aligned, + .expected_panic_msgs = expected_panic_msgs_aligned, + .next_index = 0, + .prog_node = prog_node, + }; + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + .test_results => { + const md = metadata.?; + + const TrHdr = std.zig.Server.Message.TestResults; + const tr_hdr = @ptrCast(*align(1) const TrHdr, body); + fail_count += @boolToInt(tr_hdr.flags.fail); + skip_count += @boolToInt(tr_hdr.flags.skip); + leak_count += @boolToInt(tr_hdr.flags.leak); + + if (tr_hdr.flags.fail or tr_hdr.flags.leak) { + const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); + const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); + const label = if (tr_hdr.flags.fail) "failed" else "leaked"; + if (msg.len > 0) { + try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); + } else { + try self.step.addError("'{s}' {s}", .{ name, label }); + } + stderr.discard(msg.len); + } + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + else => {}, // ignore other messages + } + stdout.discard(header_and_msg_len); + } + } + + if (stderr.readableLength() > 0) { + const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n"); + if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg); + } + + // Send EOF to stdin. + child.stdin.?.close(); + child.stdin = null; + + return .{ + .stdout = &.{}, + .stderr = &.{}, + .stdout_null = true, + .stderr_null = true, + .test_results = .{ + .test_count = test_count, + .fail_count = fail_count, + .skip_count = skip_count, + .leak_count = leak_count, + }, + }; +} + +const TestMetadata = struct { + names: []const u32, + async_frame_lens: []const u32, + expected_panic_msgs: []const u32, + string_bytes: []const u8, + next_index: u32, + prog_node: *std.Progress.Node, + + fn testName(tm: TestMetadata, index: u32) []const u8 { + return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); + } +}; + +fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { + while (metadata.next_index < metadata.names.len) { + const i = metadata.next_index; + metadata.next_index += 1; + + if (metadata.async_frame_lens[i] != 0) continue; + if (metadata.expected_panic_msgs[i] != 0) continue; + + const name = metadata.testName(i); + if (sub_prog_node.*) |*n| n.end(); + sub_prog_node.* = metadata.prog_node.start(name, 0); + + try sendRunTestMessage(in, i); + return; + } else { + try sendMessage(in, .exit); + } +} + +fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = tag, + .bytes_len = 0, + }; + try file.writeAll(std.mem.asBytes(&header)); +} + +fn sendRunTestMessage(file: std.fs.File, index: u32) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = .run_test, + .bytes_len = 4, + }; + const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); + try file.writeAll(full_msg); +} + +fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult { + const arena = self.step.owner.allocator; + if (self.stdin) |stdin| { child.stdin.?.writeAll(stdin) catch |err| { return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); @@ -925,17 +1167,12 @@ fn spawnChildAndCollect( } } - const term = try child.wait(); - const elapsed_ns = timer.read(); - return .{ .stdout = stdout_bytes, .stderr = stderr_bytes, .stdout_null = stdout_null, .stderr_null = stderr_null, - .term = term, - .elapsed_ns = elapsed_ns, - .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, + .test_results = .{}, }; } @@ -966,7 +1203,7 @@ fn failForeign( exe: *CompileStep, ) error{ MakeFailed, MakeSkipped, OutOfMemory } { switch (self.stdio) { - .check => { + .check, .zig_test => { if (self.skip_foreign_checks) return error.MakeSkipped; @@ -987,7 +1224,7 @@ fn failForeign( fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { switch (stdio) { - .infer_from_args, .inherit => {}, + .infer_from_args, .inherit, .zig_test => {}, .check => |checks| for (checks.items) |check| { hh.add(@as(std.meta.Tag(StdIo.Check), check)); switch (check) { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 45aa635972..05c4faa52d 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -35,11 +35,27 @@ result_cached: bool, result_duration_ns: ?u64, /// 0 means unavailable or not reported. result_peak_rss: usize, +test_results: TestResults, /// The return addresss associated with creation of this step that can be useful /// to print along with debugging messages. debug_stack_trace: [n_debug_stack_frames]usize, +pub const TestResults = struct { + fail_count: u32 = 0, + skip_count: u32 = 0, + leak_count: u32 = 0, + test_count: u32 = 0, + + pub fn isSuccess(tr: TestResults) bool { + return tr.fail_count == 0 and tr.leak_count == 0; + } + + pub fn passCount(tr: TestResults) u32 { + return tr.test_count - tr.fail_count - tr.skip_count; + } +}; + pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; const n_debug_stack_frames = 4; @@ -134,6 +150,7 @@ pub fn init(options: Options) Step { .result_cached = false, .result_duration_ns = null, .result_peak_rss = 0, + .test_results = .{}, }; } @@ -152,6 +169,10 @@ pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkip }, }; + if (!s.test_results.isSuccess()) { + return error.MakeFailed; + } + if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) { const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{ s.result_peak_rss, s.max_rss, @@ -346,9 +367,7 @@ pub fn evalZigProcess( s.result_cached = ebp_hdr.flags.cache_hit; result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); }, - _ => { - // Unrecognized message. - }, + else => {}, // ignore other messages } stdout.discard(header_and_msg_len); } @@ -475,3 +494,11 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path }); } + +pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void { + if (s.test_results.isSuccess()) { + man.writeManifest() catch |err| { + try s.addError("unable to write cache manifest: {s}", .{@errorName(err)}); + }; + } +} diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 9b033e5ae2..dee79af5be 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -282,7 +282,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }); } - try man.writeManifest(); + try step.writeManifest(&man); } const std = @import("../std.zig"); diff --git a/lib/std/zig/Client.zig b/lib/std/zig/Client.zig index a68c189e57..af4c29d37d 100644 --- a/lib/std/zig/Client.zig +++ b/lib/std/zig/Client.zig @@ -26,6 +26,13 @@ pub const Message = struct { /// swap. /// No body. hot_update, + /// Ask the test runner for metadata about all the unit tests that can + /// be run. Server will respond with a `test_metadata` message. + /// No body. + query_test_metadata, + /// Ask the test runner to run a particular test. + /// The message body is a u32 test index. + run_test, _, }; diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index d34b2193e9..3238f22043 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -1,3 +1,7 @@ +in: std.fs.File, +out: std.fs.File, +receive_fifo: std.fifo.LinearFifo(u8, .Dynamic), + pub const Message = struct { pub const Header = extern struct { tag: Tag, @@ -14,6 +18,11 @@ pub const Message = struct { progress, /// Body is a EmitBinPath. emit_bin_path, + /// Body is a TestMetadata + test_metadata, + /// Body is a TestResults + test_results, + _, }; @@ -26,6 +35,33 @@ pub const Message = struct { string_bytes_len: u32, }; + /// Trailing: + /// * name: [tests_len]u32 + /// - null-terminated string_bytes index + /// * async_frame_len: [tests_len]u32, + /// - 0 means not async + /// * expected_panic_msg: [tests_len]u32, + /// - null-terminated string_bytes index + /// - 0 means does not expect pani + /// * string_bytes: [string_bytes_len]u8, + pub const TestMetadata = extern struct { + string_bytes_len: u32, + tests_len: u32, + }; + + pub const TestResults = extern struct { + index: u32, + flags: Flags, + + pub const Flags = packed struct(u8) { + fail: bool, + skip: bool, + leak: bool, + + reserved: u5 = 0, + }; + }; + /// Trailing: /// * the file system path the emitted binary can be found pub const EmitBinPath = extern struct { @@ -37,3 +73,167 @@ pub const Message = struct { }; }; }; + +pub const Options = struct { + gpa: Allocator, + in: std.fs.File, + out: std.fs.File, + zig_version: []const u8, +}; + +pub fn init(options: Options) !Server { + var s: Server = .{ + .in = options.in, + .out = options.out, + .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa), + }; + try s.serveStringMessage(.zig_version, options.zig_version); + return s; +} + +pub fn deinit(s: *Server) void { + s.receive_fifo.deinit(); + s.* = undefined; +} + +pub fn receiveMessage(s: *Server) !InMessage.Header { + const Header = InMessage.Header; + const fifo = &s.receive_fifo; + + while (true) { + const buf = fifo.readableSlice(0); + assert(fifo.readableLength() == buf.len); + if (buf.len >= @sizeOf(Header)) { + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + + if (buf.len - @sizeOf(Header) >= header.bytes_len) { + const result = header.*; + fifo.discard(@sizeOf(Header)); + return result; + } else { + const needed = header.bytes_len - (buf.len - @sizeOf(Header)); + const write_buffer = try fifo.writableWithSize(needed); + const amt = try s.in.read(write_buffer); + fifo.update(amt); + continue; + } + } + + const write_buffer = try fifo.writableWithSize(256); + const amt = try s.in.read(write_buffer); + fifo.update(amt); + } +} + +pub fn receiveBody_u32(s: *Server) !u32 { + const fifo = &s.receive_fifo; + const buf = fifo.readableSlice(0); + const result = @ptrCast(*align(1) const u32, buf[0..4]).*; + fifo.discard(4); + return result; +} + +pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { + return s.serveMessage(.{ + .tag = tag, + .bytes_len = @intCast(u32, msg.len), + }, &.{msg}); +} + +pub fn serveMessage( + s: *const Server, + header: OutMessage.Header, + bufs: []const []const u8, +) !void { + var iovecs: [10]std.os.iovec_const = undefined; + iovecs[0] = .{ + .iov_base = @ptrCast([*]const u8, &header), + .iov_len = @sizeOf(OutMessage.Header), + }; + for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { + iovec.* = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + } + try s.out.writevAll(iovecs[0 .. bufs.len + 1]); +} + +pub fn serveEmitBinPath( + s: *Server, + fs_path: []const u8, + header: OutMessage.EmitBinPath, +) !void { + try s.serveMessage(.{ + .tag = .emit_bin_path, + .bytes_len = @intCast(u32, fs_path.len + @sizeOf(OutMessage.EmitBinPath)), + }, &.{ + std.mem.asBytes(&header), + fs_path, + }); +} + +pub fn serveTestResults( + s: *Server, + msg: OutMessage.TestResults, +) !void { + try s.serveMessage(.{ + .tag = .test_results, + .bytes_len = @intCast(u32, @sizeOf(OutMessage.TestResults)), + }, &.{ + std.mem.asBytes(&msg), + }); +} + +pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { + const eb_hdr: OutMessage.ErrorBundle = .{ + .extra_len = @intCast(u32, error_bundle.extra.len), + .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len), + }; + const bytes_len = @sizeOf(OutMessage.ErrorBundle) + + 4 * error_bundle.extra.len + error_bundle.string_bytes.len; + try s.serveMessage(.{ + .tag = .error_bundle, + .bytes_len = @intCast(u32, bytes_len), + }, &.{ + std.mem.asBytes(&eb_hdr), + // TODO: implement @ptrCast between slices changing the length + std.mem.sliceAsBytes(error_bundle.extra), + error_bundle.string_bytes, + }); +} + +pub const TestMetadata = struct { + names: []const u32, + async_frame_sizes: []const u32, + expected_panic_msgs: []const u32, + string_bytes: []const u8, +}; + +pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { + const header: OutMessage.TestMetadata = .{ + .tests_len = @intCast(u32, test_metadata.names.len), + .string_bytes_len = @intCast(u32, test_metadata.string_bytes.len), + }; + const bytes_len = @sizeOf(OutMessage.TestMetadata) + + 3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len; + return s.serveMessage(.{ + .tag = .test_metadata, + .bytes_len = @intCast(u32, bytes_len), + }, &.{ + std.mem.asBytes(&header), + // TODO: implement @ptrCast between slices changing the length + std.mem.sliceAsBytes(test_metadata.names), + std.mem.sliceAsBytes(test_metadata.async_frame_sizes), + std.mem.sliceAsBytes(test_metadata.expected_panic_msgs), + test_metadata.string_bytes, + }); +} + +const OutMessage = std.zig.Server.Message; +const InMessage = std.zig.Client.Message; + +const Server = @This(); +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; diff --git a/lib/test_runner.zig b/lib/test_runner.zig index 5968fdaa54..47ababbc2c 100644 --- a/lib/test_runner.zig +++ b/lib/test_runner.zig @@ -8,14 +8,130 @@ pub const std_options = struct { }; var log_err_count: usize = 0; +var cmdline_buffer: [4096]u8 = undefined; +var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer); pub fn main() void { - if (builtin.zig_backend != .stage1 and - builtin.zig_backend != .stage2_llvm and - builtin.zig_backend != .stage2_c) + if (builtin.zig_backend == .stage2_wasm or + builtin.zig_backend == .stage2_x86_64 or + builtin.zig_backend == .stage2_aarch64) { - return main2() catch @panic("test failure"); + return mainSimple() catch @panic("test failure"); + } + + const args = std.process.argsAlloc(fba.allocator()) catch + @panic("unable to parse command line args"); + + var listen = false; + + for (args[1..]) |arg| { + if (std.mem.eql(u8, arg, "--listen=-")) { + listen = true; + } else { + @panic("unrecognized command line argument"); + } + } + + if (listen) { + return mainServer(); + } else { + return mainTerminal(); + } +} + +fn mainServer() void { + return mainServerFallible() catch @panic("internal test runner failure"); +} + +fn mainServerFallible() !void { + var server = try std.zig.Server.init(.{ + .gpa = fba.allocator(), + .in = std.io.getStdIn(), + .out = std.io.getStdOut(), + .zig_version = builtin.zig_version_string, + }); + defer server.deinit(); + + while (true) { + const hdr = try server.receiveMessage(); + switch (hdr.tag) { + .exit => { + return std.process.exit(0); + }, + .query_test_metadata => { + std.testing.allocator_instance = .{}; + defer if (std.testing.allocator_instance.deinit()) { + @panic("internal test runner memory leak"); + }; + + var string_bytes: std.ArrayListUnmanaged(u8) = .{}; + defer string_bytes.deinit(std.testing.allocator); + try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null. + + const test_fns = builtin.test_functions; + const names = try std.testing.allocator.alloc(u32, test_fns.len); + defer std.testing.allocator.free(names); + const async_frame_sizes = try std.testing.allocator.alloc(u32, test_fns.len); + defer std.testing.allocator.free(async_frame_sizes); + const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len); + defer std.testing.allocator.free(expected_panic_msgs); + + for (test_fns, names, async_frame_sizes, expected_panic_msgs) |test_fn, *name, *async_frame_size, *expected_panic_msg| { + name.* = @intCast(u32, string_bytes.items.len); + try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1); + string_bytes.appendSliceAssumeCapacity(test_fn.name); + string_bytes.appendAssumeCapacity(0); + + async_frame_size.* = @intCast(u32, test_fn.async_frame_size orelse 0); + expected_panic_msg.* = 0; + } + + try server.serveTestMetadata(.{ + .names = names, + .async_frame_sizes = async_frame_sizes, + .expected_panic_msgs = expected_panic_msgs, + .string_bytes = string_bytes.items, + }); + }, + + .run_test => { + std.testing.allocator_instance = .{}; + const index = try server.receiveBody_u32(); + const test_fn = builtin.test_functions[index]; + if (test_fn.async_frame_size != null) + @panic("TODO test runner implement async tests"); + var fail = false; + var skip = false; + var leak = false; + test_fn.func() catch |err| switch (err) { + error.SkipZigTest => skip = true, + else => { + fail = true; + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + }, + }; + leak = std.testing.allocator_instance.deinit(); + try server.serveTestResults(.{ + .index = index, + .flags = .{ + .fail = fail, + .skip = skip, + .leak = leak, + }, + }); + }, + + else => { + std.debug.print("unsupported message: {x}", .{@enumToInt(hdr.tag)}); + std.process.exit(1); + }, + } } +} + +fn mainTerminal() void { const test_fn_list = builtin.test_functions; var ok_count: usize = 0; var skip_count: usize = 0; @@ -118,51 +234,17 @@ pub fn log( } } -pub fn main2() anyerror!void { - var skipped: usize = 0; - var failed: usize = 0; - // Simpler main(), exercising fewer language features, so that stage2 can handle it. +/// Simpler main(), exercising fewer language features, so that +/// work-in-progress backends can handle it. +pub fn mainSimple() anyerror!void { + //const stderr = std.io.getStdErr(); for (builtin.test_functions) |test_fn| { test_fn.func() catch |err| { if (err != error.SkipZigTest) { - failed += 1; - } else { - skipped += 1; + //stderr.writeAll(test_fn.name) catch {}; + //stderr.writeAll("\n") catch {}; + return err; } }; } - if (builtin.zig_backend == .stage2_wasm or - builtin.zig_backend == .stage2_x86_64 or - builtin.zig_backend == .stage2_aarch64 or - builtin.zig_backend == .stage2_llvm or - builtin.zig_backend == .stage2_c) - { - const passed = builtin.test_functions.len - skipped - failed; - const stderr = std.io.getStdErr(); - writeInt(stderr, passed) catch {}; - stderr.writeAll(" passed; ") catch {}; - writeInt(stderr, skipped) catch {}; - stderr.writeAll(" skipped; ") catch {}; - writeInt(stderr, failed) catch {}; - stderr.writeAll(" failed.\n") catch {}; - } - if (failed != 0) { - return error.TestsFailed; - } -} - -fn writeInt(stderr: std.fs.File, int: usize) anyerror!void { - const base = 10; - var buf: [100]u8 = undefined; - var a: usize = int; - var index: usize = buf.len; - while (true) { - const digit = a % base; - index -= 1; - buf[index] = std.fmt.digitToChar(@intCast(u8, digit), .lower); - a /= base; - if (a == 0) break; - } - const slice = buf[index..]; - try stderr.writeAll(slice); } diff --git a/src/Server.zig b/src/Server.zig deleted file mode 100644 index a25dc93857..0000000000 --- a/src/Server.zig +++ /dev/null @@ -1,113 +0,0 @@ -in: std.fs.File, -out: std.fs.File, -receive_fifo: std.fifo.LinearFifo(u8, .Dynamic), - -pub const Options = struct { - gpa: Allocator, - in: std.fs.File, - out: std.fs.File, -}; - -pub fn init(options: Options) !Server { - var s: Server = .{ - .in = options.in, - .out = options.out, - .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa), - }; - try s.serveStringMessage(.zig_version, build_options.version); - return s; -} - -pub fn deinit(s: *Server) void { - s.receive_fifo.deinit(); - s.* = undefined; -} - -pub fn receiveMessage(s: *Server) !InMessage.Header { - const Header = InMessage.Header; - const fifo = &s.receive_fifo; - - while (true) { - const buf = fifo.readableSlice(0); - assert(fifo.readableLength() == buf.len); - if (buf.len >= @sizeOf(Header)) { - const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); - if (header.bytes_len != 0) - return error.InvalidClientMessage; - const result = header.*; - fifo.discard(@sizeOf(Header)); - return result; - } - - const write_buffer = try fifo.writableWithSize(256); - const amt = try s.in.read(write_buffer); - fifo.update(amt); - } -} - -pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { - return s.serveMessage(.{ - .tag = tag, - .bytes_len = @intCast(u32, msg.len), - }, &.{msg}); -} - -pub fn serveMessage( - s: *const Server, - header: OutMessage.Header, - bufs: []const []const u8, -) !void { - var iovecs: [10]std.os.iovec_const = undefined; - iovecs[0] = .{ - .iov_base = @ptrCast([*]const u8, &header), - .iov_len = @sizeOf(OutMessage.Header), - }; - for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { - iovec.* = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }; - } - try s.out.writevAll(iovecs[0 .. bufs.len + 1]); -} - -pub fn serveEmitBinPath( - s: *Server, - fs_path: []const u8, - header: std.zig.Server.Message.EmitBinPath, -) !void { - try s.serveMessage(.{ - .tag = .emit_bin_path, - .bytes_len = @intCast(u32, fs_path.len + @sizeOf(std.zig.Server.Message.EmitBinPath)), - }, &.{ - std.mem.asBytes(&header), - fs_path, - }); -} - -pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { - const eb_hdr: std.zig.Server.Message.ErrorBundle = .{ - .extra_len = @intCast(u32, error_bundle.extra.len), - .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len), - }; - const bytes_len = @sizeOf(std.zig.Server.Message.ErrorBundle) + - 4 * error_bundle.extra.len + error_bundle.string_bytes.len; - try s.serveMessage(.{ - .tag = .error_bundle, - .bytes_len = @intCast(u32, bytes_len), - }, &.{ - std.mem.asBytes(&eb_hdr), - // TODO: implement @ptrCast between slices changing the length - std.mem.sliceAsBytes(error_bundle.extra), - error_bundle.string_bytes, - }); -} - -const OutMessage = std.zig.Server.Message; -const InMessage = std.zig.Client.Message; - -const Server = @This(); -const std = @import("std"); -const build_options = @import("build_options"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; diff --git a/src/main.zig b/src/main.zig index e0283143d0..9602a5cd31 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,6 +10,7 @@ const ArrayList = std.ArrayList; const Ast = std.zig.Ast; const warn = std.log.warn; const ThreadPool = std.Thread.Pool; +const cleanExit = std.process.cleanExit; const tracy = @import("tracy.zig"); const Compilation = @import("Compilation.zig"); @@ -26,7 +27,7 @@ const target_util = @import("target.zig"); const crash_report = @import("crash_report.zig"); const Module = @import("Module.zig"); const AstGen = @import("AstGen.zig"); -const Server = @import("Server.zig"); +const Server = std.zig.Server; pub const std_options = struct { pub const wasiCwd = wasi_cwd; @@ -3545,6 +3546,7 @@ fn serve( .gpa = gpa, .in = in, .out = out, + .zig_version = build_options.version, }); defer server.deinit(); @@ -3656,8 +3658,8 @@ fn serve( ); } }, - _ => { - @panic("TODO unrecognized message from client"); + else => { + fatal("unrecognized message from client: 0x{x}", .{@enumToInt(hdr.tag)}); }, } } @@ -5624,19 +5626,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat return std.zig.system.NativeTargetInfo.detect(cross_target); } -/// Indicate that we are now terminating with a successful exit code. -/// In debug builds, this is a no-op, so that the calling code's -/// cleanup mechanisms are tested and so that external tools that -/// check for resource leaks can be accurate. In release builds, this -/// calls exit(0), and does not return. -pub fn cleanExit() void { - if (builtin.mode == .Debug) { - return; - } else { - process.exit(0); - } -} - const usage_ast_check = \\Usage: zig ast-check [file] \\ diff --git a/src/objcopy.zig b/src/objcopy.zig index e821a94b59..c3305e8c04 100644 --- a/src/objcopy.zig +++ b/src/objcopy.zig @@ -8,8 +8,8 @@ const assert = std.debug.assert; const main = @import("main.zig"); const fatal = main.fatal; -const cleanExit = main.cleanExit; -const Server = @import("Server.zig"); +const Server = std.zig.Server; +const build_options = @import("build_options"); pub fn cmdObjCopy( gpa: Allocator, @@ -116,6 +116,7 @@ pub fn cmdObjCopy( .gpa = gpa, .in = std.io.getStdIn(), .out = std.io.getStdOut(), + .zig_version = build_options.version, }); defer server.deinit(); @@ -124,7 +125,7 @@ pub fn cmdObjCopy( const hdr = try server.receiveMessage(); switch (hdr.tag) { .exit => { - return cleanExit(); + return std.process.cleanExit(); }, .update => { if (seen_update) { @@ -144,7 +145,7 @@ pub fn cmdObjCopy( } } } - return cleanExit(); + return std.process.cleanExit(); } const usage = diff --git a/test/link/common_symbols/build.zig b/test/link/common_symbols/build.zig index e3c302f0f7..a8c276d1f3 100644 --- a/test/link/common_symbols/build.zig +++ b/test/link/common_symbols/build.zig @@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize }); test_exe.linkLibrary(lib_a); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/link/common_symbols_alignment/build.zig b/test/link/common_symbols_alignment/build.zig index 7d1d813447..83548f2d8a 100644 --- a/test/link/common_symbols_alignment/build.zig +++ b/test/link/common_symbols_alignment/build.zig @@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize }); test_exe.linkLibrary(lib_a); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/link/interdependent_static_c_libs/build.zig b/test/link/interdependent_static_c_libs/build.zig index c4118c1ca4..0d06410a79 100644 --- a/test/link/interdependent_static_c_libs/build.zig +++ b/test/link/interdependent_static_c_libs/build.zig @@ -35,5 +35,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize test_exe.linkLibrary(lib_b); test_exe.addIncludePath("."); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/link/macho/tls/build.zig b/test/link/macho/tls/build.zig index e91f40bd59..5981fea194 100644 --- a/test/link/macho/tls/build.zig +++ b/test/link/macho/tls/build.zig @@ -32,5 +32,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize test_exe.linkLibrary(lib); test_exe.linkLibC(); - test_step.dependOn(&test_exe.step); + const run = test_exe.run(); + run.skip_foreign_checks = true; + + test_step.dependOn(&run.step); } diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 5007939b14..6affe968d1 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -547,15 +547,12 @@ pub fn lowerToBuildSteps( parent_step.dependOn(&artifact.step); }, .Execution => |expected_stdout| { - if (case.is_test) { - parent_step.dependOn(&artifact.step); - } else { - const run = b.addRunArtifact(artifact); - run.skip_foreign_checks = true; + const run = b.addRunArtifact(artifact); + run.skip_foreign_checks = true; + if (!case.is_test) { run.expectStdOutEqual(expected_stdout); - - parent_step.dependOn(&run.step); } + parent_step.dependOn(&run.step); }, .Header => @panic("TODO"), } diff --git a/test/standalone/emit_asm_and_bin/build.zig b/test/standalone/emit_asm_and_bin/build.zig index 9bdfadc33d..594bf6552e 100644 --- a/test/standalone/emit_asm_and_bin/build.zig +++ b/test/standalone/emit_asm_and_bin/build.zig @@ -11,5 +11,5 @@ pub fn build(b: *std.Build) void { main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") }; main.emit_bin = .{ .emit_to = b.pathFromRoot("main") }; - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/global_linkage/build.zig b/test/standalone/global_linkage/build.zig index 2cf1b248a5..ddcddd612a 100644 --- a/test/standalone/global_linkage/build.zig +++ b/test/standalone/global_linkage/build.zig @@ -28,5 +28,5 @@ pub fn build(b: *std.Build) void { main.linkLibrary(obj1); main.linkLibrary(obj2); - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/issue_13970/build.zig b/test/standalone/issue_13970/build.zig index bbaaac5886..1eb8a5a121 100644 --- a/test/standalone/issue_13970/build.zig +++ b/test/standalone/issue_13970/build.zig @@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void { test2.setTestRunner("src/main.zig"); test3.setTestRunner("src/main.zig"); - test_step.dependOn(&test1.step); - test_step.dependOn(&test2.step); - test_step.dependOn(&test3.step); + test_step.dependOn(&test1.run().step); + test_step.dependOn(&test2.run().step); + test_step.dependOn(&test3.run().step); } diff --git a/test/standalone/main_pkg_path/build.zig b/test/standalone/main_pkg_path/build.zig index cd49573692..a4dd301c43 100644 --- a/test/standalone/main_pkg_path/build.zig +++ b/test/standalone/main_pkg_path/build.zig @@ -9,5 +9,5 @@ pub fn build(b: *std.Build) void { }); test_exe.setMainPkgPath("."); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/standalone/options/build.zig b/test/standalone/options/build.zig index 3f1e823359..5e894102a7 100644 --- a/test/standalone/options/build.zig +++ b/test/standalone/options/build.zig @@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void { options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?); const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/pie/build.zig b/test/standalone/pie/build.zig index 615111b6c2..546b4a922f 100644 --- a/test/standalone/pie/build.zig +++ b/test/standalone/pie/build.zig @@ -17,5 +17,5 @@ pub fn build(b: *std.Build) void { }); main.pie = true; - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/static_c_lib/build.zig b/test/standalone/static_c_lib/build.zig index 5996c978d8..794b813b75 100644 --- a/test/standalone/static_c_lib/build.zig +++ b/test/standalone/static_c_lib/build.zig @@ -21,5 +21,5 @@ pub fn build(b: *std.Build) void { test_exe.linkLibrary(foo); test_exe.addIncludePath("."); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/standalone/test_runner_module_imports/build.zig b/test/standalone/test_runner_module_imports/build.zig index 973365e495..73c5536dc6 100644 --- a/test/standalone/test_runner_module_imports/build.zig +++ b/test/standalone/test_runner_module_imports/build.zig @@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void { t.addModule("module2", module2); const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&t.step); + test_step.dependOn(&t.run().step); } diff --git a/test/standalone/test_runner_path/build.zig b/test/standalone/test_runner_path/build.zig index 40aad42b21..ce5b668054 100644 --- a/test/standalone/test_runner_path/build.zig +++ b/test/standalone/test_runner_path/build.zig @@ -8,7 +8,6 @@ pub fn build(b: *std.Build) void { const test_exe = b.addTest(.{ .root_source_file = .{ .path = "test.zig" }, - .kind = .test_exe, }); test_exe.test_runner = "test_runner.zig"; diff --git a/test/standalone/use_alias/build.zig b/test/standalone/use_alias/build.zig index 947db0828d..db47fe6692 100644 --- a/test/standalone/use_alias/build.zig +++ b/test/standalone/use_alias/build.zig @@ -12,5 +12,5 @@ pub fn build(b: *std.Build) void { }); main.addIncludePath("."); - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/tests.zig b/test/tests.zig index 9ba9639e2a..76ea537bb2 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -596,7 +596,7 @@ pub fn addStandaloneTests( }); if (case.link_libc) exe.linkLibC(); - step.dependOn(&exe.step); + step.dependOn(&exe.run().step); } } } @@ -981,14 +981,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { }); const single_threaded_txt = if (test_target.single_threaded) "single" else "multi"; const backend_txt = if (test_target.backend) |backend| @tagName(backend) else "default"; - these_tests.setNamePrefix(b.fmt("{s}-{s}-{s}-{s}-{s}-{s} ", .{ - options.name, - triple_prefix, - @tagName(test_target.optimize_mode), - libc_prefix, - single_threaded_txt, - backend_txt, - })); these_tests.single_threaded = test_target.single_threaded; these_tests.setFilter(options.test_filter); if (test_target.link_libc) { @@ -1014,7 +1006,18 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { }, }; - step.dependOn(&these_tests.step); + const run = these_tests.run(); + run.skip_foreign_checks = true; + run.setName(b.fmt("run test {s}-{s}-{s}-{s}-{s}-{s}", .{ + options.name, + triple_prefix, + @tagName(test_target.optimize_mode), + libc_prefix, + single_threaded_txt, + backend_txt, + })); + + step.dependOn(&run.step); } return step; } @@ -1053,7 +1056,9 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S @tagName(optimize_mode), })); - step.dependOn(&test_step.step); + const run = test_step.run(); + run.skip_foreign_checks = true; + step.dependOn(&run.step); } } return step; -- cgit v1.2.3 From 8a8f148c8cb1254a3989584c54181218434b56c9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 12:13:37 -0700 Subject: test-stack-trace: set env to disable color The tests rely on the absence of terminal escape codes. --- test/src/StackTrace.zig | 2 ++ 1 file changed, 2 insertions(+) (limited to 'test/src') diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index 5709b9d29e..c32720a210 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -82,6 +82,8 @@ fn addExpect( }); const run = b.addRunArtifact(exe); + run.removeEnvironmentVariable("ZIG_DEBUG_COLOR"); + run.setEnvironmentVariable("NO_COLOR", "1"); run.expectExitCode(1); run.expectStdOutEqual(""); -- cgit v1.2.3 From 171977dc1c9d15a405b70bfa980c7d188410979e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 18:24:17 -0700 Subject: test-cases: fix incorrectly linking libc when backend is llvm Now link_libc=1 must be used to link with libc, instead of the test harness assuming that using the llvm backend means additionally linking with libc. --- test/cases/f32_passed_to_variadic_fn.zig | 3 ++- test/cases/fn_typeinfo_passed_to_comptime_fn.zig | 1 + test/cases/llvm/hello_world.zig | 1 + test/src/Cases.zig | 5 ++++- 4 files changed, 8 insertions(+), 2 deletions(-) (limited to 'test/src') diff --git a/test/cases/f32_passed_to_variadic_fn.zig b/test/cases/f32_passed_to_variadic_fn.zig index c029b4b69f..4ae1d2cf08 100644 --- a/test/cases/f32_passed_to_variadic_fn.zig +++ b/test/cases/f32_passed_to_variadic_fn.zig @@ -9,7 +9,8 @@ pub fn main() void { // run // backend=llvm // target=x86_64-linux-gnu +// link_libc=1 // // f64: 2.000000 // f32: 10.000000 -// \ No newline at end of file +// diff --git a/test/cases/fn_typeinfo_passed_to_comptime_fn.zig b/test/cases/fn_typeinfo_passed_to_comptime_fn.zig index 31673e5b81..fb64788126 100644 --- a/test/cases/fn_typeinfo_passed_to_comptime_fn.zig +++ b/test/cases/fn_typeinfo_passed_to_comptime_fn.zig @@ -14,4 +14,5 @@ fn foo(comptime info: std.builtin.Type) !void { // run // is_test=1 +// backend=llvm // diff --git a/test/cases/llvm/hello_world.zig b/test/cases/llvm/hello_world.zig index 4243191b0f..0f75f624ec 100644 --- a/test/cases/llvm/hello_world.zig +++ b/test/cases/llvm/hello_world.zig @@ -7,6 +7,7 @@ pub fn main() void { // run // backend=llvm // target=x86_64-linux,x86_64-macos +// link_libc=1 // // hello world! // diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 6affe968d1..c3a4c1df47 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -382,6 +382,7 @@ fn addFromDirInner( const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend); const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget); const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); + const link_libc = try manifest.getConfigForKeyAssertSingle("link_libc", bool); const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); var cases = std.ArrayList(usize).init(ctx.arena); @@ -397,7 +398,7 @@ fn addFromDirInner( .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), .is_test = is_test, .output_mode = output_mode, - .link_libc = backend == .llvm, + .link_libc = link_libc, .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), }); try cases.append(next); @@ -745,6 +746,8 @@ const TestManifestConfigDefaults = struct { }; } else if (std.mem.eql(u8, key, "is_test")) { return "0"; + } else if (std.mem.eql(u8, key, "link_libc")) { + return "0"; } else unreachable; } }; -- cgit v1.2.3