mirror of
https://github.com/caddyserver/caddy.git
synced 2025-11-15 11:03:23 -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,
|
||||
Message: "API error message",
|
||||
}
|
||||
|
||||
|
||||
result := apiErr.Error()
|
||||
expected := "underlying error"
|
||||
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Expected '%s', got '%s'", expected, result)
|
||||
}
|
||||
@ -44,10 +44,10 @@ func TestAPIError_Error_WithoutErr(t *testing.T) {
|
||||
Err: nil,
|
||||
Message: "API error message",
|
||||
}
|
||||
|
||||
|
||||
result := apiErr.Error()
|
||||
expected := "API error message"
|
||||
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Expected '%s', got '%s'", expected, result)
|
||||
}
|
||||
@ -59,10 +59,10 @@ func TestAPIError_Error_BothNil(t *testing.T) {
|
||||
Err: nil,
|
||||
Message: "",
|
||||
}
|
||||
|
||||
|
||||
result := apiErr.Error()
|
||||
expected := ""
|
||||
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Expected empty string, got '%s'", result)
|
||||
}
|
||||
@ -102,7 +102,7 @@ func TestAPIError_JSON_Serialization(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// Marshal to JSON
|
||||
@ -110,21 +110,21 @@ func TestAPIError_JSON_Serialization(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal APIError: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Unmarshal back
|
||||
var unmarshaled APIError
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal APIError: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Only Message field should survive JSON round-trip
|
||||
// HTTPStatus and Err are marked with json:"-"
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
// HTTPStatus and Err should be zero values after unmarshal
|
||||
if unmarshaled.HTTPStatus != 0 {
|
||||
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.StatusServiceUnavailable,
|
||||
}
|
||||
|
||||
|
||||
for _, status := range statusCodes {
|
||||
t.Run(fmt.Sprintf("status_%d", status), func(t *testing.T) {
|
||||
apiErr := APIError{
|
||||
HTTPStatus: status,
|
||||
Message: http.StatusText(status),
|
||||
}
|
||||
|
||||
|
||||
if apiErr.HTTPStatus != status {
|
||||
t.Errorf("Expected status %d, got %d", status, apiErr.HTTPStatus)
|
||||
}
|
||||
|
||||
|
||||
// Test that error message is reasonable
|
||||
if apiErr.Message == "" && status >= 400 {
|
||||
t.Errorf("Status %d should have a message", status)
|
||||
@ -176,12 +176,12 @@ func TestAPIError_ErrorInterface_Compliance(t *testing.T) {
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Message: "test error",
|
||||
}
|
||||
|
||||
|
||||
errorMsg := err.Error()
|
||||
if errorMsg != "test error" {
|
||||
t.Errorf("Expected 'test error', got '%s'", errorMsg)
|
||||
}
|
||||
|
||||
|
||||
// Test with underlying error
|
||||
underlyingErr := errors.New("underlying")
|
||||
err2 := APIError{
|
||||
@ -189,7 +189,7 @@ func TestAPIError_ErrorInterface_Compliance(t *testing.T) {
|
||||
Err: underlyingErr,
|
||||
Message: "wrapper",
|
||||
}
|
||||
|
||||
|
||||
if err2.Error() != "underlying" {
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
apiErr := APIError{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Message: test.message,
|
||||
}
|
||||
|
||||
|
||||
// Should be JSON serializable
|
||||
jsonData, err := json.Marshal(apiErr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal APIError: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Should be deserializable
|
||||
var unmarshaled APIError
|
||||
err = json.Unmarshal(jsonData, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal APIError: %v", err)
|
||||
}
|
||||
|
||||
|
||||
if unmarshaled.Message != test.message {
|
||||
t.Errorf("Message corrupted during JSON round-trip")
|
||||
}
|
||||
@ -253,18 +253,18 @@ func TestAPIError_Chaining(t *testing.T) {
|
||||
// Test error chaining scenarios
|
||||
rootErr := errors.New("root cause")
|
||||
wrappedErr := fmt.Errorf("wrapped: %w", rootErr)
|
||||
|
||||
|
||||
apiErr := APIError{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
Err: wrappedErr,
|
||||
Message: "API wrapper",
|
||||
}
|
||||
|
||||
|
||||
// Error() should return the underlying error message
|
||||
if apiErr.Error() != wrappedErr.Error() {
|
||||
t.Errorf("Expected underlying error message, got '%s'", apiErr.Error())
|
||||
}
|
||||
|
||||
|
||||
// Should be able to unwrap
|
||||
if !errors.Is(apiErr.Err, rootErr) {
|
||||
t.Error("Should be able to unwrap to root cause")
|
||||
@ -304,7 +304,7 @@ func TestAPIError_StatusCode_Boundaries(t *testing.T) {
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "valid 5xx",
|
||||
name: "valid 5xx",
|
||||
status: http.StatusInternalServerError,
|
||||
valid: true,
|
||||
},
|
||||
@ -314,24 +314,24 @@ func TestAPIError_StatusCode_Boundaries(t *testing.T) {
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := APIError{
|
||||
HTTPStatus: test.status,
|
||||
Message: "test",
|
||||
}
|
||||
|
||||
|
||||
// The struct allows any int value, but we can test
|
||||
// if it's a valid HTTP status
|
||||
statusText := http.StatusText(test.status)
|
||||
isValidStatus := statusText != ""
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
// Verify the struct holds the status
|
||||
if err.HTTPStatus != test.status {
|
||||
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"),
|
||||
Message: "benchmark message",
|
||||
}
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
apiErr.Error()
|
||||
@ -359,7 +359,7 @@ func BenchmarkAPIError_JSON_Marshal(b *testing.B) {
|
||||
Err: errors.New("benchmark error"),
|
||||
Message: "benchmark message",
|
||||
}
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
json.Marshal(apiErr)
|
||||
@ -368,7 +368,7 @@ func BenchmarkAPIError_JSON_Marshal(b *testing.B) {
|
||||
|
||||
func BenchmarkAPIError_JSON_Unmarshal(b *testing.B) {
|
||||
jsonData := []byte(`{"error": "benchmark message"}`)
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var result APIError
|
||||
|
||||
@ -187,7 +187,7 @@ func TestUsagePool_Delete_Basic(t *testing.T) {
|
||||
|
||||
func TestUsagePool_Delete_NonExistentKey(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
|
||||
|
||||
deleted, err := pool.Delete("non-existent")
|
||||
if err != nil {
|
||||
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) {
|
||||
// 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
|
||||
pool := NewUsagePool()
|
||||
key := "test-key"
|
||||
@ -206,7 +206,7 @@ func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) {
|
||||
|
||||
// Store the value to get it in the pool
|
||||
pool.LoadOrStore(key, mockVal)
|
||||
|
||||
|
||||
// Get the pool value to manipulate its refs directly
|
||||
pool.Lock()
|
||||
upv, exists := pool.pool[key]
|
||||
@ -214,7 +214,7 @@ func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) {
|
||||
pool.Unlock()
|
||||
t.Fatal("Value should exist in pool")
|
||||
}
|
||||
|
||||
|
||||
// Manually set refs to 1 to test the panic condition
|
||||
atomic.StoreInt32(&upv.refs, 1)
|
||||
pool.Unlock()
|
||||
@ -241,14 +241,14 @@ func TestUsagePool_Delete_PanicOnNegativeRefs(t *testing.T) {
|
||||
|
||||
func TestUsagePool_Range(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
|
||||
|
||||
// Add multiple values
|
||||
values := map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
|
||||
for key, value := range values {
|
||||
pool.LoadOrStore(key, &mockDestructor{value: value})
|
||||
}
|
||||
@ -273,7 +273,7 @@ func TestUsagePool_Range(t *testing.T) {
|
||||
|
||||
func TestUsagePool_Range_EarlyReturn(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
|
||||
|
||||
// Add multiple values
|
||||
for i := 0; i < 5; i++ {
|
||||
pool.LoadOrStore(i, &mockDestructor{value: "value"})
|
||||
@ -295,11 +295,11 @@ func TestUsagePool_Concurrent_LoadOrNew(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
key := "concurrent-key"
|
||||
constructorCalls := int32(0)
|
||||
|
||||
|
||||
const numGoroutines = 100
|
||||
var wg sync.WaitGroup
|
||||
results := make([]any, numGoroutines)
|
||||
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(index int) {
|
||||
@ -317,14 +317,14 @@ func TestUsagePool_Concurrent_LoadOrNew(t *testing.T) {
|
||||
results[index] = val
|
||||
}(i)
|
||||
}
|
||||
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
// Constructor should only be called once
|
||||
if calls := atomic.LoadInt32(&constructorCalls); calls != 1 {
|
||||
t.Errorf("Expected constructor to be called once, was called %d times", calls)
|
||||
}
|
||||
|
||||
|
||||
// All goroutines should get the same value
|
||||
firstVal := results[0]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reference count should equal number of goroutines
|
||||
refs, exists := pool.References(key)
|
||||
if !exists {
|
||||
@ -347,17 +347,17 @@ func TestUsagePool_Concurrent_Delete(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
key := "concurrent-delete-key"
|
||||
mockVal := &mockDestructor{value: "test-value"}
|
||||
|
||||
|
||||
const numRefs = 50
|
||||
|
||||
|
||||
// Add multiple references
|
||||
for i := 0; i < numRefs; i++ {
|
||||
pool.LoadOrStore(key, mockVal)
|
||||
}
|
||||
|
||||
|
||||
var wg sync.WaitGroup
|
||||
deleteResults := make([]bool, numRefs)
|
||||
|
||||
|
||||
// Delete concurrently
|
||||
for i := 0; i < numRefs; i++ {
|
||||
wg.Add(1)
|
||||
@ -371,9 +371,9 @@ func TestUsagePool_Concurrent_Delete(t *testing.T) {
|
||||
deleteResults[index] = deleted
|
||||
}(i)
|
||||
}
|
||||
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
// Exactly one delete should have returned true (when refs reached 0)
|
||||
deletedCount := 0
|
||||
for _, deleted := range deleteResults {
|
||||
@ -384,12 +384,12 @@ func TestUsagePool_Concurrent_Delete(t *testing.T) {
|
||||
if deletedCount != 1 {
|
||||
t.Errorf("Expected exactly 1 delete to return true, got %d", deletedCount)
|
||||
}
|
||||
|
||||
|
||||
// Value should be destroyed
|
||||
if !mockVal.IsDestroyed() {
|
||||
t.Error("Value should be destroyed after all references deleted")
|
||||
}
|
||||
|
||||
|
||||
// Key should not exist
|
||||
refs, exists := pool.References(key)
|
||||
if exists {
|
||||
@ -407,7 +407,7 @@ func TestUsagePool_DestructorError(t *testing.T) {
|
||||
mockVal := &mockDestructor{value: "test-value", err: expectedErr}
|
||||
|
||||
pool.LoadOrStore(key, mockVal)
|
||||
|
||||
|
||||
deleted, err := pool.Delete(key)
|
||||
if err != expectedErr {
|
||||
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) {
|
||||
pool := NewUsagePool()
|
||||
keys := []string{"key1", "key2", "key3"}
|
||||
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const opsPerKey = 10
|
||||
|
||||
|
||||
// Test concurrent operations but with more controlled behavior
|
||||
for _, key := range keys {
|
||||
for i := 0; i < opsPerKey; i++ {
|
||||
wg.Add(2) // LoadOrStore and Delete
|
||||
|
||||
|
||||
// LoadOrStore (safer than LoadOrNew for concurrency)
|
||||
go func(k string) {
|
||||
defer wg.Done()
|
||||
pool.LoadOrStore(k, &mockDestructor{value: k + "-value"})
|
||||
}(key)
|
||||
|
||||
|
||||
// Delete (may fail if refs are 0, that's fine)
|
||||
go func(k string) {
|
||||
defer wg.Done()
|
||||
@ -445,9 +445,9 @@ func TestUsagePool_Mixed_Concurrent_Operations(t *testing.T) {
|
||||
}(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
// Test that the pool is in a consistent state
|
||||
for _, key := range keys {
|
||||
refs, exists := pool.References(key)
|
||||
@ -459,17 +459,17 @@ func TestUsagePool_Mixed_Concurrent_Operations(t *testing.T) {
|
||||
|
||||
func TestUsagePool_Range_SkipsErrorValues(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
|
||||
|
||||
// Add value that will succeed
|
||||
goodKey := "good-key"
|
||||
pool.LoadOrStore(goodKey, &mockDestructor{value: "good-value"})
|
||||
|
||||
|
||||
// Try to add value that will fail construction
|
||||
badKey := "bad-key"
|
||||
pool.LoadOrNew(badKey, func() (Destructor, error) {
|
||||
return nil, errors.New("construction failed")
|
||||
})
|
||||
|
||||
|
||||
// Range should only iterate good values
|
||||
count := 0
|
||||
pool.Range(func(key, value any) bool {
|
||||
@ -479,7 +479,7 @@ func TestUsagePool_Range_SkipsErrorValues(t *testing.T) {
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
if count != 1 {
|
||||
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) {
|
||||
pool := NewUsagePool()
|
||||
key := "error-recovery-key"
|
||||
|
||||
|
||||
// First, create a value that fails construction
|
||||
_, _, err := pool.LoadOrNew(key, func() (Destructor, error) {
|
||||
return nil, errors.New("construction failed")
|
||||
@ -496,7 +496,7 @@ func TestUsagePool_LoadOrStore_ErrorRecovery(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Expected constructor error")
|
||||
}
|
||||
|
||||
|
||||
// Now try LoadOrStore with a good value - should recover
|
||||
goodVal := &mockDestructor{value: "recovery-value"}
|
||||
val, loaded := pool.LoadOrStore(key, goodVal)
|
||||
@ -511,15 +511,15 @@ func TestUsagePool_LoadOrStore_ErrorRecovery(t *testing.T) {
|
||||
func TestUsagePool_MemoryLeak_Prevention(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
key := "memory-leak-test"
|
||||
|
||||
|
||||
// Create many references
|
||||
const numRefs = 1000
|
||||
mockVal := &mockDestructor{value: "leak-test"}
|
||||
|
||||
|
||||
for i := 0; i < numRefs; i++ {
|
||||
pool.LoadOrStore(key, mockVal)
|
||||
}
|
||||
|
||||
|
||||
// Delete all references
|
||||
for i := 0; i < numRefs; i++ {
|
||||
deleted, err := pool.Delete(key)
|
||||
@ -532,12 +532,12 @@ func TestUsagePool_MemoryLeak_Prevention(t *testing.T) {
|
||||
t.Errorf("Delete %d should return false", i)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Verify destructor was called
|
||||
if !mockVal.IsDestroyed() {
|
||||
t.Error("Value should be destroyed after all references deleted")
|
||||
}
|
||||
|
||||
|
||||
// Verify no memory leak - key should be removed from map
|
||||
refs, exists := pool.References(key)
|
||||
if exists {
|
||||
@ -552,29 +552,29 @@ func TestUsagePool_RaceCondition_RefsCounter(t *testing.T) {
|
||||
pool := NewUsagePool()
|
||||
key := "race-test-key"
|
||||
mockVal := &mockDestructor{value: "race-value"}
|
||||
|
||||
|
||||
const numOperations = 100
|
||||
var wg sync.WaitGroup
|
||||
|
||||
|
||||
// Mix of increment and decrement operations
|
||||
for i := 0; i < numOperations; i++ {
|
||||
wg.Add(2)
|
||||
|
||||
|
||||
// Increment (LoadOrStore)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
pool.LoadOrStore(key, mockVal)
|
||||
}()
|
||||
|
||||
|
||||
// Decrement (Delete) - may fail if refs are 0, that's ok
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
pool.Delete(key)
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
// Final reference count should be consistent
|
||||
refs, exists := pool.References(key)
|
||||
if exists {
|
||||
@ -587,7 +587,7 @@ func TestUsagePool_RaceCondition_RefsCounter(t *testing.T) {
|
||||
func BenchmarkUsagePool_LoadOrNew(b *testing.B) {
|
||||
pool := NewUsagePool()
|
||||
key := "bench-key"
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.LoadOrNew(key, func() (Destructor, error) {
|
||||
@ -600,7 +600,7 @@ func BenchmarkUsagePool_LoadOrStore(b *testing.B) {
|
||||
pool := NewUsagePool()
|
||||
key := "bench-key"
|
||||
mockVal := &mockDestructor{value: "bench-value"}
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.LoadOrStore(key, mockVal)
|
||||
@ -611,12 +611,12 @@ func BenchmarkUsagePool_Delete(b *testing.B) {
|
||||
pool := NewUsagePool()
|
||||
key := "bench-key"
|
||||
mockVal := &mockDestructor{value: "bench-value"}
|
||||
|
||||
|
||||
// Pre-populate with many references
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.LoadOrStore(key, mockVal)
|
||||
}
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.Delete(key)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user