Skip to content

Commit 725a109

Browse files
authored
chore: validate state machine lock in tests (#2362)
1 parent 8fa788b commit 725a109

File tree

3 files changed

+52
-25
lines changed

3 files changed

+52
-25
lines changed

api/controllers/install/controller_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ func TestConfigureInstallation(t *testing.T) {
246246
assert.Eventually(t, func() bool {
247247
return sm.CurrentState() == tt.expectedState
248248
}, time.Second, 100*time.Millisecond, "state should be %s but is %s", tt.expectedState, sm.CurrentState())
249+
assert.False(t, sm.IsLockAcquired(), "state machine should not be locked after configuration")
249250

250251
mockManager.AssertExpectations(t)
251252
})
@@ -413,6 +414,7 @@ func TestRunHostPreflights(t *testing.T) {
413414
assert.Eventually(t, func() bool {
414415
return sm.CurrentState() == tt.expectedState
415416
}, time.Second, 100*time.Millisecond, "state should be %s but is %s", tt.expectedState, sm.CurrentState())
417+
assert.False(t, sm.IsLockAcquired(), "state machine should not be locked after running preflights")
416418

417419
mockPreflightManager.AssertExpectations(t)
418420
})
@@ -746,6 +748,7 @@ func TestSetupInfra(t *testing.T) {
746748
assert.Eventually(t, func() bool {
747749
return sm.CurrentState() == tt.expectedState
748750
}, time.Second, 100*time.Millisecond, "state should be %s but is %s", tt.expectedState, sm.CurrentState())
751+
assert.False(t, sm.IsLockAcquired(), "state machine should not be locked after running infra setup")
749752

750753
mockPreflightManager.AssertExpectations(t)
751754
mockInstallationManager.AssertExpectations(t)

api/internal/statemachine/statemachine.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type Interface interface {
2626
Transition(lock Lock, nextState State) error
2727
// AcquireLock acquires a lock on the state machine.
2828
AcquireLock() (Lock, error)
29+
// IsLockAcquired checks if a lock already exists on the state machine.
30+
IsLockAcquired() bool
2931
}
3032

3133
type Lock interface {
@@ -83,6 +85,13 @@ func (sm *stateMachine) AcquireLock() (Lock, error) {
8385
return sm.lock, nil
8486
}
8587

88+
func (sm *stateMachine) IsLockAcquired() bool {
89+
sm.mu.RLock()
90+
defer sm.mu.RUnlock()
91+
92+
return sm.lock != nil
93+
}
94+
8695
func (sm *stateMachine) ValidateTransition(lock Lock, nextState State) error {
8796
sm.mu.RLock()
8897
defer sm.mu.RUnlock()

api/internal/statemachine/statemachine_test.go

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ var validStateTransitions = map[State][]State{
4343

4444
func TestLockAcquisitionAndRelease(t *testing.T) {
4545
sm := New(StateNew, validStateTransitions)
46+
assert.False(t, sm.IsLockAcquired())
4647

4748
// Test valid lock acquisition
4849
lock, err := sm.AcquireLock()
4950
assert.NoError(t, err)
5051
assert.NotNil(t, lock)
52+
assert.True(t, sm.IsLockAcquired())
5153

5254
// Test transition with lock
5355
err = sm.Transition(lock, StateInstallationConfigured)
@@ -56,120 +58,118 @@ func TestLockAcquisitionAndRelease(t *testing.T) {
5658

5759
// Release lock
5860
lock.Release()
61+
assert.False(t, sm.IsLockAcquired())
5962

6063
// Test double lock acquisition
6164
lock, err = sm.AcquireLock()
6265
assert.NoError(t, err)
6366
assert.NotNil(t, lock)
67+
assert.True(t, sm.IsLockAcquired())
6468

6569
err = sm.Transition(lock, StatePreflightsRunning)
6670
assert.NoError(t, err)
6771

6872
// Release lock
6973
lock.Release()
7074
assert.Equal(t, StatePreflightsRunning, sm.CurrentState())
75+
assert.False(t, sm.IsLockAcquired())
7176
}
7277

7378
func TestDoubleLockAcquisition(t *testing.T) {
7479
sm := New(StateNew, validStateTransitions)
80+
assert.False(t, sm.IsLockAcquired())
7581

7682
lock1, err := sm.AcquireLock()
7783
assert.NoError(t, err)
84+
assert.True(t, sm.IsLockAcquired())
7885

7986
// Try to acquire second lock while first is held
8087
lock2, err := sm.AcquireLock()
8188
assert.Error(t, err, "second lock acquisition should fail while first is held")
8289
assert.Nil(t, lock2)
8390
assert.Contains(t, err.Error(), "lock already acquired")
91+
assert.True(t, sm.IsLockAcquired())
8492

8593
// Release first lock
8694
lock1.Release()
95+
assert.False(t, sm.IsLockAcquired())
8796

8897
// Now second lock should work
8998
lock2, err = sm.AcquireLock()
9099
assert.NoError(t, err)
91100
assert.NotNil(t, lock2)
101+
assert.True(t, sm.IsLockAcquired())
92102

93103
// Release second lock
94104
lock2.Release()
105+
assert.False(t, sm.IsLockAcquired())
95106
}
96107

97108
func TestLockReleaseAfterTransition(t *testing.T) {
98109
sm := New(StateNew, validStateTransitions)
110+
assert.False(t, sm.IsLockAcquired())
99111

100112
lock, err := sm.AcquireLock()
101113
assert.NoError(t, err)
114+
assert.True(t, sm.IsLockAcquired())
102115

103116
err = sm.Transition(lock, StateInstallationConfigured)
104117
assert.NoError(t, err)
118+
assert.True(t, sm.IsLockAcquired())
105119

106120
// Release lock after transition
107121
lock.Release()
122+
assert.False(t, sm.IsLockAcquired())
108123

109124
// State should remain changed
110125
assert.Equal(t, StateInstallationConfigured, sm.CurrentState())
111126
}
112127

113128
func TestDoubleLockRelease(t *testing.T) {
114129
sm := New(StateNew, validStateTransitions)
130+
assert.False(t, sm.IsLockAcquired())
115131

116132
lock, err := sm.AcquireLock()
117133
assert.NoError(t, err)
134+
assert.True(t, sm.IsLockAcquired())
118135

119136
// Release lock
120137
lock.Release()
138+
assert.False(t, sm.IsLockAcquired())
121139

122140
// Acquire another lock
123141
lock2, err := sm.AcquireLock()
124142
assert.NoError(t, err)
125143
assert.NotNil(t, lock2)
144+
assert.True(t, sm.IsLockAcquired())
126145

127146
// Second release should not actually do anything
128147
lock.Release()
148+
assert.True(t, sm.IsLockAcquired())
129149

130150
// Should not be able to acquire lock after as the other lock is still held
131151
nilLock, err := sm.AcquireLock()
132152
assert.Error(t, err, "should not be able to acquire lock after as the other lock is still held")
133153
assert.Nil(t, nilLock)
154+
assert.True(t, sm.IsLockAcquired())
134155

135156
// Release the second lock
136157
lock2.Release()
158+
assert.False(t, sm.IsLockAcquired())
137159

138160
// Should be able to acquire lock after the other lock is released
139161
lock3, err := sm.AcquireLock()
140162
assert.NoError(t, err)
141163
assert.NotNil(t, lock3)
164+
assert.True(t, sm.IsLockAcquired())
142165

143166
lock3.Release()
144-
}
145-
146-
func TestConcurrentLockBlocking(t *testing.T) {
147-
sm := New(StateNew, validStateTransitions)
148-
149-
// Start first lock acquisition
150-
lock1, err := sm.AcquireLock()
151-
assert.NoError(t, err)
152-
assert.NotNil(t, lock1)
153-
154-
// Try to acquire second lock while first is held
155-
lock2, err := sm.AcquireLock()
156-
assert.Error(t, err, "second lock should fail while first is held")
157-
assert.Nil(t, lock2)
158-
assert.Contains(t, err.Error(), "lock already acquired")
159-
160-
// Release first lock
161-
lock1.Release()
162-
163-
// Now second lock should work
164-
lock2, err = sm.AcquireLock()
165-
assert.NoError(t, err)
166-
assert.NotNil(t, lock2)
167-
168-
lock2.Release()
167+
assert.False(t, sm.IsLockAcquired())
169168
}
170169

171170
func TestRaceConditionMultipleGoroutines(t *testing.T) {
172171
sm := New(StateNew, validStateTransitions)
172+
assert.False(t, sm.IsLockAcquired())
173173

174174
var wg sync.WaitGroup
175175
successCount := 0
@@ -203,10 +203,13 @@ func TestRaceConditionMultipleGoroutines(t *testing.T) {
203203
// Only one transition should succeed
204204
assert.Equal(t, 1, successCount, "only one transition should succeed")
205205
assert.Equal(t, StateInstallationConfigured, sm.CurrentState())
206+
// There should be no lock acquired at the end
207+
assert.False(t, sm.IsLockAcquired())
206208
}
207209

208210
func TestRaceConditionReadWrite(t *testing.T) {
209211
sm := New(StateNew, validStateTransitions)
212+
assert.False(t, sm.IsLockAcquired())
210213

211214
var wg sync.WaitGroup
212215

@@ -257,6 +260,8 @@ func TestRaceConditionReadWrite(t *testing.T) {
257260
finalState := sm.CurrentState()
258261
assert.True(t, finalState == StateInstallationConfigured || finalState == StatePreflightsRunning,
259262
"final state should be one of the expected states")
263+
// There should be no lock acquired at the end
264+
assert.False(t, sm.IsLockAcquired())
260265
}
261266

262267
func TestIsFinalState(t *testing.T) {
@@ -309,11 +314,13 @@ func TestFinalStateTransitionBlocking(t *testing.T) {
309314

310315
func TestMultiStateTransitionWithLock(t *testing.T) {
311316
sm := New(StateNew, validStateTransitions)
317+
assert.False(t, sm.IsLockAcquired())
312318

313319
// Acquire lock and transition through multiple states
314320
lock, err := sm.AcquireLock()
315321
assert.NoError(t, err)
316322
assert.NotNil(t, lock)
323+
assert.True(t, sm.IsLockAcquired())
317324

318325
// Transition 1: New -> StateInstallationConfigured
319326
err = sm.Transition(lock, StateInstallationConfigured)
@@ -335,18 +342,22 @@ func TestMultiStateTransitionWithLock(t *testing.T) {
335342
assert.NoError(t, err)
336343
assert.Equal(t, StateInfrastructureInstalling, sm.CurrentState())
337344

345+
assert.True(t, sm.IsLockAcquired())
338346
// Release the lock
339347
lock.Release()
348+
assert.False(t, sm.IsLockAcquired())
340349

341350
// State should be the final state in the transition chain
342351
assert.Equal(t, StateInfrastructureInstalling, sm.CurrentState(), "state should be the final transitioned state after lock release")
343352
}
344353

345354
func TestInvalidTransition(t *testing.T) {
346355
sm := New(StateNew, validStateTransitions)
356+
assert.False(t, sm.IsLockAcquired())
347357

348358
lock, err := sm.AcquireLock()
349359
assert.NoError(t, err)
360+
assert.True(t, sm.IsLockAcquired())
350361

351362
// Try invalid transition
352363
err = sm.Transition(lock, StateSucceeded)
@@ -356,12 +367,15 @@ func TestInvalidTransition(t *testing.T) {
356367
// State should remain unchanged
357368
assert.Equal(t, StateNew, sm.CurrentState())
358369

370+
assert.True(t, sm.IsLockAcquired())
359371
lock.Release()
372+
assert.False(t, sm.IsLockAcquired())
360373
}
361374

362375
func TestTransitionWithoutLock(t *testing.T) {
363376
sm := New(StateNew, validStateTransitions)
364377

378+
assert.False(t, sm.IsLockAcquired())
365379
err := sm.Transition(nil, StateInstallationConfigured)
366380
assert.Error(t, err, "transition should be invalid")
367381
assert.Contains(t, err.Error(), "lock not acquired")
@@ -370,6 +384,7 @@ func TestTransitionWithoutLock(t *testing.T) {
370384
func TestValidateTransitionWithoutLock(t *testing.T) {
371385
sm := New(StateNew, validStateTransitions)
372386

387+
assert.False(t, sm.IsLockAcquired())
373388
err := sm.ValidateTransition(nil, StateInstallationConfigured)
374389
assert.Error(t, err, "transition should be invalid")
375390
assert.Contains(t, err.Error(), "lock not acquired")

0 commit comments

Comments
 (0)