Let go of a hold whose resource is no longer declared, touching nothing on disk (hq ADR 0100)

This commit is contained in:
2026-09-22 18:14:37 +02:00
parent 824cb60cbb
commit 4a095df2f9
2 changed files with 153 additions and 2 deletions
+135 -2
View File
@@ -332,8 +332,141 @@ func TestAHeldFileIsNeverRemovedWhenItsModuleIsUnassigned(t *testing.T) {
if m.removed("hello-web") {
t.Fatal("a held container was removed when its module left")
}
if _, still := state.HeldAt("hello-web.page"); !still {
t.Error("the hold was forgotten, so a return of the module would read the file as the mesh's")
if _, still := state.HeldAt("hello-web.page"); still {
t.Error("a hold outlived its resource leaving the declaration, so the node reports it for ever")
}
if _, recorded := state.Find("hello-web.page"); recorded {
t.Error("a file let go was recorded as the mesh's")
}
}
func TestAHoldNoLongerDeclaredIsLetGoAndFoundAgainIfItsModuleReturns(t *testing.T) {
dir, page, m := predecessor(t)
_, state := applyAdopted(t, adopted(t, untakenWeb, webResources(page)), store.State{}, m, dir)
first, _ := state.HeldAt("hello-web.page")
report, state := applyAdopted(t, adopted(t, `{"taken":[]}`, withConf(dir)), state, m, dir)
if o := outcomeOf(report, "hello-web.page"); o.Action != "forgotten" || o.Detail != "no longer declared; left as found" {
t.Errorf("letting a hold go was not reported: %+v", o)
}
if len(state.Held) != 0 {
t.Errorf("holds outlived their resources: %+v", state.Held)
}
// The module comes back: what is there is found again, and the original first kept stays.
if err := os.WriteFile(page, []byte("the predecessor wrote again\n"), 0o640); err != nil {
t.Fatal(err)
}
report, state = applyAdopted(t, adopted(t, untakenWeb, webResources(page)), state, m, dir)
if o := outcomeOf(report, "hello-web.page"); o.Action != "held" {
t.Fatalf("a returning module's found file was not held again: %+v", o)
}
again, _ := state.HeldAt("hello-web.page")
if kept, _ := os.ReadFile(again.Kept); again.Kept != first.Kept || string(kept) != "the predecessor's page\n" {
t.Errorf("the first kept original was lost: %s %q", again.Kept, kept)
}
if got, _ := os.ReadFile(page); string(got) != "the predecessor wrote again\n" {
t.Errorf("the found file was touched: %q", got)
}
}
func TestACarriedApplyLetsNoHoldGo(t *testing.T) {
dir, page, m := predecessor(t)
_, state := applyAdopted(t, adopted(t, untakenWeb, webResources(page)), store.State{}, m, dir)
carried := parse(t, `{"declaration":1,"resources":[`+withConf(dir)+`]}`)
_, state, err := ApplyKeeping(context.Background(), archHost(t), carried, state, store.OriginCarried,
m.run, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
if len(state.Held) != 2 {
t.Errorf("a carried apply let holds go: %+v", state.Held)
}
}
func TestAHeldFileRewrittenIsReportedAndNotReverted(t *testing.T) {
dir, page, m := predecessor(t)
d := adopted(t, untakenWeb, webResources(page))
_, state := applyAdopted(t, d, store.State{}, m, dir)
if err := os.WriteFile(page, []byte("the predecessor wrote again\n"), 0o640); err != nil {
t.Fatal(err)
}
report, state := applyAdopted(t, d, state, m, dir)
if got, _ := os.ReadFile(page); string(got) != "the predecessor wrote again\n" {
t.Fatalf("a held file was reverted: %q", got)
}
if h, _ := state.HeldAt("hello-web.page"); h.Changed != "rewritten" || h.ChangedAt.IsZero() {
t.Errorf("a rewrite was not recorded: %+v", h)
}
if o := outcomeOf(report, "hello-web.page"); !strings.Contains(o.Detail, "rewritten") {
t.Errorf("a rewrite was not reported: %+v", o)
}
h, _ := state.HeldAt("hello-web.page")
if kept, _ := os.ReadFile(h.Kept); string(kept) != "the predecessor's page\n" {
t.Errorf("the kept original was overwritten by a later write: %q", kept)
}
}
func TestAHeldContainerStoppedOrReplacedIsReportedAndNotRestarted(t *testing.T) {
for _, c := range []struct {
change func(*machine)
want string
}{
{func(m *machine) { m.containers["hello-web"].running = false }, "stopped"},
{func(m *machine) { m.containers["hello-web"].id = "another" }, "replaced"},
{func(m *machine) { delete(m.containers, "hello-web") }, "gone"},
} {
dir, page, m := predecessor(t)
d := adopted(t, untakenWeb, webResources(page))
_, state := applyAdopted(t, d, store.State{}, m, dir)
c.change(m)
m.asked = nil
_, state = applyAdopted(t, d, state, m, dir)
if h, _ := state.HeldAt("hello-web.server"); h.Changed != c.want {
t.Errorf("%s: recorded as %q", c.want, h.Changed)
}
for _, a := range m.asked {
if strings.HasPrefix(a, "docker run") || strings.HasPrefix(a, "docker rm") ||
strings.HasPrefix(a, "docker start") {
t.Errorf("%s: the held container was acted on: %s", c.want, a)
}
}
}
}
func TestAHeldFileThatVanishesIsNotCreated(t *testing.T) {
dir, page, m := predecessor(t)
d := adopted(t, untakenWeb, webResources(page))
_, state := applyAdopted(t, d, store.State{}, m, dir)
if err := os.Remove(page); err != nil {
t.Fatal(err)
}
_, state = applyAdopted(t, d, state, m, dir)
if _, err := os.Stat(page); !errors.Is(err, os.ErrNotExist) {
t.Fatal("a held file that vanished was created before its module was taken")
}
if h, _ := state.HeldAt("hello-web.page"); h.Changed != "gone" {
t.Errorf("a vanished held file was not reported gone: %+v", h)
}
}
func TestAConvergedNodeStillReplacesWhatItFinds(t *testing.T) {
// No adoption, no holds: byte for byte what a converged node did before ADR 0100.
dir, page, m := predecessor(t)
d := parse(t, `{"declaration":1,"resources":[`+webResources(page)+`]}`)
report, state := applyAdopted(t, d, store.State{}, m, dir)
if got, _ := os.ReadFile(page); string(got) != "the mesh's page\n" {
t.Errorf("a converged node kept a found file: %q", got)
}
if !m.removed("hello-web") {
t.Error("a converged node kept a found container")
}
if len(state.Held) != 0 || outcomeOf(report, "hello-web.page").Action == "held" {
t.Errorf("a converged node held something: %+v", state.Held)
}
if _, err := os.Stat(filepath.Join(dir, "kept")); !errors.Is(err, os.ErrNotExist) {
t.Error("a converged node kept originals")
}
}