mirror of
https://github.com/caddyserver/caddy.git
synced 2025-11-15 19:13:24 -05:00
fmt
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
This commit is contained in:
parent
c2d586c458
commit
6872a66604
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user