authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-10-25 23:20:12+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-25 23:20:12+02:00
log10ea7accf76d4ad59222ea5ff3585d6cc7cfe3b0
tree4cac1b809102b963687c05c1505c489ba1c760f1
parent5c5d1f93c4aa469cfba3f7838ae4a7db18e7152b
parentbc081901dc73aa0f2dd64350d4693425e822ed89
signature Signed by PGP key 4AEE18F83AFDEB23

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

elf: test error generation

4 files changed, 196 insertions(+), 34 deletions(-)

lib/std/Build/Step.zig+1-1
......@@ -415,7 +415,7 @@ pub fn evalZigProcess(
415415 .Exited => {
416416 // Note that the exit code may be 0 in this case due to the
417417 // compiler server protocol.
418 if (compile.expect_errors.len != 0 and s.result_error_bundle.errorMessageCount() > 0) {
418 if (compile.expect_errors != null and s.result_error_bundle.errorMessageCount() > 0) {
419419 return error.NeedCompileErrorCheck;
420420 }
421421 },
lib/std/Build/Step/Compile.zig+68-32
......@@ -204,10 +204,10 @@ use_llvm: ?bool,
204204use_lld: ?bool,
205205
206206/// This is an advanced setting that can change the intent of this Compile step.
207/// If this slice has nonzero length, it means that this Compile step exists to
207/// If this value is non-null, it means that this Compile step exists to
208208/// check for compile errors and return *success* if they match, and failure
209209/// otherwise.
210expect_errors: []const []const u8 = &.{},
210expect_errors: ?ExpectedCompileErrors = null,
211211
212212emit_directory: ?*GeneratedFile,
213213
......@@ -220,6 +220,11 @@ generated_llvm_bc: ?*GeneratedFile,
220220generated_llvm_ir: ?*GeneratedFile,
221221generated_h: ?*GeneratedFile,
222222
223pub const ExpectedCompileErrors = union(enum) {
224 contains: []const u8,
225 exact: []const []const u8,
226};
227
223228pub const CSourceFiles = struct {
224229 dependency: ?*std.Build.Dependency,
225230 /// If `dependency` is not null relative to it,
......@@ -2131,7 +2136,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
21312136
21322137 const maybe_output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) {
21332138 error.NeedCompileErrorCheck => {
2134 assert(self.expect_errors.len != 0);
2139 assert(self.expect_errors != null);
21352140 try checkCompileErrors(self);
21362141 return;
21372142 },
......@@ -2390,39 +2395,70 @@ fn checkCompileErrors(self: *Compile) !void {
23902395
23912396 // Render the expected lines into a string that we can compare verbatim.
23922397 var expected_generated = std.ArrayList(u8).init(arena);
2398 const expect_errors = self.expect_errors.?;
23932399
23942400 var actual_line_it = mem.splitScalar(u8, actual_stderr, '\n');
2395 for (self.expect_errors) |expect_line| {
2396 const actual_line = actual_line_it.next() orelse {
2397 try expected_generated.appendSlice(expect_line);
2398 try expected_generated.append('\n');
2399 continue;
2400 };
2401 if (mem.endsWith(u8, actual_line, expect_line)) {
2402 try expected_generated.appendSlice(actual_line);
2403 try expected_generated.append('\n');
2404 continue;
2405 }
2406 if (mem.startsWith(u8, expect_line, ":?:?: ")) {
2407 if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) {
2408 try expected_generated.appendSlice(actual_line);
2401
2402 // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile
2403 switch (expect_errors) {
2404 .contains => |expect_line| {
2405 while (actual_line_it.next()) |actual_line| {
2406 if (!matchCompileError(actual_line, expect_line)) continue;
2407 return;
2408 }
2409
2410 return self.step.fail(
2411 \\
2412 \\========= should contain: ===============
2413 \\{s}
2414 \\========= but not found: ================
2415 \\{s}
2416 \\=========================================
2417 , .{ expect_line, actual_stderr });
2418 },
2419 .exact => |expect_lines| {
2420 for (expect_lines) |expect_line| {
2421 const actual_line = actual_line_it.next() orelse {
2422 try expected_generated.appendSlice(expect_line);
2423 try expected_generated.append('\n');
2424 continue;
2425 };
2426 if (matchCompileError(actual_line, expect_line)) {
2427 try expected_generated.appendSlice(actual_line);
2428 try expected_generated.append('\n');
2429 continue;
2430 }
2431 try expected_generated.appendSlice(expect_line);
24092432 try expected_generated.append('\n');
2410 continue;
24112433 }
2412 }
2413 try expected_generated.appendSlice(expect_line);
2414 try expected_generated.append('\n');
2415 }
24162434
2417 if (mem.eql(u8, expected_generated.items, actual_stderr)) return;
2435 if (mem.eql(u8, expected_generated.items, actual_stderr)) return;
24182436
2419 // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile
2420 return self.step.fail(
2421 \\
2422 \\========= expected: =====================
2423 \\{s}
2424 \\========= but found: ====================
2425 \\{s}
2426 \\=========================================
2427 , .{ expected_generated.items, actual_stderr });
2437 return self.step.fail(
2438 \\
2439 \\========= expected: =====================
2440 \\{s}
2441 \\========= but found: ====================
2442 \\{s}
2443 \\=========================================
2444 , .{ expected_generated.items, actual_stderr });
2445 },
2446 }
2447}
2448
2449fn matchCompileError(actual: []const u8, expected: []const u8) bool {
2450 if (mem.endsWith(u8, actual, expected)) return true;
2451 if (mem.startsWith(u8, expected, ":?:?: ")) {
2452 if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true;
2453 }
2454 // We scan for /?/ in expected line and if there is a match, we match everything
2455 // up to and after /?/.
2456 const expected_trim = mem.trim(u8, expected, " ");
2457 if (mem.indexOf(u8, expected_trim, "/?/")) |index| {
2458 const actual_trim = mem.trim(u8, actual, " ");
2459 const lhs = expected_trim[0..index];
2460 const rhs = expected_trim[index + "/?/".len ..];
2461 if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true;
2462 }
2463 return false;
24282464}
test/link/elf.zig+126
......@@ -76,6 +76,8 @@ pub fn build(b: *Build) void {
7676 elf_step.dependOn(testLargeBss(b, .{ .target = glibc_target }));
7777 elf_step.dependOn(testLinkOrder(b, .{ .target = glibc_target }));
7878 elf_step.dependOn(testLdScript(b, .{ .target = glibc_target }));
79 elf_step.dependOn(testLdScriptPathError(b, .{ .target = glibc_target }));
80 elf_step.dependOn(testMismatchedCpuArchitectureError(b, .{ .target = glibc_target }));
7981 // https://github.com/ziglang/zig/issues/17451
8082 // elf_step.dependOn(testNoEhFrameHdr(b, .{ .target = glibc_target }));
8183 elf_step.dependOn(testPie(b, .{ .target = glibc_target }));
......@@ -99,6 +101,8 @@ pub fn build(b: *Build) void {
99101 elf_step.dependOn(testTlsOffsetAlignment(b, .{ .target = glibc_target }));
100102 elf_step.dependOn(testTlsPic(b, .{ .target = glibc_target }));
101103 elf_step.dependOn(testTlsSmallAlignment(b, .{ .target = glibc_target }));
104 elf_step.dependOn(testUnknownFileTypeError(b, .{ .target = glibc_target }));
105 elf_step.dependOn(testUnresolvedError(b, .{ .target = glibc_target }));
102106 elf_step.dependOn(testWeakExports(b, .{ .target = glibc_target }));
103107 elf_step.dependOn(testWeakUndefsDso(b, .{ .target = glibc_target }));
104108 elf_step.dependOn(testZNow(b, .{ .target = glibc_target }));
......@@ -1601,6 +1605,56 @@ fn testLdScript(b: *Build, opts: Options) *Step {
16011605 return test_step;
16021606}
16031607
1608fn testLdScriptPathError(b: *Build, opts: Options) *Step {
1609 const test_step = addTestStep(b, "ld-script-path-error", opts);
1610
1611 const scripts = WriteFile.create(b);
1612 _ = scripts.add("liba.so", "INPUT(libfoo.so)");
1613
1614 const exe = addExecutable(b, "main", opts);
1615 addCSourceBytes(exe, "int main() { return 0; }", &.{});
1616 exe.linkSystemLibrary2("a", .{});
1617 exe.addLibraryPath(scripts.getDirectory());
1618 exe.linkLibC();
1619
1620 expectLinkErrors(
1621 exe,
1622 test_step,
1623 .{
1624 .contains = "error: missing library dependency: GNU ld script '/?/liba.so' requires 'libfoo.so', but file not found",
1625 },
1626 );
1627
1628 return test_step;
1629}
1630
1631fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step {
1632 const test_step = addTestStep(b, "mismatched-cpu-architecture-error", opts);
1633
1634 const obj = addObject(b, "a", .{
1635 .target = .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu },
1636 });
1637 addCSourceBytes(obj, "int foo;", &.{});
1638 obj.strip = true;
1639
1640 const exe = addExecutable(b, "main", opts);
1641 addCSourceBytes(exe,
1642 \\extern int foo;
1643 \\int main() {
1644 \\ return foo;
1645 \\}
1646 , &.{});
1647 exe.addObject(obj);
1648 exe.linkLibC();
1649
1650 expectLinkErrors(exe, test_step, .{ .exact = &.{
1651 "invalid cpu architecture: expected 'x86_64', but found 'aarch64'",
1652 "note: while parsing /?/a.o",
1653 } });
1654
1655 return test_step;
1656}
1657
16041658fn testLinkingC(b: *Build, opts: Options) *Step {
16051659 const test_step = addTestStep(b, "linking-c", opts);
16061660
......@@ -2783,6 +2837,72 @@ fn testTlsStatic(b: *Build, opts: Options) *Step {
27832837 return test_step;
27842838}
27852839
2840fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
2841 const test_step = addTestStep(b, "unknown-file-type-error", opts);
2842
2843 const dylib = addSharedLibrary(b, "a", .{
2844 .target = .{ .cpu_arch = .x86_64, .os_tag = .macos },
2845 });
2846 addZigSourceBytes(dylib, "export var foo: i32 = 0;");
2847
2848 const exe = addExecutable(b, "main", opts);
2849 addCSourceBytes(exe,
2850 \\extern int foo;
2851 \\int main() {
2852 \\ return foo;
2853 \\}
2854 , &.{});
2855 exe.linkLibrary(dylib);
2856 exe.linkLibC();
2857
2858 expectLinkErrors(exe, test_step, .{ .exact = &.{
2859 "unknown file type",
2860 "note: while parsing /?/liba.dylib",
2861 "undefined symbol: foo",
2862 "note: referenced by /?/a.o:.text",
2863 } });
2864
2865 return test_step;
2866}
2867
2868fn testUnresolvedError(b: *Build, opts: Options) *Step {
2869 const test_step = addTestStep(b, "unresolved-error", opts);
2870
2871 const obj1 = addObject(b, "a", opts);
2872 addCSourceBytes(obj1,
2873 \\#include <stdio.h>
2874 \\int foo();
2875 \\int bar() {
2876 \\ return foo() + 1;
2877 \\}
2878 , &.{"-ffunction-sections"});
2879 obj1.linkLibC();
2880
2881 const obj2 = addObject(b, "b", opts);
2882 addCSourceBytes(obj2,
2883 \\#include <stdio.h>
2884 \\int foo();
2885 \\int bar();
2886 \\int main() {
2887 \\ return foo() + bar();
2888 \\}
2889 , &.{"-ffunction-sections"});
2890 obj2.linkLibC();
2891
2892 const exe = addExecutable(b, "main", opts);
2893 exe.addObject(obj1);
2894 exe.addObject(obj2);
2895 exe.linkLibC();
2896
2897 expectLinkErrors(exe, test_step, .{ .exact = &.{
2898 "error: undefined symbol: foo",
2899 "note: referenced by /?/a.o:.text.bar",
2900 "note: referenced by /?/b.o:.text.main",
2901 } });
2902
2903 return test_step;
2904}
2905
27862906fn testWeakExports(b: *Build, opts: Options) *Step {
27872907 const test_step = addTestStep(b, "weak-exports", opts);
27882908
......@@ -3081,6 +3201,12 @@ fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void {
30813201 comp.addAssemblyFile(file);
30823202}
30833203
3204fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void {
3205 comp.expect_errors = expected_errors;
3206 const bin_file = comp.getEmittedBin();
3207 bin_file.addStepDependencies(test_step);
3208}
3209
30843210const std = @import("std");
30853211
30863212const Build = std.Build;
test/src/Cases.zig+1-1
......@@ -640,7 +640,7 @@ pub fn lowerToBuildSteps(
640640 },
641641 .Error => |expected_msgs| {
642642 assert(expected_msgs.len != 0);
643 artifact.expect_errors = expected_msgs;
643 artifact.expect_errors = .{ .exact = expected_msgs };
644644 parent_step.dependOn(&artifact.step);
645645 },
646646 .Execution => |expected_stdout| no_exec: {