Skip to content

Commit be893b3

Browse files
authored
replace time.Sleep with assert.Eventually in tests (#42)
* switch examples tests away from time.Sleep * remove time.Sleep from composite * adjust mutex behavior in httpcluster example to unlock before sending to config siphon * remove comment
1 parent e27be63 commit be893b3

File tree

4 files changed

+46
-44
lines changed

4 files changed

+46
-44
lines changed

examples/composite/reload_membership_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ func TestMembershipChangesBasic(t *testing.T) {
157157
}()
158158

159159
// Wait for initial configuration to be applied
160-
time.Sleep(250 * time.Millisecond)
160+
assert.Eventually(t, func() bool {
161+
return runner.IsRunning()
162+
}, 1*time.Second, 10*time.Millisecond)
161163

162164
// --- Test Step 1: Remove worker1, add worker3 ---
163165
worker3, err := NewTestWorker(t,

examples/composite/worker_test.go

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const (
2525
// --- Helper Functions ---
2626

2727
// testLogger creates a logger for tests, optionally capturing output.
28-
func testLogger(t *testing.T, capture bool) (*slog.Logger, *bytes.Buffer) { //nolint:thelper
28+
func testLogger(t *testing.T, capture bool) (*slog.Logger, *bytes.Buffer) {
29+
t.Helper()
2930
var buf bytes.Buffer
3031
w := io.Discard
3132
if capture {
@@ -40,14 +41,16 @@ func testLogger(t *testing.T, capture bool) (*slog.Logger, *bytes.Buffer) { //no
4041
}
4142

4243
// readWorkerConfig safely reads the current config from the worker.
43-
func readWorkerConfig(w *Worker) WorkerConfig {
44+
func readWorkerConfig(t *testing.T, w *Worker) WorkerConfig {
45+
t.Helper()
4446
w.mu.Lock()
4547
defer w.mu.Unlock()
4648
return w.config // Return a copy
4749
}
4850

4951
// readWorkerName safely reads the current name from the worker.
50-
func readWorkerName(w *Worker) string {
52+
func readWorkerName(t *testing.T, w *Worker) string {
53+
t.Helper()
5154
w.mu.Lock()
5255
defer w.mu.Unlock()
5356
return w.name
@@ -218,7 +221,7 @@ func TestWorker_Run_ContextCancel(t *testing.T) {
218221
// TestWorker_ReloadWithConfig tests applying valid and invalid configs via ReloadWithConfig.
219222
func TestWorker_ReloadWithConfig(t *testing.T) {
220223
t.Parallel()
221-
logger, logBuf := testLogger(t, true) // Capture logs
224+
logger, _ := testLogger(t, false)
222225

223226
originalConfig := WorkerConfig{
224227
Interval: 100 * time.Millisecond,
@@ -242,8 +245,10 @@ func TestWorker_ReloadWithConfig(t *testing.T) {
242245

243246
go func() {
244247
defer wg.Done()
245-
// Ignore error here, checked elsewhere
246-
_ = worker.Run(ctx) //nolint:errcheck
248+
err = worker.Run(ctx)
249+
if err != nil {
250+
t.Errorf("Worker run failed: %v", err)
251+
}
247252
}()
248253

249254
// Wait for worker to start running (at least one tick)
@@ -261,61 +266,47 @@ func TestWorker_ReloadWithConfig(t *testing.T) {
261266

262267
// Wait for the configuration to be applied using Eventually
263268
require.Eventually(t, func() bool {
264-
current := readWorkerConfig(worker)
265-
name := readWorkerName(worker)
269+
current := readWorkerConfig(t, worker)
270+
name := readWorkerName(t, worker)
266271
return current.Interval == newConfig.Interval && current.JobName == newConfig.JobName &&
267272
name == newConfig.JobName
268273
}, 1*time.Second, pollInterval, "Worker config was not updated after valid ReloadWithConfig")
269-
t.Logf("Worker config updated successfully to: %+v", readWorkerConfig(worker))
274+
t.Logf("Worker config updated successfully to: %+v", readWorkerConfig(t, worker))
270275

271276
// --- Test Invalid Config Type ---
272-
configAfterValidReload := readWorkerConfig(worker) // Store state before invalid reload
277+
configAfterValidReload := readWorkerConfig(t, worker) // Store state before invalid reload
273278
t.Log("Reloading with invalid type 'string'")
274279
worker.ReloadWithConfig("invalid type") // Pass a non-WorkerConfig type
275280

276281
// Wait a short time and assert config hasn't changed
277-
time.Sleep(50 * time.Millisecond) // Allow select loop to process if needed
278-
currentConfig := readWorkerConfig(worker)
282+
assert.Eventually(t, func() bool {
283+
return worker.tickCount.Load() > 0
284+
}, 1*time.Second, pollInterval, "Worker did not start ticking")
285+
286+
currentConfig := readWorkerConfig(t, worker)
279287
assert.Equal(
280288
t,
281289
configAfterValidReload,
282290
currentConfig,
283291
"Config should not change after invalid type reload",
284292
)
285-
assert.Contains(
286-
t,
287-
logBuf.String(),
288-
"Invalid config type received",
289-
"Should log error for invalid type",
290-
)
291-
logBuf.Reset() // Clear buffer for next check
292293

293294
// --- Test Invalid Config Values ---
294295
invalidValueConfig := WorkerConfig{Interval: 0, JobName: "wont-apply"}
295296
t.Logf("Reloading with invalid value config: %+v", invalidValueConfig)
296297
worker.ReloadWithConfig(invalidValueConfig)
297298

298299
// Wait and assert config hasn't changed
299-
time.Sleep(50 * time.Millisecond)
300-
currentConfig = readWorkerConfig(worker)
300+
assert.Eventually(t, func() bool {
301+
return worker.tickCount.Load() > 0
302+
}, 1*time.Second, pollInterval, "Worker did not start ticking")
303+
currentConfig = readWorkerConfig(t, worker)
301304
assert.Equal(
302305
t,
303306
configAfterValidReload,
304307
currentConfig,
305308
"Config should not change after invalid value reload",
306309
)
307-
assert.Contains(
308-
t,
309-
logBuf.String(),
310-
"Invalid configuration received in ReloadWithConfig",
311-
"Should log error for invalid value",
312-
)
313-
assert.Contains(
314-
t,
315-
logBuf.String(),
316-
"interval must be positive",
317-
"Should log specific validation error",
318-
)
319310
}
320311

321312
// TestWorker_Execution_Timing tests that the worker ticks according to the configured interval,
@@ -411,7 +402,7 @@ func TestWorker_Execution_Timing(t *testing.T) {
411402

412403
// Ensure config is applied before measuring again
413404
require.Eventually(t, func() bool {
414-
return readWorkerConfig(worker).Interval == reloadedInterval
405+
return readWorkerConfig(t, worker).Interval == reloadedInterval
415406
}, 1*time.Second, pollInterval, "Config interval did not update after reload")
416407
t.Log("Config interval updated.")
417408

@@ -481,7 +472,7 @@ func TestWorker_ReloadWithConfig_Concurrency(t *testing.T) {
481472
finalConfig := WorkerConfig{} // Store the config from the last reload goroutine
482473

483474
// Launch concurrent reloads
484-
for i := 0; i < numReloads; i++ {
475+
for i := range numReloads {
485476
go func(index int) {
486477
defer reloadWg.Done()
487478
// Vary interval and name slightly for each reload
@@ -494,8 +485,6 @@ func TestWorker_ReloadWithConfig_Concurrency(t *testing.T) {
494485
finalConfig = cfg
495486
}
496487
worker.ReloadWithConfig(cfg)
497-
// Add a small random sleep to increase inter-leaving chances
498-
// time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
499488
}(i)
500489
}
501490

@@ -509,7 +498,7 @@ func TestWorker_ReloadWithConfig_Concurrency(t *testing.T) {
509498
time.Sleep(2 * time.Second)
510499

511500
t.Logf("Final config details - Expected: %v, Current: %v",
512-
finalConfig.Interval, readWorkerConfig(worker).Interval)
501+
finalConfig.Interval, readWorkerConfig(t, worker).Interval)
513502

514503
// Because ReloadWithConfig replaces the config in the channel if full,
515504
// we expect the *last* successfully queued config to eventually be applied.
@@ -524,7 +513,7 @@ func TestWorker_ReloadWithConfig_Concurrency(t *testing.T) {
524513

525514
t.Logf(
526515
"Final applied config interval: %v (expected: %v)",
527-
readWorkerConfig(worker).Interval,
516+
readWorkerConfig(t, worker).Interval,
528517
finalConfig.Interval,
529518
)
530519
// Check tick count is still advancing (worker didn't get stuck)

examples/httpcluster/main.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ func (cm *ConfigManager) getCurrentPort() string {
7171
// updatePort updates the cluster configuration with a new port
7272
func (cm *ConfigManager) updatePort(newPort string) error {
7373
cm.mu.Lock()
74-
defer cm.mu.Unlock()
7574

7675
// Validate port format using the networking package helper
7776
validatedPort, err := networking.ValidatePort(newPort)
7877
if err != nil {
78+
cm.mu.Unlock()
7979
return fmt.Errorf("invalid port: %w", err)
8080
}
8181

@@ -90,6 +90,7 @@ func (cm *ConfigManager) updatePort(newPort string) error {
9090
cm.commonMw...,
9191
)
9292
if err != nil {
93+
cm.mu.Unlock()
9394
return fmt.Errorf("failed to create status route: %w", err)
9495
}
9596

@@ -100,6 +101,7 @@ func (cm *ConfigManager) updatePort(newPort string) error {
100101
cm.commonMw...,
101102
)
102103
if err != nil {
104+
cm.mu.Unlock()
103105
return fmt.Errorf("failed to create config route: %w", err)
104106
}
105107

@@ -110,15 +112,22 @@ func (cm *ConfigManager) updatePort(newPort string) error {
110112
httpserver.WithDrainTimeout(DrainTimeout),
111113
)
112114
if err != nil {
115+
cm.mu.Unlock()
113116
return fmt.Errorf("failed to create config: %w", err)
114117
}
115118

116-
// Send configuration update (will block until cluster is ready)
119+
// Store the old port and update to new port before releasing lock
117120
oldPort := cm.currentPort
121+
cm.currentPort = newPort
122+
123+
// Release the mutex before blocking channel send
124+
cm.mu.Unlock()
125+
126+
// Send configuration update (will block until cluster is ready)
118127
cm.cluster.GetConfigSiphon() <- map[string]*httpserver.Config{
119128
"main": config,
120129
}
121-
cm.currentPort = newPort
130+
122131
if newPort != oldPort {
123132
cm.logger.Info("Configuration updated", "old_port", oldPort, "new_port", newPort)
124133
}

runnables/composite/reload_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1315,7 +1315,9 @@ func TestReloadMembershipChanged(t *testing.T) {
13151315
runner.Reload()
13161316

13171317
// Wait for reload to complete
1318-
time.Sleep(50 * time.Millisecond)
1318+
require.Eventually(t, func() bool {
1319+
return runner.GetState() == finitestate.StatusRunning
1320+
}, 1*time.Second, 10*time.Millisecond, "Runner should transition to Running state")
13191321

13201322
// Verify the config was updated with the new runnable
13211323
updatedConfig := runner.getConfig()

0 commit comments

Comments
 (0)