Test the capability gate, the sweep's mark re-read and the lock use of a send and of the sweep, each failing under its mutation (issues 1696, 1697; review round 2)

This commit is contained in:
2026-10-21 16:28:24 +02:00
parent a3b4e0601d
commit 087e9266d1
4 changed files with 263 additions and 0 deletions
@@ -467,3 +467,68 @@ func TestAPutThatFailsSendAfterSendRaisesAConditionAndClearsWhenItWorks(t *testi
t.Fatal("the count did not start again after a put that worked")
}
}
// aBusThatSays is a bus that keeps the declarations published to it and, at each, asks whether the node's bucket is held.
type aBusThatSays struct {
link.Bus
published chan string
heldAtPublish func() bool
}
func (b *aBusThatSays) PublishDeclaration(_ context.Context, node string, _ []byte) error {
if b.heldAtPublish != nil && !b.heldAtPublish() {
return errors.New("published while the node's bucket was not held")
}
b.published <- node
return errors.New("stop here: nothing is recorded")
}
type aSignerOfZeros struct{}
func (aSignerOfZeros) Sign(context.Context, []byte) ([]byte, error) { return make([]byte, 64), nil }
// whileHeld says whether the node's lock is held by someone right now, without keeping it.
func whileHeld(node string) bool {
got := make(chan struct{})
go func() {
defer lockFilesOf(node)()
close(got)
}()
select {
case <-got:
return false
case <-time.After(100 * time.Millisecond):
// held: the goroutine stays waiting and takes and releases the lock once it is let go.
return true
}
}
// 1696/AC9: a send holds the node's lock from the put to the publication, so the sweep never removes an object between them. The
// test goes through overTheBus.declare: while another holds the lock the declaration is not published, and at the publication the
// lock is held.
func TestASendHoldsTheNodesLockFromThePutToThePublication(t *testing.T) { // 1696/AC9
const node = "lock-send-node"
bus := &aBusThatSays{published: make(chan string, 1)}
bus.heldAtPublish = func() bool { return whileHeld(node) }
send := overTheBus{server: link.SendingOn(bus), signer: aSignerOfZeros{}}
release := lockFilesOf(node) // a sweep is judging this node's bucket
done := make(chan error, 1)
go func() {
_, err := send.declare(t.Context(), readyNode{node: node}, []byte(`{}`))
done <- err
}()
select {
case n := <-bus.published:
t.Fatalf("%s was sent its declaration while the sweep held the node's bucket", n)
case <-time.After(150 * time.Millisecond):
}
release()
select {
case <-bus.published:
case <-time.After(5 * time.Second):
t.Fatal("the send did not go on once the sweep let go")
}
if err := <-done; err == nil || !strings.Contains(err.Error(), "nothing is recorded") {
t.Fatalf("the send did not reach the publication (a lock held at it): %v", err)
}
}