package hostclaim import ( "errors" "reflect" "testing" ) func plan9D1Landing(phase string) *Claim { return &Claim{V: schemaVersion, Host: "a.go.example.com", Gen: 12, Holder: &Holder{Family: "instance", Incarnation: "inc", Location: "instances/a.go.example.com", Generation: "m1", Stamp: 10, Session: 11, WriterBox: "uk-box"}, Op: Op{Kind: KindLanding, ID: "m1", Phase: phase, Exec: "uk-box", Participants: []string{"ie-box", "uk-box"}, Intent: map[string]string{"to": "uk", "source": "ie-box", "target": "uk-box"}}} } func TestPlan9D1SessionStepsAreForwardOnlyAndPerKind(t *testing.T) { for _, tc := range []struct { kind, from, to string want bool }{ {KindLanding, "probing", "probing", true}, {KindLanding, "probing", "probed", true}, {KindLanding, "probed", "committed", true}, {KindLanding, "probing", "committed", false}, {KindLanding, "probed", "probing", false}, {KindLanding, "committed", "probed", false}, {KindLanding, "restored", "probing", false}, {KindLanding, "probing", "checking", false}, {KindServing, "", "", true}, {KindServing, "", "probed", false}, {KindMoving, "probing", "probed", false}, {KindRelocating, "probing", "serving", true}, // claim Plan 11's relocating row {KindRelocating, "probing", "probe-failed", false}, } { if got := SessionContinues(tc.kind, tc.from, tc.to); got != tc.want { t.Errorf("SessionContinues(%s, %q, %q) = %v, want %v", tc.kind, tc.from, tc.to, got, tc.want) } } } func TestPlan9D1KeepWriterSessionGuardsTheCommit(t *testing.T) { commit := func(c *Claim) error { return SetMovePhase("m1", KindLanding, "probed", "committed")(c) } c := plan9D1Landing("probed") if err := KeepWriterSession("m1", commit)(c); err != nil { t.Fatal(err) } if c.Op.Phase != "committed" || c.Holder.Session != 11 || c.Holder.Stamp != 10 || c.Holder.Generation != "m1" { t.Fatalf("commit changed the session: %+v %+v", c.Op, *c.Holder) } for name, fn := range map[string]func(*Claim) error{ "generation": func(c *Claim) error { c.Holder.Generation = "fresh"; return commit(c) }, "session": func(c *Claim) error { c.Holder.Session++; return commit(c) }, "stamp": func(c *Claim) error { c.Holder.Stamp = c.Gen + 1; return commit(c) }, "writer": func(c *Claim) error { c.Holder.WriterBox = "ie-box"; return commit(c) }, "executor": func(c *Claim) error { c.Op.Exec = ""; return commit(c) }, "backward": SetMovePhase("m1", KindLanding, "probed", "probing"), "kind": func(c *Claim) error { c.Op.Kind = KindRestoring; c.Op.Phase = "released"; return nil }, "holder": func(c *Claim) error { c.Holder = nil; return nil }, } { t.Run(name, func(t *testing.T) { c := plan9D1Landing("probed") if err := KeepWriterSession("m1", fn)(c); !errors.Is(err, ErrWrongState) { t.Fatalf("%s kept the session: %v", name, err) } }) } other := plan9D1Landing("probed") before := *other.Holder if err := KeepWriterSession("m2", commit)(other); !errors.Is(err, ErrStaleOp) { t.Fatalf("another op's step: %v", err) } if other.Op.Phase != "probed" || !reflect.DeepEqual(*other.Holder, before) { t.Fatalf("a refused op ran its step: %+v", other.Op) } if err := KeepWriterSession("m1", nil)(plan9D1Landing("probed")); !errors.Is(err, ErrMalformed) { t.Fatalf("nil step: %v", err) } } // M9: a single session-keeping CAS moves one row step at a time. The landing // cannot skip from probing straight to committed in one write. func TestPlan9M9KeepWriterSessionRefusesASkippedStep(t *testing.T) { for _, tc := range []struct { kind, from, to string want bool }{ {KindLanding, "probing", "probing", true}, {KindLanding, "probing", "probed", true}, {KindLanding, "probed", "committed", true}, {KindLanding, "committed", "committed", true}, {KindLanding, "probing", "committed", false}, {KindLanding, "probed", "probing", false}, {KindLanding, "committed", "probed", false}, {KindLanding, "restored", "probing", false}, {KindServing, "", "", true}, {KindServing, "", "probed", false}, {KindMoving, "probing", "probed", false}, } { if got := SessionContinues(tc.kind, tc.from, tc.to); got != tc.want { t.Errorf("SessionContinues(%s, %q, %q) = %v, want %v", tc.kind, tc.from, tc.to, got, tc.want) } } c := plan9D1Landing("probing") before := *c.Holder skip := SetMovePhase("m1", KindLanding, "probing", "committed") if err := KeepWriterSession("m1", skip)(c); !errors.Is(err, ErrWrongState) { t.Fatalf("a skipped step kept the session: %v", err) } if !reflect.DeepEqual(*c.Holder, before) { t.Fatalf("holder changed: %+v", *c.Holder) } if err := KeepWriterSession("m1", SetMovePhase("m1", KindLanding, "probing", "probed"))(plan9D1Landing("probing")); err != nil { t.Fatalf("adjacent step: %v", err) } }