From fc9e61536e016f5804da07f8305a895f798c2e42 Mon Sep 17 00:00:00 2001 From: RW Date: Sat, 24 May 2025 23:14:30 +0200 Subject: [PATCH 1/5] Improve ConvertToBytes performance --- common.go | 18 +++++++++--------- go.mod | 1 + http.go | 6 +++--- strings.go | 18 ++++++++++++------ 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/common.go b/common.go index 30a223d..f2e65e5 100644 --- a/common.go +++ b/common.go @@ -9,7 +9,6 @@ import ( "crypto/rand" "encoding/binary" "encoding/hex" - "math" "net" "os" "reflect" @@ -17,7 +16,6 @@ import ( "strconv" "sync" "sync/atomic" - "unicode" "github.com/google/uuid" ) @@ -32,10 +30,11 @@ const ( // All rights reserved. var ( - uuidSeed [24]byte - uuidCounter uint64 - uuidSetup sync.Once - unitsSlice = []byte("kmgtp") + uuidSeed [24]byte + uuidCounter uint64 + uuidSetup sync.Once + unitsSlice = []byte("kmgtp") + sizeMultipliers = [...]float64{1e3, 1e6, 1e9, 1e12, 1e15} ) // UUID generates an universally unique identifier (UUID) @@ -125,10 +124,11 @@ func ConvertToBytes(humanReadableString string) int { // loop the string for i := strLen - 1; i >= 0; i-- { // check if the char is a number - if unicode.IsDigit(rune(humanReadableString[i])) { + c := humanReadableString[i] + if c >= '0' && c <= '9' { lastNumberPos = i break - } else if humanReadableString[i] != ' ' { + } else if c != ' ' { unitPrefixPos = i } } @@ -144,7 +144,7 @@ func ConvertToBytes(humanReadableString string) int { // convert multiplier char to lowercase and check if exists in units slice index := bytes.IndexByte(unitsSlice, toLowerTable[humanReadableString[unitPrefixPos]]) if index != -1 { - size *= math.Pow(1000, float64(index+1)) + size *= sizeMultipliers[index] } } diff --git a/go.mod b/go.mod index 7edf58b..49412df 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/gofiber/utils/v2 go 1.23.0 + require ( github.com/fxamacker/cbor/v2 v2.8.0 github.com/google/uuid v1.6.0 diff --git a/http.go b/http.go index ed80825..19e7fe2 100644 --- a/http.go +++ b/http.go @@ -41,14 +41,14 @@ func GetMIME(extension string) string { // if it is parsable to any known types. If its not vendor specific then returns // the original content type. func ParseVendorSpecificContentType(cType string) string { - plusIndex := strings.Index(cType, "+") + plusIndex := strings.IndexByte(cType, '+') if plusIndex == -1 { return cType } var parsableType string - if semiColonIndex := strings.Index(cType, ";"); semiColonIndex == -1 { + if semiColonIndex := strings.IndexByte(cType, ';'); semiColonIndex == -1 { parsableType = cType[plusIndex+1:] } else if plusIndex < semiColonIndex { parsableType = cType[plusIndex+1 : semiColonIndex] @@ -56,7 +56,7 @@ func ParseVendorSpecificContentType(cType string) string { return cType[:semiColonIndex] } - slashIndex := strings.Index(cType, "/") + slashIndex := strings.IndexByte(cType, '/') if slashIndex == -1 { return cType diff --git a/strings.go b/strings.go index d05c5f6..83f641c 100644 --- a/strings.go +++ b/strings.go @@ -6,10 +6,13 @@ package utils // ToLower converts ascii string to lower-case func ToLower(b string) string { + if len(b) == 0 { + return b + } + res := make([]byte, len(b)) - copy(res, b) - for i := 0; i < len(res); i++ { - res[i] = toLowerTable[res[i]] + for i := 0; i < len(b); i++ { + res[i] = toLowerTable[b[i]] } return UnsafeString(res) @@ -17,10 +20,13 @@ func ToLower(b string) string { // ToUpper converts ascii string to upper-case func ToUpper(b string) string { + if len(b) == 0 { + return b + } + res := make([]byte, len(b)) - copy(res, b) - for i := 0; i < len(res); i++ { - res[i] = toUpperTable[res[i]] + for i := 0; i < len(b); i++ { + res[i] = toUpperTable[b[i]] } return UnsafeString(res) From 4a228d076185fb4c95227dca1ca15bddf95574fc Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 21:37:31 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Add=20g?= =?UTF-8?q?it/strings=5Ftest.go=20and=20update=20test=20files=20for=20stri?= =?UTF-8?q?ngs,=20common,=20and=20http?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common_test.go | 75 +++++++++++++- git/strings_test.go | 20 ++++ http_test.go | 64 +++++++++++- strings_test.go | 233 +++++++++++++++++++++++++++----------------- 4 files changed, 297 insertions(+), 95 deletions(-) create mode 100644 git/strings_test.go diff --git a/common_test.go b/common_test.go index 65cbb22..795e5b8 100644 --- a/common_test.go +++ b/common_test.go @@ -75,13 +75,26 @@ func Test_UUIDv4_Concurrency(t *testing.T) { func Test_ConvertToBytes(t *testing.T) { t.Parallel() + // initial assertions require.Equal(t, 0, ConvertToBytes("")) require.Equal(t, 42, ConvertToBytes("42")) + + // Test empty string + require.Equal(t, 0, ConvertToBytes("")) + + // Test basic numbers (digit detection optimization) + require.Equal(t, 42, ConvertToBytes("42")) + require.Equal(t, 0, ConvertToBytes("0")) + require.Equal(t, 1, ConvertToBytes("1")) + require.Equal(t, 999, ConvertToBytes("999")) + + // Test with 'b' and 'B' suffixes require.Equal(t, 42, ConvertToBytes("42b")) require.Equal(t, 42, ConvertToBytes("42B")) require.Equal(t, 42, ConvertToBytes("42 b")) require.Equal(t, 42, ConvertToBytes("42 B")) + // Test sizeMultipliers array usage (k/K - 1e3) require.Equal(t, 42*1000, ConvertToBytes("42k")) require.Equal(t, 42*1000, ConvertToBytes("42K")) require.Equal(t, 42*1000, ConvertToBytes("42kb")) @@ -89,12 +102,70 @@ func Test_ConvertToBytes(t *testing.T) { require.Equal(t, 42*1000, ConvertToBytes("42 kb")) require.Equal(t, 42*1000, ConvertToBytes("42 KB")) + // Test sizeMultipliers array usage (m/M - 1e6) require.Equal(t, 42*1000000, ConvertToBytes("42M")) + require.Equal(t, 42*1000000, ConvertToBytes("42m")) + require.Equal(t, 42*1000000, ConvertToBytes("42MB")) + require.Equal(t, 42*1000000, ConvertToBytes("42mb")) require.Equal(t, int(42.5*1000000), ConvertToBytes("42.5MB")) - require.Equal(t, 42*1000000000, ConvertToBytes("42G")) + // Test sizeMultipliers array usage (g/G - 1e9) + require.Equal(t, 42*1000000000, ConvertToBytes("42G")) + require.Equal(t, 42*1000000000, ConvertToBytes("42g")) + require.Equal(t, 42*1000000000, ConvertToBytes("42GB")) + require.Equal(t, 42*1000000000, ConvertToBytes("42gb")) + + // Test sizeMultipliers array usage (t/T - 1e12) + require.Equal(t, 42*1000000000000, ConvertToBytes("42T")) + require.Equal(t, 42*1000000000000, ConvertToBytes("42t")) + require.Equal(t, 42*1000000000000, ConvertToBytes("42TB")) + require.Equal(t, 42*1000000000000, ConvertToBytes("42tb")) + + // Test sizeMultipliers array usage (p/P - 1e15) + require.Equal(t, 42*1000000000000000, ConvertToBytes("42P")) + require.Equal(t, 42*1000000000000000, ConvertToBytes("42p")) + require.Equal(t, 42*1000000000000000, ConvertToBytes("42PB")) + require.Equal(t, 42*1000000000000000, ConvertToBytes("42pb")) + + // Test edge cases and error conditions require.Equal(t, 0, ConvertToBytes("string")) require.Equal(t, 0, ConvertToBytes("MB")) + require.Equal(t, 0, ConvertToBytes("invalidunit")) + require.Equal(t, 0, ConvertToBytes("42X")) // invalid unit + require.Equal(t, 0, ConvertToBytes("42.5.5MB")) // invalid format + + // Test decimal numbers with various units + require.Equal(t, int(1.5*1000), ConvertToBytes("1.5k")) + require.Equal(t, int(2.25*1000000), ConvertToBytes("2.25m")) + require.Equal(t, int(0.5*1000000000), ConvertToBytes("0.5g")) + + // Test space handling + require.Equal(t, 100*1000, ConvertToBytes("100 k")) + require.Equal(t, 100*1000, ConvertToBytes("100 k")) // multiple spaces + require.Equal(t, 100*1000, ConvertToBytes(" 100 k ")) // leading/trailing spaces +} + +func Test_ConvertToBytes_DigitDetection(t *testing.T) { + t.Parallel() + // Test the new direct byte comparison digit detection + testCases := []struct { + input string + expected int + desc string + }{ + {"0", 0, "digit 0"}, + {"1", 1, "digit 1"}, + {"9", 9, "digit 9"}, + {"123", 123, "multiple digits"}, + {"123k", 123000, "digits with unit"}, + {"a123", 0, "non-digit start"}, + {"12a3", 12, "non-digit in middle stops parsing"}, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + require.Equal(t, tc.expected, ConvertToBytes(tc.input), "input: %s", tc.input) + }) + } } func Test_GetArgument(t *testing.T) { @@ -161,4 +232,4 @@ func Benchmark_UUID(b *testing.B) { } require.Len(b, res, 36) }) -} +} \ No newline at end of file diff --git a/git/strings_test.go b/git/strings_test.go new file mode 100644 index 0000000..dda0647 --- /dev/null +++ b/git/strings_test.go @@ -0,0 +1,20 @@ +package git + +import ( + "testing" + "github.com/stretchr/testify/require" +) + +func Test_ToUpper(t *testing.T) { + t.Parallel() + // Test empty string early return optimization + require.Equal(t, "", ToUpper("")) + + require.Equal(t, "/MY/NAME/IS/:PARAM/*", ToUpper("/my/name/is/:param/*")) + + // Test single character optimizations + require.Equal(t, "A", ToUpper("a")) + require.Equal(t, "Z", ToUpper("z")) + require.Equal(t, "1", ToUpper("1")) // non-letter remains unchanged + require.Equal(t, "!", ToUpper("!")) // special character remains unchanged +} \ No newline at end of file diff --git a/http_test.go b/http_test.go index addc070..4e2331a 100644 --- a/http_test.go +++ b/http_test.go @@ -75,6 +75,44 @@ func Test_ParseVendorSpecificContentType(t *testing.T) { cType := ParseVendorSpecificContentType("application/json") require.Equal(t, "application/json", cType) + // Test with parameters (semicolon IndexByte optimization) + cType = ParseVendorSpecificContentType("multipart/form-data; boundary=abc123") + require.Equal(t, "multipart/form-data", cType) + + // Test vendor-specific content types (plus IndexByte optimization) + cType = ParseVendorSpecificContentType("application/vnd.api+json; version=1") + require.Equal(t, "application/json", cType) + cType = ParseVendorSpecificContentType("application/vnd.dummy+x-www-form-urlencoded") + require.Equal(t, "application/x-www-form-urlencoded", cType) + + // Test invalid cases (slash IndexByte optimization) + cType = ParseVendorSpecificContentType("something invalid") + require.Equal(t, "something invalid", cType) + + // Additional edge cases for IndexByte optimization + cType = ParseVendorSpecificContentType("application/vnd.custom+xml; charset=utf-8") + require.Equal(t, "application/xml", cType) + + cType = ParseVendorSpecificContentType("text/vnd.example+plain") + require.Equal(t, "text/plain", cType) + + cType = ParseVendorSpecificContentType("application/vnd.test+json;boundary=test") + require.Equal(t, "application/json", cType) + + // Edge cases with multiple special characters + cType = ParseVendorSpecificContentType("application/vnd.api+json+extra; param=value") + require.Equal(t, "application/json+extra", cType) + + // Semicolon before plus + cType = ParseVendorSpecificContentType("application/json; charset=utf-8+extra") + require.Equal(t, "application/json", cType) + + // Empty and single-character inputs + require.Equal(t, "", ParseVendorSpecificContentType("")) + require.Equal(t, "+", ParseVendorSpecificContentType("+")) + require.Equal(t, ";", ParseVendorSpecificContentType(";")) + require.Equal(t, "/", ParseVendorSpecificContentType("/")) + cType = ParseVendorSpecificContentType("multipart/form-data; boundary=dart-http-boundary-ZnVy.ICWq+7HOdsHqWxCFa8g3D.KAhy+Y0sYJ_lBADypu8po3_X") require.Equal(t, "multipart/form-data", cType) @@ -97,6 +135,30 @@ func Test_ParseVendorSpecificContentType(t *testing.T) { require.Equal(t, "invalid+withoutSlash", cType) } +func Test_ParseVendorSpecificContentType_IndexByteOptimization(t *testing.T) { + t.Parallel() + testCases := []struct { + input string + expected string + desc string + }{ + {"application/vnd.api+json", "application/json", "plus in middle"}, + {"+json", "+json", "plus at start, no slash"}, + {"application/+json", "application/json", "plus after slash"}, + {"application/json;charset=utf-8", "application/json", "semicolon after content type"}, + {";charset=utf-8", ";charset=utf-8", "semicolon at start"}, + {"application/vnd.api+json;version=1", "application/json", "plus before semicolon"}, + {"application/json", "application/json", "normal content type with slash"}, + {"applicationjson", "applicationjson", "no slash in content type"}, + {"app/vnd.test+data/extra", "app/data/extra", "multiple slashes"}, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + require.Equal(t, tc.expected, ParseVendorSpecificContentType(tc.input), "input: %s", tc.input) + }) + } +} + func Benchmark_ParseVendorSpecificContentType(b *testing.B) { b.Run("vendorContentType", func(b *testing.B) { for n := 0; n < b.N; n++ { @@ -150,4 +212,4 @@ func Benchmark_StatusMessage(b *testing.B) { http.StatusText(http.StatusNotExtended) } }) -} +} \ No newline at end of file diff --git a/strings_test.go b/strings_test.go index f837b2d..d6f9c6c 100644 --- a/strings_test.go +++ b/strings_test.go @@ -5,123 +5,172 @@ package utils import ( - "strings" - "testing" + "strings" + "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/require" ) func Test_ToUpper(t *testing.T) { - t.Parallel() - require.Equal(t, "/MY/NAME/IS/:PARAM/*", ToUpper("/my/name/is/:param/*")) + t.Parallel() + // Test empty string early return optimization + require.Equal(t, "", ToUpper("")) + require.Equal(t, "/MY/NAME/IS/:PARAM/*", ToUpper("/my/name/is/:param/*")) } const ( - largeStr = "/RePos/GoFiBer/FibEr/iSsues/187643/CoMmEnts/RePos/GoFiBer/FibEr/iSsues/CoMmEnts" - upperStr = "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS/REPOS/GOFIBER/FIBER/ISSUES/COMMENTS" - lowerStr = "/repos/gofiber/fiber/issues/187643/comments/repos/gofiber/fiber/issues/comments" + largeStr = "/RePos/GoFiBer/FibEr/iSsues/187643/CoMmEnts/RePos/GoFiBer/FibEr/iSsues/CoMmEnts" + upperStr = "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS/REPOS/GOFIBER/FIBER/ISSUES/COMMENTS" + lowerStr = "/repos/gofiber/fiber/issues/187643/comments/repos/gofiber/fiber/issues/comments" ) func Benchmark_ToUpper(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToUpper(largeStr) - } - require.Equal(b, upperStr, res) - }) - b.Run("IfToUpper-Upper", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToUpper(upperStr) - } - require.Equal(b, upperStr, res) - }) - b.Run("IfToUpper-Mixed", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToUpper(largeStr) - } - require.Equal(b, upperStr, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToUpper(largeStr) - } - require.Equal(b, upperStr, res) - }) + var res string + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = ToUpper(largeStr) + } + require.Equal(b, upperStr, res) + }) + b.Run("IfToUpper-Upper", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToUpper(upperStr) + } + require.Equal(b, upperStr, res) + }) + b.Run("IfToUpper-Mixed", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToUpper(largeStr) + } + require.Equal(b, upperStr, res) + }) + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = strings.ToUpper(largeStr) + } + require.Equal(b, upperStr, res) + }) } func Test_ToLower(t *testing.T) { - t.Parallel() - require.Equal(t, "/my/name/is/:param/*", ToLower("/MY/NAME/IS/:PARAM/*")) - require.Equal(t, "/my1/name/is/:param/*", ToLower("/MY1/NAME/IS/:PARAM/*")) - require.Equal(t, "/my2/name/is/:param/*", ToLower("/MY2/NAME/IS/:PARAM/*")) - require.Equal(t, "/my3/name/is/:param/*", ToLower("/MY3/NAME/IS/:PARAM/*")) - require.Equal(t, "/my4/name/is/:param/*", ToLower("/MY4/NAME/IS/:PARAM/*")) + t.Parallel() + // Test empty string early return optimization + require.Equal(t, "", ToLower("")) + require.Equal(t, "/my/name/is/:param/*", ToLower("/MY/NAME/IS/:PARAM/*")) + require.Equal(t, "/my1/name/is/:param/*", ToLower("/MY1/NAME/IS/:PARAM/*")) + require.Equal(t, "/my2/name/is/:param/*", ToLower("/MY2/NAME/IS/:PARAM/*")) + require.Equal(t, "/my3/name/is/:param/*", ToLower("/MY3/NAME/IS/:PARAM/*")) + require.Equal(t, "/my4/name/is/:param/*", ToLower("/MY4/NAME/IS/:PARAM/*")) + // Test single character optimizations + require.Equal(t, "a", ToLower("A")) + require.Equal(t, "z", ToLower("Z")) + require.Equal(t, "1", ToLower("1")) // non-letter should remain unchanged + require.Equal(t, "!", ToLower("!")) // special character should remain unchanged } func Benchmark_ToLower(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToLower(largeStr) - } - require.Equal(b, lowerStr, res) - }) - b.Run("IfToLower-Lower", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToLower(lowerStr) - } - require.Equal(b, lowerStr, res) - }) - b.Run("IfToLower-Mixed", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToLower(largeStr) - } - require.Equal(b, lowerStr, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToLower(largeStr) - } - require.Equal(b, lowerStr, res) - }) + var res string + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = ToLower(largeStr) + } + require.Equal(b, lowerStr, res) + }) + b.Run("IfToLower-Lower", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower(lowerStr) + } + require.Equal(b, lowerStr, res) + }) + b.Run("IfToLower-Mixed", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower(largeStr) + } + require.Equal(b, lowerStr, res) + }) + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = strings.ToLower(largeStr) + } + require.Equal(b, lowerStr, res) + }) } func Test_IfToUpper(t *testing.T) { - t.Parallel() - require.Equal(t, "MYNAMEISPARAM", IfToUpper("MYNAMEISPARAM")) // already uppercase - require.Equal(t, "MYNAMEISPARAM", IfToUpper("mynameisparam")) // lowercase to uppercase - require.Equal(t, "MYNAMEISPARAM", IfToUpper("MyNameIsParam")) // mixed case + t.Parallel() + require.Equal(t, "MYNAMEISPARAM", IfToUpper("MYNAMEISPARAM")) // already uppercase + require.Equal(t, "MYNAMEISPARAM", IfToUpper("mynameisparam")) // lowercase to uppercase + require.Equal(t, "MYNAMEISPARAM", IfToUpper("MyNameIsParam")) // mixed case } func Test_IfToLower(t *testing.T) { - t.Parallel() - require.Equal(t, "mynameisparam", IfToLower("mynameisparam")) // already lowercase - require.Equal(t, "mynameisparam", IfToLower("myNameIsParam")) // mixed case - require.Equal(t, "https://gofiber.io", IfToLower("https://gofiber.io")) // Origin Header Type URL - require.Equal(t, "mynameisparam", IfToLower("MYNAMEISPARAM")) // uppercase to lowercase + t.Parallel() + require.Equal(t, "mynameisparam", IfToLower("mynameisparam")) // already lowercase + require.Equal(t, "mynameisparam", IfToLower("myNameIsParam")) // mixed case + require.Equal(t, "https://gofiber.io", IfToLower("https://gofiber.io")) // Origin Header Type URL + require.Equal(t, "mynameisparam", IfToLower("MYNAMEISPARAM")) // uppercase to lowercase } // Benchmark_IfToLower_HeadersOrigin benchmarks the IfToLower function with an origin header type URL. // These headers are typically lowercase, so the function should return the input string without modification. func Benchmark_IfToToLower_HeadersOrigin(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToLower("https://gofiber.io") - } - require.Equal(b, "https://gofiber.io", res) - }) - b.Run("IfToLower-Lower", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToLower("https://gofiber.io") - } - require.Equal(b, "https://gofiber.io", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToLower("https://gofiber.io") - } - require.Equal(b, "https://gofiber.io", res) - }) + var res string + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = ToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) + b.Run("IfToLower-Lower", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = strings.ToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) } + +func Test_ToLower_DirectByteIteration(t *testing.T) { + t.Parallel() + // Test various ASCII characters to ensure direct byte iteration works correctly + testCases := []struct { + input string + expected string + }{ + {"ABC123!@#", "abc123!@#"}, + {"MiXeD cAsE", "mixed case"}, + {"ALLUPPERCASE", "alluppercase"}, + {"alllowercase", "alllowercase"}, + {"Numbers123AndSymbols!@#", "numbers123andsymbols!@#"}, + } + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + require.Equal(t, tc.expected, ToLower(tc.input)) + }) + } +} + +func Test_ToUpper_DirectByteIteration(t *testing.T) { + t.Parallel() + // Test various ASCII characters to ensure direct byte iteration works correctly + testCases := []struct { + input string + expected string + }{ + {"abc123!@#", "ABC123!@#"}, + {"MiXeD cAsE", "MIXED CASE"}, + {"ALLUPPERCASE", "ALLUPPERCASE"}, + {"alllowercase", "ALLLOWERCASE"}, + {"Numbers123AndSymbols!@#", "NUMBERS123ANDSYMBOLS!@#"}, + } + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + require.Equal(t, tc.expected, ToUpper(tc.input)) + }) + } +} \ No newline at end of file From 10422f46df7a43497a24db3b37bd41b0cbbea102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sat, 24 May 2025 23:49:06 +0200 Subject: [PATCH 3/5] fix tests --- common_test.go | 9 ++++----- git/strings_test.go | 20 -------------------- http_test.go | 6 +++--- 3 files changed, 7 insertions(+), 28 deletions(-) delete mode 100644 git/strings_test.go diff --git a/common_test.go b/common_test.go index 795e5b8..7c45a37 100644 --- a/common_test.go +++ b/common_test.go @@ -131,7 +131,7 @@ func Test_ConvertToBytes(t *testing.T) { require.Equal(t, 0, ConvertToBytes("string")) require.Equal(t, 0, ConvertToBytes("MB")) require.Equal(t, 0, ConvertToBytes("invalidunit")) - require.Equal(t, 0, ConvertToBytes("42X")) // invalid unit + require.Equal(t, 42, ConvertToBytes("42X")) // invalid unit require.Equal(t, 0, ConvertToBytes("42.5.5MB")) // invalid format // Test decimal numbers with various units @@ -141,8 +141,7 @@ func Test_ConvertToBytes(t *testing.T) { // Test space handling require.Equal(t, 100*1000, ConvertToBytes("100 k")) - require.Equal(t, 100*1000, ConvertToBytes("100 k")) // multiple spaces - require.Equal(t, 100*1000, ConvertToBytes(" 100 k ")) // leading/trailing spaces + require.Equal(t, 100*1000, ConvertToBytes("100 k")) // multiple spaces } func Test_ConvertToBytes_DigitDetection(t *testing.T) { @@ -159,7 +158,7 @@ func Test_ConvertToBytes_DigitDetection(t *testing.T) { {"123", 123, "multiple digits"}, {"123k", 123000, "digits with unit"}, {"a123", 0, "non-digit start"}, - {"12a3", 12, "non-digit in middle stops parsing"}, + {"12a3", 0, "non-digit in middle stops parsing"}, } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { @@ -232,4 +231,4 @@ func Benchmark_UUID(b *testing.B) { } require.Len(b, res, 36) }) -} \ No newline at end of file +} diff --git a/git/strings_test.go b/git/strings_test.go deleted file mode 100644 index dda0647..0000000 --- a/git/strings_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package git - -import ( - "testing" - "github.com/stretchr/testify/require" -) - -func Test_ToUpper(t *testing.T) { - t.Parallel() - // Test empty string early return optimization - require.Equal(t, "", ToUpper("")) - - require.Equal(t, "/MY/NAME/IS/:PARAM/*", ToUpper("/my/name/is/:param/*")) - - // Test single character optimizations - require.Equal(t, "A", ToUpper("a")) - require.Equal(t, "Z", ToUpper("z")) - require.Equal(t, "1", ToUpper("1")) // non-letter remains unchanged - require.Equal(t, "!", ToUpper("!")) // special character remains unchanged -} \ No newline at end of file diff --git a/http_test.go b/http_test.go index 4e2331a..3467d4e 100644 --- a/http_test.go +++ b/http_test.go @@ -77,7 +77,7 @@ func Test_ParseVendorSpecificContentType(t *testing.T) { // Test with parameters (semicolon IndexByte optimization) cType = ParseVendorSpecificContentType("multipart/form-data; boundary=abc123") - require.Equal(t, "multipart/form-data", cType) + require.Equal(t, "multipart/form-data; boundary=abc123", cType) // Test vendor-specific content types (plus IndexByte optimization) cType = ParseVendorSpecificContentType("application/vnd.api+json; version=1") @@ -145,7 +145,7 @@ func Test_ParseVendorSpecificContentType_IndexByteOptimization(t *testing.T) { {"application/vnd.api+json", "application/json", "plus in middle"}, {"+json", "+json", "plus at start, no slash"}, {"application/+json", "application/json", "plus after slash"}, - {"application/json;charset=utf-8", "application/json", "semicolon after content type"}, + {"application/json;charset=utf-8", "application/json;charset=utf-8", "semicolon after content type"}, {";charset=utf-8", ";charset=utf-8", "semicolon at start"}, {"application/vnd.api+json;version=1", "application/json", "plus before semicolon"}, {"application/json", "application/json", "normal content type with slash"}, @@ -212,4 +212,4 @@ func Benchmark_StatusMessage(b *testing.B) { http.StatusText(http.StatusNotExtended) } }) -} \ No newline at end of file +} From 90ca8f4302d770bba39f853051c5ecbff5f84865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sat, 24 May 2025 23:52:51 +0200 Subject: [PATCH 4/5] fix format --- common_test.go | 2 + http_test.go | 2 + strings_test.go | 276 ++++++++++++++++++++++++------------------------ 3 files changed, 144 insertions(+), 136 deletions(-) diff --git a/common_test.go b/common_test.go index 7c45a37..a673faf 100644 --- a/common_test.go +++ b/common_test.go @@ -162,6 +162,8 @@ func Test_ConvertToBytes_DigitDetection(t *testing.T) { } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.expected, ConvertToBytes(tc.input), "input: %s", tc.input) }) } diff --git a/http_test.go b/http_test.go index 3467d4e..f1fd1a5 100644 --- a/http_test.go +++ b/http_test.go @@ -154,6 +154,8 @@ func Test_ParseVendorSpecificContentType_IndexByteOptimization(t *testing.T) { } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.expected, ParseVendorSpecificContentType(tc.input), "input: %s", tc.input) }) } diff --git a/strings_test.go b/strings_test.go index d6f9c6c..cbd65e4 100644 --- a/strings_test.go +++ b/strings_test.go @@ -5,172 +5,176 @@ package utils import ( - "strings" - "testing" + "strings" + "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/require" ) func Test_ToUpper(t *testing.T) { - t.Parallel() - // Test empty string early return optimization - require.Equal(t, "", ToUpper("")) - require.Equal(t, "/MY/NAME/IS/:PARAM/*", ToUpper("/my/name/is/:param/*")) + t.Parallel() + // Test empty string early return optimization + require.Equal(t, "", ToUpper("")) + require.Equal(t, "/MY/NAME/IS/:PARAM/*", ToUpper("/my/name/is/:param/*")) } const ( - largeStr = "/RePos/GoFiBer/FibEr/iSsues/187643/CoMmEnts/RePos/GoFiBer/FibEr/iSsues/CoMmEnts" - upperStr = "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS/REPOS/GOFIBER/FIBER/ISSUES/COMMENTS" - lowerStr = "/repos/gofiber/fiber/issues/187643/comments/repos/gofiber/fiber/issues/comments" + largeStr = "/RePos/GoFiBer/FibEr/iSsues/187643/CoMmEnts/RePos/GoFiBer/FibEr/iSsues/CoMmEnts" + upperStr = "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS/REPOS/GOFIBER/FIBER/ISSUES/COMMENTS" + lowerStr = "/repos/gofiber/fiber/issues/187643/comments/repos/gofiber/fiber/issues/comments" ) func Benchmark_ToUpper(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToUpper(largeStr) - } - require.Equal(b, upperStr, res) - }) - b.Run("IfToUpper-Upper", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToUpper(upperStr) - } - require.Equal(b, upperStr, res) - }) - b.Run("IfToUpper-Mixed", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToUpper(largeStr) - } - require.Equal(b, upperStr, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToUpper(largeStr) - } - require.Equal(b, upperStr, res) - }) + var res string + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = ToUpper(largeStr) + } + require.Equal(b, upperStr, res) + }) + b.Run("IfToUpper-Upper", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToUpper(upperStr) + } + require.Equal(b, upperStr, res) + }) + b.Run("IfToUpper-Mixed", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToUpper(largeStr) + } + require.Equal(b, upperStr, res) + }) + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = strings.ToUpper(largeStr) + } + require.Equal(b, upperStr, res) + }) } func Test_ToLower(t *testing.T) { - t.Parallel() - // Test empty string early return optimization - require.Equal(t, "", ToLower("")) - require.Equal(t, "/my/name/is/:param/*", ToLower("/MY/NAME/IS/:PARAM/*")) - require.Equal(t, "/my1/name/is/:param/*", ToLower("/MY1/NAME/IS/:PARAM/*")) - require.Equal(t, "/my2/name/is/:param/*", ToLower("/MY2/NAME/IS/:PARAM/*")) - require.Equal(t, "/my3/name/is/:param/*", ToLower("/MY3/NAME/IS/:PARAM/*")) - require.Equal(t, "/my4/name/is/:param/*", ToLower("/MY4/NAME/IS/:PARAM/*")) - // Test single character optimizations - require.Equal(t, "a", ToLower("A")) - require.Equal(t, "z", ToLower("Z")) - require.Equal(t, "1", ToLower("1")) // non-letter should remain unchanged - require.Equal(t, "!", ToLower("!")) // special character should remain unchanged + t.Parallel() + // Test empty string early return optimization + require.Equal(t, "", ToLower("")) + require.Equal(t, "/my/name/is/:param/*", ToLower("/MY/NAME/IS/:PARAM/*")) + require.Equal(t, "/my1/name/is/:param/*", ToLower("/MY1/NAME/IS/:PARAM/*")) + require.Equal(t, "/my2/name/is/:param/*", ToLower("/MY2/NAME/IS/:PARAM/*")) + require.Equal(t, "/my3/name/is/:param/*", ToLower("/MY3/NAME/IS/:PARAM/*")) + require.Equal(t, "/my4/name/is/:param/*", ToLower("/MY4/NAME/IS/:PARAM/*")) + // Test single character optimizations + require.Equal(t, "a", ToLower("A")) + require.Equal(t, "z", ToLower("Z")) + require.Equal(t, "1", ToLower("1")) // non-letter should remain unchanged + require.Equal(t, "!", ToLower("!")) // special character should remain unchanged } func Benchmark_ToLower(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToLower(largeStr) - } - require.Equal(b, lowerStr, res) - }) - b.Run("IfToLower-Lower", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToLower(lowerStr) - } - require.Equal(b, lowerStr, res) - }) - b.Run("IfToLower-Mixed", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToLower(largeStr) - } - require.Equal(b, lowerStr, res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToLower(largeStr) - } - require.Equal(b, lowerStr, res) - }) + var res string + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = ToLower(largeStr) + } + require.Equal(b, lowerStr, res) + }) + b.Run("IfToLower-Lower", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower(lowerStr) + } + require.Equal(b, lowerStr, res) + }) + b.Run("IfToLower-Mixed", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower(largeStr) + } + require.Equal(b, lowerStr, res) + }) + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = strings.ToLower(largeStr) + } + require.Equal(b, lowerStr, res) + }) } func Test_IfToUpper(t *testing.T) { - t.Parallel() - require.Equal(t, "MYNAMEISPARAM", IfToUpper("MYNAMEISPARAM")) // already uppercase - require.Equal(t, "MYNAMEISPARAM", IfToUpper("mynameisparam")) // lowercase to uppercase - require.Equal(t, "MYNAMEISPARAM", IfToUpper("MyNameIsParam")) // mixed case + t.Parallel() + require.Equal(t, "MYNAMEISPARAM", IfToUpper("MYNAMEISPARAM")) // already uppercase + require.Equal(t, "MYNAMEISPARAM", IfToUpper("mynameisparam")) // lowercase to uppercase + require.Equal(t, "MYNAMEISPARAM", IfToUpper("MyNameIsParam")) // mixed case } func Test_IfToLower(t *testing.T) { - t.Parallel() - require.Equal(t, "mynameisparam", IfToLower("mynameisparam")) // already lowercase - require.Equal(t, "mynameisparam", IfToLower("myNameIsParam")) // mixed case - require.Equal(t, "https://gofiber.io", IfToLower("https://gofiber.io")) // Origin Header Type URL - require.Equal(t, "mynameisparam", IfToLower("MYNAMEISPARAM")) // uppercase to lowercase + t.Parallel() + require.Equal(t, "mynameisparam", IfToLower("mynameisparam")) // already lowercase + require.Equal(t, "mynameisparam", IfToLower("myNameIsParam")) // mixed case + require.Equal(t, "https://gofiber.io", IfToLower("https://gofiber.io")) // Origin Header Type URL + require.Equal(t, "mynameisparam", IfToLower("MYNAMEISPARAM")) // uppercase to lowercase } // Benchmark_IfToLower_HeadersOrigin benchmarks the IfToLower function with an origin header type URL. // These headers are typically lowercase, so the function should return the input string without modification. func Benchmark_IfToToLower_HeadersOrigin(b *testing.B) { - var res string - b.Run("fiber", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = ToLower("https://gofiber.io") - } - require.Equal(b, "https://gofiber.io", res) - }) - b.Run("IfToLower-Lower", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = IfToLower("https://gofiber.io") - } - require.Equal(b, "https://gofiber.io", res) - }) - b.Run("default", func(b *testing.B) { - for n := 0; n < b.N; n++ { - res = strings.ToLower("https://gofiber.io") - } - require.Equal(b, "https://gofiber.io", res) - }) + var res string + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = ToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) + b.Run("IfToLower-Lower", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = strings.ToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) } func Test_ToLower_DirectByteIteration(t *testing.T) { - t.Parallel() - // Test various ASCII characters to ensure direct byte iteration works correctly - testCases := []struct { - input string - expected string - }{ - {"ABC123!@#", "abc123!@#"}, - {"MiXeD cAsE", "mixed case"}, - {"ALLUPPERCASE", "alluppercase"}, - {"alllowercase", "alllowercase"}, - {"Numbers123AndSymbols!@#", "numbers123andsymbols!@#"}, - } - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - require.Equal(t, tc.expected, ToLower(tc.input)) - }) - } + t.Parallel() + // Test various ASCII characters to ensure direct byte iteration works correctly + testCases := []struct { + input string + expected string + }{ + {"ABC123!@#", "abc123!@#"}, + {"MiXeD cAsE", "mixed case"}, + {"ALLUPPERCASE", "alluppercase"}, + {"alllowercase", "alllowercase"}, + {"Numbers123AndSymbols!@#", "numbers123andsymbols!@#"}, + } + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + t.Parallel() + + require.Equal(t, tc.expected, ToLower(tc.input)) + }) + } } func Test_ToUpper_DirectByteIteration(t *testing.T) { - t.Parallel() - // Test various ASCII characters to ensure direct byte iteration works correctly - testCases := []struct { - input string - expected string - }{ - {"abc123!@#", "ABC123!@#"}, - {"MiXeD cAsE", "MIXED CASE"}, - {"ALLUPPERCASE", "ALLUPPERCASE"}, - {"alllowercase", "ALLLOWERCASE"}, - {"Numbers123AndSymbols!@#", "NUMBERS123ANDSYMBOLS!@#"}, - } - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - require.Equal(t, tc.expected, ToUpper(tc.input)) - }) - } -} \ No newline at end of file + t.Parallel() + // Test various ASCII characters to ensure direct byte iteration works correctly + testCases := []struct { + input string + expected string + }{ + {"abc123!@#", "ABC123!@#"}, + {"MiXeD cAsE", "MIXED CASE"}, + {"ALLUPPERCASE", "ALLUPPERCASE"}, + {"alllowercase", "ALLLOWERCASE"}, + {"Numbers123AndSymbols!@#", "NUMBERS123ANDSYMBOLS!@#"}, + } + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + t.Parallel() + + require.Equal(t, tc.expected, ToUpper(tc.input)) + }) + } +} From 988a407d2f493bd3e8753eecceebd1c1f3aa9d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sun, 25 May 2025 00:06:15 +0200 Subject: [PATCH 5/5] revert ToLower ToUpper changes --- strings.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/strings.go b/strings.go index 83f641c..165c80b 100644 --- a/strings.go +++ b/strings.go @@ -11,8 +11,9 @@ func ToLower(b string) string { } res := make([]byte, len(b)) - for i := 0; i < len(b); i++ { - res[i] = toLowerTable[b[i]] + copy(res, b) + for i := 0; i < len(res); i++ { + res[i] = toLowerTable[res[i]] } return UnsafeString(res) @@ -25,8 +26,9 @@ func ToUpper(b string) string { } res := make([]byte, len(b)) - for i := 0; i < len(b); i++ { - res[i] = toUpperTable[b[i]] + copy(res, b) + for i := 0; i < len(res); i++ { + res[i] = toUpperTable[res[i]] } return UnsafeString(res)