Skip to content

Commit a74472e

Browse files
authored
funder: fix race test failure in ci (#883)
## Summary of Changes - Update 2 funder tests to lock access to some variables used for testing expectations to fix race test failures observed in CI: https://github.com/malbeclabs/doublezero/actions/runs/16423356624/job/46407460385 https://github.com/malbeclabs/doublezero/actions/runs/16423503263/job/46407954786?pr=882 ## Testing Verification - CI should be 🟢
1 parent 8c6ce3a commit a74472e

File tree

1 file changed

+33
-13
lines changed

1 file changed

+33
-13
lines changed

controlplane/funder/internal/funder/funder_test.go

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ func TestTelemetry_Funder_Run(t *testing.T) {
146146
ProgramIDFunc: func() solana.PublicKey { return solana.PublicKey{} },
147147
}
148148

149-
var transferAmount uint64
149+
var (
150+
transferAmount uint64
151+
mu sync.Mutex
152+
)
150153

151154
sol := &mockSolana{
152155
GetBalanceFunc: func(ctx context.Context, pubkey solana.PublicKey, _ solanarpc.CommitmentType) (*solanarpc.GetBalanceResult, error) {
@@ -175,7 +178,10 @@ func TestTelemetry_Funder_Run(t *testing.T) {
175178
require.Equal(t, devicePK, to)
176179
require.Equal(t, 5*solana.LAMPORTS_PER_SOL, amount)
177180

181+
mu.Lock()
178182
transferAmount = amount
183+
mu.Unlock()
184+
179185
tracker.mark("transfer")
180186
cancel()
181187
return solana.Signature{}, nil
@@ -197,7 +203,11 @@ func TestTelemetry_Funder_Run(t *testing.T) {
197203

198204
tracker.wait(t, 200*time.Millisecond)
199205

200-
require.Equal(t, 5*solana.LAMPORTS_PER_SOL, transferAmount)
206+
mu.Lock()
207+
got := transferAmount
208+
mu.Unlock()
209+
210+
require.Equal(t, 5*solana.LAMPORTS_PER_SOL, got)
201211
})
202212

203213
t.Run("no top up when device balance is above min", func(t *testing.T) {
@@ -602,6 +612,7 @@ func TestTelemetry_Funder_Run(t *testing.T) {
602612

603613
tracker.wait(t, 200*time.Millisecond)
604614
})
615+
605616
t.Run("handles multiple devices with mixed balances", func(t *testing.T) {
606617
t.Parallel()
607618

@@ -639,6 +650,7 @@ func TestTelemetry_Funder_Run(t *testing.T) {
639650
var (
640651
transferTo solana.PublicKey
641652
deviceLowBalanceCalls atomic.Int32
653+
mu sync.Mutex
642654
)
643655

644656
sol := &mockSolana{
@@ -651,7 +663,6 @@ func TestTelemetry_Funder_Run(t *testing.T) {
651663
return &solanarpc.GetBalanceResult{Value: 5 * solana.LAMPORTS_PER_SOL}, nil
652664
case deviceLow:
653665
tracker.mark("device-low")
654-
// Simulate balance increasing after the first poll in waitForBalance
655666
if deviceLowBalanceCalls.Add(1) >= 2 {
656667
return &solanarpc.GetBalanceResult{Value: 5 * solana.LAMPORTS_PER_SOL}, nil
657668
}
@@ -677,7 +688,10 @@ func TestTelemetry_Funder_Run(t *testing.T) {
677688
require.Equal(t, deviceLow, to)
678689
require.Equal(t, 5*solana.LAMPORTS_PER_SOL, amount)
679690

691+
mu.Lock()
680692
transferTo = to
693+
mu.Unlock()
694+
681695
tracker.mark("transfer")
682696
return solana.Signature{}, nil
683697
},
@@ -698,7 +712,10 @@ func TestTelemetry_Funder_Run(t *testing.T) {
698712
tracker.wait(t, 500*time.Millisecond)
699713
cancel()
700714

701-
require.Equal(t, deviceLow, transferTo, "should only transfer to underfunded device")
715+
mu.Lock()
716+
got := transferTo
717+
mu.Unlock()
718+
require.Equal(t, deviceLow, got, "should only transfer to underfunded device")
702719
})
703720

704721
t.Run("skips devices with zero MetricsPublisherPubKey", func(t *testing.T) {
@@ -781,7 +798,6 @@ func TestTelemetry_Funder_Run(t *testing.T) {
781798
ctx, cancel := context.WithCancel(context.Background())
782799
defer cancel()
783800

784-
// Make this short so test runs quickly
785801
const waitForBalanceTimeout = 100 * time.Millisecond
786802

787803
svc := &mockServiceability{
@@ -794,9 +810,10 @@ func TestTelemetry_Funder_Run(t *testing.T) {
794810
ProgramIDFunc: func() solana.PublicKey { return solana.PublicKey{} },
795811
}
796812

797-
// Track calls to waitForBalance internally
798-
var transferCalled atomic.Bool
799-
var balanceCheckCount atomic.Int32
813+
var (
814+
transferCalled atomic.Bool
815+
balanceCheckCount atomic.Int32
816+
)
800817

801818
sol := &mockSolana{
802819
GetBalanceFunc: func(ctx context.Context, pubkey solana.PublicKey, _ solanarpc.CommitmentType) (*solanarpc.GetBalanceResult, error) {
@@ -806,7 +823,7 @@ func TestTelemetry_Funder_Run(t *testing.T) {
806823
case devicePK:
807824
if transferCalled.Load() {
808825
balanceCheckCount.Add(1)
809-
return &solanarpc.GetBalanceResult{Value: uint64(0.01 * float64(solana.LAMPORTS_PER_SOL))}, nil // never enough
826+
return &solanarpc.GetBalanceResult{Value: uint64(0.01 * float64(solana.LAMPORTS_PER_SOL))}, nil
810827
}
811828
return &solanarpc.GetBalanceResult{Value: uint64(0.01 * float64(solana.LAMPORTS_PER_SOL))}, nil
812829
default:
@@ -844,16 +861,19 @@ func TestTelemetry_Funder_Run(t *testing.T) {
844861
require.NoError(t, err)
845862

846863
errCh := make(chan error, 1)
847-
go func() { errCh <- f.Run(ctx) }()
864+
var once sync.Once
865+
go func() {
866+
err := f.Run(ctx)
867+
once.Do(func() { errCh <- err })
868+
}()
848869

849870
select {
850-
case <-errCh:
851-
t.Fatal("funder unexpectedly exited early")
871+
case err := <-errCh:
872+
t.Fatalf("funder unexpectedly exited early: %v", err)
852873
case <-time.After(waitForBalanceTimeout + 200*time.Millisecond):
853874
cancel()
854875
}
855876

856-
// Ensure it checked balance multiple times (wait loop kicked in)
857877
require.GreaterOrEqual(t, balanceCheckCount.Load(), int32(2), "should retry balance during wait")
858878
})
859879

0 commit comments

Comments
 (0)