-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsafemap_test.go
507 lines (414 loc) · 12.4 KB
/
safemap_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
package utils
import (
"math/rand"
"strconv"
"sync"
"testing"
"time"
)
func TestSafeMapBasicOperations(t *testing.T) {
sm := NewSafeMap[int]()
// Test Set and Get
sm.Set("key1", 1)
sm.Set("key2", 2)
value, exists := sm.Get("key1")
if !exists || value != 1 {
t.Errorf("Expected key1 to have value 1, got %v", value)
}
value, exists = sm.Get("key2")
if !exists || value != 2 {
t.Errorf("Expected key2 to have value 2, got %v", value)
}
// Test Exists
if !sm.Exists("key1") {
t.Errorf("Expected key1 to exist")
}
if sm.Exists("key3") {
t.Errorf("Expected key3 to not exist")
}
// Test Delete
sm.Delete("key1")
if sm.Exists("key1") {
t.Errorf("Expected key1 to be deleted")
}
// Test Len
if sm.Len() != 1 {
t.Errorf("Expected length to be 1, got %d", sm.Len())
}
// Test Keys
keys := sm.Keys()
if len(keys) != 1 || keys[0] != "key2" {
t.Errorf("Expected keys to be [\"key2\"], got %v", keys)
}
// Test Clear
sm.Clear()
if sm.Len() != 0 {
t.Errorf("Expected length to be 0 after Clear, got %d", sm.Len())
}
}
func TestSafeMapExpiration(t *testing.T) {
sm := NewSafeMap[string]()
// Set a key with expiration
sm.SetWithExpireDuration("tempKey", "tempValue", 100*time.Millisecond)
// Immediately check if key exists
value, exists := sm.Get("tempKey")
if !exists || value != "tempValue" {
t.Errorf("Expected tempKey to exist with value 'tempValue', got %v", value)
}
// Wait for expiration
time.Sleep(150 * time.Millisecond)
// Check if key has expired
value, exists = sm.Get("tempKey")
if exists {
t.Errorf("Expected tempKey to have expired")
}
// Test ExpiredAndGet
sm.SetWithExpireDuration("tempKey2", "tempValue2", 100*time.Millisecond)
value, exists = sm.ExpiredAndGet("tempKey2")
if !exists || value != "tempValue2" {
t.Errorf("Expected tempKey2 to exist with value 'tempValue2', got %v", value)
}
time.Sleep(150 * time.Millisecond)
value, exists = sm.ExpiredAndGet("tempKey2")
if exists {
t.Errorf("Expected tempKey2 to have expired")
}
}
func TestSafeMapUpdateExpireTime(t *testing.T) {
sm := NewSafeMap[int]()
// Set a key with expiration
sm.SetWithExpireDuration("key", 1, 100*time.Millisecond)
// Update the expiration time
time.Sleep(50 * time.Millisecond)
updated := sm.UpdateExpireTime("key", 200*time.Millisecond)
if !updated {
t.Errorf("Expected to update expiration time for key")
}
// Wait and check if key still exists
time.Sleep(100 * time.Millisecond)
value, exists := sm.Get("key")
if !exists || value != 1 {
t.Errorf("Expected key to still exist after updating expiration time")
}
// Wait until after the updated expiration
time.Sleep(150 * time.Millisecond)
value, exists = sm.Get("key")
if exists {
t.Errorf("Expected key to have expired after updated expiration time")
}
}
func TestSafeMapDeleteAllKeysStartingWith(t *testing.T) {
sm := NewSafeMap[int]()
sm.Set("prefix_key1", 1)
sm.Set("prefix_key2", 2)
sm.Set("other_key", 3)
sm.DeleteAllKeysStartingWith("prefix_")
if sm.Exists("prefix_key1") || sm.Exists("prefix_key2") {
t.Errorf("Expected keys starting with 'prefix_' to be deleted")
}
if !sm.Exists("other_key") {
t.Errorf("Expected 'other_key' to still exist")
}
}
func TestSafeMapConcurrentAccess(t *testing.T) {
sm := NewSafeMap[int]()
var wg sync.WaitGroup
numGoroutines := 100
numOperations := 1000
// Writer goroutines
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < numOperations; j++ {
key := "key_" + strconv.Itoa(rand.Intn(100))
value := rand.Intn(1000)
sm.Set(key, value)
}
}(i)
}
// Reader goroutines
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < numOperations; j++ {
key := "key_" + strconv.Itoa(rand.Intn(100))
_, _ = sm.Get(key)
}
}(i)
}
wg.Wait()
// Just ensure that the map has some entries
if sm.Len() == 0 {
t.Errorf("Expected SafeMap to have some entries after concurrent access")
}
}
func TestSafeMapSetWithExpireDurationConcurrent(t *testing.T) {
sm := NewSafeMap[int]()
var wg sync.WaitGroup
numGoroutines := 50
numOperations := 200
// Set keys with expiration concurrently
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < numOperations; j++ {
key := "tempKey_" + strconv.Itoa(rand.Intn(100))
value := rand.Intn(1000)
expire := time.Duration(rand.Intn(100)) * time.Millisecond
sm.SetWithExpireDuration(key, value, expire)
}
}(i)
}
wg.Wait()
// Wait for all possible expirations to occur
time.Sleep(200 * time.Millisecond)
// Ensure that expired keys are removed
keys := sm.Keys()
for _, key := range keys {
_, exists := sm.Get(key)
if !exists {
t.Errorf("Expected key %s to exist", key)
}
}
}
func TestSafeMapExpiredAndGetConcurrent(t *testing.T) {
sm := NewSafeMap[int]()
var wg sync.WaitGroup
numGoroutines := 50
numOperations := 200
// Set keys with expiration
for i := 0; i < numGoroutines; i++ {
for j := 0; j < numOperations; j++ {
key := "key_" + strconv.Itoa(i*numOperations+j)
value := rand.Intn(1000)
expire := time.Duration(rand.Intn(100)+50) * time.Millisecond
sm.SetWithExpireDuration(key, value, expire)
}
}
// Concurrently call ExpiredAndGet
for i := 0; i < numGoroutines*2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < numOperations; j++ {
key := "key_" + strconv.Itoa(rand.Intn(numGoroutines*numOperations))
_, _ = sm.ExpiredAndGet(key)
time.Sleep(10 * time.Millisecond)
}
}()
}
wg.Wait()
// Wait for all possible expirations to occur
time.Sleep(200 * time.Millisecond)
// Ensure that expired keys are removed
if sm.Len() != 0 {
t.Errorf("Expected all keys to have expired")
}
}
func TestSafeMapRange(t *testing.T) {
sm := NewSafeMap[int]()
// Set some key-value pairs
sm.Set("key1", 1)
sm.Set("key2", 2)
sm.Set("key3", 3)
// Use Range to iterate over the map and collect the keys and values
collected := make(map[string]int)
sm.Range(func(key string, value int) bool {
collected[key] = value
return true // Continue iteration
})
// Verify that all entries are collected
if len(collected) != 3 {
t.Errorf("Expected to collect 3 entries, got %d", len(collected))
}
for i := 1; i <= 3; i++ {
key := "key" + strconv.Itoa(i)
value, exists := collected[key]
if !exists || value != i {
t.Errorf("Expected collected[%s] to be %d, got %v", key, i, value)
}
}
}
func TestSafeMapClearConcurrent(t *testing.T) {
sm := NewSafeMap[int]()
var wg sync.WaitGroup
// Fill the map with some data
for i := 0; i < 1000; i++ {
sm.Set("key_"+strconv.Itoa(i), i)
}
// Ensure the data was added properly
if sm.Len() != 1000 {
t.Errorf("Expected to have 1000 entries before clearing, got %d", sm.Len())
}
// Number of goroutines to run
numGoroutines := 10
// Start goroutines that use the map while clearing
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if id == 0 {
// One goroutine clears the map
sm.Clear()
} else if id % 2 == 0 {
// Half of the others read from the map
for j := 0; j < 100; j++ {
key := "key_" + strconv.Itoa(rand.Intn(1000))
_, _ = sm.Get(key)
time.Sleep(time.Microsecond)
}
} else {
// The other half write to the map
for j := 0; j < 100; j++ {
key := "new_key_" + strconv.Itoa(id) + "_" + strconv.Itoa(j)
sm.Set(key, j)
time.Sleep(time.Microsecond)
}
}
}(i)
}
wg.Wait()
// Ensure the map still works after concurrent clearing and operations
// Add some data again
for i := 0; i < 10; i++ {
sm.Set("post_clear_key_"+strconv.Itoa(i), i)
}
// Verify we can read the data
for i := 0; i < 10; i++ {
key := "post_clear_key_" + strconv.Itoa(i)
val, exists := sm.Get(key)
if !exists || val != i {
t.Errorf("Expected %s to exist with value %d after Clear and new additions", key, i)
}
}
}
func TestSafeMapClearAfterExpiration(t *testing.T) {
sm := NewSafeMap[int]()
// Add some items with expiration
for i := 0; i < 10; i++ {
sm.SetWithExpireDuration("expire_key_"+strconv.Itoa(i), i, 50*time.Millisecond)
}
// Add some items without expiration
for i := 0; i < 10; i++ {
sm.Set("permanent_key_"+strconv.Itoa(i), i)
}
// Wait for expiration
time.Sleep(100 * time.Millisecond)
// Verify expired items are gone
for i := 0; i < 10; i++ {
_, exists := sm.Get("expire_key_" + strconv.Itoa(i))
if exists {
t.Errorf("Key expire_key_%d should have expired", i)
}
}
// Now clear the map
sm.Clear()
// Verify all items are gone
if sm.Len() != 0 {
t.Errorf("Expected length to be 0 after Clear, got %d", sm.Len())
}
// Add new items and verify they work correctly
sm.Set("new_key", 100)
val, exists := sm.Get("new_key")
if !exists || val != 100 {
t.Errorf("Expected new key to exist with value 100 after Clear")
}
}
func TestSafeMapClearAndDelete(t *testing.T) {
sm := NewSafeMap[int]()
// Add some items
for i := 0; i < 100; i++ {
sm.Set("key_"+strconv.Itoa(i), i)
}
// Clear the map
sm.Clear()
// Verify it's empty
if sm.Len() != 0 {
t.Errorf("Expected length to be 0 after Clear, got %d", sm.Len())
}
// Try to delete keys that don't exist anymore
for i := 0; i < 10; i++ {
sm.Delete("key_" + strconv.Itoa(i))
}
// Verify the length is still 0
if sm.Len() != 0 {
t.Errorf("Expected length to be 0 after deleting cleared keys, got %d", sm.Len())
}
// Add new items
for i := 0; i < 10; i++ {
sm.Set("new_key_"+strconv.Itoa(i), i)
}
// Verify the length is correct
if sm.Len() != 10 {
t.Errorf("Expected length to be 10 after adding new keys, got %d", sm.Len())
}
}
func TestSafeMapDeleteWithComplexPrefixes(t *testing.T) {
sm := NewSafeMap[string]()
// Add keys with complex prefixes
userUUID := "550e8400-e29b-41d4-a716-446655440000"
personaUUID := "f47ac10b-58cc-4372-a567-0e02b2c3d479"
slugName := "john-doe"
userSlug := "user-123"
// Add various types of keys
sm.Set("/Persona/"+slugName+"/details", "persona details")
sm.Set("/Persona/"+slugName+"/settings", "persona settings")
sm.Set("/Persona/"+slugName+"/posts/recent", "recent posts")
sm.Set("/User/_Personas/"+userSlug+"/list", "user personas list")
sm.Set("/User/_Personas/"+userSlug+"/count", "user personas count")
sm.Set("persona:"+personaUUID+":info", "persona info by UUID")
sm.Set("persona:"+personaUUID+":stats", "persona stats by UUID")
sm.Set("user:"+userUUID+":session", "user session")
sm.Set("user:"+userUUID+":prefs", "user preferences")
sm.Set("thread:user:"+userUUID+":posts", "user posts")
sm.Set("unrelated:key", "unrelated value")
// Initial count should be 11
if sm.Len() != 11 {
t.Errorf("Expected 11 items, got %d", sm.Len())
}
// Test deleting with the Persona slug prefix
sm.DeleteAllKeysStartingWith("/Persona/" + slugName)
// Should have deleted 3 keys
if sm.Len() != 8 {
t.Errorf("Expected 8 remaining items after deleting Persona slug prefix, got %d", sm.Len())
}
// Verify specific keys are gone
if sm.Exists("/Persona/"+slugName+"/details") {
t.Errorf("Expected /Persona/%s/details to be deleted", slugName)
}
// Test deleting with the User personas prefix
sm.DeleteAllKeysStartingWith("/User/_Personas/" + userSlug)
// Should have deleted 2 more keys
if sm.Len() != 6 {
t.Errorf("Expected 6 remaining items after deleting User personas prefix, got %d", sm.Len())
}
// Test deleting with the persona UUID prefix
sm.DeleteAllKeysStartingWith("persona:" + personaUUID)
// Should have deleted 2 more keys
if sm.Len() != 4 {
t.Errorf("Expected 4 remaining items after deleting persona UUID prefix, got %d", sm.Len())
}
// Test deleting with the user UUID prefix (includes thread:user:)
sm.DeleteAllKeysStartingWith("user:" + userUUID)
// Should have deleted 2 more keys (but not thread:user:)
if sm.Len() != 2 {
t.Errorf("Expected 2 remaining items after deleting user UUID prefix, got %d", sm.Len())
}
// Verify thread:user: key still exists
if !sm.Exists("thread:user:"+userUUID+":posts") {
t.Errorf("Expected thread:user:%s:posts to still exist", userUUID)
}
// Now test exact match prefix for thread:user:
sm.DeleteAllKeysStartingWith("thread:user:" + userUUID)
// Should have deleted 1 more key
if sm.Len() != 1 {
t.Errorf("Expected 1 remaining item after deleting thread:user: prefix, got %d", sm.Len())
}
// Only unrelated:key should remain
if !sm.Exists("unrelated:key") {
t.Errorf("Expected unrelated:key to still exist")
}
}