zig/test/tests.zig
Andrew Kelley 105db13536 std.Build: implement --host-target, --host-cpu, --host-dynamic-linker
This also makes a long-overdue change of extracting common state from
Build into a shared Graph object.

Getting the semantics right for these flags turned out to be quite
tricky. In the end it works like this:
* The override only happens when the target is fully native, with no
  additional query parameters, such as versions or CPU features added.
* The override affects the resolved Target but leaves the original Query
  unmodified.
* The "is native?" detection logic operates on the original, unmodified
  query. This makes it possible to provide invalid host target
  information, causing confusing errors to occur. Don't do that.

There are some minor breaking changes to std.Build API such as the fact
that `b.zig_exe` is now moved to `b.graph.zig_exe`, as well as a handful
of other similar flags.
2024-02-02 20:43:01 -07:00

1322 lines
42 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const mem = std.mem;
const OptimizeMode = std.builtin.OptimizeMode;
const Step = std.Build.Step;
// Cases
const compare_output = @import("compare_output.zig");
const standalone = @import("standalone.zig");
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 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/CompareOutput.zig");
pub const StackTracesContext = @import("src/StackTrace.zig");
const TestTarget = struct {
target: std.Target.Query = .{},
optimize_mode: std.builtin.OptimizeMode = .Debug,
link_libc: ?bool = null,
single_threaded: ?bool = null,
use_llvm: ?bool = null,
use_lld: ?bool = null,
pic: ?bool = null,
strip: ?bool = null,
};
const test_targets = blk: {
// getBaselineCpuFeatures calls populateDependencies which has a O(N ^ 2) algorithm
// (where N is roughly 160, which technically makes it O(1), but it adds up to a
// lot of branches)
@setEvalBranchQuota(50000);
break :blk [_]TestTarget{
.{},
.{
.link_libc = true,
},
.{
.single_threaded = true,
},
.{
.optimize_mode = .ReleaseFast,
},
.{
.link_libc = true,
.optimize_mode = .ReleaseFast,
},
.{
.optimize_mode = .ReleaseFast,
.single_threaded = true,
},
.{
.optimize_mode = .ReleaseSafe,
},
.{
.link_libc = true,
.optimize_mode = .ReleaseSafe,
},
.{
.optimize_mode = .ReleaseSafe,
.single_threaded = true,
},
.{
.optimize_mode = .ReleaseSmall,
},
.{
.link_libc = true,
.optimize_mode = .ReleaseSmall,
},
.{
.optimize_mode = .ReleaseSmall,
.single_threaded = true,
},
.{
.target = .{
.ofmt = .c,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .none,
},
.use_llvm = false,
.use_lld = false,
},
.{
.target = .{
.cpu_arch = .x86_64,
.cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 },
.os_tag = .linux,
.abi = .none,
},
.use_llvm = false,
.use_lld = false,
.pic = true,
},
.{
.target = .{
.cpu_arch = .x86_64,
.cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 },
.os_tag = .linux,
.abi = .none,
},
.use_llvm = false,
.use_lld = false,
.strip = true,
},
// Doesn't support new liveness
//.{
// .target = .{
// .cpu_arch = .aarch64,
// .os_tag = .linux,
// },
// .use_llvm = false,
// .use_lld = false,
//},
.{
.target = .{
.cpu_arch = .wasm32,
.os_tag = .wasi,
},
.use_llvm = false,
.use_lld = false,
},
// https://github.com/ziglang/zig/issues/13623
//.{
// .target = .{
// .cpu_arch = .arm,
// .os_tag = .linux,
// },
// .use_llvm = false,
// .use_lld = false,
//},
// https://github.com/ziglang/zig/issues/13623
//.{
// .target = std.Target.Query.parse(.{
// .arch_os_abi = "arm-linux-none",
// .cpu_features = "generic+v8a",
// }) catch unreachable,
// .use_llvm = false,
// .use_lld = false,
//},
// Doesn't support new liveness
//.{
// .target = .{
// .cpu_arch = .aarch64,
// .os_tag = .macos,
// .abi = .none,
// },
// .use_llvm = false,
// .use_lld = false,
//},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .macos,
.abi = .none,
},
.use_llvm = false,
.use_lld = false,
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
},
.use_llvm = false,
.use_lld = false,
},
.{
.target = .{
.cpu_arch = .wasm32,
.os_tag = .wasi,
},
.link_libc = false,
},
.{
.target = .{
.cpu_arch = .wasm32,
.os_tag = .wasi,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .gnu,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .musl,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .musl,
},
.link_libc = true,
.use_lld = false,
},
.{
.target = .{
.cpu_arch = .x86,
.os_tag = .linux,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .x86,
.os_tag = .linux,
.abi = .musl,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .x86,
.os_tag = .linux,
.abi = .gnu,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .aarch64,
.os_tag = .linux,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .aarch64,
.os_tag = .linux,
.abi = .musl,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .aarch64,
.os_tag = .linux,
.abi = .gnu,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .aarch64,
.os_tag = .windows,
.abi = .gnu,
},
.link_libc = true,
},
.{
.target = std.Target.Query.parse(.{
.arch_os_abi = "arm-linux-none",
.cpu_features = "generic+v8a",
}) catch unreachable,
},
.{
.target = std.Target.Query.parse(.{
.arch_os_abi = "arm-linux-musleabihf",
.cpu_features = "generic+v8a",
}) catch unreachable,
.link_libc = true,
},
// https://github.com/ziglang/zig/issues/3287
//.{
// .target = std.Target.Query.parse(.{
// .arch_os_abi = "arm-linux-gnueabihf",
// .cpu_features = "generic+v8a",
// }) catch unreachable,
// .link_libc = true,
//},
// https://github.com/ziglang/zig/issues/16846
//.{
// .target = .{
// .cpu_arch = .mips,
// .os_tag = .linux,
// .abi = .none,
// },
//},
// https://github.com/ziglang/zig/issues/16846
//.{
// .target = .{
// .cpu_arch = .mips,
// .os_tag = .linux,
// .abi = .musl,
// },
// .link_libc = true,
//},
// https://github.com/ziglang/zig/issues/4927
//.{
// .target = .{
// .cpu_arch = .mips,
// .os_tag = .linux,
// .abi = .gnueabihf,
// },
// .link_libc = true,
//},
// https://github.com/ziglang/zig/issues/16846
//.{
// .target = .{
// .cpu_arch = .mipsel,
// .os_tag = .linux,
// .abi = .none,
// },
//},
// https://github.com/ziglang/zig/issues/16846
//.{
// .target = .{
// .cpu_arch = .mipsel,
// .os_tag = .linux,
// .abi = .musl,
// },
// .link_libc = true,
//},
// https://github.com/ziglang/zig/issues/4927
//.{
// .target = .{
// .cpu_arch = .mipsel,
// .os_tag = .linux,
// .abi = .gnueabihf,
// },
// .link_libc = true,
//},
.{
.target = .{
.cpu_arch = .powerpc,
.os_tag = .linux,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .powerpc,
.os_tag = .linux,
.abi = .musl,
},
.link_libc = true,
},
// https://github.com/ziglang/zig/issues/2256
//.{
// .target = .{
// .cpu_arch = .powerpc,
// .os_tag = .linux,
// .abi = .gnueabihf,
// },
// .link_libc = true,
//},
.{
.target = .{
.cpu_arch = .powerpc64le,
.os_tag = .linux,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .powerpc64le,
.os_tag = .linux,
.abi = .musl,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .powerpc64le,
.os_tag = .linux,
.abi = .gnu,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .riscv64,
.os_tag = .linux,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .riscv64,
.os_tag = .linux,
.abi = .musl,
},
.link_libc = true,
},
// https://github.com/ziglang/zig/issues/3340
//.{
// .target = .{
// .cpu_arch = .riscv64,
// .os = .linux,
// .abi = .gnu,
// },
// .link_libc = true,
//},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .macos,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .aarch64,
.os_tag = .macos,
.abi = .none,
},
},
.{
.target = .{
.cpu_arch = .x86,
.os_tag = .windows,
.abi = .msvc,
},
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .msvc,
},
},
.{
.target = .{
.cpu_arch = .x86,
.os_tag = .windows,
.abi = .gnu,
},
.link_libc = true,
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
},
.link_libc = true,
},
};
};
const CAbiTarget = struct {
target: std.Target.Query = .{},
use_llvm: ?bool = null,
use_lld: ?bool = null,
pic: ?bool = null,
strip: ?bool = null,
c_defines: []const []const u8 = &.{},
};
const c_abi_targets = [_]CAbiTarget{
.{},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .musl,
},
.use_llvm = false,
.use_lld = false,
.c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
},
.{
.target = .{
.cpu_arch = .x86_64,
.cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 },
.os_tag = .linux,
.abi = .musl,
},
.use_llvm = false,
.use_lld = false,
.strip = true,
.c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
},
.{
.target = .{
.cpu_arch = .x86_64,
.cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 },
.os_tag = .linux,
.abi = .musl,
},
.use_llvm = false,
.use_lld = false,
.pic = true,
.c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
},
.{
.target = .{
.cpu_arch = .x86,
.os_tag = .linux,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .aarch64,
.os_tag = .linux,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .arm,
.os_tag = .linux,
.abi = .musleabihf,
},
},
.{
.target = .{
.cpu_arch = .mips,
.os_tag = .linux,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .riscv64,
.os_tag = .linux,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .wasm32,
.os_tag = .wasi,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .powerpc,
.os_tag = .linux,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .powerpc64le,
.os_tag = .linux,
.abi = .musl,
},
},
.{
.target = .{
.cpu_arch = .x86,
.os_tag = .windows,
.abi = .gnu,
},
},
.{
.target = .{
.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 @panic("OOM");
cases.* = CompareOutputContext{
.b = b,
.step = b.step("test-compare-output", "Run the compare output tests"),
.test_index = 0,
.test_filter = test_filter,
.optimize_modes = optimize_modes,
};
compare_output.addCases(cases);
return cases.step;
}
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 = b.host,
.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);
return cases.step;
}
pub fn addStandaloneTests(
b: *std.Build,
optimize_modes: []const OptimizeMode,
enable_macos_sdk: bool,
enable_ios_sdk: bool,
omit_stage2: bool,
enable_symlinks_windows: bool,
) *Step {
const step = b.step("test-standalone", "Run the standalone tests");
const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows;
for (standalone.simple_cases) |case| {
for (optimize_modes) |optimize| {
if (!case.all_modes and optimize != .Debug) continue;
if (case.os_filter) |os_tag| {
if (os_tag != builtin.os.tag) continue;
}
const resolved_target = b.resolveTargetQuery(case.target);
if (case.is_exe) {
const exe = b.addExecutable(.{
.name = std.fs.path.stem(case.src_path),
.root_source_file = .{ .path = case.src_path },
.optimize = optimize,
.target = resolved_target,
});
if (case.link_libc) exe.linkLibC();
_ = exe.getEmittedBin();
step.dependOn(&exe.step);
}
if (case.is_test) {
const exe = b.addTest(.{
.name = std.fs.path.stem(case.src_path),
.root_source_file = .{ .path = case.src_path },
.optimize = optimize,
.target = resolved_target,
});
if (case.link_libc) exe.linkLibC();
const run = b.addRunArtifact(exe);
step.dependOn(&run.step);
}
}
}
inline for (standalone.build_cases) |case| {
const requires_stage2 = @hasDecl(case.import, "requires_stage2") and
case.import.requires_stage2;
const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and
case.import.requires_symlinks;
const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and
case.import.requires_macos_sdk;
const requires_ios_sdk = @hasDecl(case.import, "requires_ios_sdk") and
case.import.requires_ios_sdk;
const bad =
(requires_stage2 and omit_stage2) or
(requires_symlinks and omit_symlinks) or
(requires_macos_sdk and !enable_macos_sdk) or
(requires_ios_sdk and !enable_ios_sdk);
if (!bad) {
const dep = b.anonymousDependency(case.build_root, case.import, .{});
const dep_step = dep.builder.default_step;
assert(mem.startsWith(u8, dep.builder.dep_prefix, "test."));
const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..];
dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name });
step.dependOn(dep_step);
}
}
return step;
}
pub fn addLinkTests(
b: *std.Build,
enable_macos_sdk: bool,
enable_ios_sdk: bool,
omit_stage2: bool,
enable_symlinks_windows: bool,
) *Step {
const step = b.step("test-link", "Run the linker tests");
const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows;
inline for (link.cases) |case| {
if (mem.eql(u8, @typeName(case.import), "test.link.link")) {
const dep = b.anonymousDependency(case.build_root, case.import, .{
.has_macos_sdk = enable_macos_sdk,
.has_ios_sdk = enable_ios_sdk,
.has_symlinks_windows = !omit_symlinks,
});
const dep_step = dep.builder.default_step;
assert(mem.startsWith(u8, dep.builder.dep_prefix, "test."));
const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..];
dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name });
step.dependOn(dep_step);
} else {
const requires_stage2 = @hasDecl(case.import, "requires_stage2") and
case.import.requires_stage2;
const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and
case.import.requires_symlinks;
const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and
case.import.requires_macos_sdk;
const requires_ios_sdk = @hasDecl(case.import, "requires_ios_sdk") and
case.import.requires_ios_sdk;
const bad =
(requires_stage2 and omit_stage2) or
(requires_symlinks and omit_symlinks) or
(requires_macos_sdk and !enable_macos_sdk) or
(requires_ios_sdk and !enable_ios_sdk);
if (!bad) {
const dep = b.anonymousDependency(case.build_root, case.import, .{});
const dep_step = dep.builder.default_step;
assert(mem.startsWith(u8, dep.builder.dep_prefix, "test."));
const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..];
dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name });
step.dependOn(dep_step);
}
}
}
return step;
}
pub fn addCliTests(b: *std.Build) *Step {
const step = b.step("test-cli", "Test the command line interface");
const s = std.fs.path.sep_str;
{
// Test `zig init`.
const tmp_path = b.makeTempPath();
const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init" });
init_exe.setCwd(.{ .cwd_relative = tmp_path });
init_exe.setName("zig init");
init_exe.expectStdOutEqual("");
init_exe.expectStdErrEqual("info: created build.zig\n" ++
"info: created build.zig.zon\n" ++
"info: created src" ++ s ++ "main.zig\n" ++
"info: created src" ++ s ++ "root.zig\n" ++
"info: see `zig build --help` for a menu of options\n");
// Test missing output path.
const bad_out_arg = "-femit-bin=does" ++ s ++ "not" ++ s ++ "exist" ++ s ++ "foo.exe";
const ok_src_arg = "src" ++ s ++ "main.zig";
const expected = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n";
const run_bad = b.addSystemCommand(&.{ b.graph.zig_exe, "build-exe", ok_src_arg, bad_out_arg });
run_bad.setName("zig build-exe error message for bad -femit-bin arg");
run_bad.expectExitCode(1);
run_bad.expectStdErrEqual(expected);
run_bad.expectStdOutEqual("");
run_bad.step.dependOn(&init_exe.step);
const run_test = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test" });
run_test.setCwd(.{ .cwd_relative = tmp_path });
run_test.setName("zig build test");
run_test.expectStdOutEqual("");
run_test.step.dependOn(&init_exe.step);
const run_run = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "run" });
run_run.setCwd(.{ .cwd_relative = tmp_path });
run_run.setName("zig build run");
run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");
run_run.expectStdErrEqual("All your codebase are belong to us.\n");
run_run.step.dependOn(&init_exe.step);
const cleanup = b.addRemoveDirTree(tmp_path);
cleanup.step.dependOn(&run_test.step);
cleanup.step.dependOn(&run_run.step);
cleanup.step.dependOn(&run_bad.step);
step.dependOn(&cleanup.step);
}
// Test Godbolt API
if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
const tmp_path = b.makeTempPath();
const writefile = b.addWriteFile("example.zig",
\\// Type your code here, or load an example.
\\export fn square(num: i32) i32 {
\\ return num * num;
\\}
\\extern fn zig_panic() noreturn;
\\pub fn panic(msg: []const u8, error_return_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn {
\\ _ = msg;
\\ _ = error_return_trace;
\\ zig_panic();
\\}
);
// This is intended to be the exact CLI usage used by godbolt.org.
const run = b.addSystemCommand(&.{
b.graph.zig_exe, "build-obj",
"--cache-dir", tmp_path,
"--name", "example",
"-fno-emit-bin", "-fno-emit-h",
"-fstrip", "-OReleaseFast",
});
run.addFileArg(writefile.files.items[0].getPath());
const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s");
const checkfile = b.addCheckFile(example_s, .{
.expected_matches = &.{
"square:",
"mov\teax, edi",
"imul\teax, edi",
},
});
checkfile.setName("check godbolt.org CLI usage generating valid asm");
const cleanup = b.addRemoveDirTree(tmp_path);
cleanup.step.dependOn(&checkfile.step);
step.dependOn(&cleanup.step);
}
{
// Test `zig fmt`.
// This test must use a temporary directory rather than a cache
// directory because this test will be mutating the files. The cache
// system relies on cache directories being mutated only by their
// owners.
const tmp_path = b.makeTempPath();
const unformatted_code = " // no reason for indent";
var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled");
defer dir.close();
dir.writeFile("fmt1.zig", unformatted_code) catch @panic("unhandled");
dir.writeFile("fmt2.zig", unformatted_code) catch @panic("unhandled");
dir.makeDir("subdir") catch @panic("unhandled");
var subdir = dir.openDir("subdir", .{}) catch @panic("unhandled");
defer subdir.close();
subdir.writeFile("fmt3.zig", unformatted_code) catch @panic("unhandled");
// Test zig fmt affecting only the appropriate files.
const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" });
run1.setName("run zig fmt one file");
run1.setCwd(.{ .cwd_relative = tmp_path });
run1.has_side_effects = true;
// stdout should be file path + \n
run1.expectStdOutEqual("fmt1.zig\n");
// Test excluding files and directories from a run
const run2 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "subdir", "." });
run2.setName("run zig fmt on directory with exclusions");
run2.setCwd(.{ .cwd_relative = tmp_path });
run2.has_side_effects = true;
run2.expectStdOutEqual("");
run2.step.dependOn(&run1.step);
// Test excluding non-existent file
const run3 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "nonexistent.zig", "." });
run3.setName("run zig fmt on directory with non-existent exclusion");
run3.setCwd(.{ .cwd_relative = tmp_path });
run3.has_side_effects = true;
run3.expectStdOutEqual("." ++ s ++ "subdir" ++ s ++ "fmt3.zig\n");
run3.step.dependOn(&run2.step);
// running it on the dir, only the new file should be changed
const run4 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
run4.setName("run zig fmt the directory");
run4.setCwd(.{ .cwd_relative = tmp_path });
run4.has_side_effects = true;
run4.expectStdOutEqual("." ++ s ++ "fmt2.zig\n");
run4.step.dependOn(&run3.step);
// both files have been formatted, nothing should change now
const run5 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
run5.setName("run zig fmt with nothing to do");
run5.setCwd(.{ .cwd_relative = tmp_path });
run5.has_side_effects = true;
run5.expectStdOutEqual("");
run5.step.dependOn(&run4.step);
const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
const fmt6_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt6.zig" }) catch @panic("OOM");
const write6 = b.addWriteFiles();
write6.addBytesToSource(unformatted_code_utf16, fmt6_path);
write6.step.dependOn(&run5.step);
// Test `zig fmt` handling UTF-16 decoding.
const run6 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
run6.setName("run zig fmt convert UTF-16 to UTF-8");
run6.setCwd(.{ .cwd_relative = tmp_path });
run6.has_side_effects = true;
run6.expectStdOutEqual("." ++ s ++ "fmt6.zig\n");
run6.step.dependOn(&write6.step);
// TODO change this to an exact match
const check6 = b.addCheckFile(.{ .path = fmt6_path }, .{
.expected_matches = &.{
"// no reason",
},
});
check6.step.dependOn(&run6.step);
const cleanup = b.addRemoveDirTree(tmp_path);
cleanup.step.dependOn(&check6.step);
step.dependOn(&cleanup.step);
}
{
// TODO this should move to become a CLI test rather than standalone
// cases.addBuildFile("test/standalone/options/build.zig", .{
// .extra_argv = &.{
// "-Dbool_true",
// "-Dbool_false=false",
// "-Dint=1234",
// "-De=two",
// "-Dstring=hello",
// },
// });
}
return step;
}
pub fn addAssembleAndLinkTests(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-asm-link", "Run the assemble and link tests"),
.test_index = 0,
.test_filter = test_filter,
.optimize_modes = optimize_modes,
};
assemble_and_link.addCases(cases);
return cases.step;
}
pub fn addTranslateCTests(b: *std.Build, test_filter: ?[]const u8) *Step {
const cases = b.allocator.create(TranslateCContext) catch @panic("OOM");
cases.* = TranslateCContext{
.b = b,
.step = b.step("test-translate-c", "Run the C translation tests"),
.test_index = 0,
.test_filter = test_filter,
};
translate_c.addCases(cases);
return cases.step;
}
pub fn addRunTranslatedCTests(
b: *std.Build,
test_filter: ?[]const u8,
target: std.Build.ResolvedTarget,
) *Step {
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"),
.test_index = 0,
.test_filter = test_filter,
.target = target,
};
run_translated_c.addCases(cases);
return cases.step;
}
const ModuleTestOptions = struct {
test_filter: ?[]const u8,
root_src: []const u8,
name: []const u8,
desc: []const u8,
optimize_modes: []const OptimizeMode,
skip_single_threaded: bool,
skip_non_native: bool,
skip_cross_glibc: bool,
skip_libc: bool,
max_rss: usize = 0,
};
pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
const step = b.step(b.fmt("test-{s}", .{options.name}), options.desc);
for (test_targets) |test_target| {
const is_native = test_target.target.isNative() or
(test_target.target.os_tag == builtin.os.tag and
test_target.target.cpu_arch == builtin.cpu.arch);
if (options.skip_non_native and !is_native)
continue;
const resolved_target = b.resolveTargetQuery(test_target.target);
const target = resolved_target.result;
if (options.skip_cross_glibc and !test_target.target.isNative() and
target.isGnuLibC() and test_target.link_libc == true)
continue;
if (options.skip_libc and test_target.link_libc == true)
continue;
if (options.skip_single_threaded and test_target.single_threaded == true)
continue;
// TODO get compiler-rt tests passing for self-hosted backends.
if ((target.cpu.arch != .x86_64 or target.ofmt != .elf) and
test_target.use_llvm == false and mem.eql(u8, options.name, "compiler-rt"))
continue;
// TODO get compiler-rt tests passing for wasm32-wasi
// currently causes "LLVM ERROR: Unable to expand fixed point multiplication."
if (target.cpu.arch == .wasm32 and target.os.tag == .wasi and
mem.eql(u8, options.name, "compiler-rt"))
{
continue;
}
// TODO get universal-libc tests passing for other self-hosted backends.
if (target.cpu.arch != .x86_64 and
test_target.use_llvm == false and mem.eql(u8, options.name, "universal-libc"))
continue;
// TODO get std lib tests passing for other self-hosted backends.
if ((target.cpu.arch != .x86_64 or target.os.tag != .linux) and
test_target.use_llvm == false and mem.eql(u8, options.name, "std"))
continue;
if (target.cpu.arch == .x86_64 and target.os.tag == .windows and
test_target.target.cpu_arch == null and test_target.optimize_mode != .Debug and
mem.eql(u8, options.name, "std"))
{
// https://github.com/ziglang/zig/issues/17902
continue;
}
const want_this_mode = for (options.optimize_modes) |m| {
if (m == test_target.optimize_mode) break true;
} else false;
if (!want_this_mode) continue;
const libc_suffix = if (test_target.link_libc == true) "-libc" else "";
const triple_txt = target.zigTriple(b.allocator) catch @panic("OOM");
const model_txt = target.cpu.model.name;
// wasm32-wasi builds need more RAM, idk why
const max_rss = if (target.os.tag == .wasi)
options.max_rss * 2
else
options.max_rss;
const these_tests = b.addTest(.{
.root_source_file = .{ .path = options.root_src },
.optimize = test_target.optimize_mode,
.target = resolved_target,
.max_rss = max_rss,
.filter = options.test_filter,
.link_libc = test_target.link_libc,
.single_threaded = test_target.single_threaded,
.use_llvm = test_target.use_llvm,
.use_lld = test_target.use_lld,
.zig_lib_dir = .{ .path = "lib" },
.pic = test_target.pic,
.strip = test_target.strip,
});
const single_threaded_suffix = if (test_target.single_threaded == true) "-single" else "";
const backend_suffix = if (test_target.use_llvm == true)
"-llvm"
else if (target.ofmt == std.Target.ObjectFormat.c)
"-cbe"
else if (test_target.use_llvm == false)
"-selfhosted"
else
"";
const use_lld = if (test_target.use_lld == false) "-no-lld" else "";
const use_pic = if (test_target.pic == true) "-pic" else "";
these_tests.addIncludePath(.{ .path = "test" });
if (target.os.tag == .wasi) {
// WASI's default stack size can be too small for some big tests.
these_tests.stack_size = 2 * 1024 * 1024;
}
const qualified_name = b.fmt("{s}-{s}-{s}-{s}{s}{s}{s}{s}{s}", .{
options.name,
triple_txt,
model_txt,
@tagName(test_target.optimize_mode),
libc_suffix,
single_threaded_suffix,
backend_suffix,
use_lld,
use_pic,
});
if (target.ofmt == std.Target.ObjectFormat.c) {
var altered_query = test_target.target;
altered_query.ofmt = null;
const compile_c = b.addExecutable(.{
.name = qualified_name,
.link_libc = test_target.link_libc,
.target = b.resolveTargetQuery(altered_query),
.zig_lib_dir = .{ .path = "lib" },
});
compile_c.addCSourceFile(.{
.file = these_tests.getEmittedBin(),
.flags = &.{
// TODO output -std=c89 compatible C code
"-std=c99",
"-pedantic",
"-Werror",
// TODO stop violating these pedantic errors. spotted everywhere
"-Wno-builtin-requires-header",
// TODO stop violating these pedantic errors. spotted on linux
"-Wno-address-of-packed-member",
"-Wno-gnu-folding-constant",
"-Wno-incompatible-function-pointer-types",
"-Wno-incompatible-pointer-types",
"-Wno-overlength-strings",
// TODO stop violating these pedantic errors. spotted on darwin
"-Wno-dollar-in-identifier-extension",
"-Wno-absolute-value",
},
});
compile_c.addIncludePath(.{ .path = "lib" }); // for zig.h
if (target.os.tag == .windows) {
if (true) {
// Unfortunately this requires about 8G of RAM for clang to compile
// and our Windows CI runners do not have this much.
step.dependOn(&these_tests.step);
continue;
}
if (test_target.link_libc == false) {
compile_c.subsystem = .Console;
compile_c.linkSystemLibrary("kernel32");
compile_c.linkSystemLibrary("ntdll");
}
if (mem.eql(u8, options.name, "std")) {
if (test_target.link_libc == false) {
compile_c.linkSystemLibrary("shell32");
compile_c.linkSystemLibrary("advapi32");
}
compile_c.linkSystemLibrary("crypt32");
compile_c.linkSystemLibrary("ws2_32");
compile_c.linkSystemLibrary("ole32");
}
}
const run = b.addRunArtifact(compile_c);
run.skip_foreign_checks = true;
run.enableTestRunnerMode();
run.setName(b.fmt("run test {s}", .{qualified_name}));
step.dependOn(&run.step);
} else {
const run = b.addRunArtifact(these_tests);
run.skip_foreign_checks = true;
run.setName(b.fmt("run test {s}", .{qualified_name}));
step.dependOn(&run.step);
}
}
return step;
}
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");
const optimize_modes: [3]OptimizeMode = .{ .Debug, .ReleaseSafe, .ReleaseFast };
for (optimize_modes) |optimize_mode| {
if (optimize_mode != .Debug and skip_release) continue;
for (c_abi_targets) |c_abi_target| {
if (skip_non_native and !c_abi_target.target.isNative()) continue;
const resolved_target = b.resolveTargetQuery(c_abi_target.target);
const target = resolved_target.result;
if (target.os.tag == .windows and target.cpu.arch == .aarch64) {
// https://github.com/ziglang/zig/issues/14908
continue;
}
const test_step = b.addTest(.{
.name = b.fmt("test-c-abi-{s}-{s}-{s}{s}{s}{s}", .{
target.zigTriple(b.allocator) catch @panic("OOM"),
target.cpu.model.name,
@tagName(optimize_mode),
if (c_abi_target.use_llvm == true)
"-llvm"
else if (target.ofmt == .c)
"-cbe"
else if (c_abi_target.use_llvm == false)
"-selfhosted"
else
"",
if (c_abi_target.use_lld == false) "-no-lld" else "",
if (c_abi_target.pic == true) "-pic" else "",
}),
.root_source_file = .{ .path = "test/c_abi/main.zig" },
.target = resolved_target,
.optimize = optimize_mode,
.link_libc = true,
.use_llvm = c_abi_target.use_llvm,
.use_lld = c_abi_target.use_lld,
.pic = c_abi_target.pic,
.strip = c_abi_target.strip,
});
test_step.addCSourceFile(.{
.file = .{ .path = "test/c_abi/cfuncs.c" },
.flags = &.{"-std=c99"},
});
for (c_abi_target.c_defines) |define| test_step.defineCMacro(define, null);
// This test is intentionally trying to check if the external ABI is
// done properly. LTO would be a hindrance to this.
test_step.want_lto = false;
const run = b.addRunArtifact(test_step);
run.skip_foreign_checks = true;
step.dependOn(&run.step);
}
}
return step;
}
pub fn addCases(
b: *std.Build,
parent_step: *Step,
opt_test_filter: ?[]const u8,
check_case_exe: *std.Build.Step.Compile,
build_options: @import("cases.zig").BuildOptions,
) !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.openDir("test/cases", .{ .iterate = true });
defer dir.close();
cases.addFromDir(dir, b);
try @import("cases.zig").addCases(&cases, build_options, b);
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,
);
}