From 0f1e1b09fd98d3c89be82514832075b392550388 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 9 Oct 2026 00:38:07 +0200 Subject: [PATCH] Change a test store's failure under its lock, which the keeper's goroutine reads TestAnUnreadableStoreClearsNothing wrote InMemory.Fail and its values unguarded while the keeper's teller appended to the same store, and the race detector failed mesh/repo-check on #170 (a test race on main, not the change). --- cmd/mesh-controller/conditions_test.go | 4 ++-- internal/conditions/memory.go | 8 ++++++++ internal/conditions/store_test.go | 6 ++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/mesh-controller/conditions_test.go b/cmd/mesh-controller/conditions_test.go index 6e33426d..35bdefe2 100644 --- a/cmd/mesh-controller/conditions_test.go +++ b/cmd/mesh-controller/conditions_test.go @@ -86,7 +86,7 @@ func TestASilenceThroughTheVerbHoldsAndTheConditionStaysOpen(t *testing.T) { func TestUnreadableConditionsAreNotAWellMesh(t *testing.T) { open := aMesh(t) _, store := withConditionsInMemory(t) - store.Fail = errors.New("the bus is away") + store.SetFail(errors.New("the bus is away")) asked, err := theThreeQuestions(t.Context(), open) if err != nil { t.Fatal(err) @@ -98,7 +98,7 @@ func TestUnreadableConditionsAreNotAWellMesh(t *testing.T) { if !strings.HasPrefix(said, "the open conditions could NOT be read") || strings.Contains(said, "no open conditions") { t.Fatalf("%s", said) } - store.Fail = nil + store.SetFail(nil) asked, _ = theThreeQuestions(t.Context(), open) said = printed(t, func() error { return printStatus(asked) }) if asked.well() && !strings.Contains(said, "no open conditions;") { diff --git a/internal/conditions/memory.go b/internal/conditions/memory.go index 1e23ee25..61134461 100644 --- a/internal/conditions/memory.go +++ b/internal/conditions/memory.go @@ -61,6 +61,14 @@ func (m *InMemory) Update(_ context.Context, key string, value []byte, revision return nil } +// SetFail makes every call fail with err from now on, or none with nil: under the store's lock, so a test may +// change it while the keeper's own goroutine is telling. +func (m *InMemory) SetFail(err error) { + m.mu.Lock() + defer m.mu.Unlock() + m.Fail = err +} + func (m *InMemory) Delete(_ context.Context, key string, revision uint64) error { m.mu.Lock() defer m.mu.Unlock() diff --git a/internal/conditions/store_test.go b/internal/conditions/store_test.go index 2d571abe..47d1e494 100644 --- a/internal/conditions/store_test.go +++ b/internal/conditions/store_test.go @@ -199,15 +199,17 @@ func TestAnUnreadableStoreClearsNothing(t *testing.T) { if _, err := k.Observe(ctx, silent("ace")); err != nil { t.Fatal(err) } - store.Fail = errors.New("the bus is away") + store.SetFail(errors.New("the bus is away")) if err := k.Reconcile(ctx, "S1", nil); err == nil { t.Fatal("reconciled against a store it could not read") } if _, err := k.Open(ctx); err == nil { t.Fatal("an unreadable store answered as read") } - store.Fail = nil + store.SetFail(nil) + store.mu.Lock() store.values["machine.g14.silent"] = Entry{Value: []byte("{not a condition"), Revision: 99} + store.mu.Unlock() if _, err := k.Open(ctx); err == nil || !strings.Contains(err.Error(), "machine.g14.silent") { t.Fatalf("an unreadable condition was left out rather than said: %v", err) }