tests: translate-c and run-translated-c to the test harness
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
gpa: Allocator,
|
||||
arena: Allocator,
|
||||
cases: std.ArrayList(Case),
|
||||
translate: std.ArrayList(Translate),
|
||||
incremental_cases: std.ArrayList(IncrementalCase),
|
||||
|
||||
pub const IncrementalCase = struct {
|
||||
@@ -36,7 +37,7 @@ pub const Update = struct {
|
||||
Execution: []const u8,
|
||||
/// A header update compiles the input with the equivalent of
|
||||
/// `-femit-h` and tests the produced header against the
|
||||
/// expected result
|
||||
/// expected result.
|
||||
Header: []const u8,
|
||||
},
|
||||
|
||||
@@ -61,6 +62,11 @@ pub const Backend = enum {
|
||||
llvm,
|
||||
};
|
||||
|
||||
pub const CFrontend = enum {
|
||||
clang,
|
||||
aro,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
@@ -143,6 +149,25 @@ pub const Case = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Translate = struct {
|
||||
/// The name of the test case. This is shown if a test fails, and
|
||||
/// otherwise ignored.
|
||||
name: []const u8,
|
||||
|
||||
input: [:0]const u8,
|
||||
target: CrossTarget,
|
||||
link_libc: bool,
|
||||
c_frontend: CFrontend,
|
||||
kind: union(enum) {
|
||||
/// Translate the input, run it and check that it
|
||||
/// outputs the expected text.
|
||||
run: []const u8,
|
||||
/// Translate the input and check that it contains
|
||||
/// the expected lines of code.
|
||||
translate: []const []const u8,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn addExe(
|
||||
ctx: *Cases,
|
||||
name: []const u8,
|
||||
@@ -346,9 +371,12 @@ pub fn addCompile(
|
||||
pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void {
|
||||
var current_file: []const u8 = "none";
|
||||
ctx.addFromDirInner(dir, ¤t_file) catch |err| {
|
||||
std.debug.panic("test harness failed to process file '{s}': {s}\n", .{
|
||||
current_file, @errorName(err),
|
||||
});
|
||||
std.debug.panicExtra(
|
||||
@errorReturnTrace(),
|
||||
@returnAddress(),
|
||||
"test harness failed to process file '{s}': {s}\n",
|
||||
.{ current_file, @errorName(err) },
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -395,10 +423,44 @@ fn addFromDirInner(
|
||||
|
||||
const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend);
|
||||
const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget);
|
||||
const c_frontends = try manifest.getConfigForKeyAlloc(ctx.arena, "c_frontend", CFrontend);
|
||||
const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
|
||||
const link_libc = try manifest.getConfigForKeyAssertSingle("link_libc", bool);
|
||||
const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
|
||||
|
||||
if (manifest.type == .translate_c) {
|
||||
for (c_frontends) |c_frontend| {
|
||||
for (targets) |target| {
|
||||
const output = try manifest.trailingLinesSplit(ctx.arena);
|
||||
try ctx.translate.append(.{
|
||||
.name = std.fs.path.stem(filename),
|
||||
.c_frontend = c_frontend,
|
||||
.target = target,
|
||||
.link_libc = link_libc,
|
||||
.input = src,
|
||||
.kind = .{ .translate = output },
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (manifest.type == .run_translated_c) {
|
||||
for (c_frontends) |c_frontend| {
|
||||
for (targets) |target| {
|
||||
const output = try manifest.trailingSplit(ctx.arena);
|
||||
try ctx.translate.append(.{
|
||||
.name = std.fs.path.stem(filename),
|
||||
.c_frontend = c_frontend,
|
||||
.target = target,
|
||||
.link_libc = link_libc,
|
||||
.input = src,
|
||||
.kind = .{ .run = output },
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var cases = std.ArrayList(usize).init(ctx.arena);
|
||||
|
||||
// Cross-product to get all possible test combinations
|
||||
@@ -439,21 +501,15 @@ fn addFromDirInner(
|
||||
case.addCompile(src);
|
||||
},
|
||||
.@"error" => {
|
||||
const errors = try manifest.trailingAlloc(ctx.arena);
|
||||
const errors = try manifest.trailingLines(ctx.arena);
|
||||
case.addError(src, errors);
|
||||
},
|
||||
.run => {
|
||||
var output = std.ArrayList(u8).init(ctx.arena);
|
||||
var trailing_it = manifest.trailing();
|
||||
while (trailing_it.next()) |line| {
|
||||
try output.appendSlice(line);
|
||||
try output.append('\n');
|
||||
}
|
||||
if (output.items.len > 0) {
|
||||
try output.resize(output.items.len - 1);
|
||||
}
|
||||
case.addCompareOutput(src, try output.toOwnedSlice());
|
||||
const output = try manifest.trailingSplit(ctx.arena);
|
||||
case.addCompareOutput(src, output);
|
||||
},
|
||||
.translate_c => @panic("c_frontend specified for compile case"),
|
||||
.run_translated_c => @panic("c_frontend specified for compile case"),
|
||||
.cli => @panic("TODO cli tests"),
|
||||
}
|
||||
}
|
||||
@@ -468,6 +524,7 @@ pub fn init(gpa: Allocator, arena: Allocator) Cases {
|
||||
return .{
|
||||
.gpa = gpa,
|
||||
.cases = std.ArrayList(Case).init(gpa),
|
||||
.translate = std.ArrayList(Translate).init(gpa),
|
||||
.incremental_cases = std.ArrayList(IncrementalCase).init(gpa),
|
||||
.arena = arena,
|
||||
};
|
||||
@@ -482,7 +539,7 @@ pub fn lowerToBuildSteps(
|
||||
incremental_exe: *std.Build.Step.Compile,
|
||||
) void {
|
||||
const host = std.zig.system.NativeTargetInfo.detect(.{}) catch |err|
|
||||
std.debug.panic("unable to detect notive host: {s}\n", .{@errorName(err)});
|
||||
std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)});
|
||||
|
||||
for (self.incremental_cases.items) |incr_case| {
|
||||
if (true) {
|
||||
@@ -589,7 +646,7 @@ pub fn lowerToBuildSteps(
|
||||
.Execution => |expected_stdout| no_exec: {
|
||||
const run = if (case.target.ofmt == .c) run_step: {
|
||||
const target_info = std.zig.system.NativeTargetInfo.detect(case.target) catch |err|
|
||||
std.debug.panic("unable to detect notive host: {s}\n", .{@errorName(err)});
|
||||
std.debug.panic("unable to detect target host: {s}\n", .{@errorName(err)});
|
||||
if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) {
|
||||
// We wouldn't be able to run the compiled C code.
|
||||
break :no_exec;
|
||||
@@ -623,6 +680,68 @@ pub fn lowerToBuildSteps(
|
||||
.Header => @panic("TODO"),
|
||||
}
|
||||
}
|
||||
|
||||
for (self.translate.items) |*case| switch (case.kind) {
|
||||
.run => |output| {
|
||||
const annotated_case_name = b.fmt("run-translated-c {s}", .{case.name});
|
||||
if (opt_test_filter) |filter| {
|
||||
if (std.mem.indexOf(u8, annotated_case_name, filter) == null) return;
|
||||
}
|
||||
if (!std.process.can_spawn) {
|
||||
std.debug.print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
|
||||
continue; // Pass test.
|
||||
}
|
||||
|
||||
const target_info = std.zig.system.NativeTargetInfo.detect(case.target) catch |err|
|
||||
std.debug.panic("unable to detect target host: {s}\n", .{@errorName(err)});
|
||||
if (host.getExternalExecutor(&target_info, .{ .link_libc = true }) != .native) {
|
||||
// We wouldn't be able to run the compiled C code.
|
||||
continue; // Pass test.
|
||||
}
|
||||
|
||||
const write_src = b.addWriteFiles();
|
||||
const file_source = write_src.add("tmp.c", case.input);
|
||||
|
||||
const translate_c = b.addTranslateC(.{
|
||||
.source_file = file_source,
|
||||
.optimize = .Debug,
|
||||
.target = case.target,
|
||||
.link_libc = case.link_libc,
|
||||
.use_clang = case.c_frontend == .clang,
|
||||
});
|
||||
translate_c.step.name = b.fmt("{s} translate-c", .{annotated_case_name});
|
||||
|
||||
const run_exe = translate_c.addExecutable(.{});
|
||||
run_exe.step.name = b.fmt("{s} build-exe", .{annotated_case_name});
|
||||
run_exe.linkLibC();
|
||||
const run = b.addRunArtifact(run_exe);
|
||||
run.step.name = b.fmt("{s} run", .{annotated_case_name});
|
||||
run.expectStdOutEqual(output);
|
||||
|
||||
parent_step.dependOn(&run.step);
|
||||
},
|
||||
.translate => |output| {
|
||||
const annotated_case_name = b.fmt("zig translate-c {s}", .{case.name});
|
||||
if (opt_test_filter) |filter| {
|
||||
if (std.mem.indexOf(u8, annotated_case_name, filter) == null) return;
|
||||
}
|
||||
|
||||
const write_src = b.addWriteFiles();
|
||||
const file_source = write_src.add("tmp.c", case.input);
|
||||
|
||||
const translate_c = b.addTranslateC(.{
|
||||
.source_file = file_source,
|
||||
.optimize = .Debug,
|
||||
.target = case.target,
|
||||
.link_libc = case.link_libc,
|
||||
.use_clang = case.c_frontend == .clang,
|
||||
});
|
||||
translate_c.step.name = annotated_case_name;
|
||||
|
||||
const check_file = translate_c.addCheckFile(output);
|
||||
parent_step.dependOn(&check_file.step);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig",
|
||||
@@ -780,7 +899,7 @@ const TestManifestConfigDefaults = struct {
|
||||
if (std.mem.eql(u8, key, "backend")) {
|
||||
return "stage2";
|
||||
} else if (std.mem.eql(u8, key, "target")) {
|
||||
if (@"type" == .@"error") {
|
||||
if (@"type" == .@"error" or @"type" == .translate_c or @"type" == .run_translated_c) {
|
||||
return "native";
|
||||
}
|
||||
return comptime blk: {
|
||||
@@ -807,12 +926,16 @@ const TestManifestConfigDefaults = struct {
|
||||
.@"error" => "Obj",
|
||||
.run => "Exe",
|
||||
.compile => "Obj",
|
||||
.translate_c => "Obj",
|
||||
.run_translated_c => "Obj",
|
||||
.cli => @panic("TODO test harness for CLI tests"),
|
||||
};
|
||||
} else if (std.mem.eql(u8, key, "is_test")) {
|
||||
return "0";
|
||||
return "false";
|
||||
} else if (std.mem.eql(u8, key, "link_libc")) {
|
||||
return "0";
|
||||
return "false";
|
||||
} else if (std.mem.eql(u8, key, "c_frontend")) {
|
||||
return "clang";
|
||||
} else unreachable;
|
||||
}
|
||||
};
|
||||
@@ -844,6 +967,8 @@ const TestManifest = struct {
|
||||
run,
|
||||
cli,
|
||||
compile,
|
||||
translate_c,
|
||||
run_translated_c,
|
||||
};
|
||||
|
||||
const TrailingIterator = struct {
|
||||
@@ -912,6 +1037,10 @@ const TestManifest = struct {
|
||||
break :blk .cli;
|
||||
} else if (std.mem.eql(u8, raw, "compile")) {
|
||||
break :blk .compile;
|
||||
} else if (std.mem.eql(u8, raw, "translate-c")) {
|
||||
break :blk .translate_c;
|
||||
} else if (std.mem.eql(u8, raw, "run-translated-c")) {
|
||||
break :blk .run_translated_c;
|
||||
} else {
|
||||
std.log.warn("unknown test case type requested: {s}", .{raw});
|
||||
return error.UnknownTestCaseType;
|
||||
@@ -979,7 +1108,21 @@ const TestManifest = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 {
|
||||
fn trailingSplit(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const u8 {
|
||||
var out = std.ArrayList(u8).init(allocator);
|
||||
defer out.deinit();
|
||||
var trailing_it = self.trailing();
|
||||
while (trailing_it.next()) |line| {
|
||||
try out.appendSlice(line);
|
||||
try out.append('\n');
|
||||
}
|
||||
if (out.items.len > 0) {
|
||||
try out.resize(out.items.len - 1);
|
||||
}
|
||||
return try out.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn trailingLines(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 {
|
||||
var out = std.ArrayList([]const u8).init(allocator);
|
||||
defer out.deinit();
|
||||
var it = self.trailing();
|
||||
@@ -989,6 +1132,28 @@ const TestManifest = struct {
|
||||
return try out.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn trailingLinesSplit(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 {
|
||||
// Collect output lines split by empty lines
|
||||
var out = std.ArrayList([]const u8).init(allocator);
|
||||
defer out.deinit();
|
||||
var buf = std.ArrayList(u8).init(allocator);
|
||||
defer buf.deinit();
|
||||
var it = self.trailing();
|
||||
while (it.next()) |line| {
|
||||
if (line.len == 0) {
|
||||
if (buf.items.len != 0) {
|
||||
try out.append(try buf.toOwnedSlice());
|
||||
buf.items.len = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try buf.appendSlice(line);
|
||||
try buf.append('\n');
|
||||
}
|
||||
try out.append(try buf.toOwnedSlice());
|
||||
return try out.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn ParseFn(comptime T: type) type {
|
||||
return fn ([]const u8) anyerror!T;
|
||||
}
|
||||
@@ -1011,8 +1176,10 @@ const TestManifest = struct {
|
||||
}.parse,
|
||||
.Bool => return struct {
|
||||
fn parse(str: []const u8) anyerror!T {
|
||||
const as_int = try std.fmt.parseInt(u1, str, 0);
|
||||
return as_int > 0;
|
||||
if (std.mem.eql(u8, str, "true")) return true;
|
||||
if (std.mem.eql(u8, str, "false")) return false;
|
||||
std.debug.print("{s}\n", .{str});
|
||||
return error.InvalidBool;
|
||||
}
|
||||
}.parse,
|
||||
.Enum => return struct {
|
||||
@@ -1124,9 +1291,47 @@ pub fn main() !void {
|
||||
if (cases.items.len == 0) {
|
||||
const backends = try manifest.getConfigForKeyAlloc(arena, "backend", Backend);
|
||||
const targets = try manifest.getConfigForKeyAlloc(arena, "target", CrossTarget);
|
||||
const c_frontends = try manifest.getConfigForKeyAlloc(ctx.arena, "c_frontend", CFrontend);
|
||||
const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
|
||||
const link_libc = try manifest.getConfigForKeyAssertSingle("link_libc", bool);
|
||||
const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
|
||||
|
||||
if (manifest.type == .translate_c) {
|
||||
for (c_frontends) |c_frontend| {
|
||||
for (targets) |target| {
|
||||
const output = try manifest.trailingLinesSplit(ctx.arena);
|
||||
try ctx.translate.append(.{
|
||||
.name = std.fs.path.stem(filename),
|
||||
.c_frontend = c_frontend,
|
||||
.target = target,
|
||||
.is_test = is_test,
|
||||
.link_libc = link_libc,
|
||||
.input = src,
|
||||
.kind = .{ .translate = output },
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (manifest.type == .run_translated_c) {
|
||||
for (c_frontends) |c_frontend| {
|
||||
for (targets) |target| {
|
||||
const output = try manifest.trailingSplit(ctx.arena);
|
||||
try ctx.translate.append(.{
|
||||
.name = std.fs.path.stem(filename),
|
||||
.c_frontend = c_frontend,
|
||||
.target = target,
|
||||
.is_test = is_test,
|
||||
.link_libc = link_libc,
|
||||
.output = output,
|
||||
.input = src,
|
||||
.kind = .{ .run = output },
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cross-product to get all possible test combinations
|
||||
for (backends) |backend| {
|
||||
for (targets) |target| {
|
||||
@@ -1158,7 +1363,7 @@ pub fn main() !void {
|
||||
case.addCompile(src);
|
||||
},
|
||||
.@"error" => {
|
||||
const errors = try manifest.trailingAlloc(arena);
|
||||
const errors = try manifest.trailingLines(arena);
|
||||
switch (strategy) {
|
||||
.independent => {
|
||||
case.addError(src, errors);
|
||||
@@ -1169,17 +1374,11 @@ pub fn main() !void {
|
||||
}
|
||||
},
|
||||
.run => {
|
||||
var output = std.ArrayList(u8).init(arena);
|
||||
var trailing_it = manifest.trailing();
|
||||
while (trailing_it.next()) |line| {
|
||||
try output.appendSlice(line);
|
||||
try output.append('\n');
|
||||
}
|
||||
if (output.items.len > 0) {
|
||||
try output.resize(output.items.len - 1);
|
||||
}
|
||||
case.addCompareOutput(src, try output.toOwnedSlice());
|
||||
const output = try manifest.trailingSplit(ctx.arena);
|
||||
case.addCompareOutput(src, output);
|
||||
},
|
||||
.translate_c => @panic("c_frontend specified for compile case"),
|
||||
.run_translated_c => @panic("c_frontend specified for compile case"),
|
||||
.cli => @panic("TODO cli tests"),
|
||||
}
|
||||
}
|
||||
@@ -1255,6 +1454,11 @@ fn runCases(self: *Cases, zig_exe_path: []const u8) !void {
|
||||
host,
|
||||
);
|
||||
}
|
||||
|
||||
for (self.translate.items) |*case| {
|
||||
_ = case;
|
||||
@panic("TODO is this even used?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user