Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
This commit is contained in:
Mohammed Al Sahaf 2025-09-22 12:43:15 +03:00
parent c2d586c458
commit 6872a66604
No known key found for this signature in database
2 changed files with 82 additions and 82 deletions

View File

@ -29,10 +29,10 @@ func TestAPIError_Error_WithErr(t *testing.T) {
Err: underlyingErr, Err: underlyingErr,
Message: "API error message", Message: "API error message",
} }
result := apiErr.Error() result := apiErr.Error()
expected := "underlying error" expected := "underlying error"
if result != expected { if result != expected {
t.Errorf("Expected '%s', got '%s'", expected, result) t.Errorf("Expected '%s', got '%s'", expected, result)
} }
@ -44,10 +44,10 @@ func TestAPIError_Error_WithoutErr(t *testing.T) {
Err: nil, Err: nil,
Message: "API error message", Message: "API error message",
} }
result := apiErr.Error() result := apiErr.Error()
expected := "API error message" expected := "API error message"
if result != expected { if result != expected {
t.Errorf("Expected '%s', got '%s'", expected, result) t.Errorf("Expected '%s', got '%s'", expected, result)
} }
@ -59,10 +59,10 @@ func TestAPIError_Error_BothNil(t *testing.T) {
Err: nil, Err: nil,
Message: "", Message: "",
} }
result := apiErr.Error() result := apiErr.Error()
expected := "" expected := ""
if result != expected { if result != expected {
t.Errorf("Expected empty string, got '%s'", result) t.Errorf("Expected empty string, got '%s'", result)
} }
@ -102,7 +102,7 @@ func TestAPIError_JSON_Serialization(t *testing.T) {
}, },
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
// Marshal to JSON // Marshal to JSON
@ -110,21 +110,21 @@ func TestAPIError_JSON_Serialization(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to marshal APIError: %v", err) t.Fatalf("Failed to marshal APIError: %v", err)
} }
// Unmarshal back // Unmarshal back
var unmarshaled APIError var unmarshaled APIError
err = json.Unmarshal(jsonData, &unmarshaled) err = json.Unmarshal(jsonData, &unmarshaled)
if err != nil { if err != nil {
t.Fatalf("Failed to unmarshal APIError: %v", err) t.Fatalf("Failed to unmarshal APIError: %v", err)
} }
// Only Message field should survive JSON round-trip // Only Message field should survive JSON round-trip
// HTTPStatus and Err are marked with json:"-" // HTTPStatus and Err are marked with json:"-"
if unmarshaled.Message != test.apiErr.Message { if unmarshaled.Message != test.apiErr.Message {
t.Errorf("Message mismatch: expected '%s', got '%s'", t.Errorf("Message mismatch: expected '%s', got '%s'",
test.apiErr.Message, unmarshaled.Message) test.apiErr.Message, unmarshaled.Message)
} }
// HTTPStatus and Err should be zero values after unmarshal // HTTPStatus and Err should be zero values after unmarshal
if unmarshaled.HTTPStatus != 0 { if unmarshaled.HTTPStatus != 0 {
t.Errorf("HTTPStatus should be 0 after unmarshal, got %d", unmarshaled.HTTPStatus) t.Errorf("HTTPStatus should be 0 after unmarshal, got %d", unmarshaled.HTTPStatus)
@ -150,18 +150,18 @@ func TestAPIError_HTTPStatus_Values(t *testing.T) {
http.StatusNotImplemented, http.StatusNotImplemented,
http.StatusServiceUnavailable, http.StatusServiceUnavailable,
} }
for _, status := range statusCodes { for _, status := range statusCodes {
t.Run(fmt.Sprintf("status_%d", status), func(t *testing.T) { t.Run(fmt.Sprintf("status_%d", status), func(t *testing.T) {
apiErr := APIError{ apiErr := APIError{
HTTPStatus: status, HTTPStatus: status,
Message: http.StatusText(status), Message: http.StatusText(status),
} }
if apiErr.HTTPStatus != status { if apiErr.HTTPStatus != status {
t.Errorf("Expected status %d, got %d", status, apiErr.HTTPStatus) t.Errorf("Expected status %d, got %d", status, apiErr.HTTPStatus)
} }
// Test that error message is reasonable // Test that error message is reasonable
if apiErr.Message == "" && status >= 400 { if apiErr.Message == "" && status >= 400 {
t.Errorf("Status %d should have a message", status) t.Errorf("Status %d should have a message", status)
@ -176,12 +176,12 @@ func TestAPIError_ErrorInterface_Compliance(t *testing.T) {
HTTPStatus: http.StatusBadRequest, HTTPStatus: http.StatusBadRequest,
Message: "test error", Message: "test error",
} }
errorMsg := err.Error() errorMsg := err.Error()
if errorMsg != "test error" { if errorMsg != "test error" {
t.Errorf("Expected 'test error', got '%s'", errorMsg) t.Errorf("Expected 'test error', got '%s'", errorMsg)
} }
// Test with underlying error // Test with underlying error
underlyingErr := errors.New("underlying") underlyingErr := errors.New("underlying")
err2 := APIError{ err2 := APIError{
@ -189,7 +189,7 @@ func TestAPIError_ErrorInterface_Compliance(t *testing.T) {
Err: underlyingErr, Err: underlyingErr,
Message: "wrapper", Message: "wrapper",
} }
if err2.Error() != "underlying" { if err2.Error() != "underlying" {
t.Errorf("Expected 'underlying', got '%s'", err2.Error()) t.Errorf("Expected 'underlying', got '%s'", err2.Error())
} }
@ -221,27 +221,27 @@ func TestAPIError_JSON_EdgeCases(t *testing.T) {
message: string(make([]byte, 10000)), // 10KB message message: string(make([]byte, 10000)), // 10KB message
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
apiErr := APIError{ apiErr := APIError{
HTTPStatus: http.StatusBadRequest, HTTPStatus: http.StatusBadRequest,
Message: test.message, Message: test.message,
} }
// Should be JSON serializable // Should be JSON serializable
jsonData, err := json.Marshal(apiErr) jsonData, err := json.Marshal(apiErr)
if err != nil { if err != nil {
t.Fatalf("Failed to marshal APIError: %v", err) t.Fatalf("Failed to marshal APIError: %v", err)
} }
// Should be deserializable // Should be deserializable
var unmarshaled APIError var unmarshaled APIError
err = json.Unmarshal(jsonData, &unmarshaled) err = json.Unmarshal(jsonData, &unmarshaled)
if err != nil { if err != nil {
t.Fatalf("Failed to unmarshal APIError: %v", err) t.Fatalf("Failed to unmarshal APIError: %v", err)
} }
if unmarshaled.Message != test.message { if unmarshaled.Message != test.message {
t.Errorf("Message corrupted during JSON round-trip") t.Errorf("Message corrupted during JSON round-trip")
} }
@ -253,18 +253,18 @@ func TestAPIError_Chaining(t *testing.T) {
// Test error chaining scenarios // Test error chaining scenarios
rootErr := errors.New("root cause") rootErr := errors.New("root cause")
wrappedErr := fmt.Errorf("wrapped: %w", rootErr) wrappedErr := fmt.Errorf("wrapped: %w", rootErr)
apiErr := APIError{ apiErr := APIError{
HTTPStatus: http.StatusInternalServerError, HTTPStatus: http.StatusInternalServerError,
Err: wrappedErr, Err: wrappedErr,
Message: "API wrapper", Message: "API wrapper",
} }
// Error() should return the underlying error message // Error() should return the underlying error message
if apiErr.Error() != wrappedErr.Error() { if apiErr.Error() != wrappedErr.Error() {
t.Errorf("Expected underlying error message, got '%s'", apiErr.Error()) t.Errorf("Expected underlying error message, got '%s'", apiErr.Error())
} }
// Should be able to unwrap // Should be able to unwrap
if !errors.Is(apiErr.Err, rootErr) { if !errors.Is(apiErr.Err, rootErr) {
t.Error("Should be able to unwrap to root cause") t.Error("Should be able to unwrap to root cause")
@ -304,7 +304,7 @@ func TestAPIError_StatusCode_Boundaries(t *testing.T) {
valid: true, valid: true,
}, },
{ {
name: "valid 5xx", name: "valid 5xx",
status: http.StatusInternalServerError, status: http.StatusInternalServerError,
valid: true, valid: true,
}, },
@ -314,24 +314,24 @@ func TestAPIError_StatusCode_Boundaries(t *testing.T) {
valid: false, valid: false,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
err := APIError{ err := APIError{
HTTPStatus: test.status, HTTPStatus: test.status,
Message: "test", Message: "test",
} }
// The struct allows any int value, but we can test // The struct allows any int value, but we can test
// if it's a valid HTTP status // if it's a valid HTTP status
statusText := http.StatusText(test.status) statusText := http.StatusText(test.status)
isValidStatus := statusText != "" isValidStatus := statusText != ""
if isValidStatus != test.valid { if isValidStatus != test.valid {
t.Errorf("Status %d validity: expected %v, got %v", t.Errorf("Status %d validity: expected %v, got %v",
test.status, test.valid, isValidStatus) test.status, test.valid, isValidStatus)
} }
// Verify the struct holds the status // Verify the struct holds the status
if err.HTTPStatus != test.status { if err.HTTPStatus != test.status {
t.Errorf("Status not preserved: expected %d, got %d", test.status, err.HTTPStatus) t.Errorf("Status not preserved: expected %d, got %d", test.status, err.HTTPStatus)
@ -346,7 +346,7 @@ func BenchmarkAPIError_Error(b *testing.B) {
Err: errors.New("benchmark error"), Err: errors.New("benchmark error"),
Message: "benchmark message", Message: "benchmark message",
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
apiErr.Error() apiErr.Error()
@ -359,7 +359,7 @@ func BenchmarkAPIError_JSON_Marshal(b *testing.B) {
Err: errors.New("benchmark error"), Err: errors.New("benchmark error"),
Message: "benchmark message", Message: "benchmark message",
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
json.Marshal(apiErr) json.Marshal(apiErr)
@ -368,7 +368,7 @@ func BenchmarkAPIError_JSON_Marshal(b *testing.B) {
func BenchmarkAPIError_JSON_Unmarshal(b *testing.B) { func BenchmarkAPIError_JSON_Unmarshal(b *testing.B) {
jsonData := []byte(`{"error": "benchmark message"}`) jsonData := []byte(`{"error": "benchmark message"}`)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var result APIError var result APIError

View File

@ -187,7 +187,7 @@ func TestUsagePool_Delete_Basic(t *testing.T) {
func TestUsagePool_Delete_NonExistentKey(t *testing.T) { func TestUsagePool_Delete_NonExistentKey(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
deleted, err := pool.Delete("non-existent") deleted, err := pool.Delete("non-existent")
if err != nil { if err != nil {
t.Errorf("Expected no error for non-existent key, got: %v", err) t.Errorf("Expected no error for non-existent key, got: %v", err)
@ -198,7 +198,7 @@ func TestUsagePool_Delete_NonExistentKey(t *testing.T) {
} }
func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) { func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) {
// This test demonstrates the panic condition by manipulating // This test demonstrates the panic condition by manipulating
// the ref count directly to create an invalid state // the ref count directly to create an invalid state
pool := NewUsagePool() pool := NewUsagePool()
key := "test-key" key := "test-key"
@ -206,7 +206,7 @@ func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) {
// Store the value to get it in the pool // Store the value to get it in the pool
pool.LoadOrStore(key, mockVal) pool.LoadOrStore(key, mockVal)
// Get the pool value to manipulate its refs directly // Get the pool value to manipulate its refs directly
pool.Lock() pool.Lock()
upv, exists := pool.pool[key] upv, exists := pool.pool[key]
@ -214,7 +214,7 @@ func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) {
pool.Unlock() pool.Unlock()
t.Fatal("Value should exist in pool") t.Fatal("Value should exist in pool")
} }
// Manually set refs to 1 to test the panic condition // Manually set refs to 1 to test the panic condition
atomic.StoreInt32(&upv.refs, 1) atomic.StoreInt32(&upv.refs, 1)
pool.Unlock() pool.Unlock()
@ -241,14 +241,14 @@ func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) {
func TestUsagePool_Range(t *testing.T) { func TestUsagePool_Range(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
// Add multiple values // Add multiple values
values := map[string]string{ values := map[string]string{
"key1": "value1", "key1": "value1",
"key2": "value2", "key2": "value2",
"key3": "value3", "key3": "value3",
} }
for key, value := range values { for key, value := range values {
pool.LoadOrStore(key, &mockDestructor{value: value}) pool.LoadOrStore(key, &mockDestructor{value: value})
} }
@ -273,7 +273,7 @@ func TestUsagePool_Range(t *testing.T) {
func TestUsagePool_Range_EarlyReturn(t *testing.T) { func TestUsagePool_Range_EarlyReturn(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
// Add multiple values // Add multiple values
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
pool.LoadOrStore(i, &mockDestructor{value: "value"}) pool.LoadOrStore(i, &mockDestructor{value: "value"})
@ -295,11 +295,11 @@ func TestUsagePool_Concurrent_LoadOrNew(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
key := "concurrent-key" key := "concurrent-key"
constructorCalls := int32(0) constructorCalls := int32(0)
const numGoroutines = 100 const numGoroutines = 100
var wg sync.WaitGroup var wg sync.WaitGroup
results := make([]any, numGoroutines) results := make([]any, numGoroutines)
for i := 0; i < numGoroutines; i++ { for i := 0; i < numGoroutines; i++ {
wg.Add(1) wg.Add(1)
go func(index int) { go func(index int) {
@ -317,14 +317,14 @@ func TestUsagePool_Concurrent_LoadOrNew(t *testing.T) {
results[index] = val results[index] = val
}(i) }(i)
} }
wg.Wait() wg.Wait()
// Constructor should only be called once // Constructor should only be called once
if calls := atomic.LoadInt32(&constructorCalls); calls != 1 { if calls := atomic.LoadInt32(&constructorCalls); calls != 1 {
t.Errorf("Expected constructor to be called once, was called %d times", calls) t.Errorf("Expected constructor to be called once, was called %d times", calls)
} }
// All goroutines should get the same value // All goroutines should get the same value
firstVal := results[0] firstVal := results[0]
for i, val := range results { for i, val := range results {
@ -332,7 +332,7 @@ func TestUsagePool_Concurrent_LoadOrNew(t *testing.T) {
t.Errorf("Goroutine %d got different value than first goroutine", i) t.Errorf("Goroutine %d got different value than first goroutine", i)
} }
} }
// Reference count should equal number of goroutines // Reference count should equal number of goroutines
refs, exists := pool.References(key) refs, exists := pool.References(key)
if !exists { if !exists {
@ -347,17 +347,17 @@ func TestUsagePool_Concurrent_Delete(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
key := "concurrent-delete-key" key := "concurrent-delete-key"
mockVal := &mockDestructor{value: "test-value"} mockVal := &mockDestructor{value: "test-value"}
const numRefs = 50 const numRefs = 50
// Add multiple references // Add multiple references
for i := 0; i < numRefs; i++ { for i := 0; i < numRefs; i++ {
pool.LoadOrStore(key, mockVal) pool.LoadOrStore(key, mockVal)
} }
var wg sync.WaitGroup var wg sync.WaitGroup
deleteResults := make([]bool, numRefs) deleteResults := make([]bool, numRefs)
// Delete concurrently // Delete concurrently
for i := 0; i < numRefs; i++ { for i := 0; i < numRefs; i++ {
wg.Add(1) wg.Add(1)
@ -371,9 +371,9 @@ func TestUsagePool_Concurrent_Delete(t *testing.T) {
deleteResults[index] = deleted deleteResults[index] = deleted
}(i) }(i)
} }
wg.Wait() wg.Wait()
// Exactly one delete should have returned true (when refs reached 0) // Exactly one delete should have returned true (when refs reached 0)
deletedCount := 0 deletedCount := 0
for _, deleted := range deleteResults { for _, deleted := range deleteResults {
@ -384,12 +384,12 @@ func TestUsagePool_Concurrent_Delete(t *testing.T) {
if deletedCount != 1 { if deletedCount != 1 {
t.Errorf("Expected exactly 1 delete to return true, got %d", deletedCount) t.Errorf("Expected exactly 1 delete to return true, got %d", deletedCount)
} }
// Value should be destroyed // Value should be destroyed
if !mockVal.IsDestroyed() { if !mockVal.IsDestroyed() {
t.Error("Value should be destroyed after all references deleted") t.Error("Value should be destroyed after all references deleted")
} }
// Key should not exist // Key should not exist
refs, exists := pool.References(key) refs, exists := pool.References(key)
if exists { if exists {
@ -407,7 +407,7 @@ func TestUsagePool_DestructorError(t *testing.T) {
mockVal := &mockDestructor{value: "test-value", err: expectedErr} mockVal := &mockDestructor{value: "test-value", err: expectedErr}
pool.LoadOrStore(key, mockVal) pool.LoadOrStore(key, mockVal)
deleted, err := pool.Delete(key) deleted, err := pool.Delete(key)
if err != expectedErr { if err != expectedErr {
t.Errorf("Expected destructor error, got: %v", err) t.Errorf("Expected destructor error, got: %v", err)
@ -423,21 +423,21 @@ func TestUsagePool_DestructorError(t *testing.T) {
func TestUsagePool_Mixed_Concurrent_Operations(t *testing.T) { func TestUsagePool_Mixed_Concurrent_Operations(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
keys := []string{"key1", "key2", "key3"} keys := []string{"key1", "key2", "key3"}
var wg sync.WaitGroup var wg sync.WaitGroup
const opsPerKey = 10 const opsPerKey = 10
// Test concurrent operations but with more controlled behavior // Test concurrent operations but with more controlled behavior
for _, key := range keys { for _, key := range keys {
for i := 0; i < opsPerKey; i++ { for i := 0; i < opsPerKey; i++ {
wg.Add(2) // LoadOrStore and Delete wg.Add(2) // LoadOrStore and Delete
// LoadOrStore (safer than LoadOrNew for concurrency) // LoadOrStore (safer than LoadOrNew for concurrency)
go func(k string) { go func(k string) {
defer wg.Done() defer wg.Done()
pool.LoadOrStore(k, &mockDestructor{value: k + "-value"}) pool.LoadOrStore(k, &mockDestructor{value: k + "-value"})
}(key) }(key)
// Delete (may fail if refs are 0, that's fine) // Delete (may fail if refs are 0, that's fine)
go func(k string) { go func(k string) {
defer wg.Done() defer wg.Done()
@ -445,9 +445,9 @@ func TestUsagePool_Mixed_Concurrent_Operations(t *testing.T) {
}(key) }(key)
} }
} }
wg.Wait() wg.Wait()
// Test that the pool is in a consistent state // Test that the pool is in a consistent state
for _, key := range keys { for _, key := range keys {
refs, exists := pool.References(key) refs, exists := pool.References(key)
@ -459,17 +459,17 @@ func TestUsagePool_Mixed_Concurrent_Operations(t *testing.T) {
func TestUsagePool_Range_SkipsErrorValues(t *testing.T) { func TestUsagePool_Range_SkipsErrorValues(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
// Add value that will succeed // Add value that will succeed
goodKey := "good-key" goodKey := "good-key"
pool.LoadOrStore(goodKey, &mockDestructor{value: "good-value"}) pool.LoadOrStore(goodKey, &mockDestructor{value: "good-value"})
// Try to add value that will fail construction // Try to add value that will fail construction
badKey := "bad-key" badKey := "bad-key"
pool.LoadOrNew(badKey, func() (Destructor, error) { pool.LoadOrNew(badKey, func() (Destructor, error) {
return nil, errors.New("construction failed") return nil, errors.New("construction failed")
}) })
// Range should only iterate good values // Range should only iterate good values
count := 0 count := 0
pool.Range(func(key, value any) bool { pool.Range(func(key, value any) bool {
@ -479,7 +479,7 @@ func TestUsagePool_Range_SkipsErrorValues(t *testing.T) {
} }
return true return true
}) })
if count != 1 { if count != 1 {
t.Errorf("Expected 1 value in range, got %d", count) t.Errorf("Expected 1 value in range, got %d", count)
} }
@ -488,7 +488,7 @@ func TestUsagePool_Range_SkipsErrorValues(t *testing.T) {
func TestUsagePool_LoadOrStore_ErrorRecovery(t *testing.T) { func TestUsagePool_LoadOrStore_ErrorRecovery(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
key := "error-recovery-key" key := "error-recovery-key"
// First, create a value that fails construction // First, create a value that fails construction
_, _, err := pool.LoadOrNew(key, func() (Destructor, error) { _, _, err := pool.LoadOrNew(key, func() (Destructor, error) {
return nil, errors.New("construction failed") return nil, errors.New("construction failed")
@ -496,7 +496,7 @@ func TestUsagePool_LoadOrStore_ErrorRecovery(t *testing.T) {
if err == nil { if err == nil {
t.Error("Expected constructor error") t.Error("Expected constructor error")
} }
// Now try LoadOrStore with a good value - should recover // Now try LoadOrStore with a good value - should recover
goodVal := &mockDestructor{value: "recovery-value"} goodVal := &mockDestructor{value: "recovery-value"}
val, loaded := pool.LoadOrStore(key, goodVal) val, loaded := pool.LoadOrStore(key, goodVal)
@ -511,15 +511,15 @@ func TestUsagePool_LoadOrStore_ErrorRecovery(t *testing.T) {
func TestUsagePool_MemoryLeak_Prevention(t *testing.T) { func TestUsagePool_MemoryLeak_Prevention(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
key := "memory-leak-test" key := "memory-leak-test"
// Create many references // Create many references
const numRefs = 1000 const numRefs = 1000
mockVal := &mockDestructor{value: "leak-test"} mockVal := &mockDestructor{value: "leak-test"}
for i := 0; i < numRefs; i++ { for i := 0; i < numRefs; i++ {
pool.LoadOrStore(key, mockVal) pool.LoadOrStore(key, mockVal)
} }
// Delete all references // Delete all references
for i := 0; i < numRefs; i++ { for i := 0; i < numRefs; i++ {
deleted, err := pool.Delete(key) deleted, err := pool.Delete(key)
@ -532,12 +532,12 @@ func TestUsagePool_MemoryLeak_Prevention(t *testing.T) {
t.Errorf("Delete %d should return false", i) t.Errorf("Delete %d should return false", i)
} }
} }
// Verify destructor was called // Verify destructor was called
if !mockVal.IsDestroyed() { if !mockVal.IsDestroyed() {
t.Error("Value should be destroyed after all references deleted") t.Error("Value should be destroyed after all references deleted")
} }
// Verify no memory leak - key should be removed from map // Verify no memory leak - key should be removed from map
refs, exists := pool.References(key) refs, exists := pool.References(key)
if exists { if exists {
@ -552,29 +552,29 @@ func TestUsagePool_RaceCondition_RefsCounter(t *testing.T) {
pool := NewUsagePool() pool := NewUsagePool()
key := "race-test-key" key := "race-test-key"
mockVal := &mockDestructor{value: "race-value"} mockVal := &mockDestructor{value: "race-value"}
const numOperations = 100 const numOperations = 100
var wg sync.WaitGroup var wg sync.WaitGroup
// Mix of increment and decrement operations // Mix of increment and decrement operations
for i := 0; i < numOperations; i++ { for i := 0; i < numOperations; i++ {
wg.Add(2) wg.Add(2)
// Increment (LoadOrStore) // Increment (LoadOrStore)
go func() { go func() {
defer wg.Done() defer wg.Done()
pool.LoadOrStore(key, mockVal) pool.LoadOrStore(key, mockVal)
}() }()
// Decrement (Delete) - may fail if refs are 0, that's ok // Decrement (Delete) - may fail if refs are 0, that's ok
go func() { go func() {
defer wg.Done() defer wg.Done()
pool.Delete(key) pool.Delete(key)
}() }()
} }
wg.Wait() wg.Wait()
// Final reference count should be consistent // Final reference count should be consistent
refs, exists := pool.References(key) refs, exists := pool.References(key)
if exists { if exists {
@ -587,7 +587,7 @@ func TestUsagePool_RaceCondition_RefsCounter(t *testing.T) {
func BenchmarkUsagePool_LoadOrNew(b *testing.B) { func BenchmarkUsagePool_LoadOrNew(b *testing.B) {
pool := NewUsagePool() pool := NewUsagePool()
key := "bench-key" key := "bench-key"
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pool.LoadOrNew(key, func() (Destructor, error) { pool.LoadOrNew(key, func() (Destructor, error) {
@ -600,7 +600,7 @@ func BenchmarkUsagePool_LoadOrStore(b *testing.B) {
pool := NewUsagePool() pool := NewUsagePool()
key := "bench-key" key := "bench-key"
mockVal := &mockDestructor{value: "bench-value"} mockVal := &mockDestructor{value: "bench-value"}
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pool.LoadOrStore(key, mockVal) pool.LoadOrStore(key, mockVal)
@ -611,12 +611,12 @@ func BenchmarkUsagePool_Delete(b *testing.B) {
pool := NewUsagePool() pool := NewUsagePool()
key := "bench-key" key := "bench-key"
mockVal := &mockDestructor{value: "bench-value"} mockVal := &mockDestructor{value: "bench-value"}
// Pre-populate with many references // Pre-populate with many references
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pool.LoadOrStore(key, mockVal) pool.LoadOrStore(key, mockVal)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pool.Delete(key) pool.Delete(key)