| index 2627c867b..6d450f2b2 100644 |
| --- a/internal/edge/alias_test.go |
| +++ b/internal/edge/alias_test.go |
| @@ -590,3 +590,73 @@ func TestHTTPFallbackRedirectsTunnelHost(t *testing.T) { |
| t.Fatalf("tunnel.test: Location = %q, want https://tunnel.test/register", loc) |
| } |
| } |
| + |
| +// The ranking's alias witnesses (self-serve moves plan 3 Task 3). An alias |
| +// candidate is a lifetime of its target (its primary), so a hold of the |
| +// moving instance reaches the alias's stale ready publications of that |
| +// target and nothing else, and two candidates of different targets never |
| +// compare pairs. rankAlias is the alias name every witness below asks |
| +// about; rankHost (rank_test.go) is the moving instance it points at. |
| +const ( |
| + rankAlias = "www.shop.example" |
| + // otherHost is a second instance the alias can point at instead. |
| + otherHost = "cart.other.example" |
| +) |
| + |
| +// lhold is this box's own holding candidate for target: the real one |
| +// unless alias, named by its move. |
| +func lhold(target string, alias bool, inc, seq uint64, move string) candidate { |
| + return candidate{kind: candHolding, local: true, order: -1, alias: alias, account: "bab", app: "shop", |
| + targetHost: target, incarnation: inc, servingSeq: seq, |
| + hold: &holdingView{Host: target, Account: "bab", App: "shop", MoveID: move, Incarnation: inc, ServingSeq: seq}} |
| +} |
| + |
| +// permutations is every order of cs (cs is small). |
| +func permutations(cs []candidate) [][]candidate { |
| + if len(cs) <= 1 { |
| + return [][]candidate{slices.Clone(cs)} |
| + } |
| + var out [][]candidate |
| + for i := range cs { |
| + rest := append(slices.Clone(cs[:i]), cs[i+1:]...) |
| + for _, p := range permutations(rest) { |
| + out = append(out, append([]candidate{cs[i]}, p...)) |
| + } |
| + } |
| + return out |
| +} |
| + |
| +// TestTwoHoldsForOneAlias: when only holds answer an alias, the holding |
| +// fold decides. Between holds of different targets no pair is compared: |
| +// the lower order (the earlier file, this box's own first) keeps it, and |
| +// within one file the lower target name does, so the page shown never |
| +// depends on the order the candidates were collected in. Between two holds |
| +// of one target, the higher pair is the newer lifetime and wins wherever it |
| +// was published. |
| +func TestTwoHoldsForOneAlias(t *testing.T) { |
| + cases := []struct { |
| + name string |
| + cands []candidate |
| + move string |
| + }{ |
| + {"different targets: the lower order, whatever the pairs", |
| + []candidate{rhold(1, "betty", rankHost, true, 5, 5), rhold(0, "annie", otherHost, true, 1, 0)}, "mv_annie"}, |
| + {"one target: the higher pair, whatever the order", |
| + []candidate{rhold(0, "annie", rankHost, true, 1, 1), rhold(1, "betty", rankHost, true, 1, 2)}, "mv_betty"}, |
| + {"local first against another target", |
| + []candidate{rhold(0, "annie", otherHost, true, 1, 0), lhold(rankHost, true, 0, 0, "mv_local")}, "mv_local"}, |
| + {"a remote newer lifetime of the local hold's own target replaces it", |
| + []candidate{rhold(0, "annie", rankHost, true, 1, 2), lhold(rankHost, true, 1, 1, "mv_local")}, "mv_annie"}, |
| + {"one file, two targets: the lower target name", |
| + []candidate{rhold(0, "annie-shop", rankHost, true, 1, 1), rhold(0, "annie-cart", otherHost, true, 1, 1)}, "mv_annie-cart"}, |
| + {"one file, two targets and a third file: still the lower target name of the first file", |
| + []candidate{rhold(1, "betty", rankHost, true, 9, 9), rhold(0, "annie-shop", rankHost, true, 1, 1), rhold(0, "annie-cart", otherHost, true, 1, 1)}, "mv_annie-cart"}, |
| + } |
| + for _, tc := range cases { |
| + t.Run(tc.name, func(t *testing.T) { |
| + for _, p := range permutations(tc.cands) { |
| + wantHolding(t, rank(rankAlias, p, rankNow), tc.move, tc.name) |
| + } |
| + }) |
| + } |
| +} |