audit 6: documents, comments, dead code and staticcheck; the test gaps closed
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
The last stage of the 2026-10-12 audit; no cut. Doc drift: architecture.md (the sender check in § OpenDKIM, the limit: row's 48 hours, the inbound log and filter-list routes and who may open them, migrations to 0011 and every table, KitPage as production data, Reload configuration as a restore writes, the panel's roles counted), schema-migrations.md (tables and columns since v2), guide.md (a delivery's lines go at the next prune; INBOUND_JOURNAL_MILTER_SOCKET moved to architecture.md's internal list), development.md (the env guard, the deferral the e2e proves, its fixed sleeps), roadmap.md. Comments say what the code does now; TestBaselineIsTheOnlyMigration is TestChainStartsAtTheBaseline; the milter package doc names its three milters. Dead code gone: view.Facts, dmarc HostedSuggestion and ValidateHostedAddress, app UpdateMode (each with the test that alone kept it), the method checks the method-bound routes made dead; Settings.WithResult calls resultFlash. staticcheck 2026.1 in test.yml on both modules, clean: the e2e package builds without its tests now (a test and two constants moved out of non-test files), three findings fixed. The test moved, TestImageBuildPreservesLogrotateMode, had sat in a non-test file and never run; run, it failed on the entrypoint (docker run without a hostname) and now asks with --entrypoint stat. The test gaps no step named, each closed: follow() with mail.log gone past blindMin and made again; CheckRestore with a manifest that is not JSON; the origin check with a port; every /server/ pattern in web.go through server() and in the routes table (it found GET /server/health/fragment missing from the table - guarded, not tested); L9 as the owner decided it on 2026-10-12, the comment on senderSession.Headers and the test passing an h that holds the earlier messages' From:. Plan stage 6 ticked; roadmap audit row done, 81/81. Tested on the test server: go vet + go test of 27 packages, make e2e (all pass, 513 s) on this tree; staticcheck locally on both modules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -612,11 +612,12 @@ func SplitTimestamp(line string) (stamp, rest string) {
|
||||
// follow tails path line by line, calling handle for each complete line, until
|
||||
// ctx is cancelled. Where it starts is tr's decision (a persisted offset, the
|
||||
// start of a file that changed while the panel was down, or end-of-file on a
|
||||
// first ever run); it reopens the file when it is rotated (inode change from
|
||||
// logrotate's create, or truncation from copytruncate) so nothing is missed.
|
||||
// first ever run); it reopens the file when it is rotated, so nothing is missed.
|
||||
// The inode changes under logrotate's create, which build/logrotate-mail.conf
|
||||
// uses; a file shorter than the read position is taken as truncation.
|
||||
// logrotate 3.21.0, logrotate.c, rotateSingleLog(): without copytruncate the
|
||||
// file is rename()d and, with create (build/logrotate-mail.conf), a new one
|
||||
// made by createOutputFile(); with copytruncate it is copied and truncated.
|
||||
// file is rename()d and, with create, a new one made by createOutputFile();
|
||||
// with copytruncate it is copied and truncated, which that config does not use.
|
||||
//
|
||||
// caughtUp is closed after the first read that reaches end-of-file, which is
|
||||
// the point where every line the panel missed while it was down has been
|
||||
|
||||
@@ -258,6 +258,8 @@ type captureStore struct {
|
||||
missed int64
|
||||
stuckCount int64
|
||||
stuck []int64
|
||||
// blind holds the stretches AddTailerBlind was given.
|
||||
blind []time.Duration
|
||||
|
||||
// lines are the log lines kept for messages' pages; pruned is the cutoff of
|
||||
// each PruneSendLogLines call.
|
||||
@@ -357,7 +359,12 @@ func (c *captureStore) AddReconcileMissed(_ time.Time, n int64) error {
|
||||
c.missed += n
|
||||
return nil
|
||||
}
|
||||
func (c *captureStore) AddTailerBlind(time.Time, time.Duration) error { return nil }
|
||||
func (c *captureStore) AddTailerBlind(_ time.Time, d time.Duration) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.blind = append(c.blind, d)
|
||||
return nil
|
||||
}
|
||||
func (c *captureStore) AttachRefusalLine(string, string, string, time.Time, string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
@@ -456,6 +463,51 @@ func TestFollowTailsAndRotates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// mail.log gone for longer than blindMin is a stretch the tailer could not
|
||||
// read, counted for Health; the file made again is read from its start. Like
|
||||
// TestFollowTailsAndRotates this runs on the server.
|
||||
func TestFollowCountsALogItCannotReadAndReadsItAgain(t *testing.T) {
|
||||
old := pollInterval
|
||||
pollInterval = 10 * time.Millisecond
|
||||
t.Cleanup(func() { pollInterval = old })
|
||||
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "mail.log")
|
||||
if err := os.WriteFile(path, nil, 0o644); err != nil {
|
||||
t.Fatalf("seed log: %v", err)
|
||||
}
|
||||
cs := &captureStore{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- Run(ctx, path, cs, func() int { return 90 }, nil, nil) }()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
appendLine(t, path, "host postfix/smtp[1]: Q1: to=<a@example.net>, relay=x, dsn=2.0.0, status=sent (ok)")
|
||||
waitFor(t, func() bool { return contains(cs.snapshot(), "Q1|a@example.net|sent") })
|
||||
|
||||
if err := os.Remove(path); err != nil {
|
||||
t.Fatalf("remove: %v", err)
|
||||
}
|
||||
// blindness counts by the clock: the name must stay missing past blindMin.
|
||||
time.Sleep(blindMin + 500*time.Millisecond)
|
||||
if err := os.WriteFile(path, nil, 0o644); err != nil {
|
||||
t.Fatalf("recreate: %v", err)
|
||||
}
|
||||
appendLine(t, path, "host postfix/smtp[1]: Q2: to=<b@example.net>, relay=x, dsn=5.1.1, status=bounced (nope)")
|
||||
waitFor(t, func() bool { return contains(cs.snapshot(), "Q2|b@example.net|bounced") })
|
||||
waitFor(t, func() bool {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
return len(cs.blind) == 1 && cs.blind[0] >= blindMin
|
||||
})
|
||||
|
||||
cancel()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Run did not return after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFollowResumesAfterRestart covers the persisted read offset: a restart
|
||||
// must parse the delivery lines written while the tailer was down (rows that
|
||||
// would otherwise stay "queued" forever), without re-parsing what it already
|
||||
|
||||
Reference in New Issue
Block a user