Skip to content

Commit 4121069

Browse files
committed
add comprehensive cleanup and concurrency tests
1 parent 7104627 commit 4121069

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

tracker.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Tracker struct {
2323
vehicles map[string]*VehicleState
2424
maxAge time.Duration
2525
done chan struct{}
26+
stopOnce sync.Once
2627
}
2728

2829
// NewTracker creates a Tracker with the given staleness threshold.
@@ -54,7 +55,9 @@ func NewTracker(maxAge time.Duration) *Tracker {
5455
}
5556

5657
func (t *Tracker) Stop() {
57-
close(t.done)
58+
t.stopOnce.Do(func() {
59+
close(t.done)
60+
})
5861
}
5962

6063
// Update stores or replaces the latest position for a vehicle.

tracker_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"fmt"
45
"sync"
56
"testing"
67
"time"
@@ -97,6 +98,25 @@ func TestTracker_Concurrent(t *testing.T) {
9798
assert.Len(t, active, 1)
9899
}
99100

101+
func TestTracker_ConcurrentWithCleanup(t *testing.T) {
102+
tracker := NewTracker(5 * time.Millisecond)
103+
defer tracker.Stop()
104+
105+
var wg sync.WaitGroup
106+
for i := 0; i < 100; i++ {
107+
wg.Add(1)
108+
go func(id int) {
109+
defer wg.Done()
110+
tracker.Update(&LocationReport{
111+
VehicleID: fmt.Sprintf("bus-%d", id),
112+
Timestamp: 1,
113+
})
114+
tracker.ActiveVehicles()
115+
}(i)
116+
}
117+
wg.Wait()
118+
}
119+
100120
func TestNewTracker_PanicsOnInvalidMaxAge(t *testing.T) {
101121
assert.PanicsWithValue(t, "maxAge must be positive", func() {
102122
NewTracker(0)
@@ -121,3 +141,28 @@ func TestTracker_Cleanup(t *testing.T) {
121141
return len(tracker.vehicles) == 0
122142
}, 100*time.Millisecond, 5*time.Millisecond, "stale vehicle should be deleted from internal map")
123143
}
144+
145+
func TestTracker_StopIsIdempotent(t *testing.T) {
146+
tracker := NewTracker(5 * time.Minute)
147+
tracker.Stop()
148+
assert.NotPanics(t, func() { tracker.Stop() })
149+
}
150+
151+
func TestTracker_CleanupPreservesFreshVehicles(t *testing.T) {
152+
maxAge := 50 * time.Millisecond
153+
tracker := NewTracker(maxAge)
154+
defer tracker.Stop()
155+
156+
tracker.Update(&LocationReport{VehicleID: "old-bus", Timestamp: 1})
157+
time.Sleep(60 * time.Millisecond)
158+
tracker.Update(&LocationReport{VehicleID: "new-bus", Timestamp: 2})
159+
160+
assert.Eventually(t, func() bool {
161+
tracker.mu.RLock()
162+
defer tracker.mu.RUnlock()
163+
_, hasOld := tracker.vehicles["old-bus"]
164+
_, hasNew := tracker.vehicles["new-bus"]
165+
return !hasOld && hasNew
166+
}, 200*time.Millisecond, 5*time.Millisecond,
167+
"cleanup should remove old-bus but keep new-bus")
168+
}

0 commit comments

Comments
 (0)