Skip to content

Commit 9839e07

Browse files
authored
unflakey unit test (#258)
- i think init timeout test has been failing bc of the addition of thread spawning step by the sampling set when creating the logger, which is not being timed - moved the thread spawning step into the timed init() function - added ttl set unit test - edit init timeout test to check that the store and the ttl set are being initialized in the background - got a warning that errors should not have capitalization so changed that and updated unit test ![Screenshot 2025-01-21 at 6 07 49 PM](https://github.com/user-attachments/assets/03192cfb-32ce-45c9-ba3a-7ce73ad5609a)
1 parent ee7c6da commit 9839e07

File tree

5 files changed

+107
-34
lines changed

5 files changed

+107
-34
lines changed

client.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func newClientImpl(sdkKey string, options *Options) (*Client, *initContext) {
8181
diagnostics.initialize().overall().end().success(false).reason("timeout").mark()
8282
client.initInBackground()
8383
ctx := context.copy() // Goroutines are not terminated upon timeout. Clone context to avoid race condition on setting Error
84-
ctx.setError(errors.New("Timed out"))
84+
ctx.setError(errors.New("timed out"))
8585
return client, ctx
8686
}
8787
} else {
@@ -96,12 +96,14 @@ func (c *Client) init(context *initContext) {
9696
c.evaluator.initialize(context)
9797
c.evaluator.store.mu.RLock()
9898
defer c.evaluator.store.mu.RUnlock()
99+
c.logger.samplingKeySet.StartResetThread()
99100
context.setSuccess(c.evaluator.store.source != SourceUninitialized)
100101
context.setSource(c.evaluator.store.source)
101102
}
102103

103104
func (c *Client) initInBackground() {
104105
c.evaluator.store.startPolling()
106+
c.logger.samplingKeySet.StartResetThread()
105107
}
106108

107109
// Checks the value of a Feature Gate for the given user

init_details_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func TestInitDetails(t *testing.T) {
100100
if details.Success {
101101
t.Errorf("Expected initalize success to be false")
102102
}
103-
if details.Error.Error() != "Timed out" {
103+
if details.Error.Error() != "timed out" {
104104
t.Errorf("Expected initalize to have timeout error")
105105
}
106106
if details.Source != SourceUninitialized {

init_timeout_test.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestInitTimeout(t *testing.T) {
1818
defer testServer.Close()
1919

2020
user := User{UserID: "some_user_id"}
21-
initTimeBuffer := 2 * time.Millisecond // expected runtime buffer for initialize with timeout
21+
initTimeBuffer := 5 * time.Millisecond // expected runtime buffer for initialize with timeout
2222

2323
t.Run("No timeout option", func(t *testing.T) {
2424
options := &Options{
@@ -83,6 +83,13 @@ func TestInitTimeout(t *testing.T) {
8383
if gate != false {
8484
t.Errorf("Expected gate to be default off")
8585
}
86+
if !instance.evaluator.store.isPolling {
87+
t.Errorf("Expected evaluator store background polling to start")
88+
}
89+
if !instance.logger.samplingKeySet.isBackgroundThreadRunning {
90+
t.Errorf("Expected sampling key set background thread to start")
91+
}
92+
8693
ShutdownAndDangerouslyClearInstance()
8794
})
8895
}

ttl_set.go

+36-31
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import (
77
)
88

99
type TTLSet struct {
10-
store map[string]struct{}
11-
mu sync.RWMutex
12-
resetInterval time.Duration
13-
shutdown bool
10+
store map[string]struct{}
11+
mu sync.RWMutex
12+
resetInterval time.Duration
13+
shutdown bool
14+
isBackgroundThreadRunning bool
1415
}
1516

1617
func NewTTLSet() *TTLSet {
@@ -19,7 +20,6 @@ func NewTTLSet() *TTLSet {
1920
resetInterval: time.Minute,
2021
}
2122

22-
go set.startResetThread()
2323
return set
2424
}
2525

@@ -36,38 +36,43 @@ func (s *TTLSet) Contains(key string) bool {
3636
return exists
3737
}
3838

39-
func (s *TTLSet) Reset() {
39+
func (s *TTLSet) Shutdown() {
4040
s.mu.Lock()
41-
s.store = make(map[string]struct{})
42-
s.mu.Unlock()
41+
defer s.mu.Unlock()
42+
s.shutdown = true
43+
s.isBackgroundThreadRunning = false
4344
}
4445

45-
func (s *TTLSet) Shutdown() {
46+
func (s *TTLSet) StartResetThread() {
4647
s.mu.Lock()
47-
s.shutdown = true
48+
if s.isBackgroundThreadRunning {
49+
s.mu.Unlock()
50+
return
51+
}
52+
s.isBackgroundThreadRunning = true
4853
s.mu.Unlock()
49-
}
5054

51-
func (s *TTLSet) startResetThread() {
52-
for {
53-
time.Sleep(s.resetInterval)
54-
stop := func() bool {
55-
s.mu.RLock()
56-
defer s.mu.RUnlock()
57-
return s.shutdown
55+
go func() {
56+
defer func() {
57+
if r := recover(); r != nil {
58+
err := errors.New("panic in TTLSet reset thread")
59+
Logger().LogError(err)
60+
}
5861
}()
59-
if stop {
60-
break
61-
}
62-
63-
func() {
64-
defer func() {
65-
if r := recover(); r != nil {
66-
err := errors.New("panic in TTLSet reset thread")
67-
Logger().LogError(err)
68-
}
62+
for {
63+
time.Sleep(s.resetInterval)
64+
stop := func() bool {
65+
s.mu.RLock()
66+
defer s.mu.RUnlock()
67+
return s.shutdown
6968
}()
70-
s.Reset()
71-
}()
72-
}
69+
if stop {
70+
break
71+
}
72+
73+
s.mu.Lock()
74+
s.store = make(map[string]struct{})
75+
s.mu.Unlock()
76+
}
77+
}()
7378
}

ttl_set_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package statsig
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestTTLSet_AddAndContains(t *testing.T) {
9+
set := NewTTLSet()
10+
set.Add("key1")
11+
if !set.Contains("key1") {
12+
t.Errorf("key1 should exist in the set")
13+
}
14+
if set.Contains("key2") {
15+
t.Errorf("key2 should not exist in the set")
16+
}
17+
}
18+
19+
func TestTTLSet_Reset(t *testing.T) {
20+
set := NewTTLSet()
21+
set.Add("key1")
22+
set.Add("key2")
23+
set.store = make(map[string]struct{})
24+
if set.Contains("key1") {
25+
t.Errorf("key1 should not exist after reset")
26+
}
27+
if set.Contains("key2") {
28+
t.Errorf("key2 should not exist after reset")
29+
}
30+
}
31+
32+
func TestTTLSet_StartResetThread(t *testing.T) {
33+
set := NewTTLSet()
34+
set.resetInterval = 10 * time.Millisecond
35+
set.StartResetThread()
36+
37+
set.Add("key1")
38+
time.Sleep(20 * time.Millisecond)
39+
if set.Contains("key1") {
40+
t.Errorf("key1 should not exist after automatic reset")
41+
}
42+
43+
set.Shutdown()
44+
}
45+
46+
func TestTTLSet_Shutdown(t *testing.T) {
47+
set := NewTTLSet()
48+
set.resetInterval = 10 * time.Millisecond
49+
set.StartResetThread()
50+
51+
set.Add("key1")
52+
set.Shutdown()
53+
time.Sleep(20 * time.Millisecond)
54+
55+
set.Add("key2")
56+
if !set.Contains("key2") {
57+
t.Errorf("shutdown should prevent automatic reset")
58+
}
59+
}

0 commit comments

Comments
 (0)