21a55d89b6
See #449 the llvm assertion that is being triggered appears to be an llvm bug that is harmless with assertions off.
938 lines
33 KiB
Zig
938 lines
33 KiB
Zig
const std = @import("std");
|
|
const debug = std.debug;
|
|
const build = std.build;
|
|
const os = std.os;
|
|
const StdIo = os.ChildProcess.StdIo;
|
|
const Term = os.ChildProcess.Term;
|
|
const Buffer = std.Buffer;
|
|
const io = std.io;
|
|
const mem = std.mem;
|
|
const fmt = std.fmt;
|
|
const ArrayList = std.ArrayList;
|
|
const builtin = @import("builtin");
|
|
const Mode = builtin.Mode;
|
|
|
|
const compare_output = @import("compare_output.zig");
|
|
const build_examples = @import("build_examples.zig");
|
|
const compile_errors = @import("compile_errors.zig");
|
|
const assemble_and_link = @import("assemble_and_link.zig");
|
|
const debug_safety = @import("debug_safety.zig");
|
|
const parsec = @import("parsec.zig");
|
|
|
|
const TestTarget = struct {
|
|
os: builtin.Os,
|
|
arch: builtin.Arch,
|
|
environ: builtin.Environ,
|
|
};
|
|
|
|
const test_targets = []TestTarget {
|
|
TestTarget {
|
|
.os = builtin.Os.linux,
|
|
.arch = builtin.Arch.x86_64,
|
|
.environ = builtin.Environ.gnu,
|
|
},
|
|
TestTarget {
|
|
.os = builtin.Os.darwin,
|
|
.arch = builtin.Arch.x86_64,
|
|
.environ = builtin.Environ.unknown,
|
|
},
|
|
TestTarget {
|
|
.os = builtin.Os.windows,
|
|
.arch = builtin.Arch.x86_64,
|
|
.environ = builtin.Environ.msvc,
|
|
},
|
|
};
|
|
|
|
error TestFailed;
|
|
|
|
pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
|
|
const cases = %%b.allocator.create(CompareOutputContext);
|
|
*cases = CompareOutputContext {
|
|
.b = b,
|
|
.step = b.step("test-compare-output", "Run the compare output tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
};
|
|
|
|
compare_output.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addDebugSafetyTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
|
|
const cases = %%b.allocator.create(CompareOutputContext);
|
|
*cases = CompareOutputContext {
|
|
.b = b,
|
|
.step = b.step("test-debug-safety", "Run the debug safety tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
};
|
|
|
|
debug_safety.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
|
|
const cases = %%b.allocator.create(CompileErrorContext);
|
|
*cases = CompileErrorContext {
|
|
.b = b,
|
|
.step = b.step("test-compile-errors", "Run the compile error tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
};
|
|
|
|
compile_errors.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addBuildExampleTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
|
|
const cases = %%b.allocator.create(BuildExamplesContext);
|
|
*cases = BuildExamplesContext {
|
|
.b = b,
|
|
.step = b.step("test-build-examples", "Build the examples"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
};
|
|
|
|
build_examples.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addAssembleAndLinkTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
|
|
const cases = %%b.allocator.create(CompareOutputContext);
|
|
*cases = CompareOutputContext {
|
|
.b = b,
|
|
.step = b.step("test-asm-link", "Run the assemble and link tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
};
|
|
|
|
assemble_and_link.addCases(cases);
|
|
|
|
return cases.step;
|
|
}
|
|
|
|
pub fn addParseCTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
|
|
const cases = %%b.allocator.create(ParseCContext);
|
|
*cases = ParseCContext {
|
|
.b = b,
|
|
.step = b.step("test-parsec", "Run the C header file parsing tests"),
|
|
.test_index = 0,
|
|
.test_filter = test_filter,
|
|
};
|
|
|
|
parsec.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) -> &build.Step
|
|
{
|
|
return addPkgTestsRaw(b, test_filter, root_src, name, desc, false);
|
|
}
|
|
|
|
pub fn addPkgTestsAlwaysLibc(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
|
|
name:[] const u8, desc: []const u8) -> &build.Step
|
|
{
|
|
return addPkgTestsRaw(b, test_filter, root_src, name, desc, true);
|
|
}
|
|
|
|
pub fn addPkgTestsRaw(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
|
|
name:[] const u8, desc: []const u8, always_link_libc: bool) -> &build.Step
|
|
{
|
|
const libc_bools = if (always_link_libc) []bool{true} else []bool{false, true};
|
|
const step = b.step(b.fmt("test-{}", name), desc);
|
|
for (test_targets) |test_target| {
|
|
const is_native = (test_target.os == builtin.os and test_target.arch == builtin.arch);
|
|
for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
|
|
for (libc_bools) |link_libc| {
|
|
if (link_libc and !is_native) {
|
|
// don't assume we have a cross-compiling libc set up
|
|
continue;
|
|
}
|
|
const these_tests = b.addTest(root_src);
|
|
these_tests.setNamePrefix(b.fmt("{}-{}-{}-{}-{} ", name, @enumTagName(test_target.os),
|
|
@enumTagName(test_target.arch), @enumTagName(mode), if (link_libc) "c" else "bare"));
|
|
these_tests.setFilter(test_filter);
|
|
these_tests.setBuildMode(mode);
|
|
if (!is_native) {
|
|
these_tests.setTarget(test_target.arch, test_target.os, test_target.environ);
|
|
}
|
|
if (link_libc) {
|
|
these_tests.linkSystemLibrary("c");
|
|
}
|
|
step.dependOn(&these_tests.step);
|
|
}
|
|
}
|
|
}
|
|
return step;
|
|
}
|
|
|
|
pub const CompareOutputContext = struct {
|
|
b: &build.Builder,
|
|
step: &build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
|
|
const Special = enum {
|
|
None,
|
|
Asm,
|
|
DebugSafety,
|
|
};
|
|
|
|
const TestCase = struct {
|
|
name: []const u8,
|
|
sources: ArrayList(SourceFile),
|
|
expected_output: []const u8,
|
|
link_libc: bool,
|
|
special: Special,
|
|
|
|
const SourceFile = struct {
|
|
filename: []const u8,
|
|
source: []const u8,
|
|
};
|
|
|
|
pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
|
|
%%self.sources.append(SourceFile {
|
|
.filename = filename,
|
|
.source = source,
|
|
});
|
|
}
|
|
};
|
|
|
|
const RunCompareOutputStep = struct {
|
|
step: build.Step,
|
|
context: &CompareOutputContext,
|
|
exe_path: []const u8,
|
|
name: []const u8,
|
|
expected_output: []const u8,
|
|
test_index: usize,
|
|
|
|
pub fn create(context: &CompareOutputContext, exe_path: []const u8,
|
|
name: []const u8, expected_output: []const u8) -> &RunCompareOutputStep
|
|
{
|
|
const allocator = context.b.allocator;
|
|
const ptr = %%allocator.create(RunCompareOutputStep);
|
|
*ptr = RunCompareOutputStep {
|
|
.context = context,
|
|
.exe_path = exe_path,
|
|
.name = name,
|
|
.expected_output = expected_output,
|
|
.test_index = context.test_index,
|
|
.step = build.Step.init("RunCompareOutput", allocator, make),
|
|
};
|
|
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 = b.pathFromRoot(self.exe_path);
|
|
|
|
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
|
|
|
|
var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
|
|
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
|
|
{
|
|
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
};
|
|
|
|
var stdout = Buffer.initNull(b.allocator);
|
|
var stderr = Buffer.initNull(b.allocator);
|
|
|
|
%%(??child.stdout).readAll(&stdout);
|
|
%%(??child.stderr).readAll(&stderr);
|
|
|
|
const term = child.wait() %% |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
};
|
|
switch (term) {
|
|
Term.Exited => |code| {
|
|
if (code != 0) {
|
|
%%io.stderr.printf("Process {} exited with error code {}\n", full_exe_path, code);
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
else => {
|
|
%%io.stderr.printf("Process {} terminated unexpectedly\n", full_exe_path);
|
|
return error.TestFailed;
|
|
},
|
|
};
|
|
|
|
|
|
if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
|
|
%%io.stderr.printf(
|
|
\\
|
|
\\========= Expected this output: =========
|
|
\\{}
|
|
\\================================================
|
|
\\{}
|
|
\\
|
|
, self.expected_output, stdout.toSliceConst());
|
|
return error.TestFailed;
|
|
}
|
|
%%io.stderr.printf("OK\n");
|
|
}
|
|
};
|
|
|
|
const DebugSafetyRunStep = struct {
|
|
step: build.Step,
|
|
context: &CompareOutputContext,
|
|
exe_path: []const u8,
|
|
name: []const u8,
|
|
test_index: usize,
|
|
|
|
pub fn create(context: &CompareOutputContext, exe_path: []const u8,
|
|
name: []const u8) -> &DebugSafetyRunStep
|
|
{
|
|
const allocator = context.b.allocator;
|
|
const ptr = %%allocator.create(DebugSafetyRunStep);
|
|
*ptr = DebugSafetyRunStep {
|
|
.context = context,
|
|
.exe_path = exe_path,
|
|
.name = name,
|
|
.test_index = context.test_index,
|
|
.step = build.Step.init("DebugSafetyRun", allocator, make),
|
|
};
|
|
context.test_index += 1;
|
|
return ptr;
|
|
}
|
|
|
|
fn make(step: &build.Step) -> %void {
|
|
const self = @fieldParentPtr(DebugSafetyRunStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
const full_exe_path = b.pathFromRoot(self.exe_path);
|
|
|
|
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
|
|
|
|
var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
|
|
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
|
|
{
|
|
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
};
|
|
|
|
const term = child.wait() %% |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
|
|
};
|
|
|
|
const debug_trap_signal: i32 = 5;
|
|
switch (term) {
|
|
Term.Exited => |code| {
|
|
%%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
|
|
"but exited with return code {}\n", debug_trap_signal, code);
|
|
return error.TestFailed;
|
|
},
|
|
Term.Signal => |sig| {
|
|
if (sig != debug_trap_signal) {
|
|
%%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
|
|
"but instead signaled {}\n", debug_trap_signal, sig);
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
else => {
|
|
%%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
|
|
" but exited in an unexpected way\n", debug_trap_signal);
|
|
return error.TestFailed;
|
|
},
|
|
}
|
|
|
|
%%io.stderr.printf("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,
|
|
};
|
|
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) {
|
|
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) {
|
|
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) {
|
|
const tc = self.createExtra(name, source, expected_output, Special.Asm);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addDebugSafety(self: &CompareOutputContext, name: []const u8, source: []const u8) {
|
|
const tc = self.createExtra(name, source, undefined, Special.DebugSafety);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addCase(self: &CompareOutputContext, case: &const TestCase) {
|
|
const b = self.b;
|
|
|
|
const root_src = %%os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename);
|
|
|
|
switch (case.special) {
|
|
Special.Asm => {
|
|
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name);
|
|
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 = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
|
|
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.getOutputPath(), annotated_case_name,
|
|
case.expected_output);
|
|
run_and_cmp_output.step.dependOn(&exe.step);
|
|
|
|
self.step.dependOn(&run_and_cmp_output.step);
|
|
},
|
|
Special.None => {
|
|
for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
|
|
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} {} ({})",
|
|
"compare-output", case.name, @enumTagName(mode));
|
|
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 = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
|
|
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.getOutputPath(),
|
|
annotated_case_name, case.expected_output);
|
|
run_and_cmp_output.step.dependOn(&exe.step);
|
|
|
|
self.step.dependOn(&run_and_cmp_output.step);
|
|
}
|
|
},
|
|
Special.DebugSafety => {
|
|
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "safety {}", case.name);
|
|
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 = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
exe.step.dependOn(&write_src.step);
|
|
}
|
|
|
|
const run_and_cmp_output = DebugSafetyRunStep.create(self, exe.getOutputPath(), annotated_case_name);
|
|
run_and_cmp_output.step.dependOn(&exe.step);
|
|
|
|
self.step.dependOn(&run_and_cmp_output.step);
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const CompileErrorContext = struct {
|
|
b: &build.Builder,
|
|
step: &build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
|
|
const TestCase = struct {
|
|
name: []const u8,
|
|
sources: ArrayList(SourceFile),
|
|
expected_errors: ArrayList([]const u8),
|
|
link_libc: bool,
|
|
is_exe: bool,
|
|
|
|
const SourceFile = struct {
|
|
filename: []const u8,
|
|
source: []const u8,
|
|
};
|
|
|
|
pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
|
|
%%self.sources.append(SourceFile {
|
|
.filename = filename,
|
|
.source = source,
|
|
});
|
|
}
|
|
|
|
pub fn addExpectedError(self: &TestCase, text: []const u8) {
|
|
%%self.expected_errors.append(text);
|
|
}
|
|
};
|
|
|
|
const CompileCmpOutputStep = struct {
|
|
step: build.Step,
|
|
context: &CompileErrorContext,
|
|
name: []const u8,
|
|
test_index: usize,
|
|
case: &const TestCase,
|
|
build_mode: Mode,
|
|
|
|
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);
|
|
*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 = %%os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename);
|
|
const obj_path = %%os.path.join(b.allocator, b.cache_root, "test.o");
|
|
|
|
var zig_args = ArrayList([]const u8).init(b.allocator);
|
|
%%zig_args.append(if (self.case.is_exe) "build-exe" else "build-obj");
|
|
%%zig_args.append(b.pathFromRoot(root_src));
|
|
|
|
%%zig_args.append("--name");
|
|
%%zig_args.append("test");
|
|
|
|
%%zig_args.append("--output");
|
|
%%zig_args.append(b.pathFromRoot(obj_path));
|
|
|
|
switch (self.build_mode) {
|
|
Mode.Debug => {},
|
|
Mode.ReleaseSafe => %%zig_args.append("--release-safe"),
|
|
Mode.ReleaseFast => %%zig_args.append("--release-fast"),
|
|
}
|
|
|
|
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
|
|
|
|
if (b.verbose) {
|
|
printInvocation(b.zig_exe, zig_args.toSliceConst());
|
|
}
|
|
|
|
var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
|
|
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
|
|
{
|
|
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
|
|
};
|
|
|
|
var stdout_buf = Buffer.initNull(b.allocator);
|
|
var stderr_buf = Buffer.initNull(b.allocator);
|
|
|
|
%%(??child.stdout).readAll(&stdout_buf);
|
|
%%(??child.stderr).readAll(&stderr_buf);
|
|
|
|
const term = child.wait() %% |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
|
|
};
|
|
switch (term) {
|
|
Term.Exited => |code| {
|
|
if (code == 0) {
|
|
%%io.stderr.printf("Compilation incorrectly succeeded\n");
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
else => {
|
|
%%io.stderr.printf("Process {} terminated unexpectedly\n", b.zig_exe);
|
|
return error.TestFailed;
|
|
},
|
|
};
|
|
|
|
|
|
const stdout = stdout_buf.toSliceConst();
|
|
const stderr = stderr_buf.toSliceConst();
|
|
|
|
if (stdout.len != 0) {
|
|
%%io.stderr.printf(
|
|
\\
|
|
\\Expected empty stdout, instead found:
|
|
\\================================================
|
|
\\{}
|
|
\\================================================
|
|
\\
|
|
, stdout);
|
|
return error.TestFailed;
|
|
}
|
|
|
|
for (self.case.expected_errors.toSliceConst()) |expected_error| {
|
|
if (mem.indexOf(u8, stderr, expected_error) == null) {
|
|
%%io.stderr.printf(
|
|
\\
|
|
\\========= Expected this compile error: =========
|
|
\\{}
|
|
\\================================================
|
|
\\{}
|
|
\\
|
|
, expected_error, stderr);
|
|
return error.TestFailed;
|
|
}
|
|
}
|
|
%%io.stderr.printf("OK\n");
|
|
}
|
|
};
|
|
|
|
fn printInvocation(exe_path: []const u8, args: []const []const u8) {
|
|
%%io.stderr.printf("{}", exe_path);
|
|
for (args) |arg| {
|
|
%%io.stderr.printf(" {}", arg);
|
|
}
|
|
%%io.stderr.printf("\n");
|
|
}
|
|
|
|
pub fn create(self: &CompileErrorContext, name: []const u8, source: []const u8,
|
|
expected_lines: ...) -> &TestCase
|
|
{
|
|
const tc = %%self.b.allocator.create(TestCase);
|
|
*tc = TestCase {
|
|
.name = name,
|
|
.sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
|
|
.expected_errors = ArrayList([]const u8).init(self.b.allocator),
|
|
.link_libc = false,
|
|
.is_exe = false,
|
|
};
|
|
tc.addSourceFile(".tmp_source.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: ...) {
|
|
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: ...) {
|
|
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: ...) {
|
|
const tc = self.create(name, source, expected_lines);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addCase(self: &CompileErrorContext, case: &const TestCase) {
|
|
const b = self.b;
|
|
|
|
for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
|
|
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "compile-error {} ({})",
|
|
case.name, @enumTagName(mode));
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null)
|
|
continue;
|
|
}
|
|
|
|
const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, mode);
|
|
self.step.dependOn(&compile_and_cmp_errors.step);
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
compile_and_cmp_errors.step.dependOn(&write_src.step);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const BuildExamplesContext = struct {
|
|
b: &build.Builder,
|
|
step: &build.Step,
|
|
test_index: usize,
|
|
test_filter: ?[]const u8,
|
|
|
|
pub fn addC(self: &BuildExamplesContext, root_src: []const u8) {
|
|
self.addAllArgs(root_src, true);
|
|
}
|
|
|
|
pub fn add(self: &BuildExamplesContext, root_src: []const u8) {
|
|
self.addAllArgs(root_src, false);
|
|
}
|
|
|
|
pub fn addBuildFile(self: &BuildExamplesContext, build_file: []const u8) {
|
|
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);
|
|
%%zig_args.append("build");
|
|
|
|
%%zig_args.append("--build-file");
|
|
%%zig_args.append(b.pathFromRoot(build_file));
|
|
|
|
%%zig_args.append("test");
|
|
|
|
if (b.verbose) {
|
|
%%zig_args.append("--verbose");
|
|
}
|
|
|
|
const run_cmd = b.addCommand(null, b.env_map, b.zig_exe, 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: &BuildExamplesContext, root_src: []const u8, link_libc: bool) {
|
|
const b = self.b;
|
|
|
|
for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast}) |mode| {
|
|
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "build {} ({})",
|
|
root_src, @enumTagName(mode));
|
|
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 ParseCContext = 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,
|
|
|
|
const SourceFile = struct {
|
|
filename: []const u8,
|
|
source: []const u8,
|
|
};
|
|
|
|
pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
|
|
%%self.sources.append(SourceFile {
|
|
.filename = filename,
|
|
.source = source,
|
|
});
|
|
}
|
|
|
|
pub fn addExpectedError(self: &TestCase, text: []const u8) {
|
|
%%self.expected_lines.append(text);
|
|
}
|
|
};
|
|
|
|
const ParseCCmpOutputStep = struct {
|
|
step: build.Step,
|
|
context: &ParseCContext,
|
|
name: []const u8,
|
|
test_index: usize,
|
|
case: &const TestCase,
|
|
|
|
pub fn create(context: &ParseCContext, name: []const u8, case: &const TestCase) -> &ParseCCmpOutputStep {
|
|
const allocator = context.b.allocator;
|
|
const ptr = %%allocator.create(ParseCCmpOutputStep);
|
|
*ptr = ParseCCmpOutputStep {
|
|
.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(ParseCCmpOutputStep, "step", step);
|
|
const b = self.context.b;
|
|
|
|
const root_src = %%os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename);
|
|
|
|
var zig_args = ArrayList([]const u8).init(b.allocator);
|
|
%%zig_args.append("parsec");
|
|
%%zig_args.append(b.pathFromRoot(root_src));
|
|
|
|
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
|
|
|
|
if (b.verbose) {
|
|
printInvocation(b.zig_exe, zig_args.toSliceConst());
|
|
}
|
|
|
|
var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
|
|
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
|
|
{
|
|
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
|
|
};
|
|
|
|
var stdout_buf = Buffer.initNull(b.allocator);
|
|
var stderr_buf = Buffer.initNull(b.allocator);
|
|
|
|
%%(??child.stdout).readAll(&stdout_buf);
|
|
%%(??child.stderr).readAll(&stderr_buf);
|
|
|
|
const term = child.wait() %% |err| {
|
|
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
|
|
};
|
|
switch (term) {
|
|
Term.Exited => |code| {
|
|
if (code != 0) {
|
|
%%io.stderr.printf("Compilation failed with exit code {}\n", code);
|
|
return error.TestFailed;
|
|
}
|
|
},
|
|
Term.Signal => |code| {
|
|
%%io.stderr.printf("Compilation failed with signal {}\n", code);
|
|
return error.TestFailed;
|
|
},
|
|
else => {
|
|
%%io.stderr.printf("Compilation terminated unexpectedly\n");
|
|
return error.TestFailed;
|
|
},
|
|
};
|
|
|
|
const stdout = stdout_buf.toSliceConst();
|
|
const stderr = stderr_buf.toSliceConst();
|
|
|
|
if (stderr.len != 0 and !self.case.allow_warnings) {
|
|
%%io.stderr.printf(
|
|
\\====== parsec emitted warnings: ============
|
|
\\{}
|
|
\\============================================
|
|
\\
|
|
, stderr);
|
|
return error.TestFailed;
|
|
}
|
|
|
|
for (self.case.expected_lines.toSliceConst()) |expected_line| {
|
|
if (mem.indexOf(u8, stdout, expected_line) == null) {
|
|
%%io.stderr.printf(
|
|
\\
|
|
\\========= Expected this output: ================
|
|
\\{}
|
|
\\================================================
|
|
\\{}
|
|
\\
|
|
, expected_line, stdout);
|
|
return error.TestFailed;
|
|
}
|
|
}
|
|
%%io.stderr.printf("OK\n");
|
|
}
|
|
};
|
|
|
|
fn printInvocation(exe_path: []const u8, args: []const []const u8) {
|
|
%%io.stderr.printf("{}", exe_path);
|
|
for (args) |arg| {
|
|
%%io.stderr.printf(" {}", arg);
|
|
}
|
|
%%io.stderr.printf("\n");
|
|
}
|
|
|
|
pub fn create(self: &ParseCContext, allow_warnings: bool, name: []const u8,
|
|
source: []const u8, expected_lines: ...) -> &TestCase
|
|
{
|
|
const tc = %%self.b.allocator.create(TestCase);
|
|
*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,
|
|
};
|
|
tc.addSourceFile("source.h", 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 add(self: &ParseCContext, name: []const u8, source: []const u8, expected_lines: ...) {
|
|
const tc = self.create(false, name, source, expected_lines);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addAllowWarnings(self: &ParseCContext, name: []const u8, source: []const u8, expected_lines: ...) {
|
|
const tc = self.create(true, name, source, expected_lines);
|
|
self.addCase(tc);
|
|
}
|
|
|
|
pub fn addCase(self: &ParseCContext, case: &const TestCase) {
|
|
const b = self.b;
|
|
|
|
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "parsec {}", case.name);
|
|
if (self.test_filter) |filter| {
|
|
if (mem.indexOf(u8, annotated_case_name, filter) == null)
|
|
return;
|
|
}
|
|
|
|
const parsec_and_cmp = ParseCCmpOutputStep.create(self, annotated_case_name, case);
|
|
self.step.dependOn(&parsec_and_cmp.step);
|
|
|
|
for (case.sources.toSliceConst()) |src_file| {
|
|
const expanded_src_path = %%os.path.join(b.allocator, b.cache_root, src_file.filename);
|
|
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
|
|
parsec_and_cmp.step.dependOn(&write_src.step);
|
|
}
|
|
}
|
|
};
|