mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
* Implements #3768. This is a sweeping breaking change that requires many (trivial) edits to Zig source code. Array values no longer coerced to slices; however one may use `&` to obtain a reference to an array value, which may then be coerced to a slice. * Adds `IrInstruction::dump`, for debugging purposes. It's useful to call to inspect the instruction when debugging Zig IR. * Fixes bugs with result location semantics. See the new behavior test cases, and compile error test cases. * Fixes bugs with `@typeInfo` not properly resolving const values. * Behavior tests are passing but std lib tests are not yet. There is more work to do before merging this branch.
1736 lines
59 KiB
Zig
1736 lines
59 KiB
Zig
const std = @import("std");
|
|
const debug = std.debug;
|
|
const warn = debug.warn;
|
|
const build = std.build;
|
|
pub const Target = build.Target;
|
|
pub const CrossTarget = build.CrossTarget;
|
|
const Buffer = std.Buffer;
|
|
const io = std.io;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const fmt = std.fmt;
|
|
const ArrayList = std.ArrayList;
|
|
const builtin = @import("builtin");
|
|
const Mode = builtin.Mode;
|
|
const LibExeObjStep = build.LibExeObjStep;
|
|
|
|
const compare_output = @import("compare_output.zig");
|
|
const standalone = @import("standalone.zig");
|
|
const stack_traces = @import("stack_traces.zig");
|
|
const compile_errors = @import("compile_errors.zig");
|
|
const assemble_and_link = @import("assemble_and_link.zig");
|
|
const runtime_safety = @import("runtime_safety.zig");
|
|
const translate_c = @import("translate_c.zig");
|
|
const gen_h = @import("gen_h.zig");
|
|
|
|
const TestTarget = struct {
|
|
target: Target = .Native,
|
|
mode: builtin.Mode = .Debug,
|
|
link_libc: bool = false,
|
|
single_threaded: bool = false,
|
|
disable_native: bool = false,
|
|
};
|
|
|
|
const test_targets = [_]TestTarget{
|
|
TestTarget{},
|
|
TestTarget{
|
|
.link_libc = true,
|
|
},
|
|
TestTarget{
|
|
.single_threaded = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = .x86_64,
|
|
.abi = .none,
|
|
},
|
|
},
|
|
},
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = .x86_64,
|
|
.abi = .gnu,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = .x86_64,
|
|
.abi = .musl,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = builtin.Arch{ .aarch64 = builtin.Arch.Arm64.v8_5a },
|
|
.abi = .none,
|
|
},
|
|
},
|
|
},
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = builtin.Arch{ .aarch64 = builtin.Arch.Arm64.v8_5a },
|
|
.abi = .musl,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = builtin.Arch{ .aarch64 = builtin.Arch.Arm64.v8_5a },
|
|
.abi = .gnu,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = builtin.Arch{ .arm = builtin.Arch.Arm32.v8_5a },
|
|
.abi = .none,
|
|
},
|
|
},
|
|
},
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = builtin.Arch{ .arm = builtin.Arch.Arm32.v8_5a },
|
|
.abi = .musleabihf,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
// TODO https://github.com/ziglang/zig/issues/3287
|
|
//TestTarget{
|
|
// .target = Target{
|
|
// .Cross = CrossTarget{
|
|
// .os = .linux,
|
|
// .arch = builtin.Arch{ .arm = builtin.Arch.Arm32.v8_5a },
|
|
// .abi = .gnueabihf,
|
|
// },
|
|
// },
|
|
// .link_libc = true,
|
|
//},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = .mipsel,
|
|
.abi = .none,
|
|
},
|
|
},
|
|
},
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .linux,
|
|
.arch = .mipsel,
|
|
.abi = .musl,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .macosx,
|
|
.arch = .x86_64,
|
|
.abi = .gnu,
|
|
},
|
|
},
|
|
// TODO https://github.com/ziglang/zig/issues/3295
|
|
.disable_native = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .windows,
|
|
.arch = .i386,
|
|
.abi = .msvc,
|
|
},
|
|
},
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .windows,
|
|
.arch = .x86_64,
|
|
.abi = .msvc,
|
|
},
|
|
},
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .windows,
|
|
.arch = .i386,
|
|
.abi = .gnu,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.target = Target{
|
|
.Cross = CrossTarget{
|
|
.os = .windows,
|
|
.arch = .x86_64,
|
|
.abi = .gnu,
|
|
},
|
|
},
|
|
.link_libc = true,
|
|
},
|
|
|
|
// Do the release tests last because they take a long time
|
|
TestTarget{
|
|
.mode = .ReleaseFast,
|
|
},
|
|
TestTarget{
|
|
.link_libc = true,
|
|
.mode = .ReleaseFast,
|
|
},
|
|
TestTarget{
|
|
.mode = .ReleaseFast,
|
|
.single_threaded = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.mode = .ReleaseSafe,
|
|
},
|
|
TestTarget{
|
|
.link_libc = true,
|
|
.mode = .ReleaseSafe,
|
|
},
|
|
TestTarget{
|
|
.mode = .ReleaseSafe,
|
|
.single_threaded = true,
|
|
},
|
|
|
|
TestTarget{
|
|
.mode = .ReleaseSmall,
|
|
},
|
|
TestTarget{
|
|
.link_libc = true,
|
|
.mode = .ReleaseSmall,
|
|
},
|
|
TestTarget{
|
|
.mode = .ReleaseSmall,
|
|
.single_threaded = true,
|
|
},
|
|
};
|
|
|
|
const max_stdout_size = 1 * 1024 * 1024; // 1 MB
|
|
|
|
pub fn addCompareOutputTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
|
|
const cases = b.allocator.create(CompareOutputContext) catch unreachable;
|
|
cases.* = CompareOutputContext{
|
|
.b = b,
|
|
.step = b.step("test-compare-output", "Run the compare output tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
.modes = modes,
|
|
};
|
|
|
|
compare_output.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addStackTraceTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
|
|
const cases = b.allocator.create(StackTracesContext) catch unreachable;
|
|
cases.* = StackTracesContext{
|
|
.b = b,
|
|
.step = b.step("test-stack-traces", "Run the stack trace tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
.modes = modes,
|
|
};
|
|
|
|
stack_traces.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addRuntimeSafetyTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
|
|
const cases = b.allocator.create(CompareOutputContext) catch unreachable;
|
|
cases.* = CompareOutputContext{
|
|
.b = b,
|
|
.step = b.step("test-runtime-safety", "Run the runtime safety tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
.modes = modes,
|
|
};
|
|
|
|
runtime_safety.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addCompileErrorTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
|
|
const cases = b.allocator.create(CompileErrorContext) catch unreachable;
|
|
cases.* = CompileErrorContext{
|
|
.b = b,
|
|
.step = b.step("test-compile-errors", "Run the compile error tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
.modes = modes,
|
|
};
|
|
|
|
compile_errors.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addStandaloneTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
|
|
const cases = b.allocator.create(StandaloneContext) catch unreachable;
|
|
cases.* = StandaloneContext{
|
|
.b = b,
|
|
.step = b.step("test-standalone", "Run the standalone tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
.modes = modes,
|
|
};
|
|
|
|
standalone.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addCliTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
|
|
const step = b.step("test-cli", "Test the command line interface");
|
|
|
|
const exe = b.addExecutable("test-cli", "test/cli.zig");
|
|
const run_cmd = exe.run();
|
|
run_cmd.addArgs(&[_][]const u8{
|
|
fs.realpathAlloc(b.allocator, b.zig_exe) catch unreachable,
|
|
b.pathFromRoot(b.cache_root),
|
|
});
|
|
|
|
step.dependOn(&run_cmd.step);
|
|
return step;
|
|
}
|
|
|
|
pub fn addAssembleAndLinkTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
|
|
const cases = b.allocator.create(CompareOutputContext) catch unreachable;
|
|
cases.* = CompareOutputContext{
|
|
.b = b,
|
|
.step = b.step("test-asm-link", "Run the assemble and link tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
.modes = modes,
|
|
};
|
|
|
|
assemble_and_link.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addTranslateCTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step {
|
|
const cases = b.allocator.create(TranslateCContext) catch unreachable;
|
|
cases.* = TranslateCContext{
|
|
.b = b,
|
|
.step = b.step("test-translate-c", "Run the C transation tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
};
|
|
|
|
translate_c.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addGenHTests(b: *build.Builder, test_filter: ?[]const u8) *build.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;
|
|
}
|
|
|
|
pub fn addPkgTests(
|
|
b: *build.Builder,
|
|
test_filter: ?[]const u8,
|
|
root_src: []const u8,
|
|
name: []const u8,
|
|
desc: []const u8,
|
|
modes: []const Mode,
|
|
skip_single_threaded: bool,
|
|
skip_non_native: bool,
|
|
skip_libc: bool,
|
|
is_wine_enabled: bool,
|
|
is_qemu_enabled: bool,
|
|
glibc_dir: ?[]const u8,
|
|
) *build.Step {
|
|
const step = b.step(b.fmt("test-{}", name), desc);
|
|
|
|
for (test_targets) |test_target| {
|
|
if (skip_non_native and test_target.target != .Native)
|
|
continue;
|
|
|
|
if (skip_libc and test_target.link_libc)
|
|
continue;
|
|
|
|
if (test_target.link_libc and test_target.target.osRequiresLibC()) {
|
|
// This would be a redundant test.
|
|
continue;
|
|
}
|
|
|
|
if (skip_single_threaded and test_target.single_threaded)
|
|
continue;
|
|
|
|
const ArchTag = @TagType(builtin.Arch);
|
|
if (test_target.disable_native and
|
|
test_target.target.getOs() == builtin.os and
|
|
@as(ArchTag, test_target.target.getArch()) == @as(ArchTag, builtin.arch))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const want_this_mode = for (modes) |m| {
|
|
if (m == test_target.mode) break true;
|
|
} else false;
|
|
if (!want_this_mode) continue;
|
|
|
|
const libc_prefix = if (test_target.target.osRequiresLibC())
|
|
""
|
|
else if (test_target.link_libc)
|
|
"c"
|
|
else
|
|
"bare";
|
|
|
|
const triple_prefix = if (test_target.target == .Native)
|
|
@as([]const u8, "native")
|
|
else
|
|
test_target.target.zigTripleNoSubArch(b.allocator) catch unreachable;
|
|
|
|
const these_tests = b.addTest(root_src);
|
|
these_tests.setNamePrefix(b.fmt(
|
|
"{}-{}-{}-{}-{} ",
|
|
name,
|
|
triple_prefix,
|
|
@tagName(test_target.mode),
|
|
libc_prefix,
|
|
if (test_target.single_threaded) "single" else "multi",
|
|
));
|
|
these_tests.single_threaded = test_target.single_threaded;
|
|
these_tests.setFilter(test_filter);
|
|
these_tests.setBuildMode(test_target.mode);
|
|
these_tests.setTheTarget(test_target.target);
|
|
if (test_target.link_libc) {
|
|
these_tests.linkSystemLibrary("c");
|
|
}
|
|
these_tests.overrideZigLibDir("lib");
|
|
these_tests.enable_wine = is_wine_enabled;
|
|
these_tests.enable_qemu = is_qemu_enabled;
|
|
these_tests.glibc_multi_install_dir = glibc_dir;
|
|
|
|
step.dependOn(&these_tests.step);
|
|
}
|
|
return step;
|
|
}
|
|
|
|
pub const CompareOutputContext = struct {
|
|
b: *build.Builder,
|
|
step: *build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
modes: []const Mode,
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
const RunCompareOutputStep = struct {
|
|
step: build.Step,
|
|
context: *CompareOutputContext,
|
|
exe: *LibExeObjStep,
|
|
name: []const u8,
|
|
expected_output: []const u8,
|
|
test_index: usize,
|
|
cli_args: []const []const u8,
|
|
|
|
pub fn create(
|
|
context: *CompareOutputContext,
|
|
exe: *LibExeObjStep,
|
|
name: []const u8,
|
|
expected_output: []const u8,
|
|
cli_args: []const []const u8,
|
|
) *RunCompareOutputStep {
|
|
const allocator = context.b.allocator;
|
|
const ptr = allocator.create(RunCompareOutputStep) catch unreachable;
|
|
ptr.* = RunCompareOutputStep{
|
|
.context = context,
|
|
.exe = exe,
|
|
.name = name,
|
|
.expected_output = expected_output,
|
|
.test_index = context.test_index,
|
|
.step = build.Step.init("RunCompareOutput", allocator, make),
|
|
.cli_args = cli_args,
|
|
};
|
|
ptr.step.dependOn(&exe.step);
|
|
context.test_index += 1;
|
|
return ptr;
|
|
}
|
|
|
|
fn make(step: *build.Step) !void {
|
|
const self = @fieldParentPtr(RunCompareOutputStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
const full_exe_path = self.exe.getOutputPath();
|
|
var args = ArrayList([]const u8).init(b.allocator);
|
|
defer args.deinit();
|
|
|
|
args.append(full_exe_path) catch unreachable;
|
|
for (self.cli_args) |arg| {
|
|
args.append(arg) catch unreachable;
|
|
}
|
|
|
|
warn("Test {}/{} {}...", self.test_index + 1, self.context.test_index, self.name);
|
|
|
|
const child = std.ChildProcess.init(args.toSliceConst(), b.allocator) catch unreachable;
|
|
defer child.deinit();
|
|
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Pipe;
|
|
child.stderr_behavior = .Pipe;
|
|
child.env_map = b.env_map;
|
|
|
|
child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
|
|
var stdout = Buffer.initNull(b.allocator);
|
|
var stderr = Buffer.initNull(b.allocator);
|
|
|
|
var stdout_file_in_stream = child.stdout.?.inStream();
|
|
var stderr_file_in_stream = child.stderr.?.inStream();
|
|
|
|
stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable;
|
|
stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable;
|
|
|
|
const term = child.wait() catch |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
};
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code != 0) {
|
|
warn("Process {} exited with error code {}\n", full_exe_path, code);
|
|
printInvocation(args.toSliceConst());
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
else => {
|
|
warn("Process {} terminated unexpectedly\n", full_exe_path);
|
|
printInvocation(args.toSliceConst());
|
|
return error.TestFailed;
|
|
},
|
|
}
|
|
|
|
if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
|
|
warn(
|
|
\\
|
|
\\========= Expected this output: =========
|
|
\\{}
|
|
\\========= But found: ====================
|
|
\\{}
|
|
\\
|
|
, self.expected_output, stdout.toSliceConst());
|
|
return error.TestFailed;
|
|
}
|
|
warn("OK\n");
|
|
}
|
|
};
|
|
|
|
const RuntimeSafetyRunStep = struct {
|
|
step: build.Step,
|
|
context: *CompareOutputContext,
|
|
exe: *LibExeObjStep,
|
|
name: []const u8,
|
|
test_index: usize,
|
|
|
|
pub fn create(context: *CompareOutputContext, exe: *LibExeObjStep, name: []const u8) *RuntimeSafetyRunStep {
|
|
const allocator = context.b.allocator;
|
|
const ptr = allocator.create(RuntimeSafetyRunStep) catch unreachable;
|
|
ptr.* = RuntimeSafetyRunStep{
|
|
.context = context,
|
|
.exe = exe,
|
|
.name = name,
|
|
.test_index = context.test_index,
|
|
.step = build.Step.init("RuntimeSafetyRun", allocator, make),
|
|
};
|
|
ptr.step.dependOn(&exe.step);
|
|
context.test_index += 1;
|
|
return ptr;
|
|
}
|
|
|
|
fn make(step: *build.Step) !void {
|
|
const self = @fieldParentPtr(RuntimeSafetyRunStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
const full_exe_path = self.exe.getOutputPath();
|
|
|
|
warn("Test {}/{} {}...", self.test_index + 1, self.context.test_index, self.name);
|
|
|
|
const child = std.ChildProcess.init(&[_][]const u8{full_exe_path}, b.allocator) catch unreachable;
|
|
defer child.deinit();
|
|
|
|
child.env_map = b.env_map;
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Ignore;
|
|
child.stderr_behavior = .Ignore;
|
|
|
|
const term = child.spawnAndWait() catch |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
};
|
|
|
|
const expected_exit_code: u32 = 126;
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code != expected_exit_code) {
|
|
warn("\nProgram expected to exit with code {} " ++ "but exited with code {}\n", expected_exit_code, code);
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
.Signal => |sig| {
|
|
warn("\nProgram expected to exit with code {} " ++ "but instead signaled {}\n", expected_exit_code, sig);
|
|
return error.TestFailed;
|
|
},
|
|
else => {
|
|
warn("\nProgram expected to exit with code {}" ++ " but exited in an unexpected way\n", expected_exit_code);
|
|
return error.TestFailed;
|
|
},
|
|
}
|
|
|
|
warn("OK\n");
|
|
}
|
|
};
|
|
|
|
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 root_src = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, case.sources.items[0].filename },
|
|
) catch unreachable;
|
|
|
|
switch (case.special) {
|
|
Special.Asm => {
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
|
|
}
|
|
|
|
const exe = b.addExecutable("test", null);
|
|
exe.addAssemblyFile(root_src);
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, src_file.filename },
|
|
) catch unreachable;
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
exe.step.dependOn(&write_src.step);
|
|
}
|
|
|
|
const run_and_cmp_output = RunCompareOutputStep.create(
|
|
self,
|
|
exe,
|
|
annotated_case_name,
|
|
case.expected_output,
|
|
case.cli_args,
|
|
);
|
|
|
|
self.step.dependOn(&run_and_cmp_output.step);
|
|
},
|
|
Special.None => {
|
|
for (self.modes) |mode| {
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", "compare-output", case.name, @tagName(mode)) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
|
|
}
|
|
|
|
const exe = b.addExecutable("test", root_src);
|
|
exe.setBuildMode(mode);
|
|
if (case.link_libc) {
|
|
exe.linkSystemLibrary("c");
|
|
}
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, src_file.filename },
|
|
) catch unreachable;
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
exe.step.dependOn(&write_src.step);
|
|
}
|
|
|
|
const run_and_cmp_output = RunCompareOutputStep.create(
|
|
self,
|
|
exe,
|
|
annotated_case_name,
|
|
case.expected_output,
|
|
case.cli_args,
|
|
);
|
|
|
|
self.step.dependOn(&run_and_cmp_output.step);
|
|
}
|
|
},
|
|
Special.RuntimeSafety => {
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {}", case.name) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
|
|
}
|
|
|
|
const exe = b.addExecutable("test", root_src);
|
|
if (case.link_libc) {
|
|
exe.linkSystemLibrary("c");
|
|
}
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, src_file.filename },
|
|
) catch unreachable;
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
exe.step.dependOn(&write_src.step);
|
|
}
|
|
|
|
const run_and_cmp_output = RuntimeSafetyRunStep.create(self, exe, annotated_case_name);
|
|
|
|
self.step.dependOn(&run_and_cmp_output.step);
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const StackTracesContext = struct {
|
|
b: *build.Builder,
|
|
step: *build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
modes: []const Mode,
|
|
|
|
const Expect = [@typeInfo(Mode).Enum.fields.len][]const u8;
|
|
|
|
pub fn addCase(
|
|
self: *StackTracesContext,
|
|
name: []const u8,
|
|
source: []const u8,
|
|
expect: Expect,
|
|
) void {
|
|
const b = self.b;
|
|
|
|
const source_pathname = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, "source.zig" },
|
|
) catch unreachable;
|
|
|
|
for (self.modes) |mode| {
|
|
const expect_for_mode = expect[@enumToInt(mode)];
|
|
if (expect_for_mode.len == 0) continue;
|
|
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", "stack-trace", name, @tagName(mode)) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
|
|
}
|
|
|
|
const exe = b.addExecutable("test", source_pathname);
|
|
exe.setBuildMode(mode);
|
|
|
|
const write_source = b.addWriteFile(source_pathname, source);
|
|
exe.step.dependOn(&write_source.step);
|
|
|
|
const run_and_compare = RunAndCompareStep.create(
|
|
self,
|
|
exe,
|
|
annotated_case_name,
|
|
mode,
|
|
expect_for_mode,
|
|
);
|
|
|
|
self.step.dependOn(&run_and_compare.step);
|
|
}
|
|
}
|
|
|
|
const RunAndCompareStep = struct {
|
|
step: build.Step,
|
|
context: *StackTracesContext,
|
|
exe: *LibExeObjStep,
|
|
name: []const u8,
|
|
mode: Mode,
|
|
expect_output: []const u8,
|
|
test_index: usize,
|
|
|
|
pub fn create(
|
|
context: *StackTracesContext,
|
|
exe: *LibExeObjStep,
|
|
name: []const u8,
|
|
mode: Mode,
|
|
expect_output: []const u8,
|
|
) *RunAndCompareStep {
|
|
const allocator = context.b.allocator;
|
|
const ptr = allocator.create(RunAndCompareStep) catch unreachable;
|
|
ptr.* = RunAndCompareStep{
|
|
.step = build.Step.init("StackTraceCompareOutputStep", allocator, make),
|
|
.context = context,
|
|
.exe = exe,
|
|
.name = name,
|
|
.mode = mode,
|
|
.expect_output = expect_output,
|
|
.test_index = context.test_index,
|
|
};
|
|
ptr.step.dependOn(&exe.step);
|
|
context.test_index += 1;
|
|
return ptr;
|
|
}
|
|
|
|
fn make(step: *build.Step) !void {
|
|
const self = @fieldParentPtr(RunAndCompareStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
const full_exe_path = self.exe.getOutputPath();
|
|
var args = ArrayList([]const u8).init(b.allocator);
|
|
defer args.deinit();
|
|
args.append(full_exe_path) catch unreachable;
|
|
|
|
warn("Test {}/{} {}...", self.test_index + 1, self.context.test_index, self.name);
|
|
|
|
const child = std.ChildProcess.init(args.toSliceConst(), b.allocator) catch unreachable;
|
|
defer child.deinit();
|
|
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Pipe;
|
|
child.stderr_behavior = .Pipe;
|
|
child.env_map = b.env_map;
|
|
|
|
child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
|
|
var stdout = Buffer.initNull(b.allocator);
|
|
var stderr = Buffer.initNull(b.allocator);
|
|
|
|
var stdout_file_in_stream = child.stdout.?.inStream();
|
|
var stderr_file_in_stream = child.stderr.?.inStream();
|
|
|
|
stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable;
|
|
stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable;
|
|
|
|
const term = child.wait() catch |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
};
|
|
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
const expect_code: u32 = 1;
|
|
if (code != expect_code) {
|
|
warn("Process {} exited with error code {} but expected code {}\n", full_exe_path, code, expect_code);
|
|
printInvocation(args.toSliceConst());
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
.Signal => |signum| {
|
|
warn("Process {} terminated on signal {}\n", full_exe_path, signum);
|
|
printInvocation(args.toSliceConst());
|
|
return error.TestFailed;
|
|
},
|
|
.Stopped => |signum| {
|
|
warn("Process {} stopped on signal {}\n", full_exe_path, signum);
|
|
printInvocation(args.toSliceConst());
|
|
return error.TestFailed;
|
|
},
|
|
.Unknown => |code| {
|
|
warn("Process {} terminated unexpectedly with error code {}\n", full_exe_path, code);
|
|
printInvocation(args.toSliceConst());
|
|
return error.TestFailed;
|
|
},
|
|
}
|
|
|
|
// process result
|
|
// - keep only basename of source file path
|
|
// - replace address with symbolic string
|
|
// - skip empty lines
|
|
const got: []const u8 = got_result: {
|
|
var buf = try Buffer.initSize(b.allocator, 0);
|
|
defer buf.deinit();
|
|
var bytes = stderr.toSliceConst();
|
|
if (bytes.len != 0 and bytes[bytes.len - 1] == '\n') bytes = bytes[0 .. bytes.len - 1];
|
|
var it = mem.separate(bytes, "\n");
|
|
process_lines: while (it.next()) |line| {
|
|
if (line.len == 0) continue;
|
|
const delims = [_][]const u8{ ":", ":", ":", " in " };
|
|
var marks = [_]usize{0} ** 4;
|
|
// offset search past `[drive]:` on windows
|
|
var pos: usize = if (builtin.os == .windows) 2 else 0;
|
|
for (delims) |delim, i| {
|
|
marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse {
|
|
try buf.append(line);
|
|
try buf.append("\n");
|
|
continue :process_lines;
|
|
};
|
|
pos = marks[i] + delim.len;
|
|
}
|
|
pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse {
|
|
try buf.append(line);
|
|
try buf.append("\n");
|
|
continue :process_lines;
|
|
};
|
|
try buf.append(line[pos + 1 .. marks[2] + delims[2].len]);
|
|
try buf.append(" [address]");
|
|
try buf.append(line[marks[3]..]);
|
|
try buf.append("\n");
|
|
}
|
|
break :got_result buf.toOwnedSlice();
|
|
};
|
|
|
|
if (!mem.eql(u8, self.expect_output, got)) {
|
|
warn(
|
|
\\
|
|
\\========= Expected this output: =========
|
|
\\{}
|
|
\\================================================
|
|
\\{}
|
|
\\
|
|
, self.expect_output, got);
|
|
return error.TestFailed;
|
|
}
|
|
warn("OK\n");
|
|
}
|
|
};
|
|
};
|
|
|
|
pub const CompileErrorContext = struct {
|
|
b: *build.Builder,
|
|
step: *build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
modes: []const Mode,
|
|
|
|
const TestCase = struct {
|
|
name: []const u8,
|
|
sources: ArrayList(SourceFile),
|
|
expected_errors: ArrayList([]const u8),
|
|
expect_exact: bool,
|
|
link_libc: bool,
|
|
is_exe: bool,
|
|
is_test: bool,
|
|
target: Target = .Native,
|
|
|
|
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 addExpectedError(self: *TestCase, text: []const u8) void {
|
|
self.expected_errors.append(text) catch unreachable;
|
|
}
|
|
};
|
|
|
|
const CompileCmpOutputStep = struct {
|
|
step: build.Step,
|
|
context: *CompileErrorContext,
|
|
name: []const u8,
|
|
test_index: usize,
|
|
case: *const TestCase,
|
|
build_mode: Mode,
|
|
|
|
const ErrLineIter = struct {
|
|
lines: mem.SplitIterator,
|
|
|
|
const source_file = "tmp.zig";
|
|
|
|
fn init(input: []const u8) ErrLineIter {
|
|
return ErrLineIter{ .lines = mem.separate(input, "\n") };
|
|
}
|
|
|
|
fn next(self: *ErrLineIter) ?[]const u8 {
|
|
while (self.lines.next()) |line| {
|
|
if (mem.indexOf(u8, line, source_file) != null)
|
|
return line;
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub fn create(context: *CompileErrorContext, name: []const u8, case: *const TestCase, build_mode: Mode) *CompileCmpOutputStep {
|
|
const allocator = context.b.allocator;
|
|
const ptr = allocator.create(CompileCmpOutputStep) catch unreachable;
|
|
ptr.* = CompileCmpOutputStep{
|
|
.step = build.Step.init("CompileCmpOutput", allocator, make),
|
|
.context = context,
|
|
.name = name,
|
|
.test_index = context.test_index,
|
|
.case = case,
|
|
.build_mode = build_mode,
|
|
};
|
|
|
|
context.test_index += 1;
|
|
return ptr;
|
|
}
|
|
|
|
fn make(step: *build.Step) !void {
|
|
const self = @fieldParentPtr(CompileCmpOutputStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
const root_src = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, self.case.sources.items[0].filename },
|
|
) catch unreachable;
|
|
|
|
var zig_args = ArrayList([]const u8).init(b.allocator);
|
|
zig_args.append(b.zig_exe) catch unreachable;
|
|
|
|
if (self.case.is_exe) {
|
|
try zig_args.append("build-exe");
|
|
} else if (self.case.is_test) {
|
|
try zig_args.append("test");
|
|
} else {
|
|
try zig_args.append("build-obj");
|
|
}
|
|
zig_args.append(b.pathFromRoot(root_src)) catch unreachable;
|
|
|
|
zig_args.append("--name") catch unreachable;
|
|
zig_args.append("test") catch unreachable;
|
|
|
|
zig_args.append("--output-dir") catch unreachable;
|
|
zig_args.append(b.pathFromRoot(b.cache_root)) catch unreachable;
|
|
|
|
switch (self.case.target) {
|
|
.Native => {},
|
|
.Cross => {
|
|
try zig_args.append("-target");
|
|
try zig_args.append(try self.case.target.zigTriple(b.allocator));
|
|
},
|
|
}
|
|
|
|
switch (self.build_mode) {
|
|
Mode.Debug => {},
|
|
Mode.ReleaseSafe => zig_args.append("--release-safe") catch unreachable,
|
|
Mode.ReleaseFast => zig_args.append("--release-fast") catch unreachable,
|
|
Mode.ReleaseSmall => zig_args.append("--release-small") catch unreachable,
|
|
}
|
|
|
|
warn("Test {}/{} {}...", self.test_index + 1, self.context.test_index, self.name);
|
|
|
|
if (b.verbose) {
|
|
printInvocation(zig_args.toSliceConst());
|
|
}
|
|
|
|
const child = std.ChildProcess.init(zig_args.toSliceConst(), b.allocator) catch unreachable;
|
|
defer child.deinit();
|
|
|
|
child.env_map = b.env_map;
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Pipe;
|
|
child.stderr_behavior = .Pipe;
|
|
|
|
child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err));
|
|
|
|
var stdout_buf = Buffer.initNull(b.allocator);
|
|
var stderr_buf = Buffer.initNull(b.allocator);
|
|
|
|
var stdout_file_in_stream = child.stdout.?.inStream();
|
|
var stderr_file_in_stream = child.stderr.?.inStream();
|
|
|
|
stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable;
|
|
stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable;
|
|
|
|
const term = child.wait() catch |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err));
|
|
};
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code == 0) {
|
|
printInvocation(zig_args.toSliceConst());
|
|
return error.CompilationIncorrectlySucceeded;
|
|
}
|
|
},
|
|
else => {
|
|
warn("Process {} terminated unexpectedly\n", b.zig_exe);
|
|
printInvocation(zig_args.toSliceConst());
|
|
return error.TestFailed;
|
|
},
|
|
}
|
|
|
|
const stdout = stdout_buf.toSliceConst();
|
|
const stderr = stderr_buf.toSliceConst();
|
|
|
|
if (stdout.len != 0) {
|
|
warn(
|
|
\\
|
|
\\Expected empty stdout, instead found:
|
|
\\================================================
|
|
\\{}
|
|
\\================================================
|
|
\\
|
|
, stdout);
|
|
return error.TestFailed;
|
|
}
|
|
|
|
var ok = true;
|
|
if (self.case.expect_exact) {
|
|
var err_iter = ErrLineIter.init(stderr);
|
|
var i: usize = 0;
|
|
ok = while (err_iter.next()) |line| : (i += 1) {
|
|
if (i >= self.case.expected_errors.len) break false;
|
|
const expected = self.case.expected_errors.at(i);
|
|
if (mem.indexOf(u8, line, expected) == null) break false;
|
|
continue;
|
|
} else true;
|
|
|
|
ok = ok and i == self.case.expected_errors.len;
|
|
|
|
if (!ok) {
|
|
warn("\n======== Expected these compile errors: ========\n");
|
|
for (self.case.expected_errors.toSliceConst()) |expected| {
|
|
warn("{}\n", expected);
|
|
}
|
|
}
|
|
} else {
|
|
for (self.case.expected_errors.toSliceConst()) |expected| {
|
|
if (mem.indexOf(u8, stderr, expected) == null) {
|
|
warn(
|
|
\\
|
|
\\=========== Expected compile error: ============
|
|
\\{}
|
|
\\
|
|
, expected);
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
warn(
|
|
\\================= Full output: =================
|
|
\\{}
|
|
\\
|
|
, stderr);
|
|
return error.TestFailed;
|
|
}
|
|
|
|
warn("OK\n");
|
|
}
|
|
};
|
|
|
|
pub fn create(self: *CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) *TestCase {
|
|
const tc = self.b.allocator.create(TestCase) catch unreachable;
|
|
tc.* = TestCase{
|
|
.name = name,
|
|
.sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
|
|
.expected_errors = ArrayList([]const u8).init(self.b.allocator),
|
|
.expect_exact = false,
|
|
.link_libc = false,
|
|
.is_exe = false,
|
|
.is_test = false,
|
|
};
|
|
|
|
tc.addSourceFile("tmp.zig", source);
|
|
comptime var arg_i = 0;
|
|
inline while (arg_i < expected_lines.len) : (arg_i += 1) {
|
|
tc.addExpectedError(expected_lines[arg_i]);
|
|
}
|
|
return tc;
|
|
}
|
|
|
|
pub fn addC(self: *CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
var tc = self.create(name, source, expected_lines);
|
|
tc.link_libc = true;
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addExe(self: *CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
var tc = self.create(name, source, expected_lines);
|
|
tc.is_exe = true;
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn add(self: *CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
const tc = self.create(name, source, expected_lines);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addTest(self: *CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
const tc = self.create(name, source, expected_lines);
|
|
tc.is_test = true;
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addCase(self: *CompileErrorContext, case: *const TestCase) void {
|
|
const b = self.b;
|
|
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "compile-error {}", case.name) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
|
|
}
|
|
|
|
const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, .Debug);
|
|
self.step.dependOn(&compile_and_cmp_errors.step);
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, src_file.filename },
|
|
) catch unreachable;
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
compile_and_cmp_errors.step.dependOn(&write_src.step);
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const StandaloneContext = struct {
|
|
b: *build.Builder,
|
|
step: *build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
modes: []const Mode,
|
|
|
|
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) void {
|
|
const b = self.b;
|
|
|
|
const annotated_case_name = b.fmt("build {} (Debug)", 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, b.zig_exe) catch unreachable;
|
|
zig_args.append(rel_zig_exe) catch unreachable;
|
|
zig_args.append("build") catch unreachable;
|
|
|
|
zig_args.append("--build-file") catch unreachable;
|
|
zig_args.append(b.pathFromRoot(build_file)) catch unreachable;
|
|
|
|
zig_args.append("test") catch unreachable;
|
|
|
|
if (b.verbose) {
|
|
zig_args.append("--verbose") catch unreachable;
|
|
}
|
|
|
|
const run_cmd = b.addSystemCommand(zig_args.toSliceConst());
|
|
|
|
const log_step = b.addLog("PASS {}\n", annotated_case_name);
|
|
log_step.step.dependOn(&run_cmd.step);
|
|
|
|
self.step.dependOn(&log_step.step);
|
|
}
|
|
|
|
pub fn addAllArgs(self: *StandaloneContext, root_src: []const u8, link_libc: bool) void {
|
|
const b = self.b;
|
|
|
|
for (self.modes) |mode| {
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {} ({})", root_src, @tagName(mode)) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
|
|
}
|
|
|
|
const exe = b.addExecutable("test", root_src);
|
|
exe.setBuildMode(mode);
|
|
if (link_libc) {
|
|
exe.linkSystemLibrary("c");
|
|
}
|
|
|
|
const log_step = b.addLog("PASS {}\n", annotated_case_name);
|
|
log_step.step.dependOn(&exe.step);
|
|
|
|
self.step.dependOn(&log_step.step);
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const TranslateCContext = struct {
|
|
b: *build.Builder,
|
|
step: *build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
|
|
const TestCase = struct {
|
|
name: []const u8,
|
|
sources: ArrayList(SourceFile),
|
|
expected_lines: ArrayList([]const u8),
|
|
allow_warnings: bool,
|
|
stage2: bool,
|
|
|
|
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 TranslateCCmpOutputStep = struct {
|
|
step: build.Step,
|
|
context: *TranslateCContext,
|
|
name: []const u8,
|
|
test_index: usize,
|
|
case: *const TestCase,
|
|
|
|
pub fn create(context: *TranslateCContext, name: []const u8, case: *const TestCase) *TranslateCCmpOutputStep {
|
|
const allocator = context.b.allocator;
|
|
const ptr = allocator.create(TranslateCCmpOutputStep) catch unreachable;
|
|
ptr.* = TranslateCCmpOutputStep{
|
|
.step = build.Step.init("ParseCCmpOutput", allocator, make),
|
|
.context = context,
|
|
.name = name,
|
|
.test_index = context.test_index,
|
|
.case = case,
|
|
};
|
|
|
|
context.test_index += 1;
|
|
return ptr;
|
|
}
|
|
|
|
fn make(step: *build.Step) !void {
|
|
const self = @fieldParentPtr(TranslateCCmpOutputStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
const root_src = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, self.case.sources.items[0].filename },
|
|
) catch unreachable;
|
|
|
|
var zig_args = ArrayList([]const u8).init(b.allocator);
|
|
zig_args.append(b.zig_exe) catch unreachable;
|
|
|
|
const translate_c_cmd = if (self.case.stage2) "translate-c-2" else "translate-c";
|
|
zig_args.append(translate_c_cmd) catch unreachable;
|
|
zig_args.append(b.pathFromRoot(root_src)) catch unreachable;
|
|
|
|
warn("Test {}/{} {}...", self.test_index + 1, self.context.test_index, self.name);
|
|
|
|
if (b.verbose) {
|
|
printInvocation(zig_args.toSliceConst());
|
|
}
|
|
|
|
const child = std.ChildProcess.init(zig_args.toSliceConst(), b.allocator) catch unreachable;
|
|
defer child.deinit();
|
|
|
|
child.env_map = b.env_map;
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Pipe;
|
|
child.stderr_behavior = .Pipe;
|
|
|
|
child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err));
|
|
|
|
var stdout_buf = Buffer.initNull(b.allocator);
|
|
var stderr_buf = Buffer.initNull(b.allocator);
|
|
|
|
var stdout_file_in_stream = child.stdout.?.inStream();
|
|
var stderr_file_in_stream = child.stderr.?.inStream();
|
|
|
|
stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable;
|
|
stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable;
|
|
|
|
const term = child.wait() catch |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err));
|
|
};
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code != 0) {
|
|
warn("Compilation failed with exit code {}\n", code);
|
|
printInvocation(zig_args.toSliceConst());
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
.Signal => |code| {
|
|
warn("Compilation failed with signal {}\n", code);
|
|
printInvocation(zig_args.toSliceConst());
|
|
return error.TestFailed;
|
|
},
|
|
else => {
|
|
warn("Compilation terminated unexpectedly\n");
|
|
printInvocation(zig_args.toSliceConst());
|
|
return error.TestFailed;
|
|
},
|
|
}
|
|
|
|
const stdout = stdout_buf.toSliceConst();
|
|
const stderr = stderr_buf.toSliceConst();
|
|
|
|
if (stderr.len != 0 and !self.case.allow_warnings) {
|
|
warn(
|
|
\\====== translate-c emitted warnings: =======
|
|
\\{}
|
|
\\============================================
|
|
\\
|
|
, stderr);
|
|
printInvocation(zig_args.toSliceConst());
|
|
return error.TestFailed;
|
|
}
|
|
|
|
for (self.case.expected_lines.toSliceConst()) |expected_line| {
|
|
if (mem.indexOf(u8, stdout, expected_line) == null) {
|
|
warn(
|
|
\\
|
|
\\========= Expected this output: ================
|
|
\\{}
|
|
\\========= But found: ===========================
|
|
\\{}
|
|
\\
|
|
, expected_line, stdout);
|
|
printInvocation(zig_args.toSliceConst());
|
|
return error.TestFailed;
|
|
}
|
|
}
|
|
warn("OK\n");
|
|
}
|
|
};
|
|
|
|
fn printInvocation(args: []const []const u8) void {
|
|
for (args) |arg| {
|
|
warn("{} ", arg);
|
|
}
|
|
warn("\n");
|
|
}
|
|
|
|
pub fn create(self: *TranslateCContext, allow_warnings: bool, filename: []const u8, name: []const u8, source: []const u8, expected_lines: ...) *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),
|
|
.allow_warnings = allow_warnings,
|
|
.stage2 = false,
|
|
};
|
|
|
|
tc.addSourceFile(filename, source);
|
|
comptime var arg_i = 0;
|
|
inline while (arg_i < expected_lines.len) : (arg_i += 1) {
|
|
tc.addExpectedLine(expected_lines[arg_i]);
|
|
}
|
|
return tc;
|
|
}
|
|
|
|
pub fn add(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
const tc = self.create(false, "source.h", name, source, expected_lines);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addC(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
const tc = self.create(false, "source.c", name, source, expected_lines);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn add_both(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
for ([_]bool{ false, true }) |stage2| {
|
|
const tc = self.create(false, "source.h", name, source, expected_lines);
|
|
tc.stage2 = stage2;
|
|
self.addCase(tc);
|
|
}
|
|
}
|
|
|
|
pub fn addC_both(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
for ([_]bool{ false, true }) |stage2| {
|
|
const tc = self.create(false, "source.c", name, source, expected_lines);
|
|
tc.stage2 = stage2;
|
|
self.addCase(tc);
|
|
}
|
|
}
|
|
|
|
pub fn add_2(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
const tc = self.create(false, "source.h", name, source, expected_lines);
|
|
tc.stage2 = true;
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addC_2(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
const tc = self.create(false, "source.c", name, source, expected_lines);
|
|
tc.stage2 = true;
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addAllowWarnings(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
|
|
const tc = self.create(true, "source.h", name, source, expected_lines);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addCase(self: *TranslateCContext, case: *const TestCase) void {
|
|
const b = self.b;
|
|
|
|
const translate_c_cmd = if (case.stage2) "translate-c-2" else "translate-c";
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {}", translate_c_cmd, case.name) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
|
|
}
|
|
|
|
const translate_c_and_cmp = TranslateCCmpOutputStep.create(self, annotated_case_name, case);
|
|
self.step.dependOn(&translate_c_and_cmp.step);
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, src_file.filename },
|
|
) catch unreachable;
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
translate_c_and_cmp.step.dependOn(&write_src.step);
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const GenHContext = struct {
|
|
b: *build.Builder,
|
|
step: *build.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: build.Step,
|
|
context: *GenHContext,
|
|
obj: *LibExeObjStep,
|
|
name: []const u8,
|
|
test_index: usize,
|
|
case: *const TestCase,
|
|
|
|
pub fn create(
|
|
context: *GenHContext,
|
|
obj: *LibExeObjStep,
|
|
name: []const u8,
|
|
case: *const TestCase,
|
|
) *GenHCmpOutputStep {
|
|
const allocator = context.b.allocator;
|
|
const ptr = allocator.create(GenHCmpOutputStep) catch unreachable;
|
|
ptr.* = GenHCmpOutputStep{
|
|
.step = build.Step.init("ParseCCmpOutput", allocator, 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: *build.Step) !void {
|
|
const self = @fieldParentPtr(GenHCmpOutputStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
warn("Test {}/{} {}...", 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.toSliceConst()) |expected_line| {
|
|
if (mem.indexOf(u8, actual_h, expected_line) == null) {
|
|
warn(
|
|
\\
|
|
\\========= Expected this output: ================
|
|
\\{}
|
|
\\========= But found: ===========================
|
|
\\{}
|
|
\\
|
|
, expected_line, actual_h);
|
|
return error.TestFailed;
|
|
}
|
|
}
|
|
warn("OK\n");
|
|
}
|
|
};
|
|
|
|
fn printInvocation(args: []const []const u8) void {
|
|
for (args) |arg| {
|
|
warn("{} ", arg);
|
|
}
|
|
warn("\n");
|
|
}
|
|
|
|
pub fn create(self: *GenHContext, filename: []const u8, name: []const u8, source: []const u8, expected_lines: ...) *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);
|
|
comptime var arg_i = 0;
|
|
inline 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: ...) 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 root_src = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, case.sources.items[0].filename },
|
|
) catch unreachable;
|
|
|
|
const mode = builtin.Mode.Debug;
|
|
const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {} ({})", case.name, @tagName(mode)) catch unreachable;
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
|
|
}
|
|
|
|
const obj = b.addObject("test", root_src);
|
|
obj.setBuildMode(mode);
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = fs.path.join(
|
|
b.allocator,
|
|
&[_][]const u8{ b.cache_root, src_file.filename },
|
|
) catch unreachable;
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
obj.step.dependOn(&write_src.step);
|
|
}
|
|
|
|
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| {
|
|
warn("{} ", arg);
|
|
}
|
|
warn("\n");
|
|
}
|