milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 5m6s
CI / frontend (push) Successful in 45s
CI / cross (push) Successful in 7m53s
CI / docker (push) Failing after 1h10m57s

This commit is contained in:
2026-08-07 18:20:30 +02:00
parent c50c6d285a
commit 6f67940995
82 changed files with 3167 additions and 3114 deletions
+49 -4
View File
@@ -757,9 +757,15 @@ fn checkFile(r: Runner, arena: Allocator, path: []const u8, probe: bool) !u8 {
const cfg = std.zon.parse.fromSliceAlloc(model.Config, arena, source, &zon_diag, .{}) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
// The rendering carries the line and column, which is the whole value of
// running `check` against a file the operator just edited.
// running `check` against a file the operator just edited. It is
// multi-line, and `check` promises one line per problem, so it goes
// through the same `Diagnostics` channel `nxdns import` uses rather than
// into one `FAIL` record with newlines inside it.
error.ParseZon => {
try r.out.print("FAIL {s}: {f}\n", .{ path, &zon_diag });
var diags: validate.Diagnostics = .init(r.gpa);
defer diags.deinit();
try import.reportParseFailure(&diags, &zon_diag);
try diags.writeAll(r.out);
return exit_check;
},
};
@@ -917,8 +923,10 @@ fn probeUpstreams(r: Runner, cfg: model.Config) !usize {
const tls_buffers = try r.gpa.alloc(u8, 4 * chunk);
defer r.gpa.free(tls_buffers);
var request_buf: [1024]u8 = undefined;
var transfer_buf: [4096]u8 = undefined;
// The same sizes `nxdns run` serves with, so the probe reports on the
// buffers the server will actually use.
var request_buf: [doh_client.default_request_buf_len]u8 = undefined;
var transfer_buf: [doh_client.default_transfer_buf_len]u8 = undefined;
const response_buf = try r.gpa.alloc(u8, transport.max_message_len);
defer r.gpa.free(response_buf);
@@ -1461,6 +1469,43 @@ test "check --config naming a missing file is a reported failure at exit 2" {
try testing.expectEqualStrings("", captured.err.written());
}
test "check renders a multi-line ZON failure as one FAIL line per message" {
// The rendering used to go inline into a single `FAIL` record, which put
// newlines mid-line and broke the one-line-per-problem promise the rest of
// `check` keeps.
var env: CheckEnv = undefined;
try env.init();
defer env.deinit();
var captured: Captured = .init(testing.allocator);
defer captured.deinit();
const r = captured.runner();
// An unexpected field renders as an "error:" line and a "note:" line.
try env.tmp.dir.writeFile(r.io, .{ .sub_path = "config.zon", .data = ".{ .grops = .{} }\n" });
var path_buf: [160]u8 = undefined;
const config_path = try env.path(&path_buf, "config.zon");
const code = runCheck(r, .{
.paths = .{ .config = config_path },
.config_explicit = true,
}, false);
try testing.expectEqual(exit_check, code);
const text = captured.out.written();
var lines = std.mem.splitScalar(u8, std.mem.trimEnd(u8, text, "\n"), '\n');
// The first line names what was checked; every line after it is a problem.
try testing.expect(std.mem.startsWith(u8, lines.next().?, "checking configuration file "));
var failures: usize = 0;
while (lines.next()) |line| {
try testing.expect(std.mem.startsWith(u8, line, "FAIL config: "));
failures += 1;
}
try testing.expectEqual(@as(usize, 2), failures);
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "error: unexpected field 'grops'"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "note: supported: "));
}
test "check reads config.db without writing to it" {
// D6: the database branch opened read/write, chmod'ed 0600, turned WAL on —
// which is what creates the two sidecars — and committed migration steps,