Files
mesh-catalog/modules/mesh-config/cmd/mesh-config/pulls_test.go
T

496 lines
21 KiB
Go

package main
import (
"encoding/json"
"errors"
"strings"
"testing"
"time"
)
// Issue 1826 (novox/hq ADR 0502): a value held for the open pull request that introduces its key.
const thePull = "novox/mesh-catalog#434"
// n8nPullDeclares is what the controller tells of the pull request: n8n gets a required number media-gid.
func n8nPullDeclares() map[string]any {
return map[string]any{"n8n": map[string]any{"media-gid": map[string]any{"type": "number",
"description": "the media group", "required": true, "minimum": 1, "maximum": 65535}}}
}
// aHoldingMesh is a proposing mesh with n8n assigned to node-a and declaring nothing on the trunk (the case of the
// issue), and a stand-in forge.
func aHoldingMesh(t *testing.T) (*Config, *memStore, *standIn, *standForge) {
t.Helper()
c, m, r := aProposingMesh(t)
m.mirror(t, DeclaredConfigRead, "n8n", `{"module":"n8n","settings":{}}`)
m.assign(t, "node-a", "n8n")
f := &standForge{state: map[string][2]bool{}}
c.Forge = f
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
m.events, m.handActs = nil, nil
return c, m, r, f
}
type standForge struct {
state map[string][2]bool // pull -> open, merged
head string
err error
}
func (f *standForge) Pull(owner, repo string, n int) (bool, bool, string, error) {
if f.err != nil {
return false, false, "", f.err
}
st, ok := f.state[(PullEvent{Owner: owner, Repo: repo, Number: n}).name()]
if !ok {
return true, false, f.head, nil
}
return st[0], st[1], f.head, nil
}
func tell(t *testing.T, c *Config, pull, head string, declares map[string]any) *PullDeclaresAnswer {
t.Helper()
a, err := c.Do(Call{Action: "pull-declares", Pull: pull, Head: head, Declares: declares, Caller: ControllerCaller})
if err != nil {
t.Fatalf("pull-declares: %v", err)
}
return a.(*PullDeclaresAnswer)
}
// holdIt proposes media-gid=2000 for n8n on node-a held for the pull request and has the operator approve it.
func holdIt(t *testing.T, c *Config, m *memStore, r *standIn) string {
t.Helper()
a := propose(t, c, Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull,
Why: "n8n's container joins the media group, held for #434"})
r.choose(t, a.Proposal, OptionApprove, nil)
said, err := c.Decide()
if err != nil || len(said) != 1 || !strings.Contains(said[0], "approved by the operator and held") {
t.Fatalf("the Approve decided %v (%v)", said, err)
}
return a.Proposal
}
func keptCount(m *memStore) int { k, _ := m.Keys(KeptState); return len(k) }
// 1826/K1: mesh-config takes what a pull request declares from the controller alone, and keeps it per pull request.
func TestAPullRequestsDeclarationsAreTakenFromTheControllerAlone(t *testing.T) {
c, m, _, _ := aHoldingMesh(t)
for _, caller := range []string{"node-b.node-tools", Unproven, ""} {
if _, err := c.Do(Call{Action: "pull-declares", Pull: thePull, Head: "abc1234", Declares: n8nPullDeclares(),
Caller: caller}); err == nil || !strings.Contains(err.Error(), "controller") {
t.Errorf("pull-declares from %q: %v", caller, err)
}
}
for _, bad := range []Call{{Pull: "mesh-catalog#434", Head: "a"}, {Pull: thePull}, {Pull: "a/b#0", Head: "a"}} {
bad.Action, bad.Caller = "pull-declares", ControllerCaller
if _, err := c.Do(bad); err == nil {
t.Errorf("pull-declares %+v was taken", bad)
}
}
a := tell(t, c, thePull, "abc1234def", n8nPullDeclares())
if len(a.Modules) != 1 || a.Modules[0] != "n8n" {
t.Errorf("answered %+v", a)
}
e, _ := m.Get(PullsState, "novox.mesh-catalog.434")
var pd PullDecl
if e == nil || json.Unmarshal(e.Value, &pd) != nil || pd.Head != "abc1234def" ||
pd.Declares["n8n"]["media-gid"].Type != "number" || !pd.Declares["n8n"]["media-gid"].Required {
t.Errorf("kept %s", func() string {
if e == nil {
return "nothing"
}
return string(e.Value)
}())
}
// Nothing is resolved from a pull request's declaration.
if r, _ := m.Get(ResolvedState, ResolvedKey("node-a", "n8n", "media-gid")); r != nil {
t.Errorf("a pull request's declaration was resolved: %s", r.Value)
}
}
// 1826/K2: a proposal for a key only the pull request declares is refused without its declaration, for a mesh or
// node level, for a clear, for an undeclared key, and for a module not assigned to the node; and trunk-only
// proposals are judged as before.
func TestAProposalNamingAPullRequestIsJudgedAgainstItsDeclaration(t *testing.T) {
c, m, _, _ := aHoldingMesh(t)
try := func(call Call) error {
call.Action, call.Caller, call.Why = "propose", "node-b.node-tools", "held for 434"
_, err := c.Propose(call)
return err
}
base := Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull}
if err := try(base); err == nil || !strings.Contains(err.Error(), "holds no declaration for "+thePull) {
t.Errorf("without the pull request's declaration: %v", err)
}
// And without the pull argument it is the trunk's refusal, as before.
plain := base
plain.Pull = ""
if err := try(plain); err == nil || !strings.Contains(err.Error(), `n8n declares no value "media-gid"`) {
t.Errorf("the trunk's refusal changed: %v", err)
}
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
for name, tc := range map[string]struct {
mutate func(*Call)
want string
}{
"a mesh level": {func(c *Call) { c.Module, c.Node = "", "" }, "a module's own value on one node"},
"a node level": {func(c *Call) { c.Module = "" }, "a module's own value on one node"},
"a clear": {func(c *Call) { c.Clear, c.Value = true, nil }, "a clear is not held"},
"a key not added": {func(c *Call) { c.Key = "other" }, "does not add or change a value"},
"a bad value": {func(c *Call) { c.Value = 0.0 }, "below the minimum"},
"a wrong type": {func(c *Call) { c.Value = "2000" }, "is not a number"},
"another node": {func(c *Call) { c.Node = "node-b" }, "is not assigned to node-b"},
"another pull": {func(c *Call) { c.Pull = "novox/mesh-catalog#435" }, "holds no declaration for"},
"a malformed pull": {func(c *Call) { c.Pull = "434" }, "owner/repository#number"},
} {
call := base
tc.mutate(&call)
if err := try(call); err == nil || !strings.Contains(err.Error(), tc.want) {
t.Errorf("%s: %v, want %q", name, err, tc.want)
}
}
// A set or a clear through the verb takes no pull request.
if _, err := c.Change(Call{Action: "set", Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull,
Caller: "node-b.node-tools", Why: "w"}); err == nil || !strings.Contains(err.Error(), "only a proposal") {
t.Errorf("a set naming a pull request: %v", err)
}
if keptCount(m) != 0 {
t.Error("something was held")
}
}
// 1826/K2: every held value is asked whatever the key's trust; the ask says it is held and dropped on a close; on
// Approve the value is kept apart, never set or resolved; history, event and hand-act say it; held answers it.
func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
a := propose(t, c, Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "held for #434"})
if len(a.Reaches) != 0 {
t.Errorf("a held value reaches %v", a.Reaches)
}
ask := r.asks[a.Proposal]
if err := ask.Check(c.now()); err != nil {
t.Fatalf("the ask: %v", err)
}
for _, want := range []string{thePull, "merges", "dropped if it closes unmerged", "media-gid", "n8n", "node-a"} {
if !strings.Contains(ask.Explanation, want) {
t.Errorf("the ask does not say %q:\n%s", want, ask.Explanation)
}
}
if strings.Contains(ask.Explanation, "takes effect at once") || !strings.Contains(ask.Headline, "Hold") {
t.Errorf("the ask: %q\n%s", ask.Headline, ask.Explanation)
}
if ap := ask.Options[0]; !strings.Contains(ap.Does, thePull) || ap.Level == "" {
t.Errorf("the approve option does %q", ap.Does)
}
if keptCount(m) != 0 {
t.Fatal("a proposal held something")
}
r.choose(t, a.Proposal, OptionApprove, nil)
said, _ := c.Decide()
if len(said) != 1 || !strings.Contains(said[0], "held at abc1234d for "+thePull) {
t.Fatalf("decided %v", said)
}
// Kept apart: not a value, not resolved.
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set {
t.Error("an Approve set the value")
}
if e, _ := m.Get(ResolvedState, ResolvedKey("node-a", "n8n", "media-gid")); e != nil {
t.Errorf("an Approve resolved the value: %s", e.Value)
}
if keptCount(m) != 1 {
t.Fatalf("%d values held", keptCount(m))
}
if len(m.events) != 1 || m.events[0].Action != ActionHold || m.events[0].Pull != thePull ||
m.events[0].Key != "media-gid" || m.events[0].Head != "abc1234def" || len(m.events[0].Moved) != 0 ||
!strings.Contains(m.events[0].Approved, "approved by the operator") {
t.Errorf("raised %+v", m.events)
}
if len(m.handActs) != 1 || !strings.Contains(m.handActs[0], thePull) {
t.Errorf("hand-acts %q", m.handActs)
}
h, _ := c.History(Call{Module: "n8n", Node: "node-a"})
if len(h) != 1 || h[0].Action != ActionHold || h[0].Pull != thePull {
t.Errorf("history %+v", h)
}
// held answers it, at the head it is kept at, to the controller alone.
if _, err := c.Do(Call{Action: "held", Caller: "node-b.node-tools", Pulls: []PullAt{{thePull, "abc1234def"}}}); err == nil {
t.Error("held answered a caller that is not the controller")
}
got, err := c.Do(Call{Action: "held", Caller: ControllerCaller, Pulls: []PullAt{{thePull, "abc1234def"}}})
if err != nil {
t.Fatal(err)
}
ha := got.(*HeldAnswer)
if len(ha.Held) != 1 || ha.Held[0].Module != "n8n" || ha.Held[0].Node != "node-a" || ha.Held[0].Key != "media-gid" ||
string(ha.Held[0].Value) != "2000" || !strings.Contains(ha.Held[0].Approved, "approved by the operator") ||
ha.Held[0].Pull != thePull || len(ha.Skipped) != 0 {
t.Errorf("held answered %+v", ha)
}
// Another head, and another pull request, are not answered.
got, _ = c.Do(Call{Action: "held", Caller: ControllerCaller,
Pulls: []PullAt{{thePull, "ffff0000"}, {"novox/mesh-catalog#435", "abc"}}})
if ha = got.(*HeldAnswer); len(ha.Held) != 0 || len(ha.Skipped) != 1 || !strings.Contains(ha.Skipped[0].Why, "held at") {
t.Errorf("held at another head answered %+v", ha)
}
// The listing shows it as held, and the proposal as held for the pull request.
l, _ := c.List(Call{Module: "n8n"})
if len(l.Held) != 1 || len(l.Set) != 0 {
t.Errorf("the listing: held %+v set %+v", l.Held, l.Set)
}
rows, _ := c.Proposals(Call{})
if len(rows) != 1 || rows[0].Pull != thePull || rows[0].State != ProposalApproved {
t.Errorf("proposals %+v", rows)
}
}
// 1826/K2: an Approve is refused when the pull request's declaration changed after the operator was shown it.
func TestAnApproveOfAHeldValueIsRefusedWhenTheDeclarationChanged(t *testing.T) {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
a := propose(t, c, Call{Module: "n8n", Node: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "w"})
changed := n8nPullDeclares()
changed["n8n"].(map[string]any)["media-gid"].(map[string]any)["maximum"] = 1000.0
tell(t, c, thePull, "def5678abc", changed)
r.choose(t, a.Proposal, OptionApprove, nil)
said, _ := c.Decide()
if len(said) != 1 || !strings.Contains(said[0], "nothing is held") || keptCount(m) != 0 {
t.Errorf("decided %v; %d held", said, keptCount(m))
}
}
// 1826/K1: at a new head a held value is kept if its key is declared the same and it still passes; otherwise it is
// dropped, recorded, raised and said.
func TestANewHeadKeepsAHeldValueOnlyWhileItsDeclarationIsUnchanged(t *testing.T) {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
m.events = nil
// The same declaration at a new head (the description may differ): kept, moved to the new head.
same := n8nPullDeclares()
same["n8n"].(map[string]any)["media-gid"].(map[string]any)["description"] = "reworded"
if a := tell(t, c, thePull, "1111111111", same); len(a.Dropped) != 0 {
t.Errorf("an unchanged declaration dropped %v", a.Dropped)
}
got, _ := c.Do(Call{Action: "held", Caller: ControllerCaller, Pulls: []PullAt{{thePull, "1111111111"}}})
if len(got.(*HeldAnswer).Held) != 1 || len(m.events) != 0 {
t.Errorf("after an unchanged head: %+v, events %+v", got, m.events)
}
// A changed declaration: dropped.
changed := n8nPullDeclares()
changed["n8n"].(map[string]any)["media-gid"].(map[string]any)["maximum"] = 1000.0
a := tell(t, c, thePull, "2222222222", changed)
if len(a.Dropped) != 1 || !strings.Contains(a.Dropped[0], "declares the key otherwise") || keptCount(m) != 0 {
t.Fatalf("a changed declaration: %+v, %d held", a, keptCount(m))
}
if len(m.events) != 1 || m.events[0].Action != ActionDrop || m.events[0].Pull != thePull {
t.Errorf("raised %+v", m.events)
}
got, _ = c.Do(Call{Action: "held", Caller: ControllerCaller, Pulls: []PullAt{{thePull, "2222222222"}}})
if len(got.(*HeldAnswer).Held) != 0 {
t.Error("a dropped value was answered")
}
// A head that no longer adds the key drops it too.
tell(t, c, thePull, "3333333333", n8nPullDeclares())
holdIt(t, c, m, r)
if a = tell(t, c, thePull, "4444444444", map[string]any{}); len(a.Dropped) != 1 ||
!strings.Contains(a.Dropped[0], "no longer adds or changes") {
t.Errorf("a head without the key: %+v", a)
}
}
// 1826/K3: the merge writes the held value as the module's own value with its approval kept, before the trunk
// declares the key; it resolves right after the declared-config mirror moves.
func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *testing.T) {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
approved := func() string {
raw, _ := m.Get(KeptState, "novox.mesh-catalog.434.node-a.n8n.media-gid")
var k KeptValue
_ = json.Unmarshal(raw.Value, &k)
return k.Approved
}()
m.events, m.handActs = nil, nil
said := c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: "abc1234def"}, true)
if len(said) == 0 {
t.Fatal("the merge said nothing")
}
v, set := storedValue(t, m, "module.node-a.n8n.media-gid")
if !set || string(v.Value) != "2000" || v.Approved != approved || v.Approved == "" || v.By != "the operator" ||
!strings.Contains(v.Why, thePull) {
t.Fatalf("the merge wrote %+v", v)
}
if keptCount(m) != 0 {
t.Error("the value is still held after the merge")
}
if p, _ := m.Keys(PullsState); len(p) != 0 {
t.Errorf("the pull request's declarations are still kept: %v", p)
}
if len(m.events) != 1 || m.events[0].Action != "set" || m.events[0].Pull != thePull ||
m.events[0].Approved != approved || len(m.handActs) != 1 {
t.Errorf("raised %+v, recorded %q", m.events, m.handActs)
}
// Not resolved before the trunk declares it.
if e, _ := m.Get(ResolvedState, ResolvedKey("node-a", "n8n", "media-gid")); e != nil {
t.Errorf("resolved before the trunk declares the key: %s", e.Value)
}
m.mirror(t, DeclaredConfigRead, "n8n", `{"module":"n8n","settings":{"media-gid":{"kind":"","default":null,"why":"",`+
`"type":"number","required":true,"minimum":1,"maximum":65535}}}`)
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
if res := m.resolved(t, "node-a", "n8n", "media-gid"); string(res.Value) != "2000" || res.Source != SourceModule {
t.Errorf("media-gid resolves as %+v", res)
}
l, _ := c.List(Call{Module: "n8n", Node: "node-a"})
if len(l.Set) != 1 || l.Set[0].Approved != approved || len(l.Held) != 0 {
t.Errorf("the listing: set %+v held %+v", l.Set, l.Held)
}
// A merge heard twice sets nothing twice.
m.events = nil
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: "abc1234def"}, true)
if len(m.events) != 0 {
t.Errorf("a second merge raised %+v", m.events)
}
}
// 1826/K3: a pull request closed unmerged drops its held values and says so; nothing is set or resolved.
func TestAClosedPullRequestDropsItsHeldValuesAndSaysSo(t *testing.T) {
c, m, r, f := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
m.events = nil
f.state[thePull] = [2]bool{false, false}
said := c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434}, false)
if len(said) == 0 || !strings.Contains(strings.Join(said, "\n"), "closed unmerged: dropped, nothing was set") {
t.Errorf("said %v", said)
}
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set || keptCount(m) != 0 {
t.Error("a closed pull request left a value")
}
if len(m.events) != 1 || m.events[0].Action != ActionDrop || m.events[0].Pull != thePull ||
!strings.Contains(m.events[0].Why, "closed unmerged") {
t.Errorf("raised %+v", m.events)
}
if p, _ := m.Keys(PullsState); len(p) != 0 {
t.Errorf("declarations of a closed pull request kept: %v", p)
}
}
// 1826/K3: a close event for a pull request the forge says merged is the merge.
func TestACloseEventForAMergedPullRequestIsItsMerge(t *testing.T) {
c, m, r, f := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
f.state[thePull] = [2]bool{false, true}
f.head = "abc1234def"
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434}, false)
if v, set := storedValue(t, m, "module.node-a.n8n.media-gid"); !set || string(v.Value) != "2000" {
t.Errorf("the merge of a pull request announced closed set %+v", v)
}
}
// 1826/K3: the five-minute look finds a merge, a close, and an open pull request, and says a forge that does not
// answer.
func TestTheLookAtTheForgeFindsAMissedMergeOrCloseAndLeavesAnOpenPullRequest(t *testing.T) {
c, m, r, f := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
if said := c.SweepPulls(); len(said) != 0 || keptCount(m) != 1 {
t.Errorf("an open pull request: said %v, %d held", said, keptCount(m))
}
f.err = errors.New("the forge is down")
if said := c.SweepPulls(); len(said) != 1 || !strings.Contains(said[0], "does not say whether") || keptCount(m) != 1 {
t.Errorf("a silent forge: said %v", said)
}
f.err = nil
f.head = "abc1234def"
f.state[thePull] = [2]bool{false, true}
c.SweepPulls()
if v, set := storedValue(t, m, "module.node-a.n8n.media-gid"); !set || string(v.Value) != "2000" || keptCount(m) != 0 {
t.Errorf("a missed merge: %+v", v)
}
// A missed close.
tell(t, c, "novox/mesh-catalog#440", "abc", n8nPullDeclares())
f.state["novox/mesh-catalog#440"] = [2]bool{false, false}
c.SweepPulls()
if p, _ := m.Keys(PullsState); len(p) != 0 {
t.Errorf("a missed close left %v", p)
}
}
// 1826/K1: the new arguments are read strictly and the new actions are the verb's.
func TestThePullArgumentsAreRead(t *testing.T) {
call, err := callOf(map[string]any{"action": "pull-declares", "pull": " " + thePull + " ", "head": "abc",
"declares": map[string]any{"n8n": map[string]any{}}, "pulls": []any{map[string]any{"pull": thePull, "head": "h"}}})
if err != nil || call.Pull != thePull || call.Head != "abc" || len(call.Declares) != 1 || len(call.Pulls) != 1 ||
call.Pulls[0].Head != "h" {
t.Errorf("read %+v (%v)", call, err)
}
if _, err := callOf(map[string]any{"declares": "x"}); err == nil {
t.Error("declares that is not an object was read")
}
if _, err := callOf(map[string]any{"pulls": "x"}); err == nil {
t.Error("pulls that is not a list was read")
}
if _, err := callOf(map[string]any{"bogus": 1}); err == nil || !strings.Contains(err.Error(), "pull") {
t.Errorf("an unknown argument: %v", err)
}
_ = time.Second
}
// 1826 review r1: the merge applies a value only if it is held at the merged head; otherwise it is dropped and said.
func TestTheMergeAppliesOnlyAValueHeldAtTheMergedHead(t *testing.T) {
for name, head := range map[string]string{"another head": "9999999999", "no head told": ""} {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
said := c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: head}, true)
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set || keptCount(m) != 0 ||
!strings.Contains(strings.Join(said, "\n"), "dropped, nothing was set") {
t.Errorf("%s: set=%v held=%d said %v", name, set, keptCount(m), said)
}
if len(m.events) == 0 || m.events[len(m.events)-1].Action != ActionDrop {
t.Errorf("%s: raised %+v", name, m.events)
}
}
}
// 1826 review r1: a later head without the declaration drops the value, so the merge has nothing to apply.
func TestALaterHeadWithoutTheDeclarationLeavesNothingToApplyAtTheMerge(t *testing.T) {
c, m, r, _ := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
tell(t, c, thePull, "bbbbbbbbbb", map[string]any{})
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: "bbbbbbbbbb"}, true)
if _, set := storedValue(t, m, "module.node-a.n8n.media-gid"); set {
t.Error("a value held for a declaration the merged head lacks was set")
}
}
// 1826 review r1: a close the forge cannot be asked about drops nothing; the five-minute look decides.
func TestACloseThatTheForgeCannotBeAskedAboutDropsNothing(t *testing.T) {
c, m, r, f := aHoldingMesh(t)
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
holdIt(t, c, m, r)
f.err = errors.New("the forge is down")
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434}, false)
if keptCount(m) != 1 {
t.Fatal("a close the forge could not confirm dropped the value")
}
f.err = nil
f.state[thePull] = [2]bool{false, false}
c.SweepPulls()
if keptCount(m) != 0 {
t.Error("the look did not drop it")
}
}