Source file
src/net/http/pattern_test.go
1
2
3
4
5 package http
6
7 import (
8 "slices"
9 "strings"
10 "testing"
11 )
12
13 func TestParsePattern(t *testing.T) {
14 lit := func(name string) segment {
15 return segment{s: name}
16 }
17
18 wild := func(name string) segment {
19 return segment{s: name, wild: true}
20 }
21
22 multi := func(name string) segment {
23 s := wild(name)
24 s.multi = true
25 return s
26 }
27
28 for _, test := range []struct {
29 in string
30 want pattern
31 }{
32 {"/", pattern{segments: []segment{multi("")}}},
33 {"/a", pattern{segments: []segment{lit("a")}}},
34 {
35 "/a/",
36 pattern{segments: []segment{lit("a"), multi("")}},
37 },
38 {"/path/to/something", pattern{segments: []segment{
39 lit("path"), lit("to"), lit("something"),
40 }}},
41 {
42 "/{w1}/lit/{w2}",
43 pattern{
44 segments: []segment{wild("w1"), lit("lit"), wild("w2")},
45 },
46 },
47 {
48 "/{w1}/lit/{w2}/",
49 pattern{
50 segments: []segment{wild("w1"), lit("lit"), wild("w2"), multi("")},
51 },
52 },
53 {
54 "example.com/",
55 pattern{host: "example.com", segments: []segment{multi("")}},
56 },
57 {
58 "GET /",
59 pattern{method: "GET", segments: []segment{multi("")}},
60 },
61 {
62 "POST example.com/foo/{w}",
63 pattern{
64 method: "POST",
65 host: "example.com",
66 segments: []segment{lit("foo"), wild("w")},
67 },
68 },
69 {
70 "/{$}",
71 pattern{segments: []segment{lit("/")}},
72 },
73 {
74 "DELETE example.com/a/{foo12}/{$}",
75 pattern{method: "DELETE", host: "example.com", segments: []segment{lit("a"), wild("foo12"), lit("/")}},
76 },
77 {
78 "/foo/{$}",
79 pattern{segments: []segment{lit("foo"), lit("/")}},
80 },
81 {
82 "/{a}/foo/{rest...}",
83 pattern{segments: []segment{wild("a"), lit("foo"), multi("rest")}},
84 },
85 {
86 "//",
87 pattern{segments: []segment{lit(""), multi("")}},
88 },
89 {
90 "/foo///./../bar",
91 pattern{segments: []segment{lit("foo"), lit(""), lit(""), lit("."), lit(".."), lit("bar")}},
92 },
93 {
94 "a.com/foo//",
95 pattern{host: "a.com", segments: []segment{lit("foo"), lit(""), multi("")}},
96 },
97 {
98 "/%61%62/%7b/%",
99 pattern{segments: []segment{lit("ab"), lit("{"), lit("%")}},
100 },
101 } {
102 got := mustParsePattern(t, test.in)
103 if !got.equal(&test.want) {
104 t.Errorf("%q:\ngot %#v\nwant %#v", test.in, got, &test.want)
105 }
106 }
107 }
108
109 func TestParsePatternError(t *testing.T) {
110 for _, test := range []struct {
111 in string
112 contains string
113 }{
114 {"", "empty pattern"},
115 {"A=B /", "at offset 0: invalid method"},
116 {" ", "at offset 1: host/path missing /"},
117 {"/{w}x", "at offset 1: bad wildcard segment"},
118 {"/x{w}", "at offset 1: bad wildcard segment"},
119 {"/{wx", "at offset 1: bad wildcard segment"},
120 {"/a/{/}/c", "at offset 3: bad wildcard segment"},
121 {"/a/{%61}/c", "at offset 3: bad wildcard name"},
122 {"/{a$}", "at offset 1: bad wildcard name"},
123 {"/{}", "at offset 1: empty wildcard"},
124 {"POST a.com/x/{}/y", "at offset 13: empty wildcard"},
125 {"/{...}", "at offset 1: empty wildcard"},
126 {"/{$...}", "at offset 1: bad wildcard"},
127 {"/{$}/", "at offset 1: {$} not at end"},
128 {"/{$}/x", "at offset 1: {$} not at end"},
129 {"/abc/{$}/x", "at offset 5: {$} not at end"},
130 {"/{a...}/", "at offset 1: {...} wildcard not at end"},
131 {"/{a...}/x", "at offset 1: {...} wildcard not at end"},
132 {"{a}/b", "at offset 0: host contains '{' (missing initial '/'?)"},
133 {"/a/{x}/b/{x...}", "at offset 9: duplicate wildcard name"},
134 {"GET //", "at offset 4: non-CONNECT pattern with unclean path"},
135 } {
136 _, err := parsePattern(test.in)
137 if err == nil || !strings.Contains(err.Error(), test.contains) {
138 t.Errorf("%q:\ngot %v, want error containing %q", test.in, err, test.contains)
139 }
140 }
141 }
142
143 func (p1 *pattern) equal(p2 *pattern) bool {
144 return p1.method == p2.method && p1.host == p2.host &&
145 slices.Equal(p1.segments, p2.segments)
146 }
147
148 func mustParsePattern(tb testing.TB, s string) *pattern {
149 tb.Helper()
150 p, err := parsePattern(s)
151 if err != nil {
152 tb.Fatal(err)
153 }
154 return p
155 }
156
157 func TestCompareMethods(t *testing.T) {
158 for _, test := range []struct {
159 p1, p2 string
160 want relationship
161 }{
162 {"/", "/", equivalent},
163 {"GET /", "GET /", equivalent},
164 {"HEAD /", "HEAD /", equivalent},
165 {"POST /", "POST /", equivalent},
166 {"GET /", "POST /", disjoint},
167 {"GET /", "/", moreSpecific},
168 {"HEAD /", "/", moreSpecific},
169 {"GET /", "HEAD /", moreGeneral},
170 } {
171 pat1 := mustParsePattern(t, test.p1)
172 pat2 := mustParsePattern(t, test.p2)
173 got := pat1.compareMethods(pat2)
174 if got != test.want {
175 t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want)
176 }
177 got2 := pat2.compareMethods(pat1)
178 want2 := inverseRelationship(test.want)
179 if got2 != want2 {
180 t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2)
181 }
182 }
183 }
184
185 func TestComparePaths(t *testing.T) {
186 for _, test := range []struct {
187 p1, p2 string
188 want relationship
189 }{
190
191
192
193
194
195
196 {"/a", "/a", equivalent},
197 {"/a", "/b", disjoint},
198 {"/a", "/", moreSpecific},
199 {"/a", "/{$}", disjoint},
200 {"/a", "/{x}", moreSpecific},
201 {"/a", "/{x...}", moreSpecific},
202
203
204 {"/b/a", "/b/a", equivalent},
205 {"/b/a", "/b/b", disjoint},
206 {"/b/a", "/b/", moreSpecific},
207 {"/b/a", "/b/{$}", disjoint},
208 {"/b/a", "/b/{x}", moreSpecific},
209 {"/b/a", "/b/{x...}", moreSpecific},
210 {"/{z}/a", "/{z}/a", equivalent},
211 {"/{z}/a", "/{z}/b", disjoint},
212 {"/{z}/a", "/{z}/", moreSpecific},
213 {"/{z}/a", "/{z}/{$}", disjoint},
214 {"/{z}/a", "/{z}/{x}", moreSpecific},
215 {"/{z}/a", "/{z}/{x...}", moreSpecific},
216
217
218 {"/{z}", "/a", moreGeneral},
219 {"/{z}", "/a/b", disjoint},
220 {"/{z}", "/{$}", disjoint},
221 {"/{z}", "/{x}", equivalent},
222 {"/{z}", "/", moreSpecific},
223 {"/{z}", "/{x...}", moreSpecific},
224 {"/b/{z}", "/b/a", moreGeneral},
225 {"/b/{z}", "/b/a/b", disjoint},
226 {"/b/{z}", "/b/{$}", disjoint},
227 {"/b/{z}", "/b/{x}", equivalent},
228 {"/b/{z}", "/b/", moreSpecific},
229 {"/b/{z}", "/b/{x...}", moreSpecific},
230
231
232 {"/", "/a", moreGeneral},
233 {"/", "/a/b", moreGeneral},
234 {"/", "/{$}", moreGeneral},
235 {"/", "/{x}", moreGeneral},
236 {"/", "/", equivalent},
237 {"/", "/{x...}", equivalent},
238
239 {"/b/", "/b/a", moreGeneral},
240 {"/b/", "/b/a/b", moreGeneral},
241 {"/b/", "/b/{$}", moreGeneral},
242 {"/b/", "/b/{x}", moreGeneral},
243 {"/b/", "/b/", equivalent},
244 {"/b/", "/b/{x...}", equivalent},
245
246 {"/{z}/", "/{z}/a", moreGeneral},
247 {"/{z}/", "/{z}/a/b", moreGeneral},
248 {"/{z}/", "/{z}/{$}", moreGeneral},
249 {"/{z}/", "/{z}/{x}", moreGeneral},
250 {"/{z}/", "/{z}/", equivalent},
251 {"/{z}/", "/a/", moreGeneral},
252 {"/{z}/", "/{z}/{x...}", equivalent},
253 {"/{z}/", "/a/{x...}", moreGeneral},
254 {"/a/{z}/", "/{z}/a/", overlaps},
255 {"/a/{z}/b/", "/{x}/c/{y...}", overlaps},
256
257
258 {"/{m...}", "/a", moreGeneral},
259 {"/{m...}", "/a/b", moreGeneral},
260 {"/{m...}", "/{$}", moreGeneral},
261 {"/{m...}", "/{x}", moreGeneral},
262 {"/{m...}", "/", equivalent},
263 {"/{m...}", "/{x...}", equivalent},
264
265 {"/b/{m...}", "/b/a", moreGeneral},
266 {"/b/{m...}", "/b/a/b", moreGeneral},
267 {"/b/{m...}", "/b/{$}", moreGeneral},
268 {"/b/{m...}", "/b/{x}", moreGeneral},
269 {"/b/{m...}", "/b/", equivalent},
270 {"/b/{m...}", "/b/{x...}", equivalent},
271 {"/b/{m...}", "/a/{x...}", disjoint},
272
273 {"/{z}/{m...}", "/{z}/a", moreGeneral},
274 {"/{z}/{m...}", "/{z}/a/b", moreGeneral},
275 {"/{z}/{m...}", "/{z}/{$}", moreGeneral},
276 {"/{z}/{m...}", "/{z}/{x}", moreGeneral},
277 {"/{z}/{m...}", "/{w}/", equivalent},
278 {"/{z}/{m...}", "/a/", moreGeneral},
279 {"/{z}/{m...}", "/{z}/{x...}", equivalent},
280 {"/{z}/{m...}", "/a/{x...}", moreGeneral},
281 {"/a/{m...}", "/a/b/{y...}", moreGeneral},
282 {"/a/{m...}", "/a/{x}/{y...}", moreGeneral},
283 {"/a/{z}/{m...}", "/a/b/{y...}", moreGeneral},
284 {"/a/{z}/{m...}", "/{z}/a/", overlaps},
285 {"/a/{z}/{m...}", "/{z}/b/{y...}", overlaps},
286 {"/a/{z}/b/{m...}", "/{x}/c/{y...}", overlaps},
287 {"/a/{z}/a/{m...}", "/{x}/b", disjoint},
288
289
290 {"/{$}", "/a", disjoint},
291 {"/{$}", "/a/b", disjoint},
292 {"/{$}", "/{$}", equivalent},
293 {"/{$}", "/{x}", disjoint},
294 {"/{$}", "/", moreSpecific},
295 {"/{$}", "/{x...}", moreSpecific},
296
297 {"/b/{$}", "/b", disjoint},
298 {"/b/{$}", "/b/a", disjoint},
299 {"/b/{$}", "/b/a/b", disjoint},
300 {"/b/{$}", "/b/{$}", equivalent},
301 {"/b/{$}", "/b/{x}", disjoint},
302 {"/b/{$}", "/b/", moreSpecific},
303 {"/b/{$}", "/b/{x...}", moreSpecific},
304 {"/b/{$}", "/b/c/{x...}", disjoint},
305 {"/b/{x}/a/{$}", "/{x}/c/{y...}", overlaps},
306 {"/{x}/b/{$}", "/a/{x}/{y}", disjoint},
307 {"/{x}/b/{$}", "/a/{x}/c", disjoint},
308
309 {"/{z}/{$}", "/{z}/a", disjoint},
310 {"/{z}/{$}", "/{z}/a/b", disjoint},
311 {"/{z}/{$}", "/{z}/{$}", equivalent},
312 {"/{z}/{$}", "/{z}/{x}", disjoint},
313 {"/{z}/{$}", "/{z}/", moreSpecific},
314 {"/{z}/{$}", "/a/", overlaps},
315 {"/{z}/{$}", "/a/{x...}", overlaps},
316 {"/{z}/{$}", "/{z}/{x...}", moreSpecific},
317 {"/a/{z}/{$}", "/{z}/a/", overlaps},
318 } {
319 pat1 := mustParsePattern(t, test.p1)
320 pat2 := mustParsePattern(t, test.p2)
321 if g := pat1.comparePaths(pat1); g != equivalent {
322 t.Errorf("%s does not match itself; got %s", pat1, g)
323 }
324 if g := pat2.comparePaths(pat2); g != equivalent {
325 t.Errorf("%s does not match itself; got %s", pat2, g)
326 }
327 got := pat1.comparePaths(pat2)
328 if got != test.want {
329 t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want)
330 t.Logf("pat1: %+v\n", pat1.segments)
331 t.Logf("pat2: %+v\n", pat2.segments)
332 }
333 want2 := inverseRelationship(test.want)
334 got2 := pat2.comparePaths(pat1)
335 if got2 != want2 {
336 t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2)
337 }
338 }
339 }
340
341 func TestConflictsWith(t *testing.T) {
342 for _, test := range []struct {
343 p1, p2 string
344 want bool
345 }{
346 {"/a", "/a", true},
347 {"/a", "/ab", false},
348 {"/a/b/cd", "/a/b/cd", true},
349 {"/a/b/cd", "/a/b/c", false},
350 {"/a/b/c", "/a/c/c", false},
351 {"/{x}", "/{y}", true},
352 {"/{x}", "/a", false},
353 {"/{x}/{y}", "/{x}/a", false},
354 {"/{x}/{y}", "/{x}/a/b", false},
355 {"/{x}", "/a/{y}", false},
356 {"/{x}/{y}", "/{x}/a/", false},
357 {"/{x}", "/a/{y...}", false},
358 {"/{x}/a/{y}", "/{x}/a/{y...}", false},
359 {"/{x}/{y}", "/{x}/a/{$}", false},
360 {"/{x}/{y}/{$}", "/{x}/a/{$}", false},
361 {"/a/{x}", "/{x}/b", true},
362 {"/", "GET /", false},
363 {"/", "GET /foo", false},
364 {"GET /", "GET /foo", false},
365 {"GET /", "/foo", true},
366 {"GET /foo", "HEAD /", true},
367 } {
368 pat1 := mustParsePattern(t, test.p1)
369 pat2 := mustParsePattern(t, test.p2)
370 got := pat1.conflictsWith(pat2)
371 if got != test.want {
372 t.Errorf("%q.ConflictsWith(%q) = %t, want %t",
373 test.p1, test.p2, got, test.want)
374 }
375
376 got = pat2.conflictsWith(pat1)
377 if got != test.want {
378 t.Errorf("%q.ConflictsWith(%q) = %t, want %t",
379 test.p2, test.p1, got, test.want)
380 }
381 }
382 }
383
384 func TestRegisterConflict(t *testing.T) {
385 mux := NewServeMux()
386 pat1 := "/a/{x}/"
387 if err := mux.registerErr(pat1, NotFoundHandler()); err != nil {
388 t.Fatal(err)
389 }
390 pat2 := "/a/{y}/{z...}"
391 err := mux.registerErr(pat2, NotFoundHandler())
392 var got string
393 if err == nil {
394 got = "<nil>"
395 } else {
396 got = err.Error()
397 }
398 want := "matches the same requests as"
399 if !strings.Contains(got, want) {
400 t.Errorf("got\n%s\nwant\n%s", got, want)
401 }
402 }
403
404 func TestDescribeConflict(t *testing.T) {
405 for _, test := range []struct {
406 p1, p2 string
407 want string
408 }{
409 {"/a/{x}", "/a/{y}", "the same requests"},
410 {"/", "/{m...}", "the same requests"},
411 {"/a/{x}", "/{y}/b", "both match some paths"},
412 {"/a", "GET /{x}", "matches more methods than GET /{x}, but has a more specific path pattern"},
413 {"GET /a", "HEAD /", "matches more methods than HEAD /, but has a more specific path pattern"},
414 {"POST /", "/a", "matches fewer methods than /a, but has a more general path pattern"},
415 } {
416 got := describeConflict(mustParsePattern(t, test.p1), mustParsePattern(t, test.p2))
417 if !strings.Contains(got, test.want) {
418 t.Errorf("%s vs. %s:\ngot:\n%s\nwhich does not contain %q",
419 test.p1, test.p2, got, test.want)
420 }
421 }
422 }
423
424 func TestCommonPath(t *testing.T) {
425 for _, test := range []struct {
426 p1, p2 string
427 want string
428 }{
429 {"/a/{x}", "/{x}/a", "/a/a"},
430 {"/a/{z}/", "/{z}/a/", "/a/a/"},
431 {"/a/{z}/{m...}", "/{z}/a/", "/a/a/"},
432 {"/{z}/{$}", "/a/", "/a/"},
433 {"/{z}/{$}", "/a/{x...}", "/a/"},
434 {"/a/{z}/{$}", "/{z}/a/", "/a/a/"},
435 {"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/c/b/"},
436 {"/a/{x}/b/", "/{x}/c/{y...}", "/a/c/b/"},
437 {"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/c/b/"},
438 {"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/b/"},
439 } {
440 pat1 := mustParsePattern(t, test.p1)
441 pat2 := mustParsePattern(t, test.p2)
442 if pat1.comparePaths(pat2) != overlaps {
443 t.Fatalf("%s does not overlap %s", test.p1, test.p2)
444 }
445 got := commonPath(pat1, pat2)
446 if got != test.want {
447 t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want)
448 }
449 }
450 }
451
452 func TestDifferencePath(t *testing.T) {
453 for _, test := range []struct {
454 p1, p2 string
455 want string
456 }{
457 {"/a/{x}", "/{x}/a", "/a/x"},
458 {"/{x}/a", "/a/{x}", "/x/a"},
459 {"/a/{z}/", "/{z}/a/", "/a/z/"},
460 {"/{z}/a/", "/a/{z}/", "/z/a/"},
461 {"/{a}/a/", "/a/{z}/", "/ax/a/"},
462 {"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/z/"},
463 {"/{z}/b/{y...}", "/a/{z}/{x...}", "/z/b/"},
464 {"/a/b/", "/a/b/c", "/a/b/"},
465 {"/a/b/{x...}", "/a/b/c", "/a/b/"},
466 {"/a/b/{x...}", "/a/b/c/d", "/a/b/"},
467 {"/a/b/{x...}", "/a/b/c/d/", "/a/b/"},
468 {"/a/{z}/{m...}", "/{z}/a/", "/a/z/"},
469 {"/{z}/a/", "/a/{z}/{m...}", "/z/a/"},
470 {"/{z}/{$}", "/a/", "/z/"},
471 {"/a/", "/{z}/{$}", "/a/x"},
472 {"/{z}/{$}", "/a/{x...}", "/z/"},
473 {"/a/{foo...}", "/{z}/{$}", "/a/foo"},
474 {"/a/{z}/{$}", "/{z}/a/", "/a/z/"},
475 {"/{z}/a/", "/a/{z}/{$}", "/z/a/x"},
476 {"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/x/b/"},
477 {"/{x}/c/{y...}", "/a/{x}/b/{y...}", "/x/c/"},
478 {"/a/{c}/b/", "/{x}/c/{y...}", "/a/cx/b/"},
479 {"/{x}/c/{y...}", "/a/{c}/b/", "/x/c/"},
480 {"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/x/b/"},
481 {"/{x}/c/{y...}", "/a/{x}/b/{$}", "/x/c/"},
482 } {
483 pat1 := mustParsePattern(t, test.p1)
484 pat2 := mustParsePattern(t, test.p2)
485 rel := pat1.comparePaths(pat2)
486 if rel != overlaps && rel != moreGeneral {
487 t.Fatalf("%s vs. %s are %s, need overlaps or moreGeneral", pat1, pat2, rel)
488 }
489 got := differencePath(pat1, pat2)
490 if got != test.want {
491 t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want)
492 }
493 }
494 }
495
View as plain text