|
6 | 6 | package gin |
7 | 7 |
|
8 | 8 | import ( |
| 9 | + "regexp" |
9 | 10 | "runtime" |
10 | 11 | "strings" |
11 | 12 | "testing" |
@@ -70,6 +71,44 @@ var cleanTests = []cleanPathTest{ |
70 | 71 | {"abc/../../././../def", "/def"}, |
71 | 72 | } |
72 | 73 |
|
| 74 | +var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") |
| 75 | + |
| 76 | +func removeRepeatedSlashRegexp(s string) string { |
| 77 | + return regRemoveRepeatedChar.ReplaceAllString(s, "/") |
| 78 | +} |
| 79 | + |
| 80 | +func removeRepeatedSlashLoopReplace(s string) string { |
| 81 | + for strings.Contains(s, "//") { |
| 82 | + s = strings.ReplaceAll(s, "//", "/") |
| 83 | + } |
| 84 | + return s |
| 85 | +} |
| 86 | + |
| 87 | +func removeRepeatedSlashStringBuilder(s string) string { |
| 88 | + if !strings.Contains(s, "//") { |
| 89 | + return s |
| 90 | + } |
| 91 | + |
| 92 | + var sb strings.Builder |
| 93 | + sb.Grow(len(s) - 1) |
| 94 | + prevChar := rune(0) |
| 95 | + |
| 96 | + for _, r := range s { |
| 97 | + if r == '/' && prevChar == '/' { |
| 98 | + continue |
| 99 | + } |
| 100 | + sb.WriteRune(r) |
| 101 | + prevChar = r |
| 102 | + } |
| 103 | + |
| 104 | + return sb.String() |
| 105 | +} |
| 106 | + |
| 107 | +// removeRepeatedSlash removes multiple consecutive slashes from a string. |
| 108 | +func removeRepeatedSlash(s string) string { |
| 109 | + return removeRepeatedChar(s, '/') |
| 110 | +} |
| 111 | + |
73 | 112 | func TestPathClean(t *testing.T) { |
74 | 113 | for _, test := range cleanTests { |
75 | 114 | assert.Equal(t, test.result, cleanPath(test.path)) |
@@ -144,3 +183,104 @@ func BenchmarkPathCleanLong(b *testing.B) { |
144 | 183 | } |
145 | 184 | } |
146 | 185 | } |
| 186 | + |
| 187 | +func TestRemoveRepeatedChar(t *testing.T) { |
| 188 | + testCases := []struct { |
| 189 | + name string |
| 190 | + str string |
| 191 | + char byte |
| 192 | + want string |
| 193 | + }{ |
| 194 | + { |
| 195 | + name: "empty", |
| 196 | + str: "", |
| 197 | + char: 'a', |
| 198 | + want: "", |
| 199 | + }, |
| 200 | + { |
| 201 | + name: "noSlash", |
| 202 | + str: "abc", |
| 203 | + char: ',', |
| 204 | + want: "abc", |
| 205 | + }, |
| 206 | + { |
| 207 | + name: "withSlash", |
| 208 | + str: "/a/b/c/", |
| 209 | + char: '/', |
| 210 | + want: "/a/b/c/", |
| 211 | + }, |
| 212 | + { |
| 213 | + name: "withRepeatedSlashes", |
| 214 | + str: "/a//b///c////", |
| 215 | + char: '/', |
| 216 | + want: "/a/b/c/", |
| 217 | + }, |
| 218 | + { |
| 219 | + name: "threeSlashes", |
| 220 | + str: "///", |
| 221 | + char: '/', |
| 222 | + want: "/", |
| 223 | + }, |
| 224 | + } |
| 225 | + |
| 226 | + for _, tc := range testCases { |
| 227 | + t.Run(tc.name, func(t *testing.T) { |
| 228 | + res := removeRepeatedChar(tc.str, tc.char) |
| 229 | + assert.Equal(t, tc.want, res) |
| 230 | + }) |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +func benchmarkRemoveRepeatedSlash(b *testing.B, prefix string) { |
| 235 | + testCases := []struct { |
| 236 | + name string |
| 237 | + fn func(string) string |
| 238 | + }{ |
| 239 | + { |
| 240 | + name: "regexp", |
| 241 | + fn: removeRepeatedSlashRegexp, |
| 242 | + }, |
| 243 | + { |
| 244 | + name: "loopReplace", |
| 245 | + fn: removeRepeatedSlashLoopReplace, |
| 246 | + }, |
| 247 | + { |
| 248 | + name: "stringBuilder", |
| 249 | + fn: removeRepeatedSlashStringBuilder, |
| 250 | + }, |
| 251 | + { |
| 252 | + name: "buff", |
| 253 | + fn: removeRepeatedSlash, |
| 254 | + }, |
| 255 | + } |
| 256 | + |
| 257 | + for _, tc := range testCases { |
| 258 | + b.Run(prefix+" "+tc.name, func(b *testing.B) { |
| 259 | + b.ResetTimer() |
| 260 | + b.ReportAllocs() |
| 261 | + for b.Loop() { |
| 262 | + tc.fn(prefix) |
| 263 | + } |
| 264 | + }) |
| 265 | + } |
| 266 | +} |
| 267 | + |
| 268 | +func BenchmarkRemoveRepeatedSlash_MultipleSlashes(b *testing.B) { |
| 269 | + prefix := "/somePrefix/more//text///more////" |
| 270 | + benchmarkRemoveRepeatedSlash(b, prefix) |
| 271 | +} |
| 272 | + |
| 273 | +func BenchmarkRemoveRepeatedSlash_TwoSlashes(b *testing.B) { |
| 274 | + prefix := "/somePrefix/more//" |
| 275 | + benchmarkRemoveRepeatedSlash(b, prefix) |
| 276 | +} |
| 277 | + |
| 278 | +func BenchmarkRemoveNoRepeatedSlash(b *testing.B) { |
| 279 | + prefix := "/somePrefix/more/text/" |
| 280 | + benchmarkRemoveRepeatedSlash(b, prefix) |
| 281 | +} |
| 282 | + |
| 283 | +func BenchmarkRemoveNoSlash(b *testing.B) { |
| 284 | + prefix := "/somePrefixmoretext" |
| 285 | + benchmarkRemoveRepeatedSlash(b, prefix) |
| 286 | +} |
0 commit comments