Resolve a group's order rules by precedence, not as a cycle (hq issue 309)
The group feat/a-machine-joins-through-the-tunnel was refused: the controller's member moved the build agent, which builds the node-engine (built by: controller first), and the node-engine goes before the controller (engine before controller: engine first). Both rules applied to one pair in opposite directions, and every two-way pair was a cycle. Rules now have a precedence (hq ADR 0249): a declared after: line, then what the graph and the change say (built by, version skew), then the rollout default engine-before-controller. The higher rule decides the pair, which says what it won over. Rules of one rank both ways are still refused, as a contradiction naming both rules and how to declare the order. TestReplay309 replays the group with only what orderOf had before.
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -63,11 +64,82 @@ func TestAGroupIsOrderedByTheGraphAndWhatItsPullRequestsSay(t *testing.T) {
|
||||
t.Fatalf("another reading ordered %v", again.Order)
|
||||
}
|
||||
|
||||
// A declared order against an inferred one is a cycle: named, and nothing ordered.
|
||||
// A declared order against an inferred one wins (novox/hq ADR 0249), and the pair says what it won over.
|
||||
members[1].After = []string{"mesh-catalog"}
|
||||
got = orderOf(members, reach, edges)
|
||||
if !reflect.DeepEqual(got.Cycle, []string{"cat", "ctl"}) {
|
||||
t.Fatalf("the cycle is %v, ordered %v", got.Cycle, got.Order)
|
||||
if len(got.Cycle) > 0 || len(got.Contradictions) > 0 {
|
||||
t.Fatalf("a declared order did not win: cycle %v, %+v", got.Cycle, got.Contradictions)
|
||||
}
|
||||
if want := []string{"agent", "lab", "app", "cat", "host", "ctl"}; !reflect.DeepEqual(got.Order, want) {
|
||||
t.Fatalf("ordered %v, wanted %v", got.Order, want)
|
||||
}
|
||||
if !slices.ContainsFunc(got.Pairs, func(p orderPair) bool {
|
||||
return p.Before == "cat" && p.After == "ctl" && p.Why == orderDeclared && reflect.DeepEqual(p.Over, []string{orderVersionSkew})
|
||||
}) || slices.ContainsFunc(got.Pairs, func(p orderPair) bool { return p.Before == "ctl" && p.After == "cat" }) {
|
||||
t.Fatalf("the declared pair does not say it won over version skew: %+v", got.Pairs)
|
||||
}
|
||||
|
||||
// Declared both ways is a contradiction no precedence resolves: a cycle, and how to end it.
|
||||
members[0].After = []string{"novox/mesh-controller"}
|
||||
got = orderOf(members, reach, edges)
|
||||
if !reflect.DeepEqual(got.Cycle, []string{"cat", "ctl"}) || len(got.Contradictions) != 1 {
|
||||
t.Fatalf("the cycle is %v, ordered %v, contradictions %+v", got.Cycle, got.Order, got.Contradictions)
|
||||
}
|
||||
if c := got.Contradictions[0]; !reflect.DeepEqual(c.Members, []string{"cat", "ctl"}) ||
|
||||
!strings.Contains(c.Said, "remove one of the `after:` lines") {
|
||||
t.Fatalf("the contradiction is said %+v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// novox/hq ADR 0249: when two rules order one pair both ways, the rule of higher precedence wins and the
|
||||
// pair says what it overruled; rules of one rank both ways are refused, naming both and how to declare.
|
||||
func TestAnOrderRuleYieldsToAStrongerOne(t *testing.T) {
|
||||
members := []orderMember{
|
||||
{ID: "ctl", Repository: "novox/mesh-controller", Number: 7},
|
||||
{ID: "host", Repository: "novox/mesh-host", Number: 9},
|
||||
}
|
||||
reach := map[string]orderReach{
|
||||
"ctl": {Moved: []string{"mesh-controller", "build-agent"}},
|
||||
"host": {Moved: []string{"mesh-host"}},
|
||||
}
|
||||
builtBy := []inventory.Edge{{From: "mesh-host", To: "build-agent", Kind: inventory.EdgeBuiltBy}}
|
||||
|
||||
// Built by outranks engine before controller.
|
||||
got := orderOf(members, reach, builtBy)
|
||||
if !reflect.DeepEqual(got.Order, []string{"ctl", "host"}) || len(got.Cycle) > 0 {
|
||||
t.Fatalf("ordered %v, cycle %v", got.Order, got.Cycle)
|
||||
}
|
||||
if want := []orderPair{{Before: "ctl", After: "host", Why: orderBuiltBy, Over: []string{orderEngineFirst}}}; !reflect.DeepEqual(got.Pairs, want) {
|
||||
t.Fatalf("pairs %+v, wanted %+v", got.Pairs, want)
|
||||
}
|
||||
|
||||
// A declared line outranks both.
|
||||
members[0].After = []string{"mesh-host"}
|
||||
got = orderOf(members, reach, builtBy)
|
||||
if !reflect.DeepEqual(got.Order, []string{"host", "ctl"}) || len(got.Cycle) > 0 ||
|
||||
!reflect.DeepEqual(got.Pairs[0].Over, []string{orderBuiltBy}) || got.Pairs[0].Why != orderDeclared {
|
||||
t.Fatalf("a declared order did not win: %v %+v", got.Order, got.Pairs)
|
||||
}
|
||||
|
||||
// Without the build agent, engine before controller stands alone.
|
||||
members[0].After = nil
|
||||
got = orderOf(members, map[string]orderReach{"ctl": {Moved: []string{"mesh-controller"}}, "host": reach["host"]}, builtBy)
|
||||
if !reflect.DeepEqual(got.Order, []string{"host", "ctl"}) || got.Pairs[0].Why != orderEngineFirst || len(got.Pairs[0].Over) > 0 {
|
||||
t.Fatalf("engine before controller alone: %v %+v", got.Order, got.Pairs)
|
||||
}
|
||||
|
||||
// Built by both ways is one rank against itself: refused, both rules named, and how to declare.
|
||||
both := append(builtBy, inventory.Edge{From: "build-agent", To: "mesh-host", Kind: inventory.EdgeStandsOn})
|
||||
got = orderOf(members, reach, both)
|
||||
if !reflect.DeepEqual(got.Cycle, []string{"ctl", "host"}) || len(got.Order) > 0 || len(got.Contradictions) != 1 {
|
||||
t.Fatalf("a contradiction was ordered: %v, cycle %v, %+v", got.Order, got.Cycle, got.Contradictions)
|
||||
}
|
||||
said := got.Contradictions[0].Said
|
||||
for _, w := range []string{"built by puts ctl first", "built by puts host first",
|
||||
"`after: novox/mesh-controller` in novox/mesh-host#9's description", "`after: novox/mesh-host` in novox/mesh-controller#7's"} {
|
||||
if !strings.Contains(said, w) {
|
||||
t.Errorf("the contradiction does not say %q: %s", w, said)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user