| index 14ebdf121..97d4cc0a1 100644 |
| --- a/internal/host/movesfileslive_test.go |
| +++ b/internal/host/movesfileslive_test.go |
| @@ -451,60 +451,127 @@ func TestRollbackReturnCleanupKeepsTheFilesLiveSet(t *testing.T) { |
| } |
| } |
| |
| +// returnPreMove lists the pre-move generation's objects at the source's |
| +// location, with their bodies. |
| +func returnPreMove(t *testing.T, h *returnHarness, location string) map[string]string { |
| + t.Helper() |
| + out := map[string]string{} |
| + for k, o := range h.f.buckets["data-ie"] { |
| + if strings.HasPrefix(k, location+"/g-cur/") { |
| + out[k] = o.body |
| + } |
| + } |
| + if len(out) == 0 { |
| + t.Fatal("fixture: no pre-move generation at the source's location") |
| + } |
| + return out |
| +} |
| + |
| // TestReturnResidueParkRunsThroughTheInstall (fix round 2, I3; the source's |
| -// return-residue park, movesinstall.go parking): a dirty return's parking |
| -// phase runs its residue chores through the installer's own engine with the |
| -// real view. The boundary is the target's admission: before it, the |
| -// pre-move generation is still holder.generation, authoritative in the |
| -// source's region, so its residue park is refused and its bytes stay; once |
| -// the target's probe was admitted (the holder's generation is the target's, |
| -// authoritative in uk) the same parking archives the pre-move generation |
| -// and retires it from the source's location. |
| +// return-residue park, movesinstall.go parking): with the record's evidence |
| +// that the target admitted this move (its probe's admission of the move's |
| +// prepared generation), a dirty return's parking runs its residue chores |
| +// through the installer's own engine with the real view: the pre-move |
| +// generation is archived under parked/ and retired from the source's |
| +// location, and the return moves on. |
| func TestReturnResidueParkRunsThroughTheInstall(t *testing.T) { |
| h := newReturnHarness(t, false) |
| location := strings.TrimSuffix(h.claim(t).Holder.Location, "/") |
| - var pre []string |
| - for k := range h.f.buckets["data-ie"] { |
| - if strings.HasPrefix(k, location+"/g-cur/") { |
| - pre = append(pre, k) |
| + pre := returnPreMove(t, h, location) |
| + h.rcfg.ParkView = movesParkView(MovesRecordsFrom(h.s), h.vs, "data-ie", "ie") |
| + if _, err := h.install(t); err != nil { |
| + t.Fatalf("the return with the target's admission: %v", err) |
| + } |
| + if rb := h.read(t, p1Host).Move.Rollback; rb.SourcePhase == provision.RbSourceParking { |
| + t.Fatalf("the return is still parking: %+v", rb) |
| + } |
| + for k := range pre { |
| + if _, ok := h.f.buckets["data-ie"][k]; ok { |
| + t.Fatalf("%s was not retired", k) |
| + } |
| + if !archivedUnder(h.f, "data-ie", h.inc, location, k) { |
| + t.Fatalf("%s was not archived", k) |
| } |
| } |
| - if len(pre) == 0 { |
| - t.Fatal("fixture: no pre-move generation at the source's location") |
| + if j := h.returnJournal(t); j.Steps[installStepResidueKept] != "" { |
| + t.Fatalf("a return with admission evidence journalled the no-op: %q", j.Steps[installStepResidueKept]) |
| } |
| +} |
| + |
| +// TestDirtyReturnWithoutAdmissionKeepsThePreMoveGeneration (fix round 3, |
| +// ruling (a)): the record carries no evidence that the target admitted this |
| +// move (the landing never reached probing, and the claim holds no admission |
| +// of the move's generation). The pre-move generation is still the source's |
| +// live state, so the return's parking is a journalled no-op: through the |
| +// real view and engine the return completes, the pre-move bytes are intact |
| +// and no residue park is queued. |
| +func TestDirtyReturnWithoutAdmissionKeepsThePreMoveGeneration(t *testing.T) { |
| + h := newReturnHarnessWith(t, false, false) |
| + location := strings.TrimSuffix(h.claim(t).Holder.Location, "/") |
| + pre := returnPreMove(t, h, location) |
| h.rcfg.ParkView = movesParkView(MovesRecordsFrom(h.s), h.vs, "data-ie", "ie") |
| - before := map[string]string{} |
| - for _, k := range pre { |
| - before[k] = h.f.buckets["data-ie"][k].body |
| + if _, err := h.install(t); err != nil { |
| + t.Fatalf("the return without admission evidence: %v", err) |
| } |
| + if rb := h.read(t, p1Host).Move.Rollback; rb.SourcePhase != provision.RbSourceRestored { |
| + t.Fatalf("the return did not complete its install: %+v", rb) |
| + } |
| + for k, body := range pre { |
| + if got, ok := h.f.buckets["data-ie"][k]; !ok || got.body != body { |
| + t.Fatalf("%s changed", k) |
| + } |
| + } |
| + j := h.returnJournal(t) |
| + if got := j.Steps[installStepResidueKept]; !strings.Contains(got, "never admitted") { |
| + t.Fatalf("the journal's no-op line: %q", got) |
| + } |
| + if got := j.Steps[landStepResidue]; got != "[]" { |
| + t.Fatalf("the residue list %q, want none", got) |
| + } |
| + for _, o := range h.claim(t).Obligations { |
| + if o.Kind == "park-generation" { |
| + t.Fatalf("a residue park was queued: %+v", o) |
| + } |
| + } |
| +} |
| |
| - // Before the target's admission: refused, bytes intact, still parking. |
| - if _, err := h.install(t); !errors.Is(err, park.ErrUnsafe) { |
| - t.Fatalf("the return's residue park before the target's admission: %v", err) |
| +// TestReturnResidueParkWaitsOnATransientRefusal (fix round 3): with the |
| +// target's admission recorded, a view refusal never turns the parking into |
| +// the no-op. The return waits at parking with the pre-move bytes intact and |
| +// no no-op line, and parks once the view answers. |
| +func TestReturnResidueParkWaitsOnATransientRefusal(t *testing.T) { |
| + h := newReturnHarness(t, false) |
| + location := strings.TrimSuffix(h.claim(t).Holder.Location, "/") |
| + pre := returnPreMove(t, h, location) |
| + real := movesParkView(MovesRecordsFrom(h.s), h.vs, "data-ie", "ie") |
| + down := true |
| + h.rcfg.ParkView = func(ctx context.Context, obs hostclaim.Observed) (park.View, error) { |
| + if down { |
| + return park.View{}, fmt.Errorf("%w: the record read timed out", park.ErrUnsafe) |
| + } |
| + return real(ctx, obs) |
| + } |
| + if _, err := h.install(t); err == nil { |
| + t.Fatal("the return went on through a view refusal") |
| } |
| if rb := h.read(t, p1Host).Move.Rollback; rb.SourcePhase != provision.RbSourceParking { |
| - t.Fatalf("the return went past parking: %+v", rb) |
| + t.Fatalf("the return left parking: %+v", rb) |
| + } |
| + if j := h.returnJournal(t); j.Steps[installStepResidueKept] != "" { |
| + t.Fatalf("a view refusal became the no-op: %q", j.Steps[installStepResidueKept]) |
| } |
| - for k, body := range before { |
| + for k, body := range pre { |
| if got, ok := h.f.buckets["data-ie"][k]; !ok || got.body != body { |
| - t.Fatalf("%s changed before the boundary", k) |
| + t.Fatalf("%s changed during the refusal", k) |
| } |
| } |
| - |
| - // After it: the target's probe was admitted in uk. |
| - admitTarget(t, h.landingHarness) |
| + down = false |
| if _, err := h.install(t); err != nil { |
| - t.Fatalf("the return after the target's admission: %v", err) |
| - } |
| - if rb := h.read(t, p1Host).Move.Rollback; rb.SourcePhase == provision.RbSourceParking { |
| - t.Fatalf("the return is still parking: %+v", rb) |
| + t.Fatalf("after the refusal: %v", err) |
| } |
| - for _, k := range pre { |
| + for k := range pre { |
| if _, ok := h.f.buckets["data-ie"][k]; ok { |
| - t.Fatalf("%s was not retired", k) |
| - } |
| - if !archivedUnder(h.f, "data-ie", h.inc, location, k) { |
| - t.Fatalf("%s was not archived", k) |
| + t.Fatalf("%s was not retired once the view answered", k) |
| } |
| } |
| } |