authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-10-25 11:40:16+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-10-25 11:40:16+02:00
logbc081901dc73aa0f2dd64350d4693425e822ed89
treeb6141c31588b63046240f475d942a4ad321520fd
parent55c7a6d99d7897dec8562174803c1ad73f1d0b38

Step.Compile: differentiate between fuzzy and exact matches for compile errors


4 files changed, 83 insertions(+), 58 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+67-44
......@@ -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,52 +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);
2409 try expected_generated.append('\n');
2410 continue;
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;
24112408 }
2412 }
2413 // We scan for /?/ in expected line and if there is a match, we match everything
2414 // up to and after /?/.
2415 const expect_line_trim = mem.trim(u8, expect_line, " ");
2416 if (mem.indexOf(u8, expect_line_trim, "/?/")) |exp_index| {
2417 const actual_line_trim = mem.trim(u8, actual_line, " ");
2418 const exp_lhs = expect_line_trim[0..exp_index];
2419 const exp_rhs = expect_line_trim[exp_index + "/?/".len ..];
2420 if (mem.startsWith(u8, actual_line_trim, exp_lhs) and mem.endsWith(u8, actual_line_trim, exp_rhs)) {
2421 try expected_generated.appendSlice(actual_line);
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);
24222432 try expected_generated.append('\n');
2423 continue;
24242433 }
2425 }
2426 try expected_generated.appendSlice(expect_line);
2427 try expected_generated.append('\n');
2428 }
24292434
2430 if (mem.eql(u8, expected_generated.items, actual_stderr)) return;
2435 if (mem.eql(u8, expected_generated.items, actual_stderr)) return;
24312436
2432 // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile
2433 return self.step.fail(
2434 \\
2435 \\========= expected: =====================
2436 \\{s}
2437 \\========= but found: ====================
2438 \\{s}
2439 \\=========================================
2440 , .{ 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;
24412464}
test/link/elf.zig+14-12
......@@ -1617,11 +1617,13 @@ fn testLdScriptPathError(b: *Build, opts: Options) *Step {
16171617 exe.addLibraryPath(scripts.getDirectory());
16181618 exe.linkLibC();
16191619
1620 expectLinkErrors(exe, test_step, &.{
1621 "error: missing library dependency: GNU ld script '/?/liba.so' requires 'libfoo.so', but file not found",
1622 "note: tried libfoo.so",
1623 "note: tried /?/libfoo.so",
1624 });
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 );
16251627
16261628 return test_step;
16271629}
......@@ -1645,10 +1647,10 @@ fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step {
16451647 exe.addObject(obj);
16461648 exe.linkLibC();
16471649
1648 expectLinkErrors(exe, test_step, &.{
1650 expectLinkErrors(exe, test_step, .{ .exact = &.{
16491651 "invalid cpu architecture: expected 'x86_64', but found 'aarch64'",
16501652 "note: while parsing /?/a.o",
1651 });
1653 } });
16521654
16531655 return test_step;
16541656}
......@@ -2853,12 +2855,12 @@ fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
28532855 exe.linkLibrary(dylib);
28542856 exe.linkLibC();
28552857
2856 expectLinkErrors(exe, test_step, &.{
2858 expectLinkErrors(exe, test_step, .{ .exact = &.{
28572859 "unknown file type",
28582860 "note: while parsing /?/liba.dylib",
28592861 "undefined symbol: foo",
28602862 "note: referenced by /?/a.o:.text",
2861 });
2863 } });
28622864
28632865 return test_step;
28642866}
......@@ -2892,11 +2894,11 @@ fn testUnresolvedError(b: *Build, opts: Options) *Step {
28922894 exe.addObject(obj2);
28932895 exe.linkLibC();
28942896
2895 expectLinkErrors(exe, test_step, &.{
2897 expectLinkErrors(exe, test_step, .{ .exact = &.{
28962898 "error: undefined symbol: foo",
28972899 "note: referenced by /?/a.o:.text.bar",
28982900 "note: referenced by /?/b.o:.text.main",
2899 });
2901 } });
29002902
29012903 return test_step;
29022904}
......@@ -3199,7 +3201,7 @@ fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void {
31993201 comp.addAssemblyFile(file);
32003202}
32013203
3202fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: []const []const u8) void {
3204fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void {
32033205 comp.expect_errors = expected_errors;
32043206 const bin_file = comp.getEmittedBin();
32053207 bin_file.addStepDependencies(test_step);
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: {