avoid calling into stage1 backend when AstGen fails

The motivation for this commit is that there exists source files which
produce ast-check errors, but crash stage1 or otherwise trigger stage1
bugs. Previously to this commit, Zig would run AstGen, collect the
compile errors, run stage1, report stage1 compile errors and exit if
any, and then report AstGen compile errors.

The main change in this commit is to report AstGen errors prior to
invoking stage1, and in fact if any AstGen errors occur, do not invoke
stage1 at all.

This caused most of the compile error tests to fail due to things such
as unused local variables and mismatched stage1/stage2 error messages.
It was taking a long time to update the test cases one-by-one, so I
took this opportunity to unify the stage1 and stage2 testing harness,
specifically with regards to compile errors. In this way we can start
keeping track of which tests pass for 1, 2, or both.
`zig build test-compile-errors` no longer works; it is now integrated
into `zig build test-stage2`.

This is one step closer to executing compile error tests in parallel; in
fact the ThreadPool object is already in scope.

There are some cases where the stage1 compile errors were actually
better; those are left failing in this commit, to be addressed in a
follow-up commit.

Other changes in this commit:

 * build.zig: improve support for -Dstage1 used with the test step.
 * AstGen: minor cosmetic changes to error messages.
 * stage2: add -fstage1 and -fno-stage1 flags. This now allows one to
   download a binary of the zig compiler and use the llvm backend of
   self-hosted. This was also needed for hooking up the test harness.
   However, I realized that stage1 calls exit() and also has memory
   leaks, so had to complicate the test harness by not using this flag
   after all and instead invoking as a child process.
   - These CLI flags will disappear once we start shipping the
     self-hosted compiler as the main compiler. Until then, they can be
     used to try out the work-in-progress stage2.
 * stage2: select the LLVM backend by default for release modes, as long
   as the target architecture is supported by LLVM.
 * test harness: support setting the optimize mode
This commit is contained in:
Andrew Kelley 2021-06-30 11:27:39 -07:00
parent 22b20f20b6
commit 8ce880ca75
14 changed files with 1709 additions and 1413 deletions

View File

