Source file
src/net/http/routing_tree_test.go
1
2
3
4
5 package http
6
7 import (
8 "fmt"
9 "io"
10 "strings"
11 "testing"
12
13 "slices"
14 )
15
16 func TestRoutingFirstSegment(t *testing.T) {
17 for _, test := range []struct {
18 in string
19 want []string
20 }{
21 {"/a/b/c", []string{"a", "b", "c"}},
22 {"/a/b/", []string{"a", "b", "/"}},
23 {"/", []string{"/"}},
24 {"/a/%62/c", []string{"a", "b", "c"}},
25 {"/a%2Fb%2fc", []string{"a/b/c"}},
26 } {
27 var got []string
28 rest := test.in
29 for len(rest) > 0 {
30 var seg string
31 seg, rest = firstSegment(rest)
32 got = append(got, seg)
33 }
34 if !slices.Equal(got, test.want) {
35 t.Errorf("%q: got %v, want %v", test.in, got, test.want)
36 }
37 }
38 }
39
40
41 var testTree *routingNode
42
43 func getTestTree() *routingNode {
44 if testTree == nil {
45 testTree = buildTree("/a", "/a/b", "/a/{x}",
46 "/g/h/i", "/g/{x}/j",
47 "/a/b/{x...}", "/a/b/{y}", "/a/b/{$}")
48 }
49 return testTree
50 }
51
52 func buildTree(pats ...string) *routingNode {
53 root := &routingNode{}
54 for _, p := range pats {
55 pat, err := parsePattern(p)
56 if err != nil {
57 panic(err)
58 }
59 root.addPattern(pat, nil)
60 }
61 return root
62 }
63
64 func TestRoutingAddPattern(t *testing.T) {
65 want := `"":
66 "":
67 "a":
68 "/a"
69 "":
70 "/a/{x}"
71 "b":
72 "/a/b"
73 "":
74 "/a/b/{y}"
75 "*":
76 "/a/b/{x...}"
77 "/":
78 "/a/b/{$}"
79 "g":
80 "":
81 "j":
82 "/g/{x}/j"
83 "h":
84 "i":
85 "/g/h/i"
86 `
87
88 var b strings.Builder
89 getTestTree().print(&b, 0)
90 got := b.String()
91 if got != want {
92 t.Errorf("got\n%s\nwant\n%s", got, want)
93 }
94 }
95
96 type testCase struct {
97 method, host, path string
98 wantPat string
99 wantMatches []string
100 }
101
102 func TestRoutingNodeMatch(t *testing.T) {
103
104 test := func(tree *routingNode, tests []testCase) {
105 t.Helper()
106 for _, test := range tests {
107 gotNode, gotMatches := tree.match(test.host, test.method, test.path)
108 got := ""
109 if gotNode != nil {
110 got = gotNode.pattern.String()
111 }
112 if got != test.wantPat {
113 t.Errorf("%s, %s, %s: got %q, want %q", test.host, test.method, test.path, got, test.wantPat)
114 }
115 if !slices.Equal(gotMatches, test.wantMatches) {
116 t.Errorf("%s, %s, %s: got matches %v, want %v", test.host, test.method, test.path, gotMatches, test.wantMatches)
117 }
118 }
119 }
120
121 test(getTestTree(), []testCase{
122 {"GET", "", "/a", "/a", nil},
123 {"Get", "", "/b", "", nil},
124 {"Get", "", "/a/b", "/a/b", nil},
125 {"Get", "", "/a/c", "/a/{x}", []string{"c"}},
126 {"Get", "", "/a/b/", "/a/b/{$}", nil},
127 {"Get", "", "/a/b/c", "/a/b/{y}", []string{"c"}},
128 {"Get", "", "/a/b/c/d", "/a/b/{x...}", []string{"c/d"}},
129 {"Get", "", "/g/h/i", "/g/h/i", nil},
130 {"Get", "", "/g/h/j", "/g/{x}/j", []string{"h"}},
131 })
132
133 tree := buildTree(
134 "/item/",
135 "POST /item/{user}",
136 "GET /item/{user}",
137 "/item/{user}",
138 "/item/{user}/{id}",
139 "/item/{user}/new",
140 "/item/{$}",
141 "POST alt.com/item/{user}",
142 "GET /headwins",
143 "HEAD /headwins",
144 "/path/{p...}")
145
146 test(tree, []testCase{
147 {"GET", "", "/item/jba",
148 "GET /item/{user}", []string{"jba"}},
149 {"POST", "", "/item/jba",
150 "POST /item/{user}", []string{"jba"}},
151 {"HEAD", "", "/item/jba",
152 "GET /item/{user}", []string{"jba"}},
153 {"get", "", "/item/jba",
154 "/item/{user}", []string{"jba"}},
155 {"POST", "", "/item/jba/17",
156 "/item/{user}/{id}", []string{"jba", "17"}},
157 {"GET", "", "/item/jba/new",
158 "/item/{user}/new", []string{"jba"}},
159 {"GET", "", "/item/",
160 "/item/{$}", []string{}},
161 {"GET", "", "/item/jba/17/line2",
162 "/item/", nil},
163 {"POST", "alt.com", "/item/jba",
164 "POST alt.com/item/{user}", []string{"jba"}},
165 {"GET", "alt.com", "/item/jba",
166 "GET /item/{user}", []string{"jba"}},
167 {"GET", "", "/item",
168 "", nil},
169 {"GET", "", "/headwins",
170 "GET /headwins", nil},
171 {"HEAD", "", "/headwins",
172 "HEAD /headwins", nil},
173 {"GET", "", "/path/to/file",
174 "/path/{p...}", []string{"to/file"}},
175 })
176
177
178 pat1 := "/a/b/{$}"
179 test(buildTree(pat1), []testCase{
180 {"GET", "", "/a/b", "", nil},
181 {"GET", "", "/a/b/", pat1, nil},
182 {"GET", "", "/a/b/c", "", nil},
183 {"GET", "", "/a/b/c/d", "", nil},
184 })
185
186
187 pat2 := "/a/b/{w}"
188 test(buildTree(pat2), []testCase{
189 {"GET", "", "/a/b", "", nil},
190 {"GET", "", "/a/b/", "", nil},
191 {"GET", "", "/a/b/c", pat2, []string{"c"}},
192 {"GET", "", "/a/b/c/d", "", nil},
193 })
194
195
196 pat3 := "/a/b/{w...}"
197 test(buildTree(pat3), []testCase{
198 {"GET", "", "/a/b", "", nil},
199 {"GET", "", "/a/b/", pat3, []string{""}},
200 {"GET", "", "/a/b/c", pat3, []string{"c"}},
201 {"GET", "", "/a/b/c/d", pat3, []string{"c/d"}},
202 })
203
204
205 test(buildTree(pat1, pat2, pat3), []testCase{
206 {"GET", "", "/a/b", "", nil},
207 {"GET", "", "/a/b/", pat1, nil},
208 {"GET", "", "/a/b/c", pat2, []string{"c"}},
209 {"GET", "", "/a/b/c/d", pat3, []string{"c/d"}},
210 })
211 }
212
213 func TestMatchingMethods(t *testing.T) {
214 hostTree := buildTree("GET a.com/", "PUT b.com/", "POST /foo/{x}")
215 for _, test := range []struct {
216 name string
217 tree *routingNode
218 host, path string
219 want string
220 }{
221 {
222 "post",
223 buildTree("POST /"), "", "/foo",
224 "POST",
225 },
226 {
227 "get",
228 buildTree("GET /"), "", "/foo",
229 "GET,HEAD",
230 },
231 {
232 "host",
233 hostTree, "", "/foo",
234 "",
235 },
236 {
237 "host",
238 hostTree, "", "/foo/bar",
239 "POST",
240 },
241 {
242 "host2",
243 hostTree, "a.com", "/foo/bar",
244 "GET,HEAD,POST",
245 },
246 {
247 "host3",
248 hostTree, "b.com", "/bar",
249 "PUT",
250 },
251 {
252
253
254 "empty",
255 buildTree("/"), "", "/",
256 "",
257 },
258 } {
259 t.Run(test.name, func(t *testing.T) {
260 ms := map[string]bool{}
261 test.tree.matchingMethods(test.host, test.path, ms)
262 keys := mapKeys(ms)
263 slices.Sort(keys)
264 got := strings.Join(keys, ",")
265 if got != test.want {
266 t.Errorf("got %s, want %s", got, test.want)
267 }
268 })
269 }
270 }
271
272 func (n *routingNode) print(w io.Writer, level int) {
273 indent := strings.Repeat(" ", level)
274 if n.pattern != nil {
275 fmt.Fprintf(w, "%s%q\n", indent, n.pattern)
276 }
277 if n.emptyChild != nil {
278 fmt.Fprintf(w, "%s%q:\n", indent, "")
279 n.emptyChild.print(w, level+1)
280 }
281
282 var keys []string
283 n.children.eachPair(func(k string, _ *routingNode) bool {
284 keys = append(keys, k)
285 return true
286 })
287 slices.Sort(keys)
288
289 for _, k := range keys {
290 fmt.Fprintf(w, "%s%q:\n", indent, k)
291 n, _ := n.children.find(k)
292 n.print(w, level+1)
293 }
294 }
295
View as plain text