From 8ad94c8386b2596d512a9ec2658dc46c1d2051e6 Mon Sep 17 00:00:00 2001 From: Jashwanth Kumar K <101602768+jash2105@users.noreply.github.com> Date: Fri, 28 Feb 2025 13:15:37 -0600 Subject: [PATCH] simple testing suite for async --- pkg/async/async_test.go | 141 +++++++++++++++++++++++++++++++++++++ pkg/async/asyncset_test.go | 133 ++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 pkg/async/async_test.go create mode 100644 pkg/async/asyncset_test.go diff --git a/pkg/async/async_test.go b/pkg/async/async_test.go new file mode 100644 index 0000000..ae648bb --- /dev/null +++ b/pkg/async/async_test.go @@ -0,0 +1,141 @@ +package async + +import ( + "testing" + "time" +) + +func TestAtomicBool(t *testing.T) { + // Test initialization with default value + trueVal := NewAtomicBool(true) + falseVal := NewAtomicBool(false) + + if !trueVal.Get() { + t.Errorf("NewAtomicBool(true) should return true on Get()") + } + + if falseVal.Get() { + t.Errorf("NewAtomicBool(false) should return false on Get()") + } + + // Test Set method + boolVal := NewAtomicBool(false) + boolVal.Set(true) + if !boolVal.Get() { + t.Errorf("After Set(true), Get() should return true") + } + + boolVal.Set(false) + if boolVal.Get() { + t.Errorf("After Set(false), Get() should return false") + } + + // Test CompareAndSet + atomBool := NewAtomicBool(false) + + // When current matches, should change and return true + if !atomBool.CompareAndSet(false, true) { + t.Errorf("CompareAndSet with matching current value should return true") + } + + if !atomBool.Get() { + t.Errorf("After successful CompareAndSet(false, true), value should be true") + } + + // When current doesn't match, should not change and return false + if atomBool.CompareAndSet(false, false) { + t.Errorf("CompareAndSet with non-matching current value should return false") + } + + if !atomBool.Get() { + t.Errorf("After failed CompareAndSet, value should remain unchanged") + } +} + +func TestWaitChannelBasic(t *testing.T) { + wc := NewWaitChannel() + + // Add 1 to the counter + wc.Add(1) + + // Create a channel to track completion + done := make(chan bool) + + // Start a goroutine to wait on the WaitChannel + go func() { + ch := wc.Wait() + <-ch // Wait for completion + done <- true + }() + + // Give the goroutine time to start + time.Sleep(10 * time.Millisecond) + + // Call Done to decrement the counter + wc.Done() + + // Check if the goroutine completes + select { + case <-done: + // Success - the goroutine completed + case <-time.After(100 * time.Millisecond): + t.Errorf("WaitChannel didn't complete within the expected time") + } +} + +func TestWaitChannelMultipleWaiters(t *testing.T) { + wc := NewWaitChannel() + wc.Add(1) + + // Get the wait channel twice - should be the same channel + ch1 := wc.Wait() + ch2 := wc.Wait() + + if ch1 != ch2 { + t.Errorf("Multiple calls to Wait() should return the same channel") + } + + // Mark as done + wc.Done() + + // Both channels should close + select { + case <-ch1: + // Expected + case <-time.After(100 * time.Millisecond): + t.Errorf("First wait channel wasn't closed after Done()") + } + + select { + case <-ch2: + // Expected + case <-time.After(100 * time.Millisecond): + t.Errorf("Second wait channel wasn't closed after Done()") + } +} + +func TestWaitChannelReuse(t *testing.T) { + wc := NewWaitChannel() + + // First usage + wc.Add(1) + ch1 := wc.Wait() + wc.Done() + + // Wait for channel to close + <-ch1 + + // Give some time for cleanup to finish + time.Sleep(20 * time.Millisecond) + + // Second usage + wc.Add(1) + ch2 := wc.Wait() + + // Verify it's a different channel + if ch1 == ch2 { + t.Errorf("Wait() should return a new channel after previous completion") + } + + wc.Done() +} \ No newline at end of file diff --git a/pkg/async/asyncset_test.go b/pkg/async/asyncset_test.go new file mode 100644 index 0000000..4986d38 --- /dev/null +++ b/pkg/async/asyncset_test.go @@ -0,0 +1,133 @@ +package async + +import ( + "sync" + "testing" +) + +func TestConcurrentStringSetBasic(t *testing.T) { + // Create a new set + set := NewConcurrentStringSet() + + // Initially the set should be empty + if set.Contains("test") { + t.Errorf("New set should not contain any values") + } + + // Add a value and check it exists + set.Add("hello") + if !set.Contains("hello") { + t.Errorf("Set should contain 'hello' after adding it") + } + + // Adding the same value again should be fine + set.Add("hello") + if !set.Contains("hello") { + t.Errorf("Set should still contain 'hello' after adding it again") + } + + // Add another value + set.Add("world") + if !set.Contains("world") { + t.Errorf("Set should contain 'world' after adding it") + } + + // Remove a value + set.Remove("hello") + if set.Contains("hello") { + t.Errorf("Set should not contain 'hello' after removing it") + } + if !set.Contains("world") { + t.Errorf("Set should still contain 'world'") + } + + // Remove a non-existent value (should be a no-op) + set.Remove("nonexistent") + if set.Contains("nonexistent") { + t.Errorf("Set should not contain 'nonexistent'") + } +} + +func TestConcurrentStringSetThreadSafety(t *testing.T) { + set := NewConcurrentStringSet() + const routines = 10 + const iterations = 100 + + var wg sync.WaitGroup + wg.Add(routines * 2) // Adding and checking routines + + // Start goroutines that add values + for i := 0; i < routines; i++ { + go func(id int) { + defer wg.Done() + + // Add a unique value for this goroutine multiple times + key := string(rune('A' + id)) + for j := 0; j < iterations; j++ { + set.Add(key) + } + }(i) + } + + // Start goroutines that check for values + for i := 0; i < routines; i++ { + go func(id int) { + defer wg.Done() + + key := string(rune('A' + id)) + for j := 0; j < iterations; j++ { + // Just check repeatedly - we don't know if the value + // has been added yet, so we can't assert anything here + set.Contains(key) + } + }(i) + } + + // Wait for all operations to complete + wg.Wait() + + // Verify all values were added + for i := 0; i < routines; i++ { + key := string(rune('A' + i)) + if !set.Contains(key) { + t.Errorf("Set should contain '%s' after concurrent operations", key) + } + } +} + +func TestConcurrentStringSetRemoval(t *testing.T) { + set := NewConcurrentStringSet() + + // Add some values + testValues := []string{"one", "two", "three", "four", "five"} + for _, val := range testValues { + set.Add(val) + } + + // Check they were all added + for _, val := range testValues { + if !set.Contains(val) { + t.Errorf("Set should contain '%s'", val) + } + } + + // Remove them concurrently + var wg sync.WaitGroup + wg.Add(len(testValues)) + + for _, val := range testValues { + go func(v string) { + defer wg.Done() + set.Remove(v) + }(val) + } + + wg.Wait() + + // Verify all were removed + for _, val := range testValues { + if set.Contains(val) { + t.Errorf("Set should not contain '%s' after removal", val) + } + } +} \ No newline at end of file