@@ -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