go.sdls.io/sin@v0.0.9/pkg/sin/tree_test.go (about) 1 // Copyright 2013 Julien Schmidt. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be found 3 // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE 4 5 package sin 6 7 import ( 8 "fmt" 9 "reflect" 10 "regexp" 11 "strings" 12 "testing" 13 14 "github.com/stretchr/testify/assert" 15 ) 16 17 // Used as a workaround since we can't compare functions or their addresses 18 var fakeHandlerValue string 19 20 func TestDefaultNodeStatic(t *testing.T) { 21 n := node{} 22 assert.Equal(t, static, n.nType) 23 } 24 25 func fakeHandler(val string) HandlersChain { 26 return HandlersChain{func(c *Context) { 27 fakeHandlerValue = val 28 }} 29 } 30 31 type testRequests []struct { 32 path string 33 route string 34 ps Params 35 nilHandler bool 36 } 37 38 func getParams() *Params { 39 ps := make(Params, 0, 20) 40 return &ps 41 } 42 43 func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) { 44 unescape := false 45 if len(unescapes) >= 1 { 46 unescape = unescapes[0] 47 } 48 49 for _, request := range requests { 50 value := tree.getValue(request.path, getParams(), unescape) 51 52 if value.handlers == nil { 53 if !request.nilHandler { 54 t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) 55 } 56 } else if request.nilHandler { 57 t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) 58 } else { 59 value.handlers[0](nil) 60 if fakeHandlerValue != request.route { 61 t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route) 62 } 63 } 64 65 if value.params != nil { 66 if !reflect.DeepEqual(*value.params, request.ps) { 67 t.Errorf("Params mismatch for route '%s'", request.path) 68 } 69 } 70 71 } 72 } 73 74 func checkPriorities(t *testing.T, n *node) uint32 { 75 var prio uint32 76 for i := range n.children { 77 prio += checkPriorities(t, n.children[i]) 78 } 79 80 if n.handlers != nil { 81 prio++ 82 } 83 84 if n.priority != prio { 85 t.Errorf( 86 "priority mismatch for node '%s': is %d, should be %d", 87 n.path, n.priority, prio, 88 ) 89 } 90 91 return prio 92 } 93 94 func TestCountParams(t *testing.T) { 95 if countParams("/path/:param1/static/*catch-all") != 2 { 96 t.Fail() 97 } 98 if countParams(strings.Repeat("/:param", 256)) != 256 { 99 t.Fail() 100 } 101 } 102 103 func TestTreeAddAndGet(t *testing.T) { 104 tree := &node{} 105 106 routes := [...]string{ 107 "/hi", 108 "/contact", 109 "/co", 110 "/c", 111 "/a", 112 "/ab", 113 "/doc/", 114 "/doc/go_faq.html", 115 "/doc/go1.html", 116 "/α", 117 "/β", 118 } 119 for _, route := range routes { 120 tree.addRoute(route, fakeHandler(route)) 121 } 122 123 checkRequests(t, tree, testRequests{ 124 {path: "/a", route: "/a"}, 125 {path: "/", nilHandler: true}, 126 {path: "/hi", route: "/hi"}, 127 {path: "/contact", route: "/contact"}, 128 {path: "/co", route: "/co"}, 129 {path: "/con", nilHandler: true}, // key mismatch 130 {path: "/cona", nilHandler: true}, // key mismatch 131 {path: "/no", nilHandler: true}, // no matching child 132 {path: "/ab", route: "/ab"}, 133 {path: "/α", route: "/α"}, 134 {path: "/β", route: "/β"}, 135 }) 136 137 checkPriorities(t, tree) 138 } 139 140 func TestTreeWildcard(t *testing.T) { 141 tree := &node{} 142 143 routes := [...]string{ 144 "/", 145 "/cmd/:tool/:sub", 146 "/cmd/:tool/", 147 "/cmd/whoami", 148 "/cmd/whoami/root/", 149 "/src/*filepath", 150 "/search/", 151 "/search/:query", 152 "/user_:name", 153 "/user_:name/about", 154 "/files/:dir/*filepath", 155 "/doc/", 156 "/doc/go_faq.html", 157 "/doc/go1.html", 158 "/info/:user/public", 159 "/info/:user/project/:project", 160 } 161 for _, route := range routes { 162 tree.addRoute(route, fakeHandler(route)) 163 } 164 165 checkRequests(t, tree, testRequests{ 166 {path: "/", route: "/"}, 167 {path: "/cmd/test", nilHandler: true, route: "/cmd/:tool/", ps: Params{Param{"tool", "test"}}}, 168 {path: "/cmd/test/", route: "/cmd/:tool/", ps: Params{Param{"tool", "test"}}}, 169 {path: "/cmd/whoami", route: "/cmd/whoami"}, 170 {path: "/cmd/whoami/", nilHandler: true, route: "/cmd/whoami"}, 171 {path: "/cmd/whoami/root/", route: "/cmd/whoami/root/"}, 172 {path: "/cmd/whoami/root", nilHandler: true, route: "/cmd/whoami/root/"}, 173 {path: "/cmd/test/3", route: "/cmd/:tool/:sub", ps: Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}}, 174 {path: "/src/", route: "/src/*filepath", ps: Params{Param{Key: "filepath", Value: "/"}}}, 175 {path: "/src/some/file.png", route: "/src/*filepath", ps: Params{Param{Key: "filepath", Value: "/some/file.png"}}}, 176 {path: "/search/", route: "/search/"}, 177 {path: "/search/someth!ng+in+ünìcodé", route: "/search/:query", ps: Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, 178 {path: "/search/someth!ng+in+ünìcodé/", nilHandler: true, ps: Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, 179 {path: "/user_gopher", route: "/user_:name", ps: Params{Param{Key: "name", Value: "gopher"}}}, 180 {path: "/user_gopher/about", route: "/user_:name/about", ps: Params{Param{Key: "name", Value: "gopher"}}}, 181 {path: "/files/js/inc/framework.js", route: "/files/:dir/*filepath", ps: Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}}, 182 {path: "/info/gordon/public", route: "/info/:user/public", ps: Params{Param{Key: "user", Value: "gordon"}}}, 183 {path: "/info/gordon/project/go", route: "/info/:user/project/:project", ps: Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}}, 184 }) 185 186 checkPriorities(t, tree) 187 } 188 189 func TestUnescapeParameters(t *testing.T) { 190 tree := &node{} 191 192 routes := [...]string{ 193 "/", 194 "/cmd/:tool/:sub", 195 "/cmd/:tool/", 196 "/src/*filepath", 197 "/search/:query", 198 "/files/:dir/*filepath", 199 "/info/:user/project/:project", 200 "/info/:user", 201 } 202 for _, route := range routes { 203 tree.addRoute(route, fakeHandler(route)) 204 } 205 206 checkRequests(t, tree, testRequests{ 207 {path: "/", route: "/"}, 208 {path: "/cmd/test/", route: "/cmd/:tool/", ps: Params{Param{Key: "tool", Value: "test"}}}, 209 {path: "/cmd/test", nilHandler: true, ps: Params{Param{Key: "tool", Value: "test"}}}, 210 {path: "/src/some/file.png", route: "/src/*filepath", ps: Params{Param{Key: "filepath", Value: "/some/file.png"}}}, 211 {path: "/src/some/file+test.png", route: "/src/*filepath", ps: Params{Param{Key: "filepath", Value: "/some/file test.png"}}}, 212 {path: "/src/some/file++++%%%%test.png", route: "/src/*filepath", ps: Params{Param{Key: "filepath", Value: "/some/file++++%%%%test.png"}}}, 213 {path: "/src/some/file%2Ftest.png", route: "/src/*filepath", ps: Params{Param{Key: "filepath", Value: "/some/file/test.png"}}}, 214 {path: "/search/someth!ng+in+ünìcodé", route: "/search/:query", ps: Params{Param{Key: "query", Value: "someth!ng in ünìcodé"}}}, 215 {path: "/info/gordon/project/go", route: "/info/:user/project/:project", ps: Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}}, 216 {path: "/info/slash%2Fgordon", route: "/info/:user", ps: Params{Param{Key: "user", Value: "slash/gordon"}}}, 217 {path: "/info/slash%2Fgordon/project/Project%20%231", route: "/info/:user/project/:project", ps: Params{Param{Key: "user", Value: "slash/gordon"}, Param{Key: "project", Value: "Project #1"}}}, 218 {path: "/info/slash%%%%", route: "/info/:user", ps: Params{Param{Key: "user", Value: "slash%%%%"}}}, 219 {path: "/info/slash%%%%2Fgordon/project/Project%%%%20%231", route: "/info/:user/project/:project", ps: Params{Param{Key: "user", Value: "slash%%%%2Fgordon"}, Param{Key: "project", Value: "Project%%%%20%231"}}}, 220 }, true) 221 222 checkPriorities(t, tree) 223 } 224 225 func catchPanic(testFunc func()) (recv interface{}) { 226 defer func() { 227 recv = recover() 228 }() 229 230 testFunc() 231 return 232 } 233 234 type testRoute struct { 235 path string 236 conflict bool 237 } 238 239 func testRoutes(t *testing.T, routes []testRoute) { 240 tree := &node{} 241 242 for _, route := range routes { 243 recv := catchPanic(func() { 244 tree.addRoute(route.path, nil) 245 }) 246 247 if route.conflict { 248 if recv == nil { 249 t.Errorf("no panic for conflicting route '%s'", route.path) 250 } 251 } else if recv != nil { 252 t.Errorf("unexpected panic for route '%s': %v", route.path, recv) 253 } 254 } 255 } 256 257 func TestTreeWildcardConflict(t *testing.T) { 258 routes := []testRoute{ 259 {path: "/cmd/:tool/:sub"}, 260 {path: "/cmd/vet"}, 261 {path: "/foo/bar"}, 262 {path: "/foo/:name"}, 263 {path: "/foo/:names", conflict: true}, 264 {path: "/cmd/*path", conflict: true}, 265 {path: "/cmd/:badvar", conflict: true}, 266 {path: "/cmd/:tool/names"}, 267 {path: "/cmd/:tool/:badsub/details", conflict: true}, 268 {path: "/src/*filepath"}, 269 {path: "/src/:file", conflict: true}, 270 {path: "/src/static.json", conflict: true}, 271 {path: "/src/*filepathx", conflict: true}, 272 {path: "/src/", conflict: true}, 273 {path: "/src/foo/bar", conflict: true}, 274 {path: "/src1/"}, 275 {path: "/src1/*filepath", conflict: true}, 276 {path: "/src2*filepath", conflict: true}, 277 {path: "/src2/*filepath"}, 278 {path: "/search/:query"}, 279 {path: "/search/valid"}, 280 {path: "/user_:name"}, 281 {path: "/user_x"}, 282 {path: "/user_:name"}, 283 {path: "/id:id"}, 284 {path: "/id/:id"}, 285 } 286 testRoutes(t, routes) 287 } 288 289 func TestCatchAllAfterSlash(t *testing.T) { 290 routes := []testRoute{ 291 {"/non-leading-*catchall", true}, 292 } 293 testRoutes(t, routes) 294 } 295 296 func TestTreeChildConflict(t *testing.T) { 297 routes := []testRoute{ 298 {path: "/cmd/vet"}, 299 {path: "/cmd/:tool"}, 300 {path: "/cmd/:tool/:sub"}, 301 {path: "/cmd/:tool/misc"}, 302 {path: "/cmd/:tool/:othersub", conflict: true}, 303 {path: "/src/AUTHORS"}, 304 {path: "/src/*filepath", conflict: true}, 305 {path: "/user_x"}, 306 {path: "/user_:name"}, 307 {path: "/id/:id"}, 308 {path: "/id:id"}, 309 {path: "/:id"}, 310 {path: "/*filepath", conflict: true}, 311 } 312 testRoutes(t, routes) 313 } 314 315 func TestTreeDupliatePath(t *testing.T) { 316 tree := &node{} 317 318 routes := [...]string{ 319 "/", 320 "/doc/", 321 "/src/*filepath", 322 "/search/:query", 323 "/user_:name", 324 } 325 for _, route := range routes { 326 recv := catchPanic(func() { 327 tree.addRoute(route, fakeHandler(route)) 328 }) 329 if recv != nil { 330 t.Fatalf("panic inserting route '%s': %v", route, recv) 331 } 332 333 // Add again 334 recv = catchPanic(func() { 335 tree.addRoute(route, nil) 336 }) 337 if recv == nil { 338 t.Fatalf("no panic while inserting duplicate route '%s", route) 339 } 340 } 341 342 // printChildren(tree, "") 343 344 checkRequests(t, tree, testRequests{ 345 {path: "/", route: "/"}, 346 {path: "/doc/", route: "/doc/"}, 347 {path: "/src/some/file.png", route: "/src/*filepath", ps: Params{Param{"filepath", "/some/file.png"}}}, 348 {path: "/search/someth!ng+in+ünìcodé", route: "/search/:query", ps: Params{Param{"query", "someth!ng+in+ünìcodé"}}}, 349 {path: "/user_gopher", route: "/user_:name", ps: Params{Param{"name", "gopher"}}}, 350 }) 351 } 352 353 func TestEmptyWildcardName(t *testing.T) { 354 tree := &node{} 355 356 routes := [...]string{ 357 "/user:", 358 "/user:/", 359 "/cmd/:/", 360 "/src/*", 361 } 362 for _, route := range routes { 363 recv := catchPanic(func() { 364 tree.addRoute(route, nil) 365 }) 366 if recv == nil { 367 t.Fatalf("no panic while inserting route with empty wildcard name '%s", route) 368 } 369 } 370 } 371 372 func TestTreeCatchAllConflict(t *testing.T) { 373 routes := []testRoute{ 374 {path: "/src/*filepath/x", conflict: true}, 375 {path: "/src2/"}, 376 {path: "/src2/*filepath/x", conflict: true}, 377 {path: "/src3/*filepath"}, 378 {path: "/src3/*filepath/x", conflict: true}, 379 } 380 testRoutes(t, routes) 381 } 382 383 func TestTreeCatchAllConflictRoot(t *testing.T) { 384 routes := []testRoute{ 385 {"/", false}, 386 {"/*filepath", true}, 387 } 388 testRoutes(t, routes) 389 } 390 391 func TestTreeCatchMaxParams(t *testing.T) { 392 tree := &node{} 393 route := "/cmd/*filepath" 394 tree.addRoute(route, fakeHandler(route)) 395 } 396 397 func TestTreeDoubleWildcard(t *testing.T) { 398 const panicMsg = "only one wildcard per path segment is allowed" 399 400 routes := [...]string{ 401 "/:foo:bar", 402 "/:foo:bar/", 403 "/:foo*bar", 404 } 405 406 for _, route := range routes { 407 tree := &node{} 408 recv := catchPanic(func() { 409 tree.addRoute(route, nil) 410 }) 411 412 if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) { 413 t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv) 414 } 415 } 416 } 417 418 /*func TestTreeDuplicateWildcard(t *testing.T) { 419 tree := &node{} 420 routes := [...]string{ 421 "/:id/:name/:id", 422 } 423 for _, route := range routes { 424 ... 425 } 426 }*/ 427 428 func TestTreeTrailingSlashRedirect(t *testing.T) { 429 tree := &node{} 430 431 routes := [...]string{ 432 "/hi", 433 "/b/", 434 "/search/:query", 435 "/cmd/:tool/", 436 "/src/*filepath", 437 "/x", 438 "/x/y", 439 "/y/", 440 "/y/z", 441 "/0/:id", 442 "/0/:id/1", 443 "/1/:id/", 444 "/1/:id/2", 445 "/aa", 446 "/a/", 447 "/admin", 448 "/admin/:category", 449 "/admin/:category/:page", 450 "/doc", 451 "/doc/go_faq.html", 452 "/doc/go1.html", 453 "/no/a", 454 "/no/b", 455 "/api/hello/:name", 456 } 457 for _, route := range routes { 458 recv := catchPanic(func() { 459 tree.addRoute(route, fakeHandler(route)) 460 }) 461 if recv != nil { 462 t.Fatalf("panic inserting route '%s': %v", route, recv) 463 } 464 } 465 466 tsrRoutes := [...]string{ 467 "/hi/", 468 "/b", 469 "/search/gopher/", 470 "/cmd/vet", 471 "/src", 472 "/x/", 473 "/y", 474 "/0/go/", 475 "/1/go", 476 "/a", 477 "/admin/", 478 "/admin/config/", 479 "/admin/config/permissions/", 480 "/doc/", 481 } 482 for _, route := range tsrRoutes { 483 value := tree.getValue(route, nil, false) 484 if value.handlers != nil { 485 t.Fatalf("non-nil handler for TSR route '%s", route) 486 } else if !value.tsr { 487 t.Errorf("expected TSR recommendation for route '%s'", route) 488 } 489 } 490 491 noTsrRoutes := [...]string{ 492 "/", 493 "/no", 494 "/no/", 495 "/_", 496 "/_/", 497 "/api/world/abc", 498 } 499 for _, route := range noTsrRoutes { 500 value := tree.getValue(route, nil, false) 501 if value.handlers != nil { 502 t.Fatalf("non-nil handler for No-TSR route '%s", route) 503 } else if value.tsr { 504 t.Errorf("expected no TSR recommendation for route '%s'", route) 505 } 506 } 507 } 508 509 func TestTreeRootTrailingSlashRedirect(t *testing.T) { 510 tree := &node{} 511 512 recv := catchPanic(func() { 513 tree.addRoute("/:test", fakeHandler("/:test")) 514 }) 515 if recv != nil { 516 t.Fatalf("panic inserting test route: %v", recv) 517 } 518 519 value := tree.getValue("/", nil, false) 520 if value.handlers != nil { 521 t.Fatalf("non-nil handler") 522 } else if value.tsr { 523 t.Errorf("expected no TSR recommendation") 524 } 525 } 526 527 func TestTreeFindCaseInsensitivePath(t *testing.T) { 528 tree := &node{} 529 530 longPath := "/l" + strings.Repeat("o", 128) + "ng" 531 lOngPath := "/l" + strings.Repeat("O", 128) + "ng/" 532 533 routes := [...]string{ 534 "/hi", 535 "/b/", 536 "/ABC/", 537 "/search/:query", 538 "/cmd/:tool/", 539 "/src/*filepath", 540 "/x", 541 "/x/y", 542 "/y/", 543 "/y/z", 544 "/0/:id", 545 "/0/:id/1", 546 "/1/:id/", 547 "/1/:id/2", 548 "/aa", 549 "/a/", 550 "/doc", 551 "/doc/go_faq.html", 552 "/doc/go1.html", 553 "/doc/go/away", 554 "/no/a", 555 "/no/b", 556 "/Π", 557 "/u/apfêl/", 558 "/u/äpfêl/", 559 "/u/öpfêl", 560 "/v/Äpfêl/", 561 "/v/Öpfêl", 562 "/w/♬", // 3 byte 563 "/w/♭/", // 3 byte, last byte differs 564 "/w/𠜎", // 4 byte 565 "/w/𠜏/", // 4 byte 566 longPath, 567 } 568 569 for _, route := range routes { 570 recv := catchPanic(func() { 571 tree.addRoute(route, fakeHandler(route)) 572 }) 573 if recv != nil { 574 t.Fatalf("panic inserting route '%s': %v", route, recv) 575 } 576 } 577 578 // Check out == in for all registered routes 579 // With fixTrailingSlash = true 580 for _, route := range routes { 581 out, found := tree.findCaseInsensitivePath(route, true) 582 if !found { 583 t.Errorf("Route '%s' not found!", route) 584 } else if string(out) != route { 585 t.Errorf("Wrong result for route '%s': %s", route, string(out)) 586 } 587 } 588 // With fixTrailingSlash = false 589 for _, route := range routes { 590 out, found := tree.findCaseInsensitivePath(route, false) 591 if !found { 592 t.Errorf("Route '%s' not found!", route) 593 } else if string(out) != route { 594 t.Errorf("Wrong result for route '%s': %s", route, string(out)) 595 } 596 } 597 598 tests := []struct { 599 in string 600 out string 601 found bool 602 slash bool 603 }{ 604 {"/HI", "/hi", true, false}, 605 {"/HI/", "/hi", true, true}, 606 {"/B", "/b/", true, true}, 607 {"/B/", "/b/", true, false}, 608 {"/abc", "/ABC/", true, true}, 609 {"/abc/", "/ABC/", true, false}, 610 {"/aBc", "/ABC/", true, true}, 611 {"/aBc/", "/ABC/", true, false}, 612 {"/abC", "/ABC/", true, true}, 613 {"/abC/", "/ABC/", true, false}, 614 {"/SEARCH/QUERY", "/search/QUERY", true, false}, 615 {"/SEARCH/QUERY/", "/search/QUERY", true, true}, 616 {"/CMD/TOOL/", "/cmd/TOOL/", true, false}, 617 {"/CMD/TOOL", "/cmd/TOOL/", true, true}, 618 {"/SRC/FILE/PATH", "/src/FILE/PATH", true, false}, 619 {"/x/Y", "/x/y", true, false}, 620 {"/x/Y/", "/x/y", true, true}, 621 {"/X/y", "/x/y", true, false}, 622 {"/X/y/", "/x/y", true, true}, 623 {"/X/Y", "/x/y", true, false}, 624 {"/X/Y/", "/x/y", true, true}, 625 {"/Y/", "/y/", true, false}, 626 {"/Y", "/y/", true, true}, 627 {"/Y/z", "/y/z", true, false}, 628 {"/Y/z/", "/y/z", true, true}, 629 {"/Y/Z", "/y/z", true, false}, 630 {"/Y/Z/", "/y/z", true, true}, 631 {"/y/Z", "/y/z", true, false}, 632 {"/y/Z/", "/y/z", true, true}, 633 {"/Aa", "/aa", true, false}, 634 {"/Aa/", "/aa", true, true}, 635 {"/AA", "/aa", true, false}, 636 {"/AA/", "/aa", true, true}, 637 {"/aA", "/aa", true, false}, 638 {"/aA/", "/aa", true, true}, 639 {"/A/", "/a/", true, false}, 640 {"/A", "/a/", true, true}, 641 {"/DOC", "/doc", true, false}, 642 {"/DOC/", "/doc", true, true}, 643 {"/NO", "", false, true}, 644 {"/DOC/GO", "", false, true}, 645 {"/π", "/Π", true, false}, 646 {"/π/", "/Π", true, true}, 647 {"/u/ÄPFÊL/", "/u/äpfêl/", true, false}, 648 {"/u/ÄPFÊL", "/u/äpfêl/", true, true}, 649 {"/u/ÖPFÊL/", "/u/öpfêl", true, true}, 650 {"/u/ÖPFÊL", "/u/öpfêl", true, false}, 651 {"/v/äpfêL/", "/v/Äpfêl/", true, false}, 652 {"/v/äpfêL", "/v/Äpfêl/", true, true}, 653 {"/v/öpfêL/", "/v/Öpfêl", true, true}, 654 {"/v/öpfêL", "/v/Öpfêl", true, false}, 655 {"/w/♬/", "/w/♬", true, true}, 656 {"/w/♭", "/w/♭/", true, true}, 657 {"/w/𠜎/", "/w/𠜎", true, true}, 658 {"/w/𠜏", "/w/𠜏/", true, true}, 659 {lOngPath, longPath, true, true}, 660 } 661 // With fixTrailingSlash = true 662 for _, test := range tests { 663 out, found := tree.findCaseInsensitivePath(test.in, true) 664 if found != test.found || (found && (string(out) != test.out)) { 665 t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", 666 test.in, string(out), found, test.out, test.found) 667 return 668 } 669 } 670 // With fixTrailingSlash = false 671 for _, test := range tests { 672 out, found := tree.findCaseInsensitivePath(test.in, false) 673 if test.slash { 674 if found { // test needs a trailingSlash fix. It must not be found! 675 t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out)) 676 } 677 } else { 678 if found != test.found || (found && (string(out) != test.out)) { 679 t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", 680 test.in, string(out), found, test.out, test.found) 681 return 682 } 683 } 684 } 685 } 686 687 func TestTreeInvalidNodeType(t *testing.T) { 688 const panicMsg = "invalid node type" 689 690 tree := &node{} 691 tree.addRoute("/", fakeHandler("/")) 692 tree.addRoute("/:page", fakeHandler("/:page")) 693 694 // set invalid node type 695 tree.children[0].nType = 42 696 697 // normal lookup 698 recv := catchPanic(func() { 699 tree.getValue("/test", nil, false) 700 }) 701 if rs, ok := recv.(string); !ok || rs != panicMsg { 702 t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) 703 } 704 705 // case-insensitive lookup 706 recv = catchPanic(func() { 707 tree.findCaseInsensitivePath("/test", true) 708 }) 709 if rs, ok := recv.(string); !ok || rs != panicMsg { 710 t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) 711 } 712 } 713 714 func TestTreeWildcardConflictEx(t *testing.T) { 715 conflicts := [...]struct { 716 route string 717 segPath string 718 existPath string 719 existSegPath string 720 }{ 721 {"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`}, 722 {"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`}, 723 {"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`}, 724 {"/con:nection", ":nection", `/con:tact`, `:tact`}, 725 } 726 727 for _, conflict := range conflicts { 728 // I have to re-create a 'tree', because the 'tree' will be 729 // in an inconsistent state when the loop recovers from the 730 // panic which threw by 'addRoute' function. 731 tree := &node{} 732 routes := [...]string{ 733 "/con:tact", 734 "/who/are/*you", 735 "/who/foo/hello", 736 } 737 738 for _, route := range routes { 739 tree.addRoute(route, fakeHandler(route)) 740 } 741 742 recv := catchPanic(func() { 743 tree.addRoute(conflict.route, fakeHandler(conflict.route)) 744 }) 745 746 if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'", conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) { 747 t.Fatalf("invalid wildcard conflict error (%v)", recv) 748 } 749 } 750 } 751 752 func TestTreeInvalidParamsType(t *testing.T) { 753 tree := &node{} 754 tree.wildChild = true 755 tree.children = append(tree.children, &node{}) 756 tree.children[0].nType = 2 757 758 // set invalid Params type 759 params := make(Params, 0) 760 761 // try to trigger slice bounds out of range with capacity 0 762 tree.getValue("/test", ¶ms, false) 763 }