-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmap_test.go
458 lines (407 loc) · 11.5 KB
/
map_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
package persist
import (
"math/rand"
"os"
"strconv"
"sync"
"testing"
"time"
)
// TestPersistMap_SetGet tests basic Set/Get/Delete operations.
func TestPersistMap_SetGet(t *testing.T) {
// Create a temporary file for the WAL.
tempFile, err := os.CreateTemp("", "persist_map_test_*.wal")
if err != nil {
t.Fatal(err)
}
path := tempFile.Name()
tempFile.Close() // Close the file, it will be reopened by NewStore.
defer os.Remove(path)
// Create a new WAL store.
pm, err := OpenSingleMap[string](path)
defer pm.Store.Close()
// Set a value.
pm.Set("foo", "bar") // Updated: no error returned
// Get the value.
val, ok := pm.Get("foo")
if !ok {
t.Fatalf("Get failed: key 'foo' not found")
}
if val != "bar" {
t.Errorf("Expected 'bar', got %q", val)
}
// Delete the key.
if !pm.DeleteAsync("foo") {
t.Errorf("Expected key 'foo' to be deleted")
}
// Delete the wrong key
if pm.Delete("not exist") {
t.Errorf("Delete returns true for `not exist` key")
}
// Get should now not find the key.
_, ok = pm.Get("foo")
if ok {
t.Errorf("Expected key 'foo' to be deleted, but it was found")
}
}
// TestPersistMap_Complex performs concurrent operations (Set/Delete) with multiple goroutines,
// then freezes final state, closes and reopens the store to validate data consistency,
// calls Shrink, and again verifies that persisted data is correct.
func TestPersistMap_Complex(t *testing.T) {
// Initialize random seed for reproducibility
rand.Seed(42)
// Create a temporary file for the WAL.
tmpFile, err := os.CreateTemp("", "persist_map_complex_test_*.wal")
if err != nil {
t.Fatal(err)
}
path := tmpFile.Name()
tmpFile.Close()
defer os.Remove(path)
// Create a new WAL store.
pm, err := OpenSingleMap[int](path)
if err != nil {
t.Fatal(err)
}
// Launch multiple goroutines performing random Set/Delete operations.
var wg sync.WaitGroup
numGoroutines := 20
iterations := 50
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < iterations; j++ {
// Randomly select a key from key0 to key9.
keyIdx := rand.Intn(10)
key := "key" + strconv.Itoa(keyIdx)
// Randomly choose operation: 0 for Set, 1 for Delete.
if rand.Intn(2) == 0 {
// Compute a value unique for this operation.
val := id*1000 + j
pm.Set(key, val) // Updated: no error returned
} else {
pm.Delete(key) // Updated: no error returned
}
// Small sleep to increase concurrency variability.
time.Sleep(time.Microsecond)
}
}(i)
}
wg.Wait()
// Freeze the final state:
// For keys "key0" ... "key9", for even indices perform Set (with value equal to key index),
// for odd indices perform Delete.
for i := 0; i < 10; i++ {
key := "key" + strconv.Itoa(i)
if i%2 == 0 {
pm.Set(key, i) // Updated: no error returned
} else {
pm.Delete(key) // Updated: no error returned
}
}
// Verify final state in-memory.
for i := 0; i < 10; i++ {
key := "key" + strconv.Itoa(i)
if i%2 == 0 {
val, ok := pm.Get(key)
if !ok {
t.Fatalf("Final get failed: key %s not found", key)
}
if val != i {
t.Errorf("Final value mismatch for key %s: expected %d, got %d", key, i, val)
}
} else {
_, ok := pm.Get(key)
if ok {
t.Errorf("Expected key %s to be deleted, but it was found", key)
}
}
}
// Close the store to flush all operations.
if err := pm.Store.Close(); err != nil {
t.Fatalf("Store close failed: %v", err)
}
// ---------- Reopen and validate state from WAL ----------
// Reopen store.
store2 := New()
err = store2.Open(path)
if err != nil {
t.Fatalf("Failed to reopen store: %v", err)
}
pmReloaded, err := Map[int](store2, "")
if err != nil {
t.Fatalf("Failed to reload persist map: %v", err)
}
// Verify state after reload.
for i := 0; i < 10; i++ {
key := "key" + strconv.Itoa(i)
if i%2 == 0 {
val, ok := pmReloaded.Get(key)
if !ok {
t.Fatalf("Reloaded get failed: key %s not found", key)
}
if val != i {
t.Errorf("Reloaded value mismatch for key %s: expected %d, got %d", key, i, val)
}
} else {
_, ok := pmReloaded.Get(key)
if ok {
t.Errorf("Reloaded expected key %s to be deleted, but it was found", key)
}
}
}
// Call shrink on the store.
if err := store2.Shrink(); err != nil {
t.Fatalf("Shrink failed: %v", err)
}
// Close store after shrink.
if err := store2.Close(); err != nil {
t.Fatalf("Store close after shrink failed: %v", err)
}
// ---------- Reopen once more after shrink and validate state ----------
pmReloaded2, err := OpenSingleMap[int](path)
if err != nil {
t.Fatalf("Failed to reload persist map after shrink: %v", err)
}
defer pmReloaded2.Store.Close()
// Final state verification after shrink.
for i := 0; i < 10; i++ {
key := "key" + strconv.Itoa(i)
if i%2 == 0 {
val, ok := pmReloaded2.Get(key)
if !ok {
t.Fatalf("Final reload get failed: key %s not found", key)
}
if val != i {
t.Errorf("Final reload value mismatch for key %s: expected %d, got %d", key, i, val)
}
} else {
_, ok := pmReloaded2.Get(key)
if ok {
t.Errorf("Final reload expected key %s to be deleted, but it was found", key)
}
}
}
}
// TestPersistMap_MultipleMaps tests persistence with multiple maps belonging to different namespaces.
func TestPersistMap_MultipleMaps(t *testing.T) {
// Create a temporary file for the WAL.
tmpFile, err := os.CreateTemp("", "persist_map_multi_test_*.wal")
if err != nil {
t.Fatal(err)
}
walPath := tmpFile.Name()
tmpFile.Close()
defer os.Remove(walPath)
// Open a new WAL store.
store := New()
err = store.Open(walPath)
if err != nil {
t.Fatalf("Failed to open store: %v", err)
}
// Create first PersistMap[string] with empty namespace
map1, err := Map[string](store, "first")
if err != nil {
t.Fatalf("Failed to create persist map 'first': %v", err)
}
// Create second PersistMap[int] with namespace "second".
map2, err := Map[int](store, "second")
if err != nil {
t.Fatalf("Failed to create persist map 'second': %v", err)
}
// Perform operations on map1.
map1.Set("", "hello")
map1.Set("key2", "world")
// Delete key2 from map1.
map1.Delete("key2")
// Perform operations on map2.
map2.Set("one", 1)
map2.Set("two", 2)
// Delete key 'two' from map2.
map2.Delete("two")
map2.Delete("Unknown")
// Close the store to flush all operations.
if err := store.Close(); err != nil {
t.Fatalf("Store close failed: %v", err)
}
// ---------- Reopen and validate state from WAL for multiple maps ----------
store2 := New()
// Reload the maps from the store. Pre-open
reloadedMap1, err := Map[string](store2, "first")
if err != nil {
t.Fatalf("Failed to reload persist map 'first': %v", err)
}
err = store2.Open(walPath)
if err != nil {
t.Fatalf("Failed to reopen store: %v", err)
}
// After-open
reloadedMap2, err := Map[int](store2, "second")
if err != nil {
t.Fatalf("Failed to reload persist map 'second': %v", err)
}
// Validate map1
val, ok := reloadedMap1.Get("")
if !ok {
t.Fatalf("Failed to get empty key from map1")
}
if val != "hello" {
t.Errorf("Expected 'hello' for key 'key1' in map1, got %q", val)
}
_, ok = reloadedMap1.Get("key2")
if ok {
t.Errorf("Expected key 'key2' to be deleted in map1, but it was found")
}
// Validate map2.
val2, ok := reloadedMap2.Get("one")
if !ok {
t.Fatalf("Failed to get 'one' from map2")
}
if val2 != 1 {
t.Errorf("Expected 1 for key 'one' in map2, got %d", val2)
}
_, ok = reloadedMap2.Get("two")
if ok {
t.Errorf("Expected key 'two' to be deleted in map2, but it was found")
}
// Close the reopened store.
if err := store2.Close(); err != nil {
t.Fatalf("Store close failed on reopened store: %v", err)
}
}
// TestPersistMap_ComplexUpdateOperations performs concurrent Update/UpdateAsync operations,
// then freezes final state and verifies that the persisted state matches.
func TestPersistMap_ComplexUpdateOperations(t *testing.T) {
// Create a temporary file for the WAL.
tmpFile, err := os.CreateTemp("", "persist_map_complex_update_test_*.wal")
if err != nil {
t.Fatal(err)
}
path := tmpFile.Name()
tmpFile.Close()
defer os.Remove(path)
// Open a new WAL store.
store := New()
err = store.Open(path)
if err != nil {
t.Fatalf("Failed to open store: %v", err)
}
// Create a PersistMap[int] with empty namespace
pm, err := Map[int](store, "")
if err != nil {
t.Fatalf("Failed to create persist map: %v", err)
}
// Define a set of keys to update.
keys := []string{"counter0", "counter1", "counter2", "counter3", "counter4", "counter5", "counter6", "counter7", "counter8", "counter9"}
var wg sync.WaitGroup
numGoroutines := 5
iterations := 100
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < iterations; j++ {
// Randomly choose one key.
key := keys[rand.Intn(len(keys))]
// Randomly choose to use Update (synchronous) or UpdateAsync.
useSync := rand.Intn(2) == 0 // 50% chance.
// Randomly decide whether to delete the key (20% chance).
deletion := rand.Intn(100) < 20
if useSync {
// Using Update method.
pm.Update(key, func(upd *Update[int]) {
if deletion {
upd.Delete()
return
}
// If exists add a random delta (1..10); if not, initialize with a random value.
delta := rand.Intn(10) + 1
if upd.Exists {
upd.Value += delta
return
}
upd.Value = delta
})
} else {
// Using UpdateAsync method.
pm.UpdateAsync(key, func(upd *Update[int]) {
if deletion {
upd.Delete()
return
}
delta := rand.Intn(10) + 1
if upd.Exists {
upd.Value += delta
return
}
upd.Value = delta
})
}
// Small sleep to increase concurrency variability.
time.Sleep(time.Microsecond)
}
}(i)
}
wg.Wait()
// Freeze final state: for each key, if its index is even, set it to a fixed value,
// if odd, remove the key.
for i, key := range keys {
if i%2 == 0 {
// Update key to have a known constant value (e.g. i * 100).
pm.Update(key, func(upd *Update[int]) {
upd.Value = i * 100
})
} else {
// Force deletion of the key.
pm.Update(key, func(upd *Update[int]) {
upd.Delete()
})
}
}
// Verify in-memory final state.
for i, key := range keys {
if i%2 == 0 {
// For even-indexed keys, the value should be exactly i*100.
val, ok := pm.Get(key)
if !ok {
t.Fatalf("Final state: expected key %s to exist", key)
}
expected := i * 100
if val != expected {
t.Errorf("Final state: for key %s expected %d, got %d", key, expected, val)
}
} else {
// For odd-indexed keys, the key should be deleted.
if _, ok := pm.Get(key); ok {
t.Errorf("Final state: expected key %s to be deleted", key)
}
}
}
// Close the store to flush all operations.
if err := store.Close(); err != nil {
t.Fatalf("Store close failed: %v", err)
}
// ---------- Reopen store and verify that persisted state matches ----------
pmReloaded, err := OpenSingleMap[int](path)
if err != nil {
t.Fatalf("Failed to reload persist map: %v", err)
}
// Validate persisted state.
for i, key := range keys {
if i%2 == 0 {
val, ok := pmReloaded.Get(key)
if !ok {
t.Fatalf("Reloaded state: expected key %s to exist", key)
}
expected := i * 100
if val != expected {
t.Errorf("Reloaded state: for key %s expected %d, got %d", key, expected, val)
}
} else {
if _, ok := pmReloaded.Get(key); ok {
t.Errorf("Reloaded state: expected key %s to be deleted", key)
}
}
}
}