@ -40,7 +40,7 @@ pub fn build(b: *Builder) !void {
var test_stage2 = b.addTest("src/test.zig");
test_stage2.setBuildMode(mode);
test_stage2.addPackagePath("stage2_tests", "test/stage2/test.zig");
test_stage2.addPackagePath("test_cases", "test/cases.zig");
const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});
@ -113,11 +113,15 @@ pub fn build(b: *Builder) !void {
if (is_stage1) {
exe.addIncludeDir("src");
exe.addIncludeDir("deps/SoftFloat-3e/source/include");
test_stage2.addIncludeDir("src");
test_stage2.addIncludeDir("deps/SoftFloat-3e/source/include");
// This is intentionally a dummy path. stage1.zig tries to @import("compiler_rt") in case
// of being built by cmake. But when built by zig it's gonna get a compiler_rt so that
// is pointless.
exe.addPackagePath("compiler_rt", "src/empty.zig");
exe.defineCMacro("ZIG_LINK_MODE", "Static");
test_stage2.defineCMacro("ZIG_LINK_MODE", "Static");
const softfloat = b.addStaticLibrary("softfloat", null);
softfloat.setBuildMode(.ReleaseFast);
@ -126,10 +130,15 @@ pub fn build(b: *Builder) !void {
softfloat.addIncludeDir("deps/SoftFloat-3e/source/8086");
softfloat.addIncludeDir("deps/SoftFloat-3e/source/include");
softfloat.addCSourceFiles(&softfloat_sources, &[_][]const u8{ "-std=c99", "-O3" });
exe.linkLibrary(softfloat);
test_stage2.linkLibrary(softfloat);
exe.addCSourceFiles(&stage1_sources, &exe_cflags);
exe.addCSourceFiles(&optimized_c_sources, &[_][]const u8{ "-std=c99", "-O3" });
test_stage2.addCSourceFiles(&stage1_sources, &exe_cflags);
test_stage2.addCSourceFiles(&optimized_c_sources, &[_][]const u8{ "-std=c99", "-O3" });
}
if (cmake_cfg) |cfg| {
// Inside this code path, we have to coordinate with system packaged LLVM, Clang, and LLD.
@ -139,8 +148,8 @@ pub fn build(b: *Builder) !void {
b.addSearchPrefix(cfg.cmake_prefix_path);
}
try addCmakeCfgOptionsToExe(b, cfg, tracy, exe);
try addCmakeCfgOptionsToExe(b, cfg, tracy, test_stage2);
try addCmakeCfgOptionsToExe(b, cfg, exe);
try addCmakeCfgOptionsToExe(b, cfg, test_stage2);
} else {
// Here we are -Denable-llvm but no cmake integration.
try addStaticLlvmOptionsToExe(exe);
@ -233,7 +242,9 @@ pub fn build(b: *Builder) !void {
const is_darling_enabled = b.option(bool, "enable-darling", "[Experimental] Use Darling to run cross compiled macOS tests") orelse false;
const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc");
test_stage2.addBuildOption(bool, "enable_logging", enable_logging);
test_stage2.addBuildOption(bool, "skip_non_native", skip_non_native);
test_stage2.addBuildOption(bool, "skip_compile_errors", skip_compile_errors);
test_stage2.addBuildOption(bool, "is_stage1", is_stage1);
test_stage2.addBuildOption(bool, "omit_stage2", omit_stage2);
test_stage2.addBuildOption(bool, "have_llvm", enable_llvm);
@ -243,7 +254,8 @@ pub fn build(b: *Builder) !void {
test_stage2.addBuildOption(u32, "mem_leak_frames", mem_leak_frames * 2);
test_stage2.addBuildOption(bool, "enable_darling", is_darling_enabled);
test_stage2.addBuildOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir);
test_stage2.addBuildOption([]const u8, "version", version);
test_stage2.addBuildOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version));
test_stage2.addBuildOption(std.SemanticVersion, "semver", semver);
const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests");
test_stage2_step.dependOn(&test_stage2.step);
@ -339,9 +351,6 @@ pub fn build(b: *Builder) !void {
}
// tests for this feature are disabled until we have the self-hosted compiler available
// toolchain_step.dependOn(tests.addGenHTests(b, test_filter));
if (!skip_compile_errors) {
toolchain_step.dependOn(tests.addCompileErrorTests(b, test_filter, modes));
}
const std_step = tests.addPkgTests(
b,
@ -383,7 +392,6 @@ const exe_cflags = [_][]const u8{
fn addCmakeCfgOptionsToExe(
b: *Builder,
cfg: CMakeConfig,
tracy: ?[]const u8,
exe: *std.build.LibExeObjStep,
) !void {
exe.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{
@ -397,7 +405,7 @@ fn addCmakeCfgOptionsToExe(
addCMakeLibraryList(exe, cfg.lld_libraries);
addCMakeLibraryList(exe, cfg.llvm_libraries);
const need_cpp_includes = tracy != null;
const need_cpp_includes = true;
// System -lc++ must be used because in this code path we are attempting to link
// against system-provided LLVM, Clang, LLD.
@ -486,9 +494,9 @@ fn addCxxKnownPath(
if (need_cpp_includes) {
// I used these temporarily for testing something but we obviously need a
// more general purpose solution here.
//exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0");
//exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0/x86_64-unknown-linux-gnu");
//exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0/backward");
//exe.addIncludeDir("/nix/store/fvf3qjqa5qpcjjkq37pb6ypnk1mzhf5h-gcc-9.3.0/lib/gcc/x86_64-unknown-linux-gnu/9.3.0/../../../../include/c++/9.3.0");
//exe.addIncludeDir("/nix/store/fvf3qjqa5qpcjjkq37pb6ypnk1mzhf5h-gcc-9.3.0/lib/gcc/x86_64-unknown-linux-gnu/9.3.0/../../../../include/c++/9.3.0/x86_64-unknown-linux-gnu");
//exe.addIncludeDir("/nix/store/fvf3qjqa5qpcjjkq37pb6ypnk1mzhf5h-gcc-9.3.0/lib/gcc/x86_64-unknown-linux-gnu/9.3.0/../../../../include/c++/9.3.0/backward");
}
}

View File

@ -284,7 +284,7 @@ pub const Tree = struct {
return stream.writeAll("bit range not allowed on slices and arrays");
},
.invalid_token => {
return stream.print("invalid token '{s}'", .{
return stream.print("invalid token: '{s}'", .{
token_tags[parse_error.token].symbol(),
});
},

View File

@ -4570,7 +4570,7 @@ fn tryExpr(
return astgen.failNode(node, "invalid 'try' outside function scope", .{});
};
if (parent_gz.in_defer) return astgen.failNode(node, "try is not allowed inside defer expression", .{});
if (parent_gz.in_defer) return astgen.failNode(node, "'try' is not allowed inside defer expression", .{});
var block_scope = parent_gz.makeSubBlock(scope);
block_scope.setBreakResultLoc(rl);
@ -6199,7 +6199,7 @@ fn identifier(
const ident_token = main_tokens[ident];
const ident_name = try astgen.identifierTokenString(ident_token);
if (mem.eql(u8, ident_name, "_")) {
return astgen.failNode(ident, "'_' may not be used as an identifier", .{});
return astgen.failNode(ident, "'_' used as an identifier without @\"_\" syntax", .{});
}
if (simple_types.get(ident_name)) |zir_const_ref| {
@ -6827,7 +6827,7 @@ fn builtinCall(
if (info.param_count) |expected| {
if (expected != params.len) {
const s = if (expected == 1) "" else "s";
return astgen.failNode(node, "expected {d} parameter{s}, found {d}", .{
return astgen.failNode(node, "expected {d} argument{s}, found {d}", .{
expected, s, params.len,
});
}

View File

@ -670,6 +670,7 @@ pub const InitOptions = struct {
use_llvm: ?bool = null,
use_lld: ?bool = null,
use_clang: ?bool = null,
use_stage1: ?bool = null,
rdynamic: bool = false,
strip: bool = false,
single_threaded: bool = false,
@ -807,8 +808,22 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
const ofmt = options.object_format orelse options.target.getObjectFormat();
const use_stage1 = options.use_stage1 orelse blk: {
if (build_options.omit_stage2)
break :blk true;
if (options.use_llvm) |use_llvm| {
if (!use_llvm) {
break :blk false;
}
}
break :blk build_options.is_stage1;
};
// Make a decision on whether to use LLVM or our own backend.
const use_llvm = if (options.use_llvm) |explicit| explicit else blk: {
const use_llvm = build_options.have_llvm and blk: {
if (options.use_llvm) |explicit|
break :blk explicit;
// If we have no zig code to compile, no need for LLVM.
if (options.root_pkg == null)
break :blk false;
@ -817,18 +832,24 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
if (ofmt == .c)
break :blk false;
// If we are the stage1 compiler, we depend on the stage1 c++ llvm backend
// The stage1 compiler depends on the stage1 C++ LLVM backend
// to compile zig code.
if (build_options.is_stage1)
if (use_stage1)
break :blk true;
// Prefer LLVM for release builds as long as it supports the target architecture.
if (options.optimize_mode != .Debug and target_util.hasLlvmSupport(options.target))
break :blk true;
// We would want to prefer LLVM for release builds when it is available, however
// we don't have an LLVM backend yet :)
// We would also want to prefer LLVM for architectures that we don't have self-hosted support for too.
break :blk false;
};
if (!use_llvm and options.machine_code_model != .default) {
return error.MachineCodeModelNotSupported;
if (!use_llvm) {
if (options.use_llvm == true) {
return error.ZigCompilerNotBuiltWithLLVMExtensions;
}
if (options.machine_code_model != .default) {
return error.MachineCodeModelNotSupportedWithoutLlvm;
}
}
const tsan = options.want_tsan orelse false;
@ -1344,6 +1365,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
.subsystem = options.subsystem,
.is_test = options.is_test,
.wasi_exec_model = wasi_exec_model,
.use_stage1 = use_stage1,
});
errdefer bin_file.destroy();
comp.* = .{
@ -1486,9 +1508,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
try comp.work_queue.writeItem(.libtsan);
}
// The `is_stage1` condition is here only because stage2 cannot yet build compiler-rt.
// The `use_stage1` condition is here only because stage2 cannot yet build compiler-rt.
// Once it is capable this condition should be removed.
if (build_options.is_stage1) {
if (comp.bin_file.options.use_stage1) {
if (comp.bin_file.options.include_compiler_rt) {
if (is_exe_or_dyn_lib) {
try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} });
@ -1519,7 +1541,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
}
}
if (build_options.is_stage1 and comp.bin_file.options.use_llvm) {
if (comp.bin_file.options.use_stage1) {
try comp.work_queue.writeItem(.{ .stage1_module = {} });
}
@ -1625,8 +1647,7 @@ pub fn update(self: *Compilation) !void {
self.c_object_work_queue.writeItemAssumeCapacity(key);
}
const use_stage1 = build_options.omit_stage2 or
(build_options.is_stage1 and self.bin_file.options.use_llvm);
const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_stage1;
if (self.bin_file.options.module) |module| {
module.compile_log_text.shrinkAndFree(module.gpa, 0);
module.generation += 1;
@ -1921,7 +1942,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
// (at least for now) single-threaded main work queue. However, C object compilation
// only needs to be finished by the end of this function.
var zir_prog_node = main_progress_node.start("AST Lowering", self.astgen_work_queue.count);
var zir_prog_node = main_progress_node.start("AST Lowering", 0);
defer zir_prog_node.end();
var c_obj_prog_node = main_progress_node.start("Compile C Objects", self.c_source_files.len);
@ -1949,13 +1970,18 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
}
}
const use_stage1 = build_options.omit_stage2 or
(build_options.is_stage1 and self.bin_file.options.use_llvm);
const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_stage1;
if (!use_stage1) {
// Iterate over all the files and look for outdated and deleted declarations.
if (self.bin_file.options.module) |mod| {
try mod.processOutdatedAndDeletedDecls();
}
} else if (self.bin_file.options.module) |mod| {
// If there are any AstGen compile errors, report them now to avoid
// hitting stage1 bugs.
if (mod.failed_files.count() != 0) {
return;
}
}
while (self.work_queue.readItem()) |work_item| switch (work_item) {
@ -3486,8 +3512,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) Alloc
const target = comp.getTarget();
const generic_arch_name = target.cpu.arch.genericName();
const use_stage1 = build_options.omit_stage2 or
(build_options.is_stage1 and comp.bin_file.options.use_llvm);
const use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1;
@setEvalBranchQuota(4000);
try buffer.writer().print(

View File

@ -92,6 +92,7 @@ pub const Options = struct {
each_lib_rpath: bool,
disable_lld_caching: bool,
is_test: bool,
use_stage1: bool,
major_subsystem_version: ?u32,
minor_subsystem_version: ?u32,
gc_sections: ?bool = null,
@ -181,7 +182,7 @@ pub const File = struct {
/// rewriting it. A malicious file is detected as incremental link failure
/// and does not cause Illegal Behavior. This operation is not atomic.
pub fn openPath(allocator: *Allocator, options: Options) !*File {
const use_stage1 = build_options.is_stage1 and options.use_llvm;
const use_stage1 = build_options.is_stage1 and options.use_stage1;
if (use_stage1 or options.emit == null) {
return switch (options.object_format) {
.coff, .pe => &(try Coff.createEmpty(allocator, options)).base,
@ -507,7 +508,7 @@ pub const File = struct {
// If there is no Zig code to compile, then we should skip flushing the output file because it
// will not be part of the linker line anyway.
const module_obj_path: ?[]const u8 = if (base.options.module) |module| blk: {
const use_stage1 = build_options.is_stage1 and base.options.use_llvm;
const use_stage1 = build_options.is_stage1 and base.options.use_stage1;
if (use_stage1) {
const obj_basename = try std.zig.binNameAlloc(arena, .{
.root_name = base.options.root_name,

View File

@ -606,7 +606,7 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
// If there is no Zig code to compile, then we should skip flushing the output file because it
// will not be part of the linker line anyway.
const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm;
const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1;
if (use_stage1) {
const obj_basename = try std.zig.binNameAlloc(arena, .{
.root_name = self.base.options.root_name,

View File

@ -556,7 +556,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
// If there is no Zig code to compile, then we should skip flushing the output file because it
// will not be part of the linker line anyway.
const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm;
const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1;
if (use_stage1) {
const obj_basename = try std.zig.binNameAlloc(arena, .{
.root_name = self.base.options.root_name,

View File

@ -350,9 +350,11 @@ const usage_build_generic =
\\ -funwind-tables Always produce unwind table entries for all functions
\\ -fno-unwind-tables Never produce unwind table entries
\\ -fLLVM Force using LLVM as the codegen backend
\\ -fno-LLVM Prevent using LLVM as a codegen backend
\\ -fno-LLVM Prevent using LLVM as the codegen backend
\\ -fClang Force using Clang as the C/C++ compilation backend
\\ -fno-Clang Prevent using Clang as the C/C++ compilation backend
\\ -fstage1 Force using bootstrap compiler as the codegen backend
\\ -fno-stage1 Prevent using bootstrap compiler as the codegen backend
\\ --strip Omit debug symbols
\\ --single-threaded Code assumes it is only used single-threaded
\\ -ofmt=[mode] Override target object format
@ -602,6 +604,7 @@ fn buildOutputType(
var use_llvm: ?bool = null;
var use_lld: ?bool = null;
var use_clang: ?bool = null;
var use_stage1: ?bool = null;
var link_eh_frame_hdr = false;
var link_emit_relocs = false;
var each_lib_rpath: ?bool = null;
@ -975,6 +978,10 @@ fn buildOutputType(
use_clang = true;
} else if (mem.eql(u8, arg, "-fno-Clang")) {
use_clang = false;
} else if (mem.eql(u8, arg, "-fstage1")) {
use_stage1 = true;
} else if (mem.eql(u8, arg, "-fno-stage1")) {
use_stage1 = false;
} else if (mem.eql(u8, arg, "-rdynamic")) {
rdynamic = true;
} else if (mem.eql(u8, arg, "-fsoname")) {
@ -2020,6 +2027,7 @@ fn buildOutputType(
.use_llvm = use_llvm,
.use_lld = use_lld,
.use_clang = use_clang,
.use_stage1 = use_stage1,
.rdynamic = rdynamic,
.linker_script = linker_script,
.version_script = version_script,

View File

@ -7,6 +7,7 @@ const assert = std.debug.assert;
const mem = std.mem;
const CrossTarget = std.zig.CrossTarget;
const Target = std.Target;
const builtin = @import("builtin");
const build_options = @import("build_options");
const stage2 = @import("main.zig");
@ -16,16 +17,19 @@ const translate_c = @import("translate_c.zig");
const target_util = @import("target.zig");
comptime {
assert(std.builtin.link_libc);
assert(builtin.link_libc);
assert(build_options.is_stage1);
assert(build_options.have_llvm);
_ = @import("compiler_rt");
if (!builtin.is_test) {
_ = @import("compiler_rt");
@export(main, .{ .name = "main" });
}
}
pub const log = stage2.log;
pub const log_level = stage2.log_level;
pub export fn main(argc: c_int, argv: [*][*:0]u8) c_int {
pub fn main(argc: c_int, argv: [*][*:0]u8) callconv(.C) c_int {
std.os.argv = argv[0..@intCast(usize, argc)];
std.debug.maybeEnableSegfaultHandler();
@ -41,7 +45,7 @@ pub export fn main(argc: c_int, argv: [*][*:0]u8) c_int {
for (args) |*arg, i| {
arg.* = mem.spanZ(argv[i]);
}
if (std.builtin.mode == .Debug) {
if (builtin.mode == .Debug) {
stage2.mainArgs(gpa, arena, args) catch unreachable;
} else {
stage2.mainArgs(gpa, arena, args) catch |err| fatal("{s}", .{@errorName(err)});
@ -147,6 +151,7 @@ pub const Module = extern struct {
}
};
pub const os_init = zig_stage1_os_init;
extern fn zig_stage1_os_init() void;
pub const create = zig_stage1_create;

View File

@ -170,6 +170,73 @@ pub fn hasValgrindSupport(target: std.Target) bool {
}
}
/// The set of targets that LLVM has non-experimental support for.
/// Used to select between LLVM backend and self-hosted backend when compiling in
/// release modes.
pub fn hasLlvmSupport(target: std.Target) bool {
return switch (target.cpu.arch) {
.arm,
.armeb,
.aarch64,
.aarch64_be,
.aarch64_32,
.arc,
.avr,
.bpfel,
.bpfeb,
.csky,
.hexagon,
.mips,
.mipsel,
.mips64,
.mips64el,
.msp430,
.powerpc,
.powerpcle,
.powerpc64,
.powerpc64le,
.r600,
.amdgcn,
.riscv32,
.riscv64,
.sparc,
.sparcv9,
.sparcel,
.s390x,
.tce,
.tcele,
.thumb,
.thumbeb,
.i386,
.x86_64,
.xcore,
.nvptx,
.nvptx64,
.le32,
.le64,
.amdil,
.amdil64,
.hsail,
.hsail64,
.spir,
.spir64,
.kalimba,
.shave,
.lanai,
.wasm32,
.wasm64,
.renderscript32,
.renderscript64,
.ve,
=> true,
.spu_2,
.spirv32,
.spirv64,
=> false,
};
}
pub fn supportsStackProbing(target: std.Target) bool {
return target.os.tag != .windows and target.os.tag != .uefi and
(target.cpu.arch == .i386 or target.cpu.arch == .x86_64);

View File

@ -10,18 +10,25 @@ const enable_wine: bool = build_options.enable_wine;
const enable_wasmtime: bool = build_options.enable_wasmtime;
const enable_darling: bool = build_options.enable_darling;
const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_dir;
const skip_compile_errors = build_options.skip_compile_errors;
const ThreadPool = @import("ThreadPool.zig");
const CrossTarget = std.zig.CrossTarget;
const print = std.debug.print;
const assert = std.debug.assert;
const zig_h = link.File.C.zig_h;
const hr = "=" ** 80;
test "self-hosted" {
test {
if (build_options.is_stage1) {
@import("stage1.zig").os_init();
}
var ctx = TestContext.init();
defer ctx.deinit();
try @import("stage2_tests").addCases(&ctx);
try @import("test_cases").addCases(&ctx);
try ctx.run();
}
@ -83,14 +90,13 @@ const ErrorMsg = union(enum) {
});
},
.plain => |plain| {
return writer.print("{s}: {s}", .{ plain.msg, @tagName(plain.kind) });
return writer.print("{s}: {s}", .{ @tagName(plain.kind), plain.msg });
},
}
}
};
pub const TestContext = struct {
/// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases)
cases: std.ArrayList(Case),
pub const Update = struct {
@ -127,6 +133,12 @@ pub const TestContext = struct {
path: []const u8,
};
pub const Backend = enum {
stage1,
stage2,
llvm,
};
/// A `Case` consists of a list of `Update`. The same `Compilation` is used for each
/// update, so each update's source is treated as a single file being
/// updated by the test harness and incrementally compiled.
@ -140,13 +152,20 @@ pub const TestContext = struct {
/// In order to be able to run e.g. Execution updates, this must be set
/// to Executable.
output_mode: std.builtin.OutputMode,
optimize_mode: std.builtin.Mode = .Debug,
updates: std.ArrayList(Update),
object_format: ?std.Target.ObjectFormat = null,
emit_h: bool = false,
llvm_backend: bool = false,
is_test: bool = false,
expect_exact: bool = false,
backend: Backend = .stage2,
files: std.ArrayList(File),
pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void {
case.files.append(.{ .path = name, .src = src }) catch @panic("out of memory");
}
/// Adds a subcase in which the module is updated with `src`, and a C
/// header is generated.
pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
@ -254,11 +273,6 @@ pub const TestContext = struct {
return ctx.addExe(name, target);
}
/// Adds a test case for ZIR input, producing an executable
pub fn exeZIR(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
return ctx.addExe(name, target, .ZIR);
}
pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
const prefixed_name = std.fmt.allocPrint(ctx.cases.allocator, "CBE: {s}", .{name}) catch
@panic("out of memory");
@ -282,7 +296,7 @@ pub const TestContext = struct {
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Exe,
.files = std.ArrayList(File).init(ctx.cases.allocator),
.llvm_backend = true,
.backend = .llvm,
}) catch @panic("out of memory");
return &ctx.cases.items[ctx.cases.items.len - 1];
}
@ -302,6 +316,22 @@ pub const TestContext = struct {
return &ctx.cases.items[ctx.cases.items.len - 1];
}
pub fn addTest(
ctx: *TestContext,
name: []const u8,
target: CrossTarget,
) *Case {
ctx.cases.append(Case{
.name = name,
.target = target,
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Exe,
.is_test = true,
.files = std.ArrayList(File).init(ctx.cases.allocator),
}) catch @panic("out of memory");
return &ctx.cases.items[ctx.cases.items.len - 1];
}
/// Adds a test case for Zig input, producing an object file.
pub fn obj(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
return ctx.addObj(name, target);
@ -333,6 +363,45 @@ pub const TestContext = struct {
ctx.addC(name, target).addHeader(src, zig_h ++ out);
}
pub fn objErrStage1(
ctx: *TestContext,
name: []const u8,
src: [:0]const u8,
expected_errors: []const []const u8,
) void {
if (skip_compile_errors) return;
const case = ctx.addObj(name, .{});
case.backend = .stage1;
case.addError(src, expected_errors);
}
pub fn testErrStage1(
ctx: *TestContext,
name: []const u8,
src: [:0]const u8,
expected_errors: []const []const u8,
) void {
if (skip_compile_errors) return;
const case = ctx.addTest(name, .{});
case.backend = .stage1;
case.addError(src, expected_errors);
}
pub fn exeErrStage1(
ctx: *TestContext,
name: []const u8,
src: [:0]const u8,
expected_errors: []const []const u8,
) void {
if (skip_compile_errors) return;
const case = ctx.addExe(name, .{});
case.backend = .stage1;
case.addError(src, expected_errors);
}
pub fn addCompareOutput(
ctx: *TestContext,
name: []const u8,
@ -386,18 +455,6 @@ pub const TestContext = struct {
ctx.addTransform(name, target, src, result);
}
/// Adds a test case that cleans up the ZIR source given in `src`, and
/// tests the resulting ZIR against `result`
pub fn transformZIR(
ctx: *TestContext,
name: []const u8,
target: CrossTarget,
src: [:0]const u8,
result: [:0]const u8,
) void {
ctx.addTransform(name, target, .ZIR, src, result);
}
pub fn addError(
ctx: *TestContext,
name: []const u8,
@ -555,7 +612,7 @@ pub const TestContext = struct {
continue;
// Skip tests that require LLVM backend when it is not available
if (!build_options.have_llvm and case.llvm_backend)
if (!build_options.have_llvm and case.backend == .llvm)
continue;
var prg_node = root_node.start(case.name, case.updates.items.len);
@ -567,7 +624,7 @@ pub const TestContext = struct {
progress.initial_delay_ns = 0;
progress.refresh_rate_ns = 0;
self.runOneCase(
runOneCase(
std.testing.allocator,
&prg_node,
case,
@ -576,17 +633,16 @@ pub const TestContext = struct {
global_cache_directory,
) catch |err| {
fail_count += 1;
std.debug.print("test '{s}' failed: {s}\n\n", .{ case.name, @errorName(err) });
print("test '{s}' failed: {s}\n\n", .{ case.name, @errorName(err) });
};
}
if (fail_count != 0) {
std.debug.print("{d} tests failed\n", .{fail_count});
print("{d} tests failed\n", .{fail_count});
return error.TestFailed;
}
}
fn runOneCase(
self: *TestContext,
allocator: *Allocator,
root_node: *std.Progress.Node,
case: Case,
@ -594,7 +650,6 @@ pub const TestContext = struct {
thread_pool: *ThreadPool,
global_cache_directory: Compilation.Directory,
) !void {
_ = self;
const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);
const target = target_info.target;
@ -607,14 +662,137 @@ pub const TestContext = struct {
var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});
defer cache_dir.close();
const tmp_dir_path = try std.fs.path.join(arena, &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path });
const tmp_dir_path = try std.fs.path.join(
arena,
&[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path },
);
const local_cache_path = try std.fs.path.join(
arena,
&[_][]const u8{ tmp_dir_path, "zig-cache" },
);
for (case.files.items) |file| {
try tmp.dir.writeFile(file.path, file.src);
}
if (case.backend == .stage1) {
// stage1 backend has limitations:
// * leaks memory
// * calls exit() when a compile error happens
// * cannot handle updates
// because of this we must spawn a child process rather than
// using Compilation directly.
assert(case.updates.items.len == 1);
const update = case.updates.items[0];
try tmp.dir.writeFile(tmp_src_path, update.src);
var zig_args = std.ArrayList([]const u8).init(arena);
try zig_args.append(std.testing.zig_exe_path);
if (case.is_test) {
try zig_args.append("test");
} else switch (case.output_mode) {
.Obj => try zig_args.append("build-obj"),
.Exe => try zig_args.append("build-exe"),
.Lib => try zig_args.append("build-lib"),
}
try zig_args.append(try std.fs.path.join(arena, &.{ tmp_dir_path, tmp_src_path }));
try zig_args.append("--name");
try zig_args.append("test");
try zig_args.append("--cache-dir");
try zig_args.append(local_cache_path);
try zig_args.append("--global-cache-dir");
try zig_args.append(global_cache_directory.path orelse ".");
if (!case.target.isNative()) {
try zig_args.append("-target");
try zig_args.append(try target.zigTriple(arena));
}
try zig_args.append("-O");
try zig_args.append(@tagName(case.optimize_mode));
const result = try std.ChildProcess.exec(.{
.allocator = arena,
.argv = zig_args.items,
});
switch (update.case) {
.Error => |case_error_list| {
switch (result.term) {
.Exited => |code| {
if (code == 0) {
dumpArgs(zig_args.items);
return error.CompilationIncorrectlySucceeded;
}
},
else => {
dumpArgs(zig_args.items);
return error.CompilationCrashed;
},
}
var ok = true;
if (case.expect_exact) {
var err_iter = ErrLineIter.init(result.stderr);
var i: usize = 0;
ok = while (err_iter.next()) |line| : (i += 1) {
if (i >= case_error_list.len) break false;
const expected = try std.fmt.allocPrint(arena, "{s}", .{case_error_list[i]});
if (std.mem.indexOf(u8, line, expected) == null) break false;
continue;
} else true;
ok = ok and i == case_error_list.len;
if (!ok) {
print("\n======== Expected these compile errors: ========\n", .{});
for (case_error_list) |msg| {
const expected = try std.fmt.allocPrint(arena, "{s}", .{msg});
print("{s}\n", .{expected});
}
}
} else {
for (case_error_list) |msg| {
const expected = try std.fmt.allocPrint(arena, "{s}", .{msg});
if (std.mem.indexOf(u8, result.stderr, expected) == null) {
print(
\\
\\=========== Expected compile error: ============
\\{s}
\\
, .{expected});
ok = false;
break;
}
}
}
if (!ok) {
print(
\\================= Full output: =================
\\{s}
\\================================================
\\
, .{result.stderr});
return error.TestFailed;
}
},
.CompareObjectFile => @panic("TODO implement in the test harness"),
.Execution => @panic("TODO implement in the test harness"),
.Header => @panic("TODO implement in the test harness"),
}
return;
}
const zig_cache_directory: Compilation.Directory = .{
.handle = cache_dir,
.path = try std.fs.path.join(arena, &[_][]const u8{ tmp_dir_path, "zig-cache" }),
.path = local_cache_path,
};
const tmp_src_path = "test_case.zig";
var root_pkg: Package = .{
.root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir },
.root_src_path = tmp_src_path,
@ -640,6 +818,14 @@ pub const TestContext = struct {
.directory = emit_directory,
.basename = "test_case.h",
} else null;
const use_llvm: ?bool = switch (case.backend) {
.llvm => true,
else => null,
};
const use_stage1: ?bool = switch (case.backend) {
.stage1 => true,
else => null,
};
const comp = try Compilation.create(allocator, .{
.local_cache_directory = zig_cache_directory,
.global_cache_directory = global_cache_directory,
@ -651,8 +837,8 @@ pub const TestContext = struct {
// and linking. This will require a rework to support multi-file
// tests.
.output_mode = case.output_mode,
// TODO: support testing optimizations
.optimize_mode = .Debug,
.is_test = case.is_test,
.optimize_mode = case.optimize_mode,
.emit_bin = emit_bin,
.emit_h = emit_h,
.root_pkg = &root_pkg,
@ -661,17 +847,13 @@ pub const TestContext = struct {
.is_native_os = case.target.isNativeOs(),
.is_native_abi = case.target.isNativeAbi(),
.dynamic_linker = target_info.dynamic_linker.get(),
.link_libc = case.llvm_backend,
.use_llvm = case.llvm_backend,
.use_lld = case.llvm_backend,
.link_libc = case.backend == .llvm,
.use_llvm = use_llvm,
.use_stage1 = use_stage1,
.self_exe_path = std.testing.zig_exe_path,
});
defer comp.destroy();
for (case.files.items) |file| {
try tmp.dir.writeFile(file.path, file.src);
}
for (case.updates.items) |update, update_index| {
var update_node = root_node.start("update", 3);
update_node.activate();
@ -692,19 +874,19 @@ pub const TestContext = struct {
var all_errors = try comp.getAllErrorsAlloc();
defer all_errors.deinit(allocator);
if (all_errors.list.len != 0) {
std.debug.print(
print(
"\nCase '{s}': unexpected errors at update_index={d}:\n{s}\n",
.{ case.name, update_index, hr },
);
for (all_errors.list) |err_msg| {
switch (err_msg) {
.src => |src| {
std.debug.print("{s}:{d}:{d}: error: {s}\n{s}\n", .{
print("{s}:{d}:{d}: error: {s}\n{s}\n", .{
src.src_path, src.line + 1, src.column + 1, src.msg, hr,
});
},
.plain => |plain| {
std.debug.print("error: {s}\n{s}\n", .{ plain.msg, hr });
print("error: {s}\n{s}\n", .{ plain.msg, hr });
},
}
}
@ -779,7 +961,7 @@ pub const TestContext = struct {
},
}
} else {
std.debug.print(
print(
"\nUnexpected error:\n{s}\n{}\n{s}",
.{ hr, ErrorMsg.init(actual_error, .@"error"), hr },
);
@ -817,7 +999,7 @@ pub const TestContext = struct {
},
}
} else {
std.debug.print(
print(
"\nUnexpected note:\n{s}\n{}\n{s}",
.{ hr, ErrorMsg.init(note.*, .note), hr },
);
@ -827,7 +1009,7 @@ pub const TestContext = struct {
for (handled_errors) |handled, i| {
if (!handled) {
std.debug.print(
print(
"\nExpected error not found:\n{s}\n{}\n{s}",
.{ hr, case_error_list[i], hr },
);
@ -836,7 +1018,7 @@ pub const TestContext = struct {
}
if (any_failed) {
std.debug.print("\nupdate_index={d} ", .{update_index});
print("\nupdate_index={d} ", .{update_index});
return error.WrongCompileErrors;
}
},
@ -932,7 +1114,7 @@ pub const TestContext = struct {
.cwd_dir = tmp.dir,
.cwd = tmp_dir_path,
}) catch |err| {
std.debug.print("\nupdate_index={d} The following command failed with {s}:\n", .{
print("\nupdate_index={d} The following command failed with {s}:\n", .{
update_index, @errorName(err),
});
dumpArgs(argv.items);
@ -947,7 +1129,7 @@ pub const TestContext = struct {
switch (exec_result.term) {
.Exited => |code| {
if (code != 0) {
std.debug.print("\n{s}\n{s}: execution exited with code {d}:\n", .{
print("\n{s}\n{s}: execution exited with code {d}:\n", .{
exec_result.stderr, case.name, code,
});
dumpArgs(argv.items);
@ -955,7 +1137,7 @@ pub const TestContext = struct {
}
},
else => {
std.debug.print("\n{s}\n{s}: execution crashed:\n", .{
print("\n{s}\n{s}: execution crashed:\n", .{
exec_result.stderr, case.name,
});
dumpArgs(argv.items);
@ -974,7 +1156,25 @@ pub const TestContext = struct {
fn dumpArgs(argv: []const []const u8) void {
for (argv) |arg| {
std.debug.print("{s} ", .{arg});
print("{s} ", .{arg});
}
std.debug.print("\n", .{});
print("\n", .{});
}
const tmp_src_path = "tmp.zig";
const ErrLineIter = struct {
lines: std.mem.SplitIterator,
fn init(input: []const u8) ErrLineIter {
return ErrLineIter{ .lines = std.mem.split(input, "\n") };
}
fn next(self: *ErrLineIter) ?[]const u8 {
while (self.lines.next()) |line| {
if (std.mem.indexOf(u8, line, tmp_src_path) != null)
return line;
}
return null;
}
};

View File

@ -1,5 +1,5 @@
const std = @import("std");
const TestContext = @import("../../src/test.zig").TestContext;
const TestContext = @import("../src/test.zig").TestContext;
// Self-hosted has differing levels of support for various architectures. For now we pass explicit
// target parameters to each test case. At some point we will take this to the next level and have
@ -12,13 +12,14 @@ const linux_x64 = std.zig.CrossTarget{
};
pub fn addCases(ctx: *TestContext) !void {
try @import("cbe.zig").addCases(ctx);
try @import("arm.zig").addCases(ctx);
try @import("aarch64.zig").addCases(ctx);
try @import("llvm.zig").addCases(ctx);
try @import("wasm.zig").addCases(ctx);
try @import("darwin.zig").addCases(ctx);
try @import("riscv64.zig").addCases(ctx);
try @import("compile_errors.zig").addCases(ctx);
try @import("stage2/cbe.zig").addCases(ctx);
try @import("stage2/arm.zig").addCases(ctx);
try @import("stage2/aarch64.zig").addCases(ctx);
try @import("stage2/llvm.zig").addCases(ctx);
try @import("stage2/wasm.zig").addCases(ctx);
try @import("stage2/darwin.zig").addCases(ctx);
try @import("stage2/riscv64.zig").addCases(ctx);
{
var case = ctx.exe("hello world with updates", linux_x64);

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,6 @@ 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");
@ -384,21 +383,6 @@ pub fn addRuntimeSafetyTests(b: *build.Builder, test_filter: ?[]const u8, modes:
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, skip_non_native: bool, target: std.zig.CrossTarget) *build.Step {
const cases = b.allocator.create(StandaloneContext) catch unreachable;
cases.* = StandaloneContext{
@ -840,304 +824,6 @@ pub const StackTracesContext = struct {
};
};
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: CrossTarget = CrossTarget{},
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 {
pub const base_id = .custom;
step: build.Step,
context: *CompileErrorContext,
name: []const u8,
test_index: usize,
case: *const TestCase,
build_mode: Mode,
write_src: *build.WriteFileStep,
const ErrLineIter = struct {
lines: mem.SplitIterator,
const source_file = "tmp.zig";
fn init(input: []const u8) ErrLineIter {
return ErrLineIter{ .lines = mem.split(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,
write_src: *build.WriteFileStep,
) *CompileCmpOutputStep {
const allocator = context.b.allocator;
const ptr = allocator.create(CompileCmpOutputStep) catch unreachable;
ptr.* = CompileCmpOutputStep{
.step = build.Step.init(.custom, "CompileCmpOutput", allocator, make),
.context = context,
.name = name,
.test_index = context.test_index,
.case = case,
.build_mode = build_mode,
.write_src = write_src,
};
context.test_index += 1;
return ptr;
}
fn make(step: *build.Step) !void {
const self = @fieldParentPtr(CompileCmpOutputStep, "step", step);
const b = self.context.b;
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");
}
const root_src_basename = self.case.sources.items[0].filename;
try zig_args.append(self.write_src.getFileSource(root_src_basename).?.getPath(b));
zig_args.append("--name") catch unreachable;
zig_args.append("test") catch unreachable;
if (!self.case.target.isNative()) {
try zig_args.append("-target");
try zig_args.append(try self.case.target.zigTriple(b.allocator));
}
zig_args.append("-O") catch unreachable;
zig_args.append(@tagName(self.build_mode)) catch unreachable;
warn("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name });
if (b.verbose) {
printInvocation(zig_args.items);
}
const child = std.ChildProcess.init(zig_args.items, 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 {s}: {s}\n", .{ zig_args.items[0], @errorName(err) });
var stdout_buf = ArrayList(u8).init(b.allocator);
var stderr_buf = ArrayList(u8).init(b.allocator);
child.stdout.?.reader().readAllArrayList(&stdout_buf, max_stdout_size) catch unreachable;
child.stderr.?.reader().readAllArrayList(&stderr_buf, max_stdout_size) catch unreachable;
const term = child.wait() catch |err| {
debug.panic("Unable to spawn {s}: {s}\n", .{ zig_args.items[0], @errorName(err) });
};
switch (term) {
.Exited => |code| {
if (code == 0) {
printInvocation(zig_args.items);
return error.CompilationIncorrectlySucceeded;
}
},
else => {
warn("Process {s} terminated unexpectedly\n", .{b.zig_exe});
printInvocation(zig_args.items);
return error.TestFailed;
},
}
const stdout = stdout_buf.items;
const stderr = stderr_buf.items;
if (stdout.len != 0) {
warn(
\\
\\Expected empty stdout, instead found:
\\================================================
\\{s}
\\================================================
\\
, .{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.items.len) break false;
const expected = self.case.expected_errors.items[i];
if (mem.indexOf(u8, line, expected) == null) break false;
continue;
} else true;
ok = ok and i == self.case.expected_errors.items.len;
if (!ok) {
warn("\n======== Expected these compile errors: ========\n", .{});
for (self.case.expected_errors.items) |expected| {
warn("{s}\n", .{expected});
}
}
} else {
for (self.case.expected_errors.items) |expected| {
if (mem.indexOf(u8, stderr, expected) == null) {
warn(
\\
\\=========== Expected compile error: ============
\\{s}
\\
, .{expected});
ok = false;
break;
}
}
}
if (!ok) {
warn(
\\================= Full output: =================
\\{s}
\\
, .{stderr});
return error.TestFailed;
}
warn("OK\n", .{});
}
};
pub fn create(
self: *CompileErrorContext,
name: []const u8,
source: []const u8,
expected_lines: []const []const u8,
) *TestCase {
const tc = self.b.allocator.create(TestCase) catch unreachable;
tc.* = TestCase{
.name = name,
.sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
.expected_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);
var arg_i: usize = 0;
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: []const []const u8) 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: []const []const u8,
) 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: []const []const u8,
) 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: []const []const u8,
) 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 {s}", .{
case.name,
}) catch unreachable;
if (self.test_filter) |filter| {
if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
}
const write_src = b.addWriteFiles();
for (case.sources.items) |src_file| {
write_src.add(src_file.filename, src_file.source);
}
const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, .Debug, write_src);
compile_and_cmp_errors.step.dependOn(&write_src.step);
self.step.dependOn(&compile_and_cmp_errors.step);
}
};
pub const StandaloneContext = struct {
b: *build.Builder,
step: *build.Step,
@ -1312,13 +998,6 @@ pub const GenHContext = struct {
}
};
fn printInvocation(args: []const []const u8) void {
for (args) |arg| {
warn("{s} ", .{arg});
}
warn("\n", .{});
}
pub fn create(
self: *GenHContext,
filename: []const u8,