mirror of
https://github.com/caddyserver/caddy.git
synced 2025-10-20 00:03:17 +00:00
407 lines
8.7 KiB
Go
407 lines
8.7 KiB
Go
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package caddy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseDuration_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expectErr bool
|
|
expected time.Duration
|
|
}{
|
|
{
|
|
name: "zero duration",
|
|
input: "0",
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "invalid format",
|
|
input: "abc",
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "negative days",
|
|
input: "-2d",
|
|
expected: -48 * time.Hour,
|
|
},
|
|
{
|
|
name: "decimal days",
|
|
input: "0.5d",
|
|
expected: 12 * time.Hour,
|
|
},
|
|
{
|
|
name: "large decimal days",
|
|
input: "365.25d",
|
|
expected: time.Duration(365.25*24) * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple days in same string",
|
|
input: "1d2d3d",
|
|
expected: (24 * 6) * time.Hour, // 6 days total
|
|
},
|
|
{
|
|
name: "days with other units",
|
|
input: "1d30m15s",
|
|
expected: 24*time.Hour + 30*time.Minute + 15*time.Second,
|
|
},
|
|
{
|
|
name: "malformed days",
|
|
input: "d",
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "invalid day value",
|
|
input: "abcd",
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "overflow protection",
|
|
input: "9999999999999999999999999d",
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "zero days",
|
|
input: "0d",
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "input at limit",
|
|
input: strings.Repeat("1", 1024) + "ns",
|
|
expectErr: true, // Likely to cause parsing error due to size
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result, err := ParseDuration(test.input)
|
|
|
|
if test.expectErr && err == nil {
|
|
t.Error("Expected error but got none")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !test.expectErr && result != test.expected {
|
|
t.Errorf("Expected %v, got %v", test.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseDuration_InputLengthLimit(t *testing.T) {
|
|
// Test the 1024 character limit
|
|
longInput := strings.Repeat("1", 1025) + "s"
|
|
|
|
_, err := ParseDuration(longInput)
|
|
if err == nil {
|
|
t.Error("Expected error for input longer than 1024 characters")
|
|
}
|
|
|
|
expectedErrMsg := "parsing duration: input string too long"
|
|
if err.Error() != expectedErrMsg {
|
|
t.Errorf("Expected error message '%s', got '%s'", expectedErrMsg, err.Error())
|
|
}
|
|
}
|
|
|
|
func TestParseDuration_ComplexNumberFormats(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected time.Duration
|
|
}{
|
|
{
|
|
input: "+1d",
|
|
expected: 24 * time.Hour,
|
|
},
|
|
{
|
|
input: "-1.5d",
|
|
expected: -36 * time.Hour,
|
|
},
|
|
{
|
|
input: "1.0d",
|
|
expected: 24 * time.Hour,
|
|
},
|
|
{
|
|
input: "0.25d",
|
|
expected: 6 * time.Hour,
|
|
},
|
|
{
|
|
input: "1.5d30m",
|
|
expected: 36*time.Hour + 30*time.Minute,
|
|
},
|
|
{
|
|
input: "2.5d1h30m45s",
|
|
expected: 60*time.Hour + time.Hour + 30*time.Minute + 45*time.Second,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.input, func(t *testing.T) {
|
|
result, err := ParseDuration(test.input)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if result != test.expected {
|
|
t.Errorf("Expected %v, got %v", test.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDuration_UnmarshalJSON_TypeValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expectErr bool
|
|
expected time.Duration
|
|
}{
|
|
{
|
|
name: "null value",
|
|
input: "null",
|
|
expectErr: false,
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "boolean value",
|
|
input: "true",
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "array value",
|
|
input: `[1,2,3]`,
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "object value",
|
|
input: `{"duration": "5m"}`,
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "negative integer",
|
|
input: "-1000000000",
|
|
expected: -time.Second,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "zero integer",
|
|
input: "0",
|
|
expected: 0,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "large integer",
|
|
input: "9223372036854775807", // Max int64
|
|
expected: time.Duration(math.MaxInt64),
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "float as integer (invalid JSON for int)",
|
|
input: "1.5",
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "string with special characters",
|
|
input: `"5m\"30s"`,
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "string with unicode",
|
|
input: `"5m🚀"`,
|
|
expectErr: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var d Duration
|
|
err := d.UnmarshalJSON([]byte(test.input))
|
|
|
|
if test.expectErr && err == nil {
|
|
t.Error("Expected error but got none")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !test.expectErr && time.Duration(d) != test.expected {
|
|
t.Errorf("Expected %v, got %v", test.expected, time.Duration(d))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDuration_JSON_RoundTrip(t *testing.T) {
|
|
tests := []struct {
|
|
duration time.Duration
|
|
asString bool
|
|
}{
|
|
{duration: 5 * time.Minute, asString: true},
|
|
{duration: 24 * time.Hour, asString: false}, // Will be stored as nanoseconds
|
|
{duration: 0, asString: false},
|
|
{duration: -time.Hour, asString: true},
|
|
{duration: time.Nanosecond, asString: false},
|
|
{duration: time.Second, asString: false},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.duration.String(), func(t *testing.T) {
|
|
d := Duration(test.duration)
|
|
|
|
// Marshal to JSON
|
|
jsonData, err := json.Marshal(d)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal: %v", err)
|
|
}
|
|
|
|
// Unmarshal back
|
|
var unmarshaled Duration
|
|
err = unmarshaled.UnmarshalJSON(jsonData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal: %v", err)
|
|
}
|
|
|
|
// Should be equal
|
|
if time.Duration(unmarshaled) != test.duration {
|
|
t.Errorf("Round trip failed: expected %v, got %v", test.duration, time.Duration(unmarshaled))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseDuration_Precision(t *testing.T) {
|
|
// Test floating point precision with days
|
|
tests := []struct {
|
|
input string
|
|
expected time.Duration
|
|
}{
|
|
{
|
|
input: "0.1d",
|
|
expected: time.Duration(0.1 * 24 * float64(time.Hour)),
|
|
},
|
|
{
|
|
input: "0.01d",
|
|
expected: time.Duration(0.01 * 24 * float64(time.Hour)),
|
|
},
|
|
{
|
|
input: "0.001d",
|
|
expected: time.Duration(0.001 * 24 * float64(time.Hour)),
|
|
},
|
|
{
|
|
input: "1.23456789d",
|
|
expected: time.Duration(1.23456789 * 24 * float64(time.Hour)),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.input, func(t *testing.T) {
|
|
result, err := ParseDuration(test.input)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Allow for small floating point differences
|
|
diff := result - test.expected
|
|
if diff < 0 {
|
|
diff = -diff
|
|
}
|
|
if diff > time.Nanosecond {
|
|
t.Errorf("Expected %v, got %v (diff: %v)", test.expected, result, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseDuration_Boundary_Values(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
name: "minimum day value",
|
|
input: "0.000000001d", // Very small but valid
|
|
},
|
|
{
|
|
name: "very large day value",
|
|
input: "999999999999999999999d",
|
|
expectErr: true, // Should overflow
|
|
},
|
|
{
|
|
name: "negative zero",
|
|
input: "-0d",
|
|
},
|
|
{
|
|
name: "positive zero",
|
|
input: "+0d",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
_, err := ParseDuration(test.input)
|
|
|
|
if test.expectErr && err == nil {
|
|
t.Error("Expected error but got none")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkParseDuration_SimpleDay(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
ParseDuration("1d")
|
|
}
|
|
}
|
|
|
|
func BenchmarkParseDuration_ComplexDay(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
ParseDuration("1.5d30m15.5s")
|
|
}
|
|
}
|
|
|
|
func BenchmarkParseDuration_MultipleDays(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
ParseDuration("1d2d3d4d5d")
|
|
}
|
|
}
|
|
|
|
func BenchmarkDuration_UnmarshalJSON_String(b *testing.B) {
|
|
input := []byte(`"5m30s"`)
|
|
var d Duration
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
d.UnmarshalJSON(input)
|
|
}
|
|
}
|
|
|
|
func BenchmarkDuration_UnmarshalJSON_Integer(b *testing.B) {
|
|
input := []byte("300000000000") // 5 minutes in nanoseconds
|
|
var d Duration
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
d.UnmarshalJSON(input)
|
|
}
|
|
}
|