|
| 1 | +package cellstore |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "os" |
| 6 | + "path/filepath" |
| 7 | + "testing" |
| 8 | + |
| 9 | + "github.com/Agent-Field/codeaf/internal/cell" |
| 10 | + "github.com/Agent-Field/codeaf/internal/furrow" |
| 11 | +) |
| 12 | + |
| 13 | +// oldEngineEnv names an older furrow release binary. The store-format tests |
| 14 | +// below skip without it: they prove that a store written by that release opens |
| 15 | +// and keeps capturing under the embedded engine, and the other way round. |
| 16 | +const oldEngineEnv = "CODEAF_FURROW_OLD" |
| 17 | + |
| 18 | +// TestStoreWrittenByAnOlderEngineOpensUnderTheEmbeddedOne seals and captures a |
| 19 | +// cell with the old release, then with the embedded engine on the SAME data |
| 20 | +// root, then with the old release again. The chain must continue unbroken in |
| 21 | +// both directions: a user who upgrades keeps their history, and a codeaf that |
| 22 | +// is rolled back still reads what the newer one wrote. |
| 23 | +func TestStoreWrittenByAnOlderEngineOpensUnderTheEmbeddedOne(t *testing.T) { |
| 24 | + old := os.Getenv(oldEngineEnv) |
| 25 | + if old == "" { |
| 26 | + t.Skipf("%s not set", oldEngineEnv) |
| 27 | + } |
| 28 | + current, err := furrow.ResolveOwned() |
| 29 | + if err != nil { |
| 30 | + t.Skipf("no embedded engine: %v", err) |
| 31 | + } |
| 32 | + if current == old { |
| 33 | + t.Skip("the embedded engine is the old engine") |
| 34 | + } |
| 35 | + ctx := context.Background() |
| 36 | + dataRoot := t.TempDir() |
| 37 | + c, err := cell.CreateIn(t.TempDir(), cell.Options{Class: cell.Sandboxed}) |
| 38 | + if err != nil { |
| 39 | + t.Fatal(err) |
| 40 | + } |
| 41 | + engineOf := func(bin string) Engine { |
| 42 | + return Engine{Binary: bin, DataRoot: dataRoot, Transport: Spawn{Binary: bin}} |
| 43 | + } |
| 44 | + note := filepath.Join(c.Root, "note.txt") |
| 45 | + write := func(s string) { |
| 46 | + t.Helper() |
| 47 | + if err := os.WriteFile(note, []byte(s), 0o600); err != nil { |
| 48 | + t.Fatal(err) |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + write("written by the old engine") |
| 53 | + first, err := engineOf(old).Seal(ctx, c, TurnInfo{}) |
| 54 | + if err != nil { |
| 55 | + t.Fatalf("old engine seal: %v", err) |
| 56 | + } |
| 57 | + |
| 58 | + write("written under the new engine") |
| 59 | + second, err := engineOf(current).Seal(ctx, c, TurnInfo{}) |
| 60 | + if err != nil { |
| 61 | + t.Fatalf("embedded engine could not seal into the old store: %v", err) |
| 62 | + } |
| 63 | + if second.Turn.Parent != first.Turn.ID { |
| 64 | + t.Fatalf("chain broke across the upgrade: parent %q, want %q", second.Turn.Parent, first.Turn.ID) |
| 65 | + } |
| 66 | + |
| 67 | + snap, err := engineOf(current).Capture(ctx, c) |
| 68 | + if err != nil || snap == "" { |
| 69 | + t.Fatalf("embedded engine capture in the old store: %q, %v", snap, err) |
| 70 | + } |
| 71 | + write("scribbled over") |
| 72 | + if err := engineOf(current).Restore(ctx, c, snap, []string{"note.txt"}); err != nil { |
| 73 | + t.Fatalf("embedded engine restore: %v", err) |
| 74 | + } |
| 75 | + if got, _ := os.ReadFile(note); string(got) != "written under the new engine" { |
| 76 | + t.Fatalf("restore gave %q", got) |
| 77 | + } |
| 78 | + |
| 79 | + write("written after rolling back") |
| 80 | + third, err := engineOf(old).Seal(ctx, c, TurnInfo{}) |
| 81 | + if err != nil { |
| 82 | + t.Fatalf("old engine could not seal into a store the newer engine wrote: %v", err) |
| 83 | + } |
| 84 | + if third.Turn.Parent != second.Turn.ID { |
| 85 | + t.Fatalf("chain broke across the rollback: parent %q, want %q", third.Turn.Parent, second.Turn.ID) |
| 86 | + } |
| 87 | +} |
0 commit comments