Skip to content

Commit 8a13f9e

Browse files
author
Hayim.Shaul@ibm.com
committed
added unit-test
Signed-off-by: Hayim.Shaul@ibm.com <hayimsha@fhe3.haifa.ibm.com>
1 parent fa4068e commit 8a13f9e

1 file changed

Lines changed: 345 additions & 0 deletions

File tree

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package auditdb
8+
9+
import (
10+
"context"
11+
"sync"
12+
"testing"
13+
"time"
14+
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestAcquireLocks_Success(t *testing.T) {
20+
store := &StoreService{
21+
eIDsLocks: sync.Map{},
22+
}
23+
ctx := context.Background()
24+
25+
// Test acquiring locks for multiple enrollment IDs
26+
err := store.AcquireLocks(ctx, "anchor1", "alice", "bob", "charlie")
27+
require.NoError(t, err)
28+
29+
// Verify that the anchor mapping was stored
30+
dedupBoxed, ok := store.eIDsLocks.Load("anchor1")
31+
require.True(t, ok, "anchor mapping should be stored")
32+
dedup := dedupBoxed.([]string)
33+
assert.ElementsMatch(t, []string{"alice", "bob", "charlie"}, dedup)
34+
35+
// Clean up
36+
store.ReleaseLocks(ctx, "anchor1")
37+
}
38+
39+
func TestAcquireLocks_Deduplication(t *testing.T) {
40+
store := &StoreService{
41+
eIDsLocks: sync.Map{},
42+
}
43+
ctx := context.Background()
44+
45+
// Test with duplicate enrollment IDs
46+
err := store.AcquireLocks(ctx, "anchor2", "alice", "bob", "alice", "charlie", "bob")
47+
require.NoError(t, err)
48+
49+
// Verify deduplication occurred
50+
dedupBoxed, ok := store.eIDsLocks.Load("anchor2")
51+
require.True(t, ok)
52+
dedup := dedupBoxed.([]string)
53+
assert.Len(t, dedup, 3, "duplicates should be removed")
54+
assert.ElementsMatch(t, []string{"alice", "bob", "charlie"}, dedup)
55+
56+
// Clean up
57+
store.ReleaseLocks(ctx, "anchor2")
58+
}
59+
60+
func TestAcquireLocks_Sorting(t *testing.T) {
61+
store := &StoreService{
62+
eIDsLocks: sync.Map{},
63+
}
64+
ctx := context.Background()
65+
66+
// Test that enrollment IDs are sorted to prevent deadlocks
67+
err := store.AcquireLocks(ctx, "anchor3", "charlie", "alice", "bob")
68+
require.NoError(t, err)
69+
70+
dedupBoxed, ok := store.eIDsLocks.Load("anchor3")
71+
require.True(t, ok)
72+
dedup := dedupBoxed.([]string)
73+
// Should be sorted alphabetically
74+
assert.Equal(t, []string{"alice", "bob", "charlie"}, dedup)
75+
76+
// Clean up
77+
store.ReleaseLocks(ctx, "anchor3")
78+
}
79+
80+
func TestAcquireLocks_ContextCancellation(t *testing.T) {
81+
store := &StoreService{
82+
eIDsLocks: sync.Map{},
83+
}
84+
85+
// First, acquire a lock on "alice"
86+
ctx1 := context.Background()
87+
err := store.AcquireLocks(ctx1, "anchor_first", "alice")
88+
require.NoError(t, err)
89+
90+
// Now try to acquire the same lock with a cancelled context
91+
ctx2, cancel := context.WithCancel(context.Background())
92+
cancel() // Cancel immediately
93+
94+
err = store.AcquireLocks(ctx2, "anchor_second", "alice")
95+
assert.Error(t, err, "should fail due to context cancellation")
96+
assert.Contains(t, err.Error(), "failed to acquire lock")
97+
98+
// Verify that the second anchor was NOT stored
99+
_, ok := store.eIDsLocks.Load("anchor_second")
100+
assert.False(t, ok, "anchor should not be stored when lock acquisition fails")
101+
102+
// Clean up
103+
store.ReleaseLocks(ctx1, "anchor_first")
104+
}
105+
106+
func TestAcquireLocks_ContextTimeout(t *testing.T) {
107+
store := &StoreService{
108+
eIDsLocks: sync.Map{},
109+
}
110+
111+
// First, acquire a lock on "bob"
112+
ctx1 := context.Background()
113+
err := store.AcquireLocks(ctx1, "anchor_holder", "bob")
114+
require.NoError(t, err)
115+
116+
// Try to acquire the same lock with a short timeout
117+
ctx2, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
118+
defer cancel()
119+
120+
err = store.AcquireLocks(ctx2, "anchor_timeout", "bob")
121+
assert.Error(t, err, "should timeout waiting for lock")
122+
123+
// Verify that the timeout anchor was NOT stored
124+
_, ok := store.eIDsLocks.Load("anchor_timeout")
125+
assert.False(t, ok, "anchor should not be stored when lock acquisition times out")
126+
127+
// Clean up
128+
store.ReleaseLocks(ctx1, "anchor_holder")
129+
}
130+
131+
func TestAcquireLocks_PartialAcquisitionRollback(t *testing.T) {
132+
store := &StoreService{
133+
eIDsLocks: sync.Map{},
134+
}
135+
136+
// First, acquire a lock on "charlie"
137+
ctx1 := context.Background()
138+
err := store.AcquireLocks(ctx1, "anchor_blocker", "charlie")
139+
require.NoError(t, err)
140+
141+
// Try to acquire locks on multiple IDs where one is already locked
142+
ctx2, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
143+
defer cancel()
144+
145+
// This should fail because "charlie" is already locked
146+
// The sorted order will be: alice, bob, charlie
147+
err = store.AcquireLocks(ctx2, "anchor_partial", "alice", "bob", "charlie")
148+
assert.Error(t, err, "should fail to acquire all locks")
149+
150+
// Verify that locks on "alice" and "bob" were rolled back
151+
// We can test this by successfully acquiring them with a new anchor
152+
ctx3 := context.Background()
153+
err = store.AcquireLocks(ctx3, "anchor_verify", "alice", "bob")
154+
assert.NoError(t, err, "alice and bob should be available (rollback successful)")
155+
156+
// Clean up
157+
store.ReleaseLocks(ctx1, "anchor_blocker")
158+
store.ReleaseLocks(ctx3, "anchor_verify")
159+
}
160+
161+
func TestAcquireLocks_ConcurrentNonOverlapping(t *testing.T) {
162+
store := &StoreService{
163+
eIDsLocks: sync.Map{},
164+
}
165+
ctx := context.Background()
166+
167+
var wg sync.WaitGroup
168+
wg.Add(2)
169+
170+
// Two goroutines acquiring non-overlapping locks should both succeed
171+
go func() {
172+
defer wg.Done()
173+
err := store.AcquireLocks(ctx, "anchor_concurrent1", "alice", "bob")
174+
assert.NoError(t, err)
175+
time.Sleep(100 * time.Millisecond)
176+
store.ReleaseLocks(ctx, "anchor_concurrent1")
177+
}()
178+
179+
go func() {
180+
defer wg.Done()
181+
err := store.AcquireLocks(ctx, "anchor_concurrent2", "charlie", "dave")
182+
assert.NoError(t, err)
183+
time.Sleep(100 * time.Millisecond)
184+
store.ReleaseLocks(ctx, "anchor_concurrent2")
185+
}()
186+
187+
wg.Wait()
188+
}
189+
190+
func TestAcquireLocks_ConcurrentOverlapping(t *testing.T) {
191+
store := &StoreService{
192+
eIDsLocks: sync.Map{},
193+
}
194+
ctx := context.Background()
195+
196+
var wg sync.WaitGroup
197+
successCount := 0
198+
var mu sync.Mutex
199+
200+
// Multiple goroutines trying to acquire overlapping locks
201+
for i := 0; i < 5; i++ {
202+
wg.Add(1)
203+
go func(id int) {
204+
defer wg.Done()
205+
anchor := "anchor_overlap_" + string(rune('0'+id))
206+
err := store.AcquireLocks(ctx, anchor, "shared_resource")
207+
if err == nil {
208+
mu.Lock()
209+
successCount++
210+
mu.Unlock()
211+
time.Sleep(50 * time.Millisecond)
212+
store.ReleaseLocks(ctx, anchor)
213+
}
214+
}(i)
215+
}
216+
217+
wg.Wait()
218+
219+
// All should eventually succeed (one at a time)
220+
assert.Equal(t, 5, successCount, "all goroutines should eventually acquire the lock")
221+
}
222+
223+
func TestAcquireLocks_DeadlockPrevention(t *testing.T) {
224+
store := &StoreService{
225+
eIDsLocks: sync.Map{},
226+
}
227+
ctx := context.Background()
228+
229+
var wg sync.WaitGroup
230+
wg.Add(2)
231+
232+
// Two goroutines trying to acquire the same locks in different order
233+
// Sorting should prevent deadlock
234+
go func() {
235+
defer wg.Done()
236+
err := store.AcquireLocks(ctx, "anchor_deadlock1", "alice", "bob")
237+
assert.NoError(t, err)
238+
time.Sleep(100 * time.Millisecond)
239+
store.ReleaseLocks(ctx, "anchor_deadlock1")
240+
}()
241+
242+
go func() {
243+
defer wg.Done()
244+
// Different order, but sorting will make it the same
245+
err := store.AcquireLocks(ctx, "anchor_deadlock2", "bob", "alice")
246+
assert.NoError(t, err)
247+
time.Sleep(100 * time.Millisecond)
248+
store.ReleaseLocks(ctx, "anchor_deadlock2")
249+
}()
250+
251+
// Use a timeout to detect if deadlock occurs
252+
done := make(chan struct{})
253+
go func() {
254+
wg.Wait()
255+
close(done)
256+
}()
257+
258+
select {
259+
case <-done:
260+
// Success - no deadlock
261+
case <-time.After(5 * time.Second):
262+
t.Fatal("Deadlock detected - goroutines did not complete")
263+
}
264+
}
265+
266+
func TestAcquireLocks_EmptyEnrollmentIDs(t *testing.T) {
267+
store := &StoreService{
268+
eIDsLocks: sync.Map{},
269+
}
270+
ctx := context.Background()
271+
272+
// Test with no enrollment IDs
273+
err := store.AcquireLocks(ctx, "anchor_empty")
274+
require.NoError(t, err)
275+
276+
// Verify that the anchor mapping was stored (with empty slice)
277+
dedupBoxed, ok := store.eIDsLocks.Load("anchor_empty")
278+
require.True(t, ok)
279+
dedup := dedupBoxed.([]string)
280+
assert.Empty(t, dedup)
281+
282+
// Clean up
283+
store.ReleaseLocks(ctx, "anchor_empty")
284+
}
285+
286+
func TestAcquireLocks_SingleEnrollmentID(t *testing.T) {
287+
store := &StoreService{
288+
eIDsLocks: sync.Map{},
289+
}
290+
ctx := context.Background()
291+
292+
// Test with a single enrollment ID
293+
err := store.AcquireLocks(ctx, "anchor_single", "alice")
294+
require.NoError(t, err)
295+
296+
dedupBoxed, ok := store.eIDsLocks.Load("anchor_single")
297+
require.True(t, ok)
298+
dedup := dedupBoxed.([]string)
299+
assert.Equal(t, []string{"alice"}, dedup)
300+
301+
// Clean up
302+
store.ReleaseLocks(ctx, "anchor_single")
303+
}
304+
305+
func TestReleaseLocks_NonExistentAnchor(t *testing.T) {
306+
store := &StoreService{
307+
eIDsLocks: sync.Map{},
308+
}
309+
ctx := context.Background()
310+
311+
// Releasing locks for a non-existent anchor should not panic
312+
store.ReleaseLocks(ctx, "non_existent_anchor")
313+
// If we reach here without panic, test passes
314+
}
315+
316+
func TestAcquireAndReleaseLocks_Integration(t *testing.T) {
317+
store := &StoreService{
318+
eIDsLocks: sync.Map{},
319+
}
320+
ctx := context.Background()
321+
322+
// Acquire locks
323+
err := store.AcquireLocks(ctx, "anchor_integration", "alice", "bob", "charlie")
324+
require.NoError(t, err)
325+
326+
// Verify locks are held
327+
dedupBoxed, ok := store.eIDsLocks.Load("anchor_integration")
328+
require.True(t, ok)
329+
330+
// Release locks
331+
store.ReleaseLocks(ctx, "anchor_integration")
332+
333+
// Verify anchor mapping is removed
334+
_, ok = store.eIDsLocks.Load("anchor_integration")
335+
assert.False(t, ok, "anchor mapping should be removed after release")
336+
337+
// Verify we can acquire the same locks again
338+
err = store.AcquireLocks(ctx, "anchor_integration2", "alice", "bob", "charlie")
339+
assert.NoError(t, err, "should be able to re-acquire released locks")
340+
341+
// Clean up
342+
store.ReleaseLocks(ctx, "anchor_integration2")
343+
}
344+
345+
// Made with Bob

0 commit comments

Comments
 (0)