diff --git a/go/internal/runtime/microvm_isolation_microvm_test.go b/go/internal/runtime/microvm_isolation_microvm_test.go index e24ec501..7b42e0e4 100644 --- a/go/internal/runtime/microvm_isolation_microvm_test.go +++ b/go/internal/runtime/microvm_isolation_microvm_test.go @@ -167,14 +167,23 @@ func sweepScript(needle, roots string) string { // The awk program: scan every FILENAME handed to this invocation, print // `:` per match, and exit non-zero when the batch had none — so // the caller's `found` accumulator keeps grep's semantics across batches. - const awkProg = `index($0, ENVIRON["SWEEP_NEEDLE"]) { print FILENAME ":" $0; hit=1 } END { exit !hit }` + // + // BEGINFILE/ERRNO is load-bearing, not defensive: gawk (agent-image ships + // pkgs.gawk) makes an unopenable input FATAL, aborting there and dropping + // later paths plus the batch's exit-status contribution. It covers OPEN + // errors only — a read error still aborts, why the scan below shows stderr. + const awkProg = `BEGINFILE { if (ERRNO) nextfile } ` + + `index($0, ENVIRON["SWEEP_NEEDLE"]) { print FILENAME ":" $0; hit=1 } END { exit !hit }` return "export SWEEP_NEEDLE='" + needle + "'; " + "shopt -s globstar nullglob dotglob; found=1; batch=(); " + // scan() runs one awk over the accumulated batch and clears it. Guarded // on a non-empty batch so a trailing flush with nothing pending does not // invoke awk on zero files (which would read stdin and hang). + // + // stderr is NOT suppressed: it carries the one signal that separates a + // real negative from a dead probe. "scan() { ((${#batch[@]})) || return 0; " + - "if awk '" + awkProg + "' \"${batch[@]}\" 2>/dev/null; then found=0; fi; batch=(); }; " + + "if awk '" + awkProg + "' \"${batch[@]}\"; then found=0; fi; batch=(); }; " + "for root in " + roots + "; do " + "for f in \"$root\"/**/*; do " + // Collapse repeated slashes before matching: a "/" root globs to @@ -186,7 +195,10 @@ func sweepScript(needle, roots string) string { "[[ -f $f && -r $f ]] || continue; " + "batch+=(\"$f\"); " + "((${#batch[@]} >= " + strconv.Itoa(sweepBatchSize) + ")) && scan; " + - "done; done 2>/dev/null; scan; exit $found" + // One `done` closes the per-file loop, the next the per-root loop. + "done; " + + "done; " + + "scan; exit $found" } // TestMicroVMSweepScriptFindsItsNeedle is the non-vacuity control for @@ -239,8 +251,11 @@ func TestMicroVMSweepScriptFindsANeedleAcrossBatches(t *testing.T) { // Comfortably more than two full batches, so at least two mid-loop flushes // happen before the trailing one. fileCount := sweepBatchSize*2 + 25 + // Zero-padded so the glob's lexicographic order matches numeric order in ANY + // collation. Unpadded, f425.txt sorts mid-run (index 362 in the guest's C + // locale), so the "final batch" row never reached the trailing flush it names. plant := "mkdir -p /workspace/many && for i in $(seq 1 " + strconv.Itoa(fileCount) + "); do " + - "printf 'filler line %s\\n' \"$i\" > /workspace/many/f$i.txt; done && ls /workspace/many | wc -l" + "printf 'filler line %s\\n' \"$i\" > \"$(printf '/workspace/many/f%03d.txt' \"$i\")\"; done && ls /workspace/many | wc -l" out, code := guestSh(t, m, id, plant) if code != 0 { t.Fatalf("planting %d filler files: exit %d, %q", fileCount, code, truncate(out)) @@ -251,10 +266,10 @@ func TestMicroVMSweepScriptFindsANeedleAcrossBatches(t *testing.T) { for _, tt := range []struct{ name, file, needle string }{ // Last file: only the TRAILING flush can find it. - {"in the final batch", "/workspace/many/f" + strconv.Itoa(fileCount) + ".txt", "SWEEP-BATCH-LAST-6c1e8f30"}, + {"in the final batch", fmt.Sprintf("/workspace/many/f%03d.txt", fileCount), "SWEEP-BATCH-LAST-6c1e8f30"}, // First file: found by a MID-LOOP flush, so `found` must survive every // later batch that matched nothing. - {"in the first batch", "/workspace/many/f1.txt", "SWEEP-BATCH-FIRST-91ad47b2"}, + {"in the first batch", "/workspace/many/f001.txt", "SWEEP-BATCH-FIRST-91ad47b2"}, } { t.Run(tt.name, func(t *testing.T) { if out, code := guestSh(t, m, id, diff --git a/go/internal/runtime/microvm_quota_linux_test.go b/go/internal/runtime/microvm_quota_linux_test.go index a75edaa7..b24ee4ae 100644 --- a/go/internal/runtime/microvm_quota_linux_test.go +++ b/go/internal/runtime/microvm_quota_linux_test.go @@ -8,9 +8,12 @@ package runtime // // These are separate from microvm_quota_test.go because mountRoot and deviceOf // only exist under //go:build linux — the pure decision they feed is covered -// there, on every GOOS. +// there, on every GOOS. The two readVolumeQuota probes at the end are here for +// the same reason: off Linux they reach the refusal stub, so one fails outright +// and the other passes for the wrong reason. import ( + "math" "os" "path/filepath" "strings" @@ -208,3 +211,45 @@ func TestReadVolumeQuotaPropagatesInconclusiveMountRoot(t *testing.T) { "required-quota startup fails closed with the real cause", volume, reading) } } + +// TestReadVolumeQuotaOnRealPath exercises the PRODUCTION statfs probe against a +// real directory. What it can honestly assert without root is bounded but real: +// the probe succeeds, reports a plausible filesystem, resolves a mount root, and +// — since no test box's temp dir carries a project quota — reads as NOT active. +// That negative is load-bearing: it is what proves the detection does not +// false-positive and pass an unbounded volume off as quota'd. +func TestReadVolumeQuotaOnRealPath(t *testing.T) { + dir := t.TempDir() + reading, err := readVolumeQuota(dir) + if err != nil { + t.Fatalf("readVolumeQuota(%q) = %v, want a successful rootless read", dir, err) + } + if reading.LimitBytes <= 0 { + t.Fatalf("reading %s has no block total; statfs must report the filesystem size", reading) + } + if reading.MountRoot == "" { + t.Fatalf("reading %s resolved no mount root", reading) + } + if reading.UsedBytes < 0 || reading.UsedBytes > reading.LimitBytes { + t.Fatalf("reading %s has nonsensical usage", reading) + } + if reading.Active() { + t.Fatalf("reading %s reports an active project quota on a plain temp dir; "+ + "the detection must not false-positive (that would pass an unbounded volume as quota'd)", reading) + } + // The utilization the preflight logs must be finite and in range even with + // no quota — V7 meters this value. + if ratio := reading.UsedRatio(); ratio < 0 || ratio > 1 || math.IsNaN(ratio) { + t.Fatalf("UsedRatio() = %v on reading %s, want a finite ratio in [0,1]", ratio, reading) + } +} + +// TestReadVolumeQuotaAbsentPath: a path that does not exist is a probe ERROR, +// not a silent "no quota". Under QuotaRequired that difference decides whether +// startup fails with the real cause (an unreachable volume) or with a misleading +// missing-quota message. +func TestReadVolumeQuotaAbsentPath(t *testing.T) { + if _, err := readVolumeQuota(t.TempDir() + "/does-not-exist"); err == nil { + t.Fatal("readVolumeQuota on an absent path = nil error, want a failure naming the path") + } +} diff --git a/go/internal/runtime/microvm_quota_test.go b/go/internal/runtime/microvm_quota_test.go index e01726c8..b9f93b9c 100644 --- a/go/internal/runtime/microvm_quota_test.go +++ b/go/internal/runtime/microvm_quota_test.go @@ -12,11 +12,10 @@ package runtime // quotaReadFn precisely so it is covered here rather than left to a leg that // skips. // -// The real statfs probe (readVolumeQuota) is exercised too, but only for what is -// honestly assertable without a quota'd filesystem: that it reads a real path, -// and that an unquota'd tree correctly reads as NOT active. A green here does -// not claim quota enforcement was proven — the guest-side ENOSPC/EDQUOT proof is -// the root-gated leg in microvm_isolation_microvm_test.go. +// The real statfs probe is Linux-only; it is exercised in +// microvm_quota_linux_test.go. Enforcement itself is proven by the +// ENOSPC/EDQUOT leg in microvm_isolation_microvm_test.go, which needs the +// microvm tag and an operator-provided quota'd filesystem. import ( "errors" @@ -310,45 +309,3 @@ func TestVerifyVolumeQuotaNeverAssigns(t *testing.T) { t.Fatalf("returned reading Path = %q, want the verified path", got.Path) } } - -// TestReadVolumeQuotaOnRealPath exercises the PRODUCTION statfs probe against a -// real directory. What it can honestly assert without root is bounded but real: -// the probe succeeds, reports a plausible filesystem, resolves a mount root, and -// — since no test box's temp dir carries a project quota — reads as NOT active. -// That negative is load-bearing: it is what proves the detection does not -// false-positive and pass an unbounded volume off as quota'd. -func TestReadVolumeQuotaOnRealPath(t *testing.T) { - dir := t.TempDir() - reading, err := readVolumeQuota(dir) - if err != nil { - t.Fatalf("readVolumeQuota(%q) = %v, want a successful rootless read", dir, err) - } - if reading.LimitBytes <= 0 { - t.Fatalf("reading %s has no block total; statfs must report the filesystem size", reading) - } - if reading.MountRoot == "" { - t.Fatalf("reading %s resolved no mount root", reading) - } - if reading.UsedBytes < 0 || reading.UsedBytes > reading.LimitBytes { - t.Fatalf("reading %s has nonsensical usage", reading) - } - if reading.Active() { - t.Fatalf("reading %s reports an active project quota on a plain temp dir; "+ - "the detection must not false-positive (that would pass an unbounded volume as quota'd)", reading) - } - // The utilization the preflight logs must be finite and in range even with - // no quota — V7 meters this value. - if ratio := reading.UsedRatio(); ratio < 0 || ratio > 1 || math.IsNaN(ratio) { - t.Fatalf("UsedRatio() = %v on reading %s, want a finite ratio in [0,1]", ratio, reading) - } -} - -// TestReadVolumeQuotaAbsentPath: a path that does not exist is a probe ERROR, -// not a silent "no quota". Under QuotaRequired that difference decides whether -// startup fails with the real cause (an unreachable volume) or with a misleading -// missing-quota message. -func TestReadVolumeQuotaAbsentPath(t *testing.T) { - if _, err := readVolumeQuota(t.TempDir() + "/does-not-exist"); err == nil { - t.Fatal("readVolumeQuota on an absent path = nil error, want a failure naming the path") - } -}