test(pipeline): pin BackfillRefs' page read with a swap race (#157)
Review round 1 on PR #176: reverting the BackfillRefs concept-page read to os.ReadFile left every test green. The static symlinked page never reaches that read, because LoadInventory does not list it; the read is reachable only when a page listed as regular is swapped for a symlink before it is read. New test does that against an in-brain target and reads the page back with O_NOFOLLOW; red under the flip (2/2), green 3/3 on head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,10 @@ package pipeline
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -134,3 +136,32 @@ func TestBackfillRefsDoesNotWriteARefFromASourceSwappedInBehindASymlink(t *testi
|
|||||||
require.Contains(t, read(), "zqinside", "positive control")
|
require.Contains(t, read(), "zqinside", "positive control")
|
||||||
brainfstest.AssertNoLeakFrom(t, src, inside, target, 1500*time.Millisecond, read)
|
brainfstest.AssertNoLeakFrom(t, src, inside, target, 1500*time.Millisecond, read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A concept page that is regular when the inventory lists it, then swapped
|
||||||
|
// for a symlink to an in-brain file before BackfillRefs reads it: only the
|
||||||
|
// page read's own check refuses it (a static symlink never gets this far,
|
||||||
|
// because LoadInventory does not list it).
|
||||||
|
func TestBackfillRefsDoesNotCopyAPageSwappedInBehindASymlink(t *testing.T) {
|
||||||
|
brain := t.TempDir()
|
||||||
|
mkdirs(t, brain, "wiki/sources", "wiki/concepts", "raw")
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(brain, "wiki", "sources", "s.md"), []byte("---\ntitle: S\n---\n[[c|C]]\n"), 0o644))
|
||||||
|
page := filepath.Join(brain, "wiki", "concepts", "c.md")
|
||||||
|
target := filepath.Join(brain, "raw", "secret.md")
|
||||||
|
inside := "---\ntitle: C\n---\nzqinside\n"
|
||||||
|
read := func() string {
|
||||||
|
// The write path still follows a swapped-in link (#158) and can
|
||||||
|
// overwrite the target, so restore it each round.
|
||||||
|
_ = os.WriteFile(target, []byte("---\ntitle: X\n---\n"+brainfstest.Sentinel+"\n"), 0o644)
|
||||||
|
_, _ = BackfillRefs(context.Background(), brain)
|
||||||
|
f, err := os.OpenFile(page, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
b, _ := io.ReadAll(f)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
require.NoError(t, os.WriteFile(page, []byte(inside), 0o644))
|
||||||
|
require.Contains(t, read(), "zqinside", "positive control: the regular page gets the ref")
|
||||||
|
brainfstest.AssertNoLeakFrom(t, page, inside, target, 3*time.Second, read)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user