Skip to content

Commit 49d0ae3

Browse files
author
gluck
committed
the topology form's crash window, its truthful default, and a Forget that writes nothing
1 parent a2725f9 commit 49d0ae3

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

‎internal/store/topology_form_test.go‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,73 @@ func TestTopologyForm_MigrationIsOnce(t *testing.T) {
246246
t.Fatalf("reopening migrated again: rev %d -> %d", rev, y.Rev())
247247
}
248248
}
249+
250+
// THE CRASH WINDOW. The migration folds the file in and then renames it, so
251+
// a crash between the two leaves a store whose form holds the edges AND
252+
// whose trunks.json is still there. The next open must not fold them again:
253+
// that is what "ordering, not a journal" means here, and it is checked by
254+
// putting the file back rather than by killing a process.
255+
func TestTopologyForm_CrashBetweenFoldAndRename(t *testing.T) {
256+
dir := t.TempDir()
257+
f := forest()
258+
legacy := filepath.Join(dir, "trunks.json")
259+
body := []byte(`{"version":1,"parent":{"A":"B"}}`)
260+
if err := os.WriteFile(legacy, body, 0o644); err != nil {
261+
t.Fatal(err)
262+
}
263+
x, be := openTopoIn(t, dir, f)
264+
rev := x.Rev()
265+
x.Close()
266+
be.Close()
267+
268+
// The crash: the fold landed, the rename did not.
269+
if err := os.WriteFile(legacy, body, 0o644); err != nil {
270+
t.Fatal(err)
271+
}
272+
y, _ := openTopoIn(t, dir, f)
273+
if y.Rev() != rev {
274+
t.Fatalf("a second fold landed: rev %d -> %d", rev, y.Rev())
275+
}
276+
if p, _ := y.Parent("A"); p != "B" {
277+
t.Fatalf("Parent(A) = %q, want B", p)
278+
}
279+
if _, err := os.Stat(legacy); !os.IsNotExist(err) {
280+
t.Fatal("the rename did not complete on the recovery pass")
281+
}
282+
}
283+
284+
// ABSENCE IS THE TRUTHFUL DEFAULT (durable-forms §1). A store whose topology
285+
// form holds nothing draws every aria where its history puts it, rather than
286+
// drawing a wrong tree. This is what makes the form's loss survivable.
287+
func TestTopologyForm_EmptyDegradesToTheTopology(t *testing.T) {
288+
x, f := openTopo(t)
289+
if !x.Normalized() {
290+
t.Fatal("an empty topology form must be normalized")
291+
}
292+
for id, want := range map[string]string{"B": "A", "C": "A", "D": "C"} {
293+
if p, _ := x.Parent(id); p != want {
294+
t.Fatalf("Parent(%s) = %q, want the topology edge %q", id, p, want)
295+
}
296+
}
297+
if len(x.Edges()) != 0 {
298+
t.Fatalf("edges on a fresh tree: %v", x.Edges())
299+
}
300+
_ = f
301+
}
302+
303+
// A Forget naming edges that are not there must not write a record. The
304+
// delete path calls it on every delete, and a record per delete on a form
305+
// with no retention is growth for nothing.
306+
func TestTopologyForm_ForgetWritesNothingWhenNothingMatches(t *testing.T) {
307+
x, _ := openTopo(t)
308+
if err := x.Promote("B"); err != nil {
309+
t.Fatal(err)
310+
}
311+
rev := x.Rev()
312+
if err := x.Forget("Z", "Y"); err != nil {
313+
t.Fatal(err)
314+
}
315+
if x.Rev() != rev {
316+
t.Fatalf("Forget of unknown ids wrote a record: rev %d -> %d", rev, x.Rev())
317+
}
318+
}

0 commit comments

Comments
 (0)