| | |
| | |
| | |
| |
|
| | package http |
| |
|
| | import ( |
| | "slices" |
| | "strings" |
| | "testing" |
| | ) |
| |
|
| | func TestParsePattern(t *testing.T) { |
| | lit := func(name string) segment { |
| | return segment{s: name} |
| | } |
| |
|
| | wild := func(name string) segment { |
| | return segment{s: name, wild: true} |
| | } |
| |
|
| | multi := func(name string) segment { |
| | s := wild(name) |
| | s.multi = true |
| | return s |
| | } |
| |
|
| | for _, test := range []struct { |
| | in string |
| | want pattern |
| | }{ |
| | {"/", pattern{segments: []segment{multi("")}}}, |
| | {"/a", pattern{segments: []segment{lit("a")}}}, |
| | { |
| | "/a/", |
| | pattern{segments: []segment{lit("a"), multi("")}}, |
| | }, |
| | {"/path/to/something", pattern{segments: []segment{ |
| | lit("path"), lit("to"), lit("something"), |
| | }}}, |
| | { |
| | "/{w1}/lit/{w2}", |
| | pattern{ |
| | segments: []segment{wild("w1"), lit("lit"), wild("w2")}, |
| | }, |
| | }, |
| | { |
| | "/{w1}/lit/{w2}/", |
| | pattern{ |
| | segments: []segment{wild("w1"), lit("lit"), wild("w2"), multi("")}, |
| | }, |
| | }, |
| | { |
| | "example.com/", |
| | pattern{host: "example.com", segments: []segment{multi("")}}, |
| | }, |
| | { |
| | "GET /", |
| | pattern{method: "GET", segments: []segment{multi("")}}, |
| | }, |
| | { |
| | "POST example.com/foo/{w}", |
| | pattern{ |
| | method: "POST", |
| | host: "example.com", |
| | segments: []segment{lit("foo"), wild("w")}, |
| | }, |
| | }, |
| | { |
| | "/{$}", |
| | pattern{segments: []segment{lit("/")}}, |
| | }, |
| | { |
| | "DELETE example.com/a/{foo12}/{$}", |
| | pattern{method: "DELETE", host: "example.com", segments: []segment{lit("a"), wild("foo12"), lit("/")}}, |
| | }, |
| | { |
| | "/foo/{$}", |
| | pattern{segments: []segment{lit("foo"), lit("/")}}, |
| | }, |
| | { |
| | "/{a}/foo/{rest...}", |
| | pattern{segments: []segment{wild("a"), lit("foo"), multi("rest")}}, |
| | }, |
| | { |
| | "//", |
| | pattern{segments: []segment{lit(""), multi("")}}, |
| | }, |
| | { |
| | "/foo///./../bar", |
| | pattern{segments: []segment{lit("foo"), lit(""), lit(""), lit("."), lit(".."), lit("bar")}}, |
| | }, |
| | { |
| | "a.com/foo//", |
| | pattern{host: "a.com", segments: []segment{lit("foo"), lit(""), multi("")}}, |
| | }, |
| | { |
| | "/%61%62/%7b/%", |
| | pattern{segments: []segment{lit("ab"), lit("{"), lit("%")}}, |
| | }, |
| | |
| | { |
| | "GET\t /", |
| | pattern{method: "GET", segments: []segment{multi("")}}, |
| | }, |
| | { |
| | "POST \t example.com/foo/{w}", |
| | pattern{ |
| | method: "POST", |
| | host: "example.com", |
| | segments: []segment{lit("foo"), wild("w")}, |
| | }, |
| | }, |
| | { |
| | "DELETE \texample.com/a/{foo12}/{$}", |
| | pattern{method: "DELETE", host: "example.com", segments: []segment{lit("a"), wild("foo12"), lit("/")}}, |
| | }, |
| | } { |
| | got := mustParsePattern(t, test.in) |
| | if !got.equal(&test.want) { |
| | t.Errorf("%q:\ngot %#v\nwant %#v", test.in, got, &test.want) |
| | } |
| | } |
| | } |
| |
|
| | func TestParsePatternError(t *testing.T) { |
| | for _, test := range []struct { |
| | in string |
| | contains string |
| | }{ |
| | {"", "empty pattern"}, |
| | {"A=B /", "at offset 0: invalid method"}, |
| | {" ", "at offset 1: host/path missing /"}, |
| | {"/{w}x", "at offset 1: bad wildcard segment"}, |
| | {"/x{w}", "at offset 1: bad wildcard segment"}, |
| | {"/{wx", "at offset 1: bad wildcard segment"}, |
| | {"/a/{/}/c", "at offset 3: bad wildcard segment"}, |
| | {"/a/{%61}/c", "at offset 3: bad wildcard name"}, |
| | {"/{a$}", "at offset 1: bad wildcard name"}, |
| | {"/{}", "at offset 1: empty wildcard"}, |
| | {"POST a.com/x/{}/y", "at offset 13: empty wildcard"}, |
| | {"/{...}", "at offset 1: empty wildcard"}, |
| | {"/{$...}", "at offset 1: bad wildcard"}, |
| | {"/{$}/", "at offset 1: {$} not at end"}, |
| | {"/{$}/x", "at offset 1: {$} not at end"}, |
| | {"/abc/{$}/x", "at offset 5: {$} not at end"}, |
| | {"/{a...}/", "at offset 1: {...} wildcard not at end"}, |
| | {"/{a...}/x", "at offset 1: {...} wildcard not at end"}, |
| | {"{a}/b", "at offset 0: host contains '{' (missing initial '/'?)"}, |
| | {"/a/{x}/b/{x...}", "at offset 9: duplicate wildcard name"}, |
| | {"GET //", "at offset 4: non-CONNECT pattern with unclean path"}, |
| | } { |
| | _, err := parsePattern(test.in) |
| | if err == nil || !strings.Contains(err.Error(), test.contains) { |
| | t.Errorf("%q:\ngot %v, want error containing %q", test.in, err, test.contains) |
| | } |
| | } |
| | } |
| |
|
| | func (p1 *pattern) equal(p2 *pattern) bool { |
| | return p1.method == p2.method && p1.host == p2.host && |
| | slices.Equal(p1.segments, p2.segments) |
| | } |
| |
|
| | func mustParsePattern(tb testing.TB, s string) *pattern { |
| | tb.Helper() |
| | p, err := parsePattern(s) |
| | if err != nil { |
| | tb.Fatal(err) |
| | } |
| | return p |
| | } |
| |
|
| | func TestCompareMethods(t *testing.T) { |
| | for _, test := range []struct { |
| | p1, p2 string |
| | want relationship |
| | }{ |
| | {"/", "/", equivalent}, |
| | {"GET /", "GET /", equivalent}, |
| | {"HEAD /", "HEAD /", equivalent}, |
| | {"POST /", "POST /", equivalent}, |
| | {"GET /", "POST /", disjoint}, |
| | {"GET /", "/", moreSpecific}, |
| | {"HEAD /", "/", moreSpecific}, |
| | {"GET /", "HEAD /", moreGeneral}, |
| | } { |
| | pat1 := mustParsePattern(t, test.p1) |
| | pat2 := mustParsePattern(t, test.p2) |
| | got := pat1.compareMethods(pat2) |
| | if got != test.want { |
| | t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want) |
| | } |
| | got2 := pat2.compareMethods(pat1) |
| | want2 := inverseRelationship(test.want) |
| | if got2 != want2 { |
| | t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2) |
| | } |
| | } |
| | } |
| |
|
| | func TestComparePaths(t *testing.T) { |
| | for _, test := range []struct { |
| | p1, p2 string |
| | want relationship |
| | }{ |
| | |
| | |
| | |
| | |
| |
|
| | |
| | {"/a", "/a", equivalent}, |
| | {"/a", "/b", disjoint}, |
| | {"/a", "/", moreSpecific}, |
| | {"/a", "/{$}", disjoint}, |
| | {"/a", "/{x}", moreSpecific}, |
| | {"/a", "/{x...}", moreSpecific}, |
| |
|
| | |
| | {"/b/a", "/b/a", equivalent}, |
| | {"/b/a", "/b/b", disjoint}, |
| | {"/b/a", "/b/", moreSpecific}, |
| | {"/b/a", "/b/{$}", disjoint}, |
| | {"/b/a", "/b/{x}", moreSpecific}, |
| | {"/b/a", "/b/{x...}", moreSpecific}, |
| | {"/{z}/a", "/{z}/a", equivalent}, |
| | {"/{z}/a", "/{z}/b", disjoint}, |
| | {"/{z}/a", "/{z}/", moreSpecific}, |
| | {"/{z}/a", "/{z}/{$}", disjoint}, |
| | {"/{z}/a", "/{z}/{x}", moreSpecific}, |
| | {"/{z}/a", "/{z}/{x...}", moreSpecific}, |
| |
|
| | |
| | {"/{z}", "/a", moreGeneral}, |
| | {"/{z}", "/a/b", disjoint}, |
| | {"/{z}", "/{$}", disjoint}, |
| | {"/{z}", "/{x}", equivalent}, |
| | {"/{z}", "/", moreSpecific}, |
| | {"/{z}", "/{x...}", moreSpecific}, |
| | {"/b/{z}", "/b/a", moreGeneral}, |
| | {"/b/{z}", "/b/a/b", disjoint}, |
| | {"/b/{z}", "/b/{$}", disjoint}, |
| | {"/b/{z}", "/b/{x}", equivalent}, |
| | {"/b/{z}", "/b/", moreSpecific}, |
| | {"/b/{z}", "/b/{x...}", moreSpecific}, |
| |
|
| | |
| | {"/", "/a", moreGeneral}, |
| | {"/", "/a/b", moreGeneral}, |
| | {"/", "/{$}", moreGeneral}, |
| | {"/", "/{x}", moreGeneral}, |
| | {"/", "/", equivalent}, |
| | {"/", "/{x...}", equivalent}, |
| |
|
| | {"/b/", "/b/a", moreGeneral}, |
| | {"/b/", "/b/a/b", moreGeneral}, |
| | {"/b/", "/b/{$}", moreGeneral}, |
| | {"/b/", "/b/{x}", moreGeneral}, |
| | {"/b/", "/b/", equivalent}, |
| | {"/b/", "/b/{x...}", equivalent}, |
| |
|
| | {"/{z}/", "/{z}/a", moreGeneral}, |
| | {"/{z}/", "/{z}/a/b", moreGeneral}, |
| | {"/{z}/", "/{z}/{$}", moreGeneral}, |
| | {"/{z}/", "/{z}/{x}", moreGeneral}, |
| | {"/{z}/", "/{z}/", equivalent}, |
| | {"/{z}/", "/a/", moreGeneral}, |
| | {"/{z}/", "/{z}/{x...}", equivalent}, |
| | {"/{z}/", "/a/{x...}", moreGeneral}, |
| | {"/a/{z}/", "/{z}/a/", overlaps}, |
| | {"/a/{z}/b/", "/{x}/c/{y...}", overlaps}, |
| |
|
| | |
| | {"/{m...}", "/a", moreGeneral}, |
| | {"/{m...}", "/a/b", moreGeneral}, |
| | {"/{m...}", "/{$}", moreGeneral}, |
| | {"/{m...}", "/{x}", moreGeneral}, |
| | {"/{m...}", "/", equivalent}, |
| | {"/{m...}", "/{x...}", equivalent}, |
| |
|
| | {"/b/{m...}", "/b/a", moreGeneral}, |
| | {"/b/{m...}", "/b/a/b", moreGeneral}, |
| | {"/b/{m...}", "/b/{$}", moreGeneral}, |
| | {"/b/{m...}", "/b/{x}", moreGeneral}, |
| | {"/b/{m...}", "/b/", equivalent}, |
| | {"/b/{m...}", "/b/{x...}", equivalent}, |
| | {"/b/{m...}", "/a/{x...}", disjoint}, |
| |
|
| | {"/{z}/{m...}", "/{z}/a", moreGeneral}, |
| | {"/{z}/{m...}", "/{z}/a/b", moreGeneral}, |
| | {"/{z}/{m...}", "/{z}/{$}", moreGeneral}, |
| | {"/{z}/{m...}", "/{z}/{x}", moreGeneral}, |
| | {"/{z}/{m...}", "/{w}/", equivalent}, |
| | {"/{z}/{m...}", "/a/", moreGeneral}, |
| | {"/{z}/{m...}", "/{z}/{x...}", equivalent}, |
| | {"/{z}/{m...}", "/a/{x...}", moreGeneral}, |
| | {"/a/{m...}", "/a/b/{y...}", moreGeneral}, |
| | {"/a/{m...}", "/a/{x}/{y...}", moreGeneral}, |
| | {"/a/{z}/{m...}", "/a/b/{y...}", moreGeneral}, |
| | {"/a/{z}/{m...}", "/{z}/a/", overlaps}, |
| | {"/a/{z}/{m...}", "/{z}/b/{y...}", overlaps}, |
| | {"/a/{z}/b/{m...}", "/{x}/c/{y...}", overlaps}, |
| | {"/a/{z}/a/{m...}", "/{x}/b", disjoint}, |
| |
|
| | |
| | {"/{$}", "/a", disjoint}, |
| | {"/{$}", "/a/b", disjoint}, |
| | {"/{$}", "/{$}", equivalent}, |
| | {"/{$}", "/{x}", disjoint}, |
| | {"/{$}", "/", moreSpecific}, |
| | {"/{$}", "/{x...}", moreSpecific}, |
| |
|
| | {"/b/{$}", "/b", disjoint}, |
| | {"/b/{$}", "/b/a", disjoint}, |
| | {"/b/{$}", "/b/a/b", disjoint}, |
| | {"/b/{$}", "/b/{$}", equivalent}, |
| | {"/b/{$}", "/b/{x}", disjoint}, |
| | {"/b/{$}", "/b/", moreSpecific}, |
| | {"/b/{$}", "/b/{x...}", moreSpecific}, |
| | {"/b/{$}", "/b/c/{x...}", disjoint}, |
| | {"/b/{x}/a/{$}", "/{x}/c/{y...}", overlaps}, |
| | {"/{x}/b/{$}", "/a/{x}/{y}", disjoint}, |
| | {"/{x}/b/{$}", "/a/{x}/c", disjoint}, |
| |
|
| | {"/{z}/{$}", "/{z}/a", disjoint}, |
| | {"/{z}/{$}", "/{z}/a/b", disjoint}, |
| | {"/{z}/{$}", "/{z}/{$}", equivalent}, |
| | {"/{z}/{$}", "/{z}/{x}", disjoint}, |
| | {"/{z}/{$}", "/{z}/", moreSpecific}, |
| | {"/{z}/{$}", "/a/", overlaps}, |
| | {"/{z}/{$}", "/a/{x...}", overlaps}, |
| | {"/{z}/{$}", "/{z}/{x...}", moreSpecific}, |
| | {"/a/{z}/{$}", "/{z}/a/", overlaps}, |
| | } { |
| | pat1 := mustParsePattern(t, test.p1) |
| | pat2 := mustParsePattern(t, test.p2) |
| | if g := pat1.comparePaths(pat1); g != equivalent { |
| | t.Errorf("%s does not match itself; got %s", pat1, g) |
| | } |
| | if g := pat2.comparePaths(pat2); g != equivalent { |
| | t.Errorf("%s does not match itself; got %s", pat2, g) |
| | } |
| | got := pat1.comparePaths(pat2) |
| | if got != test.want { |
| | t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want) |
| | t.Logf("pat1: %+v\n", pat1.segments) |
| | t.Logf("pat2: %+v\n", pat2.segments) |
| | } |
| | want2 := inverseRelationship(test.want) |
| | got2 := pat2.comparePaths(pat1) |
| | if got2 != want2 { |
| | t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2) |
| | } |
| | } |
| | } |
| |
|
| | func TestConflictsWith(t *testing.T) { |
| | for _, test := range []struct { |
| | p1, p2 string |
| | want bool |
| | }{ |
| | {"/a", "/a", true}, |
| | {"/a", "/ab", false}, |
| | {"/a/b/cd", "/a/b/cd", true}, |
| | {"/a/b/cd", "/a/b/c", false}, |
| | {"/a/b/c", "/a/c/c", false}, |
| | {"/{x}", "/{y}", true}, |
| | {"/{x}", "/a", false}, |
| | {"/{x}/{y}", "/{x}/a", false}, |
| | {"/{x}/{y}", "/{x}/a/b", false}, |
| | {"/{x}", "/a/{y}", false}, |
| | {"/{x}/{y}", "/{x}/a/", false}, |
| | {"/{x}", "/a/{y...}", false}, |
| | {"/{x}/a/{y}", "/{x}/a/{y...}", false}, |
| | {"/{x}/{y}", "/{x}/a/{$}", false}, |
| | {"/{x}/{y}/{$}", "/{x}/a/{$}", false}, |
| | {"/a/{x}", "/{x}/b", true}, |
| | {"/", "GET /", false}, |
| | {"/", "GET /foo", false}, |
| | {"GET /", "GET /foo", false}, |
| | {"GET /", "/foo", true}, |
| | {"GET /foo", "HEAD /", true}, |
| | } { |
| | pat1 := mustParsePattern(t, test.p1) |
| | pat2 := mustParsePattern(t, test.p2) |
| | got := pat1.conflictsWith(pat2) |
| | if got != test.want { |
| | t.Errorf("%q.ConflictsWith(%q) = %t, want %t", |
| | test.p1, test.p2, got, test.want) |
| | } |
| | |
| | got = pat2.conflictsWith(pat1) |
| | if got != test.want { |
| | t.Errorf("%q.ConflictsWith(%q) = %t, want %t", |
| | test.p2, test.p1, got, test.want) |
| | } |
| | } |
| | } |
| |
|
| | func TestRegisterConflict(t *testing.T) { |
| | mux := NewServeMux() |
| | pat1 := "/a/{x}/" |
| | if err := mux.registerErr(pat1, NotFoundHandler()); err != nil { |
| | t.Fatal(err) |
| | } |
| | pat2 := "/a/{y}/{z...}" |
| | err := mux.registerErr(pat2, NotFoundHandler()) |
| | var got string |
| | if err == nil { |
| | got = "<nil>" |
| | } else { |
| | got = err.Error() |
| | } |
| | want := "matches the same requests as" |
| | if !strings.Contains(got, want) { |
| | t.Errorf("got\n%s\nwant\n%s", got, want) |
| | } |
| | } |
| |
|
| | func TestDescribeConflict(t *testing.T) { |
| | for _, test := range []struct { |
| | p1, p2 string |
| | want string |
| | }{ |
| | {"/a/{x}", "/a/{y}", "the same requests"}, |
| | {"/", "/{m...}", "the same requests"}, |
| | {"/a/{x}", "/{y}/b", "both match some paths"}, |
| | {"/a", "GET /{x}", "matches more methods than GET /{x}, but has a more specific path pattern"}, |
| | {"GET /a", "HEAD /", "matches more methods than HEAD /, but has a more specific path pattern"}, |
| | {"POST /", "/a", "matches fewer methods than /a, but has a more general path pattern"}, |
| | } { |
| | got := describeConflict(mustParsePattern(t, test.p1), mustParsePattern(t, test.p2)) |
| | if !strings.Contains(got, test.want) { |
| | t.Errorf("%s vs. %s:\ngot:\n%s\nwhich does not contain %q", |
| | test.p1, test.p2, got, test.want) |
| | } |
| | } |
| | } |
| |
|
| | func TestCommonPath(t *testing.T) { |
| | for _, test := range []struct { |
| | p1, p2 string |
| | want string |
| | }{ |
| | {"/a/{x}", "/{x}/a", "/a/a"}, |
| | {"/a/{z}/", "/{z}/a/", "/a/a/"}, |
| | {"/a/{z}/{m...}", "/{z}/a/", "/a/a/"}, |
| | {"/{z}/{$}", "/a/", "/a/"}, |
| | {"/{z}/{$}", "/a/{x...}", "/a/"}, |
| | {"/a/{z}/{$}", "/{z}/a/", "/a/a/"}, |
| | {"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/c/b/"}, |
| | {"/a/{x}/b/", "/{x}/c/{y...}", "/a/c/b/"}, |
| | {"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/c/b/"}, |
| | {"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/b/"}, |
| | } { |
| | pat1 := mustParsePattern(t, test.p1) |
| | pat2 := mustParsePattern(t, test.p2) |
| | if pat1.comparePaths(pat2) != overlaps { |
| | t.Fatalf("%s does not overlap %s", test.p1, test.p2) |
| | } |
| | got := commonPath(pat1, pat2) |
| | if got != test.want { |
| | t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want) |
| | } |
| | } |
| | } |
| |
|
| | func TestDifferencePath(t *testing.T) { |
| | for _, test := range []struct { |
| | p1, p2 string |
| | want string |
| | }{ |
| | {"/a/{x}", "/{x}/a", "/a/x"}, |
| | {"/{x}/a", "/a/{x}", "/x/a"}, |
| | {"/a/{z}/", "/{z}/a/", "/a/z/"}, |
| | {"/{z}/a/", "/a/{z}/", "/z/a/"}, |
| | {"/{a}/a/", "/a/{z}/", "/ax/a/"}, |
| | {"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/z/"}, |
| | {"/{z}/b/{y...}", "/a/{z}/{x...}", "/z/b/"}, |
| | {"/a/b/", "/a/b/c", "/a/b/"}, |
| | {"/a/b/{x...}", "/a/b/c", "/a/b/"}, |
| | {"/a/b/{x...}", "/a/b/c/d", "/a/b/"}, |
| | {"/a/b/{x...}", "/a/b/c/d/", "/a/b/"}, |
| | {"/a/{z}/{m...}", "/{z}/a/", "/a/z/"}, |
| | {"/{z}/a/", "/a/{z}/{m...}", "/z/a/"}, |
| | {"/{z}/{$}", "/a/", "/z/"}, |
| | {"/a/", "/{z}/{$}", "/a/x"}, |
| | {"/{z}/{$}", "/a/{x...}", "/z/"}, |
| | {"/a/{foo...}", "/{z}/{$}", "/a/foo"}, |
| | {"/a/{z}/{$}", "/{z}/a/", "/a/z/"}, |
| | {"/{z}/a/", "/a/{z}/{$}", "/z/a/x"}, |
| | {"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/x/b/"}, |
| | {"/{x}/c/{y...}", "/a/{x}/b/{y...}", "/x/c/"}, |
| | {"/a/{c}/b/", "/{x}/c/{y...}", "/a/cx/b/"}, |
| | {"/{x}/c/{y...}", "/a/{c}/b/", "/x/c/"}, |
| | {"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/x/b/"}, |
| | {"/{x}/c/{y...}", "/a/{x}/b/{$}", "/x/c/"}, |
| | } { |
| | pat1 := mustParsePattern(t, test.p1) |
| | pat2 := mustParsePattern(t, test.p2) |
| | rel := pat1.comparePaths(pat2) |
| | if rel != overlaps && rel != moreGeneral { |
| | t.Fatalf("%s vs. %s are %s, need overlaps or moreGeneral", pat1, pat2, rel) |
| | } |
| | got := differencePath(pat1, pat2) |
| | if got != test.want { |
| | t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want) |
| | } |
| | } |
| | } |
| |
|