The module graph carries verb and bus-state edges, and leaves out every verb edge on a cycle (ADR 0614 §2, §3, §5, §6; issue 774)
A graph node now names what its manifest invokes and serves, and the verb edges the graph leaves out; a bus-state entry is a requirement of bus-state. Before and Cycle count the kept verb edges beside bases and awaited provisions, never a package or toolchain edge. LeaveOut leaves out every verb edge whose ends reach each other, so the builder's remove and a holder's builder_versions hold nothing and the check never refuses them. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
+222
-13
@@ -91,6 +91,10 @@ func ParseKey(key string) (string, error) {
|
||||
// build module and the toolchain it was computed with. Provides are the provisions its manifest offers (its own name is
|
||||
// provided too and is not listed), Requires what it requires and does not require optionally, and Awaits those of
|
||||
// Requires a want of it waits for (catalogue.Awaited, ADR 0570 §4): the provision edges that order a source revision.
|
||||
// A bus-state entry of its manifest is a requirement of the provision BusState (ADR 0614 §5). Invokes are the seat verbs
|
||||
// (`seat:<seat>.<verb>`) and module tools (`<module>.<tool>`) its manifest and its own bus accounts invoke, Serves those
|
||||
// its claims serve and its own tools answer, in the same two forms (ADR 0614 §2), and LeftOut those of Invokes whose
|
||||
// verb edge lies on a cycle and is left out of the graph, so no want waits on it (§3; LeaveOut works them out).
|
||||
// Every list is sorted and each entry once; an encode sorts them.
|
||||
type Node struct {
|
||||
V int `json:"v"`
|
||||
@@ -104,8 +108,15 @@ type Node struct {
|
||||
Provides []string `json:"provides,omitempty"`
|
||||
Requires []string `json:"requires,omitempty"`
|
||||
Awaits []string `json:"awaits,omitempty"`
|
||||
Invokes []string `json:"invokes,omitempty"`
|
||||
Serves []string `json:"serves,omitempty"`
|
||||
LeftOut []string `json:"left-out,omitempty"`
|
||||
}
|
||||
|
||||
// BusState is the provision a bus-state entry of a manifest requires (ADR 0614 §5): the bus module's build that offers it
|
||||
// goes before the module that asks bus state.
|
||||
const BusState = "bus-state"
|
||||
|
||||
// Toolchain is a build edge of one language: the build module that builds it (Module) and the toolchain version
|
||||
// and recipe version its fingerprint names.
|
||||
type Toolchain struct {
|
||||
@@ -152,9 +163,30 @@ func (g Node) check() error {
|
||||
return fmt.Errorf("graph: the graph node awaits %q, which it does not require", a)
|
||||
}
|
||||
}
|
||||
for _, list := range [][]string{g.Invokes, g.Serves} {
|
||||
for _, v := range list {
|
||||
if !isVerb(v) {
|
||||
return fmt.Errorf("graph: the graph node names %q, which is neither seat:<seat>.<verb> nor <module>.<tool>", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, l := range g.LeftOut {
|
||||
if !slices.Contains(g.Invokes, l) {
|
||||
return fmt.Errorf("graph: the graph node leaves out %q, which it does not invoke", l)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isVerb says whether v is a seat verb `seat:<seat>.<verb>` or a module tool `<module>.<tool>`.
|
||||
func isVerb(v string) bool {
|
||||
if _, _, ok := catalogue.SeatInvoke(v); ok {
|
||||
return true
|
||||
}
|
||||
module, tool, ok := strings.Cut(v, ".")
|
||||
return ok && module != "" && tool != "" && !strings.HasPrefix(v, "seat:")
|
||||
}
|
||||
|
||||
func sortedOnce(in []string) []string {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
@@ -167,6 +199,7 @@ func sortedOnce(in []string) []string {
|
||||
func (g Node) normal() Node {
|
||||
g.Bases, g.Packages = sortedOnce(g.Bases), sortedOnce(g.Packages)
|
||||
g.Provides, g.Requires, g.Awaits = sortedOnce(g.Provides), sortedOnce(g.Requires), sortedOnce(g.Awaits)
|
||||
g.Invokes, g.Serves, g.LeftOut = sortedOnce(g.Invokes), sortedOnce(g.Serves), sortedOnce(g.LeftOut)
|
||||
if len(g.Toolchains) == 0 {
|
||||
g.Toolchains = nil
|
||||
}
|
||||
@@ -304,9 +337,22 @@ func (g Graph) Dependents(module string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// Before are the modules name waits for, by the graph's ordering edges, sorted: its bases and, for each provision it
|
||||
// awaits, every other module that provides it (ADR 0603 §1). A toolchain or package edge orders nothing (§3).
|
||||
func (g Graph) Before(name string) []string {
|
||||
// Servers are the modules whose Serves names invoke (a seat verb or a module tool), sorted.
|
||||
func (g Graph) Servers(invoke string) []string {
|
||||
var out []string
|
||||
for name, n := range g {
|
||||
if slices.Contains(n.Serves, invoke) {
|
||||
out = append(out, name)
|
||||
}
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// waits are the modules name waits for, sorted: its bases, for each provision it awaits every other module that provides
|
||||
// it (ADR 0603 §1), and with verbs for each invoke the module serving it (ADR 0614 §2), those it leaves out (§3) left
|
||||
// out unless all is set.
|
||||
func (g Graph) waits(name string, verbs, all bool) []string {
|
||||
n := g[name]
|
||||
out := slices.Clone(n.Bases)
|
||||
for _, a := range n.Awaits {
|
||||
@@ -316,15 +362,101 @@ func (g Graph) Before(name string) []string {
|
||||
}
|
||||
}
|
||||
}
|
||||
if verbs {
|
||||
for _, v := range n.Invokes {
|
||||
if !all && slices.Contains(n.LeftOut, v) {
|
||||
continue
|
||||
}
|
||||
for _, s := range g.Servers(v) {
|
||||
if s != name || all {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sortedOnce(out)
|
||||
}
|
||||
|
||||
// Cycle is the first cycle of the graph's ordering edges, as the modules it passes through with the first named again
|
||||
// at its end (a -> b -> a), or nil for none (ADR 0603 §2: the check refuses it and names it). The ordering edges are a
|
||||
// module's bases and, for each provision it awaits, every other module that provides it: modules ordered on each other
|
||||
// would each wait for the other for good. A toolchain edge is no ordering edge (ADR 0603 §3: nothing waits on a build
|
||||
// module), and neither is a package edge. The modules are visited in sorted order, so the same graph names the same
|
||||
// cycle.
|
||||
// Before are the modules name waits for, by the graph's ordering edges, sorted: its bases, for each provision it awaits
|
||||
// (bus state among them, ADR 0614 §5) every other module that provides it (ADR 0603 §1), and for each verb it invokes
|
||||
// and the graph keeps, every other module that serves it (ADR 0614 §2, §3). A toolchain or package edge orders nothing:
|
||||
// it is a trigger, never a wait (ADR 0614 §6).
|
||||
func (g Graph) Before(name string) []string { return g.waits(name, true, false) }
|
||||
|
||||
// LeaveOut sets each node's LeftOut (ADR 0614 §3): every invoke whose verb edge lies on a cycle of the edges a part waits
|
||||
// on (bases, awaited provisions, every verb edge), whichever module the edge starts from, a module invoking what it
|
||||
// serves itself included. An edge lies on a cycle when the module it reaches reaches the module it starts from: both are
|
||||
// in one strongly connected part of the graph. What is left of the graph then has no cycle through a verb edge, so the
|
||||
// check's refusal (Cycle) never meets one.
|
||||
func (g Graph) LeaveOut() {
|
||||
part := g.parts(func(name string) []string { return g.waits(name, true, true) })
|
||||
for _, name := range slices.Sorted(maps.Keys(g)) {
|
||||
n := g[name]
|
||||
n.LeftOut = nil
|
||||
for _, v := range n.Invokes {
|
||||
for _, s := range g.Servers(v) {
|
||||
if part[s] == part[name] {
|
||||
n.LeftOut = append(n.LeftOut, v)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
g[name] = n.normal()
|
||||
}
|
||||
}
|
||||
|
||||
// parts numbers each module by the strongly connected part of edges it is in (Tarjan's), so two modules have one number
|
||||
// when each reaches the other.
|
||||
func (g Graph) parts(edges func(string) []string) map[string]int {
|
||||
index, low, part := map[string]int{}, map[string]int{}, map[string]int{}
|
||||
onStack := map[string]bool{}
|
||||
var stack []string
|
||||
next, parts := 0, 0
|
||||
var visit func(string)
|
||||
visit = func(name string) {
|
||||
index[name], low[name] = next, next
|
||||
next++
|
||||
stack = append(stack, name)
|
||||
onStack[name] = true
|
||||
for _, to := range edges(name) {
|
||||
if _, ours := g[to]; !ours {
|
||||
continue
|
||||
}
|
||||
if _, seen := index[to]; !seen {
|
||||
visit(to)
|
||||
low[name] = min(low[name], low[to])
|
||||
} else if onStack[to] {
|
||||
low[name] = min(low[name], index[to])
|
||||
}
|
||||
}
|
||||
if low[name] == index[name] {
|
||||
for {
|
||||
top := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
onStack[top] = false
|
||||
part[top] = parts
|
||||
if top == name {
|
||||
break
|
||||
}
|
||||
}
|
||||
parts++
|
||||
}
|
||||
}
|
||||
for _, name := range slices.Sorted(maps.Keys(g)) {
|
||||
if _, seen := index[name]; !seen {
|
||||
visit(name)
|
||||
}
|
||||
}
|
||||
return part
|
||||
}
|
||||
|
||||
// Cycle is the first cycle of the graph's ordering edges (Before), as the modules it passes through with the first named
|
||||
// again at its end (a -> b -> a), or nil for none (ADR 0603 §2: the check refuses it and names it). The ordering edges
|
||||
// are what a part waits on, provisions, bus state and the verb edges the graph keeps (ADR 0614 §6), and bases, since a
|
||||
// module built on another's artifact can never build in a cycle of bases: modules ordered on each other would each wait
|
||||
// for the other for good. A verb edge the graph leaves out (LeaveOut) is none, and neither is a toolchain edge (ADR 0603
|
||||
// §3: nothing waits on a build module) or a package edge (ADR 0614 §6: a trigger). The modules are visited in sorted
|
||||
// order, so the same graph names the same cycle.
|
||||
func (g Graph) Cycle() []string {
|
||||
edges := g.Before
|
||||
const (
|
||||
@@ -367,9 +499,11 @@ func (g Graph) Cycle() []string {
|
||||
}
|
||||
|
||||
// EdgesOf is the graph node of manifest's own edges: its bases (each module its build stands on), what it provides,
|
||||
// what it requires and does not require optionally, and of those what a want of it waits for (catalogue.Awaited, as the
|
||||
// assigner judges it), providers answering the manifests that provide a provision. The fingerprint, place, packages and
|
||||
// toolchains are the indexer's to add; this is the part a reader of manifests alone (the checker) works out the same way.
|
||||
// what it requires and does not require optionally, a bus-state entry as a requirement of BusState (ADR 0614 §5), and of
|
||||
// those what a want of it waits for (catalogue.Awaited, as the assigner judges it; BusState whenever a provider offers
|
||||
// it), providers answering the manifests that provide a provision; and the verbs and tools it invokes and serves
|
||||
// (InvokesOf, ServesOf). The fingerprint, place, packages, toolchains and the verb edges left out (LeaveOut, over the
|
||||
// whole graph) are the indexer's to add; this is the part a reader of manifests alone (the checker) works out the same way.
|
||||
func EdgesOf(manifest catalogue.Manifest, providers func(provision string) []catalogue.Manifest) Node {
|
||||
var n Node
|
||||
if manifest.Build != nil {
|
||||
@@ -388,11 +522,56 @@ func EdgesOf(manifest catalogue.Manifest, providers func(provision string) []cat
|
||||
}
|
||||
}
|
||||
n.Awaits = catalogue.Awaited(manifest, providers)
|
||||
if len(manifest.BusState) > 0 {
|
||||
n.Requires = append(n.Requires, BusState)
|
||||
if len(providers(BusState)) > 0 {
|
||||
n.Awaits = append(n.Awaits, BusState)
|
||||
}
|
||||
}
|
||||
n.Invokes, n.Serves = InvokesOf(manifest), ServesOf(manifest)
|
||||
return n.normal()
|
||||
}
|
||||
|
||||
// InvokesOf are the seat verbs and module tools manifest invokes, its own and its processes' and tools bundles' bus
|
||||
// accounts' (catalogue.Manifest.BusAccounts), sorted, each once; `*`, every tool, names no edge and is left out.
|
||||
func InvokesOf(manifest catalogue.Manifest) []string {
|
||||
all := slices.Clone(manifest.Invokes)
|
||||
for _, a := range manifest.BusAccounts() {
|
||||
all = append(all, a.Bus.Invokes...)
|
||||
}
|
||||
var out []string
|
||||
for _, v := range all {
|
||||
if isVerb(v) {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return sortedOnce(out)
|
||||
}
|
||||
|
||||
// ServesOf are the seat verbs manifest's claims serve (`seat:<seat>.<verb>`, catalogue.Claim.ServesFor) and its own
|
||||
// tools (`<module>.<tool>`), sorted, each once.
|
||||
func ServesOf(manifest catalogue.Manifest) []string {
|
||||
var out []string
|
||||
for _, c := range manifest.Claims {
|
||||
for _, v := range c.ServesFor(manifest) {
|
||||
out = append(out, "seat:"+c.Name+"."+v)
|
||||
}
|
||||
}
|
||||
for _, t := range manifest.Tools {
|
||||
out = append(out, manifest.Module+"."+t)
|
||||
}
|
||||
var verbs []string
|
||||
for _, v := range out {
|
||||
if isVerb(v) {
|
||||
verbs = append(verbs, v)
|
||||
}
|
||||
}
|
||||
return sortedOnce(verbs)
|
||||
}
|
||||
|
||||
// OfManifests is the graph of these manifests' own edges (EdgesOf), by module, each provision's providers being
|
||||
// the manifests among them that offer it: what the check works out for a change to find a cycle (ADR 0603 §2).
|
||||
// the manifests among them that offer it, with the verb edges on a cycle left out (LeaveOut): what the check works out
|
||||
// for a change to find a cycle (ADR 0603 §2, ADR 0614 §3).
|
||||
func OfManifests(manifests map[string]catalogue.Manifest) Graph {
|
||||
providers := func(provision string) []catalogue.Manifest {
|
||||
var out []catalogue.Manifest
|
||||
@@ -407,5 +586,35 @@ func OfManifests(manifests map[string]catalogue.Manifest) Graph {
|
||||
for name, m := range manifests {
|
||||
g[name] = EdgesOf(m, providers)
|
||||
}
|
||||
g.LeaveOut()
|
||||
return g
|
||||
}
|
||||
|
||||
// Served is whether a running build serves a seat verb or module tool (ADR 0614 §2, §7), counted from the holders' own
|
||||
// haves as a provider is (ADR 0603 §5): every have running healthy, by node and module, whose build's manifest serves it
|
||||
// (servesOf answers the verbs of a module's build at a fingerprint, ServesOf of its manifest, and whether it was read).
|
||||
// A build whose manifest cannot be read counts what fallback answers of its module (the graph's Serves, so a build read
|
||||
// late never holds its callers for good, or nothing), and is named in unread, sorted, so its reader says so.
|
||||
func Served(haves map[string]map[string]delivery.Have, servesOf func(module, fp string) ([]string, bool),
|
||||
fallback func(module string) []string) (served func(invoke string) bool, unread []string) {
|
||||
set := map[string]bool{}
|
||||
for _, node := range slices.Sorted(maps.Keys(haves)) {
|
||||
for _, module := range slices.Sorted(maps.Keys(haves[node])) {
|
||||
h := haves[node][module]
|
||||
if h.Phase != delivery.PhaseRunning || h.Health != delivery.HealthOK {
|
||||
continue
|
||||
}
|
||||
serves, ok := servesOf(module, h.FP)
|
||||
if !ok {
|
||||
unread = append(unread, module)
|
||||
if fallback != nil {
|
||||
serves = fallback(module)
|
||||
}
|
||||
}
|
||||
for _, v := range serves {
|
||||
set[v] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return func(v string) bool { return set[v] }, slices.Compact(slices.Sorted(slices.Values(unread)))
|
||||
}
|
||||
|
||||
+64
-10
@@ -2,6 +2,7 @@ package graph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -97,6 +98,39 @@ func TestGraphEdges(t *testing.T) {
|
||||
if got := mg.Cycle(); !reflect.DeepEqual(got, []string{"db", "web", "db"}) {
|
||||
t.Errorf("the cycle of manifests = %v", got)
|
||||
}
|
||||
// The verb and bus-state edges (ADR 0614 §2, §3, §5, §6), from manifests alone: a caller waits for the module serving
|
||||
// what it invokes, a bus-state entry requires bus-state of the bus module offering it, the builder's remove and the
|
||||
// holder's builder_versions close a cycle and both are left out, read from either side, a package edge gives no order,
|
||||
// and a module invoking its own tool is no wait.
|
||||
builder := catalogue.Manifest{Module: "builder", Tools: []string{"builder_versions"}, Invokes: []string{"seat:artifact-store.remove"}}
|
||||
store := catalogue.Manifest{Module: "store", Claims: []catalogue.Claim{{Name: "artifact-store", Scope: catalogue.ScopeMesh,
|
||||
Serves: []string{"remove"}}}, Invokes: []string{"builder.builder_versions", "*"}}
|
||||
verbs := map[string]catalogue.Manifest{
|
||||
"builder": builder, "store": store,
|
||||
"bus": {Module: "bus", Provides: []catalogue.Offer{{Name: BusState, Scope: catalogue.ScopeMesh}}},
|
||||
"keeper": {Module: "keeper", BusState: []catalogue.BusStateEntry{{Name: "cache", Storage: "key-value"}}},
|
||||
"caller": {Module: "caller", Invokes: []string{"seat:artifact-store.remove", "caller.own", "nobody.serves"}, Tools: []string{"own"}},
|
||||
}
|
||||
vg := OfManifests(verbs)
|
||||
for name, want := range map[string][]string{"caller": {"store"}, "keeper": {"bus"}, "builder": nil, "store": nil} {
|
||||
if got := vg.Before(name); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%s waits for %v, want %v", name, got, want)
|
||||
}
|
||||
}
|
||||
if b, s := vg["builder"], vg["store"]; !reflect.DeepEqual(b.LeftOut, []string{"seat:artifact-store.remove"}) ||
|
||||
!reflect.DeepEqual(s.LeftOut, []string{"builder.builder_versions"}) ||
|
||||
!reflect.DeepEqual(s.Serves, []string{"seat:artifact-store.remove"}) || !reflect.DeepEqual(s.Invokes, []string{"builder.builder_versions"}) {
|
||||
t.Errorf("the builder's cycle is not left out from either side: %+v %+v", b, s)
|
||||
}
|
||||
if k := vg["keeper"]; !reflect.DeepEqual(k.Requires, []string{BusState}) || !reflect.DeepEqual(k.Awaits, []string{BusState}) {
|
||||
t.Errorf("a bus-state entry is no requirement of bus-state: %+v", k)
|
||||
}
|
||||
if c := vg["caller"]; !reflect.DeepEqual(c.LeftOut, []string{"caller.own"}) || vg.Cycle() != nil {
|
||||
t.Errorf("a module's own tool is a wait, or a left-out verb edge a cycle: %+v %v", c, vg.Cycle())
|
||||
}
|
||||
if got := OfManifests(map[string]catalogue.Manifest{"keeper": verbs["keeper"]})["keeper"]; got.Awaits != nil {
|
||||
t.Errorf("bus-state with no module offering it is awaited: %+v", got)
|
||||
}
|
||||
for name, c := range map[string]struct {
|
||||
edit func(Graph)
|
||||
want []string
|
||||
@@ -115,6 +149,19 @@ func TestGraphEdges(t *testing.T) {
|
||||
w.Provides = []string{"site"}
|
||||
g["web"] = w
|
||||
}, []string{"db", "web", "db"}},
|
||||
"a package cycle orders nothing": {func(g Graph) {
|
||||
l := g["lib"]
|
||||
l.Packages = []string{"git.novox.be/novox/mesh-catalog/modules/app"}
|
||||
g["lib"] = l
|
||||
}, nil},
|
||||
"a verb edge with a provision, before LeaveOut leaves it out": {func(g Graph) {
|
||||
d := g["db"]
|
||||
d.Invokes = []string{"web.page"}
|
||||
g["db"] = d
|
||||
w := g["web"]
|
||||
w.Serves = []string{"web.page"}
|
||||
g["web"] = w
|
||||
}, []string{"db", "web", "db"}},
|
||||
"a requirement not awaited is no edge": {func(g Graph) {
|
||||
d := g["db"]
|
||||
d.Requires = []string{"site"}
|
||||
@@ -132,6 +179,9 @@ func TestGraphEdges(t *testing.T) {
|
||||
if got := h.Cycle(); !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("%s: cycle = %v, want %v", name, got, c.want)
|
||||
}
|
||||
if h.LeaveOut(); h.Cycle() != nil && slices.ContainsFunc(slices.Collect(maps.Values(h)), func(n Node) bool { return len(n.Invokes) > 0 }) {
|
||||
t.Errorf("%s: a cycle through a verb edge is left once its verb edges are left out: %v", name, h.Cycle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +190,9 @@ func sampleNode() Node {
|
||||
return Node{FP: "src2:" + strings.Repeat("a", 64), Repository: "novox/mesh-catalog", Path: "modules/gitea",
|
||||
Bases: []string{"z-base", "a-base"}, Packages: []string{"git.novox.be/novox/mesh-sdk/go"},
|
||||
Toolchains: map[string]Toolchain{"go": {Module: "build-go", Version: "go1.27.1", Recipe: "3"}},
|
||||
Provides: []string{"forge"}, Requires: []string{"postgres-database", "dns"}, Awaits: []string{"postgres-database"}}
|
||||
Provides: []string{"forge"}, Requires: []string{"postgres-database", "dns"}, Awaits: []string{"postgres-database"},
|
||||
Invokes: []string{"seat:mesh-catalog.modules", "builder.builder_versions"}, Serves: []string{"seat:forge.pulls"},
|
||||
LeftOut: []string{"builder.builder_versions"}}
|
||||
}
|
||||
|
||||
// nodeValues: a node round-trips with its lists sorted, an unknown version is refused, a node that does not hold is
|
||||
@@ -169,15 +221,17 @@ func nodeValues(t *testing.T) {
|
||||
change func(*Node)
|
||||
word string
|
||||
}{
|
||||
"both": {func(g *Node) { g.None = "why" }, "both"},
|
||||
"neither": {func(g *Node) { g.FP = "" }, "neither"},
|
||||
"an old form": {func(g *Node) { g.FP = "src1:x" }, "src2:"},
|
||||
"a dotted base": {func(g *Node) { g.Bases = []string{"a.b"} }, "a.b"},
|
||||
"a foreign package": {func(g *Node) { g.Packages = []string{"example.com/x"} }, "example.com/x"},
|
||||
"a toolchain of no module": {func(g *Node) { g.Toolchains = map[string]Toolchain{"go": {Version: "go1", Recipe: "1"}} }, "build module"},
|
||||
"a toolchain of no recipe": {func(g *Node) { g.Toolchains = map[string]Toolchain{"go": {Module: "b", Version: "go1"}} }, "recipe"},
|
||||
"awaiting what it does not require": {func(g *Node) { g.Awaits = []string{"other"} }, "other"},
|
||||
"no repository": {func(g *Node) { g.Repository = "" }, "repositor"},
|
||||
"both": {func(g *Node) { g.None = "why" }, "both"},
|
||||
"neither": {func(g *Node) { g.FP = "" }, "neither"},
|
||||
"an old form": {func(g *Node) { g.FP = "src1:x" }, "src2:"},
|
||||
"a dotted base": {func(g *Node) { g.Bases = []string{"a.b"} }, "a.b"},
|
||||
"a foreign package": {func(g *Node) { g.Packages = []string{"example.com/x"} }, "example.com/x"},
|
||||
"a toolchain of no module": {func(g *Node) { g.Toolchains = map[string]Toolchain{"go": {Version: "go1", Recipe: "1"}} }, "build module"},
|
||||
"a toolchain of no recipe": {func(g *Node) { g.Toolchains = map[string]Toolchain{"go": {Module: "b", Version: "go1"}} }, "recipe"},
|
||||
"awaiting what it does not require": {func(g *Node) { g.Awaits = []string{"other"} }, "other"},
|
||||
"no repository": {func(g *Node) { g.Repository = "" }, "repositor"},
|
||||
"leaving out what it does not invoke": {func(g *Node) { g.LeftOut = []string{"x.y"} }, "x.y"},
|
||||
"an invoke of no verb": {func(g *Node) { g.Invokes = []string{"seat:nothing"} }, "seat:nothing"},
|
||||
} {
|
||||
g := sampleNode()
|
||||
c.change(&g)
|
||||
|
||||
Reference in New Issue
Block a user