Merge pull request #17706 from ziglang/elf-error-tests

elf: test error generation
This commit is contained in:
Jakub Konka 2023-10-25 23:20:12 +02:00 committed by GitHub
commit 10ea7accf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 198 additions and 36 deletions

View File

@ -415,7 +415,7 @@ pub fn evalZigProcess(
.Exited => {
// Note that the exit code may be 0 in this case due to the
// compiler server protocol.
if (compile.expect_errors.len != 0 and s.result_error_bundle.errorMessageCount() > 0) {
if (compile.expect_errors != null and s.result_error_bundle.errorMessageCount() > 0) {
return error.NeedCompileErrorCheck;
}
},

View File

@ -204,10 +204,10 @@ use_llvm: ?bool,
use_lld: ?bool,
/// This is an advanced setting that can change the intent of this Compile step.
/// If this slice has nonzero length, it means that this Compile step exists to
/// If this value is non-null, it means that this Compile step exists to
/// check for compile errors and return *success* if they match, and failure
/// otherwise.
expect_errors: []const []const u8 = &.{},
expect_errors: ?ExpectedCompileErrors = null,
emit_directory: ?*GeneratedFile,
@ -220,6 +220,11 @@ generated_llvm_bc: ?*GeneratedFile,
generated_llvm_ir: ?*GeneratedFile,
generated_h: ?*GeneratedFile,
pub const ExpectedCompileErrors = union(enum) {
contains: []const u8,
exact: []const []const u8,
};
pub const CSourceFiles = struct {
dependency: ?*std.Build.Dependency,
/// If `dependency` is not null relative to it,
@ -2131,7 +2136,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const maybe_output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) {
error.NeedCompileErrorCheck => {
assert(self.expect_errors.len != 0);
assert(self.expect_errors != null);
try checkCompileErrors(self);
return;
},
@ -2390,39 +2395,70 @@ fn checkCompileErrors(self: *Compile) !void {
// Render the expected lines into a string that we can compare verbatim.
var expected_generated = std.ArrayList(u8).init(arena);
const expect_errors = self.expect_errors.?;
var actual_line_it = mem.splitScalar(u8, actual_stderr, '\n');
for (self.expect_errors) |expect_line| {
const actual_line = actual_line_it.next() orelse {
try expected_generated.appendSlice(expect_line);
try expected_generated.append('\n');
continue;
};
if (mem.endsWith(u8, actual_line, expect_line)) {
try expected_generated.appendSlice(actual_line);
try expected_generated.append('\n');
continue;
}
if (mem.startsWith(u8, expect_line, ":?:?: ")) {
if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) {
try expected_generated.appendSlice(actual_line);
try expected_generated.append('\n');
continue;
}
}
try expected_generated.appendSlice(expect_line);
try expected_generated.append('\n');
}
if (mem.eql(u8, expected_generated.items, actual_stderr)) return;
// TODO merge this with the testing.expectEqualStrings logic, and also CheckFile
return self.step.fail(
\\
\\========= expected: =====================
\\{s}
\\========= but found: ====================
\\{s}
\\=========================================
, .{ expected_generated.items, actual_stderr });
switch (expect_errors) {
.contains => |expect_line| {
while (actual_line_it.next()) |actual_line| {
if (!matchCompileError(actual_line, expect_line)) continue;
return;
}
return self.step.fail(
\\
\\========= should contain: ===============
\\{s}
\\========= but not found: ================
\\{s}
\\=========================================
, .{ expect_line, actual_stderr });
},
.exact => |expect_lines| {
for (expect_lines) |expect_line| {
const actual_line = actual_line_it.next() orelse {
try expected_generated.appendSlice(expect_line);
try expected_generated.append('\n');
continue;
};
if (matchCompileError(actual_line, expect_line)) {
try expected_generated.appendSlice(actual_line);
try expected_generated.append('\n');
continue;
}
try expected_generated.appendSlice(expect_line);
try expected_generated.append('\n');
}
if (mem.eql(u8, expected_generated.items, actual_stderr)) return;
return self.step.fail(
\\
\\========= expected: =====================
\\{s}
\\========= but found: ====================
\\{s}
\\=========================================
, .{ expected_generated.items, actual_stderr });
},
}
}
fn matchCompileError(actual: []const u8, expected: []const u8) bool {
if (mem.endsWith(u8, actual, expected)) return true;
if (mem.startsWith(u8, expected, ":?:?: ")) {
if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true;
}
// We scan for /?/ in expected line and if there is a match, we match everything
// up to and after /?/.
const expected_trim = mem.trim(u8, expected, " ");
if (mem.indexOf(u8, expected_trim, "/?/")) |index| {
const actual_trim = mem.trim(u8, actual, " ");
const lhs = expected_trim[0..index];
const rhs = expected_trim[index + "/?/".len ..];
if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true;
}
return false;
}

View File

@ -76,6 +76,8 @@ pub fn build(b: *Build) void {
elf_step.dependOn(testLargeBss(b, .{ .target = glibc_target }));
elf_step.dependOn(testLinkOrder(b, .{ .target = glibc_target }));
elf_step.dependOn(testLdScript(b, .{ .target = glibc_target }));
elf_step.dependOn(testLdScriptPathError(b, .{ .target = glibc_target }));
elf_step.dependOn(testMismatchedCpuArchitectureError(b, .{ .target = glibc_target }));
// https://github.com/ziglang/zig/issues/17451
// elf_step.dependOn(testNoEhFrameHdr(b, .{ .target = glibc_target }));
elf_step.dependOn(testPie(b, .{ .target = glibc_target }));
@ -99,6 +101,8 @@ pub fn build(b: *Build) void {
elf_step.dependOn(testTlsOffsetAlignment(b, .{ .target = glibc_target }));
elf_step.dependOn(testTlsPic(b, .{ .target = glibc_target }));
elf_step.dependOn(testTlsSmallAlignment(b, .{ .target = glibc_target }));
elf_step.dependOn(testUnknownFileTypeError(b, .{ .target = glibc_target }));
elf_step.dependOn(testUnresolvedError(b, .{ .target = glibc_target }));
elf_step.dependOn(testWeakExports(b, .{ .target = glibc_target }));
elf_step.dependOn(testWeakUndefsDso(b, .{ .target = glibc_target }));
elf_step.dependOn(testZNow(b, .{ .target = glibc_target }));
@ -1601,6 +1605,56 @@ fn testLdScript(b: *Build, opts: Options) *Step {
return test_step;
}
fn testLdScriptPathError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script-path-error", opts);
const scripts = WriteFile.create(b);
_ = scripts.add("liba.so", "INPUT(libfoo.so)");
const exe = addExecutable(b, "main", opts);
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.linkSystemLibrary2("a", .{});
exe.addLibraryPath(scripts.getDirectory());
exe.linkLibC();
expectLinkErrors(
exe,
test_step,
.{
.contains = "error: missing library dependency: GNU ld script '/?/liba.so' requires 'libfoo.so', but file not found",
},
);
return test_step;
}
fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "mismatched-cpu-architecture-error", opts);
const obj = addObject(b, "a", .{
.target = .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu },
});
addCSourceBytes(obj, "int foo;", &.{});
obj.strip = true;
const exe = addExecutable(b, "main", opts);
addCSourceBytes(exe,
\\extern int foo;
\\int main() {
\\ return foo;
\\}
, &.{});
exe.addObject(obj);
exe.linkLibC();
expectLinkErrors(exe, test_step, .{ .exact = &.{
"invalid cpu architecture: expected 'x86_64', but found 'aarch64'",
"note: while parsing /?/a.o",
} });
return test_step;
}
fn testLinkingC(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-c", opts);
@ -2783,6 +2837,72 @@ fn testTlsStatic(b: *Build, opts: Options) *Step {
return test_step;
}
fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unknown-file-type-error", opts);
const dylib = addSharedLibrary(b, "a", .{
.target = .{ .cpu_arch = .x86_64, .os_tag = .macos },
});
addZigSourceBytes(dylib, "export var foo: i32 = 0;");
const exe = addExecutable(b, "main", opts);
addCSourceBytes(exe,
\\extern int foo;
\\int main() {
\\ return foo;
\\}
, &.{});
exe.linkLibrary(dylib);
exe.linkLibC();
expectLinkErrors(exe, test_step, .{ .exact = &.{
"unknown file type",
"note: while parsing /?/liba.dylib",
"undefined symbol: foo",
"note: referenced by /?/a.o:.text",
} });
return test_step;
}
fn testUnresolvedError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error", opts);
const obj1 = addObject(b, "a", opts);
addCSourceBytes(obj1,
\\#include <stdio.h>
\\int foo();
\\int bar() {
\\ return foo() + 1;
\\}
, &.{"-ffunction-sections"});
obj1.linkLibC();
const obj2 = addObject(b, "b", opts);
addCSourceBytes(obj2,
\\#include <stdio.h>
\\int foo();
\\int bar();
\\int main() {
\\ return foo() + bar();
\\}
, &.{"-ffunction-sections"});
obj2.linkLibC();
const exe = addExecutable(b, "main", opts);
exe.addObject(obj1);
exe.addObject(obj2);
exe.linkLibC();
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: foo",
"note: referenced by /?/a.o:.text.bar",
"note: referenced by /?/b.o:.text.main",
} });
return test_step;
}
fn testWeakExports(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-exports", opts);
@ -3081,6 +3201,12 @@ fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void {
comp.addAssemblyFile(file);
}
fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void {
comp.expect_errors = expected_errors;
const bin_file = comp.getEmittedBin();
bin_file.addStepDependencies(test_step);
}
const std = @import("std");
const Build = std.Build;

View File

@ -640,7 +640,7 @@ pub fn lowerToBuildSteps(
},
.Error => |expected_msgs| {
assert(expected_msgs.len != 0);
artifact.expect_errors = expected_msgs;
artifact.expect_errors = .{ .exact = expected_msgs };
parent_step.dependOn(&artifact.step);
},
.Execution => |expected_stdout| no_exec: {