// Integration test: drive the checker over the evaluator probe corpus. // // The probes are real .flash files under tests/eval/, embedded at comptime // through tests/eval/probes.zig. A pass/ probe must check completely clean. // A reject/ probe carries one `// expect-error: ` marker per // expected diagnostic, placed on the line the diagnostic must land on; the // markers are the whole contract — every marker must be matched by a // diagnostic on its line whose message contains the fragment, and the total // diagnostic count must equal the marker count, so nothing unexpected fires // and nothing expected goes missing. Because the marker rides the offending // line, the expectation can never drift when lines move. // // The same files double as the live-binary sweep corpus (flashc must reject // each reject/ probe with exit 1 and accept each pass/ probe) and, in the // self-host phase, as the stage0-vs-stage1 diagnostics differential set. const std = @import("std"); const sema = @import("sema"); const probes = @import("probes"); const marker = "// expect-error: "; const Expected = struct { line: u32, frag: []const u8 }; // Collect the expect-error markers of `src`, one per marked line. fn parseMarkers(arena: std.mem.Allocator, src: []const u8) ![]Expected { var list: std.ArrayList(Expected) = .empty; var it = std.mem.splitScalar(u8, src, '\n'); var line: u32 = 1; while (it.next()) |text| : (line += 1) { const at = std.mem.indexOf(u8, text, marker) orelse continue; const frag = std.mem.trimEnd(u8, text[at + marker.len ..], " \r"); try list.append(arena, .{ .line = line, .frag = frag }); } return list.toOwnedSlice(arena); } // Parse and check one probe, returning the collected diagnostics. fn checkSource(arena: std.mem.Allocator, src: []const u8) ![]sema.Diag { var p = sema.Parser.init(arena, src); const program = try p.parseProgram(); return sema.check(arena, program); } fn dumpDiags(name: []const u8, src: []const u8, diags: []const sema.Diag) void { std.debug.print("probe '{s}' produced {d} diagnostic(s):\n", .{ name, diags.len }); for (diags) |d| { const loc = sema.locate(src, d.anchor); std.debug.print(" {d}:{d}: {s}\n", .{ loc.line, loc.col, d.msg }); } } test "every pass probe checks clean" { for (probes.pass) |probe| { var a = std.heap.ArenaAllocator.init(std.testing.allocator); defer a.deinit(); const diags = try checkSource(a.allocator(), probe.src); if (diags.len != 0) { dumpDiags(probe.name, probe.src, diags); return error.UnexpectedDiag; } } } test "every reject probe produces exactly its marked diagnostics" { for (probes.reject) |probe| { var a = std.heap.ArenaAllocator.init(std.testing.allocator); defer a.deinit(); const arena = a.allocator(); const expected = try parseMarkers(arena, probe.src); try std.testing.expect(expected.len > 0); // an unmarked reject probe is a corpus bug const diags = try checkSource(arena, probe.src); // Every marker is hit on its own line … marks: for (expected) |e| { for (diags) |d| { const loc = sema.locate(probe.src, d.anchor); if (loc.line == e.line and std.mem.indexOf(u8, d.msg, e.frag) != null) continue :marks; } std.debug.print("probe '{s}': no diagnostic on line {d} containing '{s}'\n", .{ probe.name, e.line, e.frag }); dumpDiags(probe.name, probe.src, diags); return error.DiagNotFound; } // … and nothing unmarked fired. if (diags.len != expected.len) { dumpDiags(probe.name, probe.src, diags); return error.DiagCountMismatch; } } }