diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 0000000..ea85763 --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,144 @@ +package cmd + +import ( + "context" + "sync" + "sync/atomic" + "testing" + "time" +) + +// Mock client to count requests +type mockClient struct { + requestCount int64 +} + +func (m *mockClient) MakeRequest(url string) { + atomic.AddInt64(&m.requestCount, 1) + // Small delay to simulate actual request + time.Sleep(1 * time.Millisecond) +} + +func newMockClient() *mockClient { + return &mockClient{} +} + +func TestGoroutineSpawning(t *testing.T) { + tests := []struct { + name string + goroutineCount int + expectedCalls int64 + }{ + { + name: "single goroutine", + goroutineCount: 1, + expectedCalls: 1, + }, + { + name: "multiple goroutines", + goroutineCount: 5, + expectedCalls: 5, + }, + { + name: "many goroutines", + goroutineCount: 10, + expectedCalls: 10, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := newMockClient() + ctx := context.Background() + + // Simulate the concurrency loop + wg := &sync.WaitGroup{} + wg.Add(tt.goroutineCount) + + for range tt.goroutineCount { + go func() { + defer wg.Done() + select { + case <-ctx.Done(): + return + default: + mockClient.MakeRequest("http://test.com") + } + }() + } + + wg.Wait() + + if mockClient.requestCount != tt.expectedCalls { + t.Errorf("Expected %d requests, got %d", tt.expectedCalls, mockClient.requestCount) + } + }) + } +} + +func TestConcurrencyWithContext(t *testing.T) { + mockClient := newMockClient() + ctx, cancel := context.WithCancel(context.Background()) + + // Cancel immediately to test cancellation behavior + cancel() + + wg := &sync.WaitGroup{} + goroutineCount := 5 + wg.Add(goroutineCount) + + for range goroutineCount { + go func() { + defer wg.Done() + select { + case <-ctx.Done(): + return + default: + mockClient.MakeRequest("http://test.com") + } + }() + } + + wg.Wait() + + // Since context is cancelled, no requests should be made + if mockClient.requestCount != 0 { + t.Errorf("Expected 0 requests due to cancellation, got %d", mockClient.requestCount) + } +} + +func TestConcurrencyTiming(t *testing.T) { + mockClient := newMockClient() + ctx := context.Background() + goroutineCount := 3 + + start := time.Now() + wg := &sync.WaitGroup{} + wg.Add(goroutineCount) + + for range goroutineCount { + go func() { + defer wg.Done() + select { + case <-ctx.Done(): + return + default: + // Simulate a 5ms request + time.Sleep(5 * time.Millisecond) + mockClient.MakeRequest("http://test.com") + } + }() + } + + wg.Wait() + duration := time.Since(start) + + // With 3 concurrent goroutines, total time should be roughly 5ms, not 15ms + if duration > 10*time.Millisecond { + t.Errorf("Concurrent execution took too long: %v, expected around 5ms", duration) + } + + if mockClient.requestCount != int64(goroutineCount) { + t.Errorf("Expected %d requests, got %d", goroutineCount, mockClient.requestCount) + } +}