From 3687f54db7974885689bad34f3188ec459aa0c54 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Thu, 22 Oct 2026 11:03:13 -0400 Subject: [PATCH] Fix a test race that can log after the test ends --- libs/capability/settings_test.go | 37 ++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/libs/capability/settings_test.go b/libs/capability/settings_test.go index 5ca23b3d3..ab309c3e4 100644 --- a/libs/capability/settings_test.go +++ b/libs/capability/settings_test.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/settings/cresettings" "github.com/smartcontractkit/chainlink-common/pkg/types/core" ) @@ -42,7 +43,7 @@ func TestFollowHostSettingsAppliesUpdates(t *testing.T) { require.NoError(t, err) host := newFakeBroadcaster() - go followHostSettings(t.Context(), logger.Test(t), host, s) + host.follow(t, s) host.send(t, core.SettingsUpdate{Settings: "[global]\nTriggerRegistrationStatusUpdateTimeout = \"15s\"\n", Hash: "a"}) @@ -60,7 +61,7 @@ func TestFollowHostSettingsKeepsThePreviousSettingsOnFailure(t *testing.T) { require.NoError(t, err) host := newFakeBroadcaster() - go followHostSettings(t.Context(), logger.Test(t), host, s) + host.follow(t, s) host.send(t, core.SettingsUpdate{Settings: "[global]\nTriggerRegistrationStatusUpdateTimeout = \"15s\"\n", Hash: "a"}) host.send(t, core.SettingsUpdate{Settings: "not a settings payload", Hash: "b"}) @@ -79,7 +80,7 @@ func TestFollowHostSettingsResubscribes(t *testing.T) { host := newFakeBroadcaster() host.failFirst.Store(true) - go followHostSettings(t.Context(), logger.Test(t), host, s) + host.follow(t, s) host.send(t, core.SettingsUpdate{Settings: "[global]\n", Hash: "a"}) host.closeStream() @@ -100,8 +101,36 @@ func newFakeBroadcaster() *fakeBroadcaster { return &fakeBroadcaster{streams: make(chan chan core.SettingsUpdate, 1)} } -func (f *fakeBroadcaster) Subscribe(context.Context) (<-chan core.SettingsUpdate, error) { +// follow runs followHostSettings against f until the test ends, then waits for it to return, so +// nothing it logs can land after the test has completed. +func (f *fakeBroadcaster) follow(t *testing.T, settings *loop.AtomicSettings) { + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + go func() { + defer close(done) + followHostSettings(ctx, logger.Test(t), f, settings) + }() + t.Cleanup(func() { + cancel() + if f.current == nil { + // Wait for the follower's current subscription, if any, so it can be ended. + select { + case f.current = <-f.streams: + case <-done: + } + } + if f.current != nil { + f.closeStream() + } + <-done + }) +} + +func (f *fakeBroadcaster) Subscribe(ctx context.Context) (<-chan core.SettingsUpdate, error) { f.subscriptions.Add(1) + if err := ctx.Err(); err != nil { + return nil, err + } if f.failFirst.CompareAndSwap(true, false) { return nil, errors.New("host unavailable") }