tree_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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. package gin
  5. import (
  6. "fmt"
  7. "reflect"
  8. "regexp"
  9. "strings"
  10. "testing"
  11. )
  12. // Used as a workaround since we can't compare functions or their addresses
  13. var fakeHandlerValue string
  14. func fakeHandler(val string) HandlersChain {
  15. return HandlersChain{func(c *Context) {
  16. fakeHandlerValue = val
  17. }}
  18. }
  19. type testRequests []struct {
  20. path string
  21. nilHandler bool
  22. route string
  23. ps Params
  24. }
  25. func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
  26. unescape := false
  27. if len(unescapes) >= 1 {
  28. unescape = unescapes[0]
  29. }
  30. for _, request := range requests {
  31. value := tree.getValue(request.path, nil, unescape)
  32. if value.handlers == nil {
  33. if !request.nilHandler {
  34. t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
  35. }
  36. } else if request.nilHandler {
  37. t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
  38. } else {
  39. value.handlers[0](nil)
  40. if fakeHandlerValue != request.route {
  41. t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
  42. }
  43. }
  44. if !reflect.DeepEqual(value.params, request.ps) {
  45. t.Errorf("Params mismatch for route '%s'", request.path)
  46. }
  47. }
  48. }
  49. func checkPriorities(t *testing.T, n *node) uint32 {
  50. var prio uint32
  51. for i := range n.children {
  52. prio += checkPriorities(t, n.children[i])
  53. }
  54. if n.handlers != nil {
  55. prio++
  56. }
  57. if n.priority != prio {
  58. t.Errorf(
  59. "priority mismatch for node '%s': is %d, should be %d",
  60. n.path, n.priority, prio,
  61. )
  62. }
  63. return prio
  64. }
  65. func checkMaxParams(t *testing.T, n *node) uint8 {
  66. var maxParams uint8
  67. for i := range n.children {
  68. params := checkMaxParams(t, n.children[i])
  69. if params > maxParams {
  70. maxParams = params
  71. }
  72. }
  73. if n.nType > root && !n.wildChild {
  74. maxParams++
  75. }
  76. if n.maxParams != maxParams {
  77. t.Errorf(
  78. "maxParams mismatch for node '%s': is %d, should be %d",
  79. n.path, n.maxParams, maxParams,
  80. )
  81. }
  82. return maxParams
  83. }
  84. func TestCountParams(t *testing.T) {
  85. if countParams("/path/:param1/static/*catch-all") != 2 {
  86. t.Fail()
  87. }
  88. if countParams(strings.Repeat("/:param", 256)) != 255 {
  89. t.Fail()
  90. }
  91. }
  92. func TestTreeAddAndGet(t *testing.T) {
  93. tree := &node{}
  94. routes := [...]string{
  95. "/hi",
  96. "/contact",
  97. "/co",
  98. "/c",
  99. "/a",
  100. "/ab",
  101. "/doc/",
  102. "/doc/go_faq.html",
  103. "/doc/go1.html",
  104. "/α",
  105. "/β",
  106. }
  107. for _, route := range routes {
  108. tree.addRoute(route, fakeHandler(route))
  109. }
  110. checkRequests(t, tree, testRequests{
  111. {"/a", false, "/a", nil},
  112. {"/", true, "", nil},
  113. {"/hi", false, "/hi", nil},
  114. {"/contact", false, "/contact", nil},
  115. {"/co", false, "/co", nil},
  116. {"/con", true, "", nil}, // key mismatch
  117. {"/cona", true, "", nil}, // key mismatch
  118. {"/no", true, "", nil}, // no matching child
  119. {"/ab", false, "/ab", nil},
  120. {"/α", false, "/α", nil},
  121. {"/β", false, "/β", nil},
  122. })
  123. checkPriorities(t, tree)
  124. checkMaxParams(t, tree)
  125. }
  126. func TestTreeWildcard(t *testing.T) {
  127. tree := &node{}
  128. routes := [...]string{
  129. "/",
  130. "/cmd/:tool/:sub",
  131. "/cmd/:tool/",
  132. "/src/*filepath",
  133. "/search/",
  134. "/search/:query",
  135. "/user_:name",
  136. "/user_:name/about",
  137. "/files/:dir/*filepath",
  138. "/doc/",
  139. "/doc/go_faq.html",
  140. "/doc/go1.html",
  141. "/info/:user/public",
  142. "/info/:user/project/:project",
  143. }
  144. for _, route := range routes {
  145. tree.addRoute(route, fakeHandler(route))
  146. }
  147. checkRequests(t, tree, testRequests{
  148. {"/", false, "/", nil},
  149. {"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}},
  150. {"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}},
  151. {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}},
  152. {"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}},
  153. {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
  154. {"/search/", false, "/search/", nil},
  155. {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
  156. {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
  157. {"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}},
  158. {"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}},
  159. {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}},
  160. {"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}},
  161. {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
  162. })
  163. checkPriorities(t, tree)
  164. checkMaxParams(t, tree)
  165. }
  166. func TestUnescapeParameters(t *testing.T) {
  167. tree := &node{}
  168. routes := [...]string{
  169. "/",
  170. "/cmd/:tool/:sub",
  171. "/cmd/:tool/",
  172. "/src/*filepath",
  173. "/search/:query",
  174. "/files/:dir/*filepath",
  175. "/info/:user/project/:project",
  176. "/info/:user",
  177. }
  178. for _, route := range routes {
  179. tree.addRoute(route, fakeHandler(route))
  180. }
  181. unescape := true
  182. checkRequests(t, tree, testRequests{
  183. {"/", false, "/", nil},
  184. {"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}},
  185. {"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}},
  186. {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
  187. {"/src/some/file+test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file test.png"}}},
  188. {"/src/some/file++++%%%%test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file++++%%%%test.png"}}},
  189. {"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file/test.png"}}},
  190. {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng in ünìcodé"}}},
  191. {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
  192. {"/info/slash%2Fgordon", false, "/info/:user", Params{Param{Key: "user", Value: "slash/gordon"}}},
  193. {"/info/slash%2Fgordon/project/Project%20%231", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "slash/gordon"}, Param{Key: "project", Value: "Project #1"}}},
  194. {"/info/slash%%%%", false, "/info/:user", Params{Param{Key: "user", Value: "slash%%%%"}}},
  195. {"/info/slash%%%%2Fgordon/project/Project%%%%20%231", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "slash%%%%2Fgordon"}, Param{Key: "project", Value: "Project%%%%20%231"}}},
  196. }, unescape)
  197. checkPriorities(t, tree)
  198. checkMaxParams(t, tree)
  199. }
  200. func catchPanic(testFunc func()) (recv interface{}) {
  201. defer func() {
  202. recv = recover()
  203. }()
  204. testFunc()
  205. return
  206. }
  207. type testRoute struct {
  208. path string
  209. conflict bool
  210. }
  211. func testRoutes(t *testing.T, routes []testRoute) {
  212. tree := &node{}
  213. for _, route := range routes {
  214. recv := catchPanic(func() {
  215. tree.addRoute(route.path, nil)
  216. })
  217. if route.conflict {
  218. if recv == nil {
  219. t.Errorf("no panic for conflicting route '%s'", route.path)
  220. }
  221. } else if recv != nil {
  222. t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
  223. }
  224. }
  225. }
  226. func TestTreeWildcardConflict(t *testing.T) {
  227. routes := []testRoute{
  228. {"/cmd/:tool/:sub", false},
  229. {"/cmd/vet", true},
  230. {"/src/*filepath", false},
  231. {"/src/*filepathx", true},
  232. {"/src/", true},
  233. {"/src1/", false},
  234. {"/src1/*filepath", true},
  235. {"/src2*filepath", true},
  236. {"/search/:query", false},
  237. {"/search/invalid", true},
  238. {"/user_:name", false},
  239. {"/user_x", true},
  240. {"/user_:name", false},
  241. {"/id:id", false},
  242. {"/id/:id", true},
  243. }
  244. testRoutes(t, routes)
  245. }
  246. func TestTreeChildConflict(t *testing.T) {
  247. routes := []testRoute{
  248. {"/cmd/vet", false},
  249. {"/cmd/:tool/:sub", true},
  250. {"/src/AUTHORS", false},
  251. {"/src/*filepath", true},
  252. {"/user_x", false},
  253. {"/user_:name", true},
  254. {"/id/:id", false},
  255. {"/id:id", true},
  256. {"/:id", true},
  257. {"/*filepath", true},
  258. }
  259. testRoutes(t, routes)
  260. }
  261. func TestTreeDupliatePath(t *testing.T) {
  262. tree := &node{}
  263. routes := [...]string{
  264. "/",
  265. "/doc/",
  266. "/src/*filepath",
  267. "/search/:query",
  268. "/user_:name",
  269. }
  270. for _, route := range routes {
  271. recv := catchPanic(func() {
  272. tree.addRoute(route, fakeHandler(route))
  273. })
  274. if recv != nil {
  275. t.Fatalf("panic inserting route '%s': %v", route, recv)
  276. }
  277. // Add again
  278. recv = catchPanic(func() {
  279. tree.addRoute(route, nil)
  280. })
  281. if recv == nil {
  282. t.Fatalf("no panic while inserting duplicate route '%s", route)
  283. }
  284. }
  285. checkRequests(t, tree, testRequests{
  286. {"/", false, "/", nil},
  287. {"/doc/", false, "/doc/", nil},
  288. {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
  289. {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
  290. {"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}},
  291. })
  292. }
  293. func TestEmptyWildcardName(t *testing.T) {
  294. tree := &node{}
  295. routes := [...]string{
  296. "/user:",
  297. "/user:/",
  298. "/cmd/:/",
  299. "/src/*",
  300. }
  301. for _, route := range routes {
  302. recv := catchPanic(func() {
  303. tree.addRoute(route, nil)
  304. })
  305. if recv == nil {
  306. t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
  307. }
  308. }
  309. }
  310. func TestTreeCatchAllConflict(t *testing.T) {
  311. routes := []testRoute{
  312. {"/src/*filepath/x", true},
  313. {"/src2/", false},
  314. {"/src2/*filepath/x", true},
  315. }
  316. testRoutes(t, routes)
  317. }
  318. func TestTreeCatchAllConflictRoot(t *testing.T) {
  319. routes := []testRoute{
  320. {"/", false},
  321. {"/*filepath", true},
  322. }
  323. testRoutes(t, routes)
  324. }
  325. func TestTreeDoubleWildcard(t *testing.T) {
  326. const panicMsg = "only one wildcard per path segment is allowed"
  327. routes := [...]string{
  328. "/:foo:bar",
  329. "/:foo:bar/",
  330. "/:foo*bar",
  331. }
  332. for _, route := range routes {
  333. tree := &node{}
  334. recv := catchPanic(func() {
  335. tree.addRoute(route, nil)
  336. })
  337. if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {
  338. t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
  339. }
  340. }
  341. }
  342. /*func TestTreeDuplicateWildcard(t *testing.T) {
  343. tree := &node{}
  344. routes := [...]string{
  345. "/:id/:name/:id",
  346. }
  347. for _, route := range routes {
  348. ...
  349. }
  350. }*/
  351. func TestTreeTrailingSlashRedirect(t *testing.T) {
  352. tree := &node{}
  353. routes := [...]string{
  354. "/hi",
  355. "/b/",
  356. "/search/:query",
  357. "/cmd/:tool/",
  358. "/src/*filepath",
  359. "/x",
  360. "/x/y",
  361. "/y/",
  362. "/y/z",
  363. "/0/:id",
  364. "/0/:id/1",
  365. "/1/:id/",
  366. "/1/:id/2",
  367. "/aa",
  368. "/a/",
  369. "/admin",
  370. "/admin/:category",
  371. "/admin/:category/:page",
  372. "/doc",
  373. "/doc/go_faq.html",
  374. "/doc/go1.html",
  375. "/no/a",
  376. "/no/b",
  377. "/api/hello/:name",
  378. }
  379. for _, route := range routes {
  380. recv := catchPanic(func() {
  381. tree.addRoute(route, fakeHandler(route))
  382. })
  383. if recv != nil {
  384. t.Fatalf("panic inserting route '%s': %v", route, recv)
  385. }
  386. }
  387. tsrRoutes := [...]string{
  388. "/hi/",
  389. "/b",
  390. "/search/gopher/",
  391. "/cmd/vet",
  392. "/src",
  393. "/x/",
  394. "/y",
  395. "/0/go/",
  396. "/1/go",
  397. "/a",
  398. "/admin/",
  399. "/admin/config/",
  400. "/admin/config/permissions/",
  401. "/doc/",
  402. }
  403. for _, route := range tsrRoutes {
  404. value := tree.getValue(route, nil, false)
  405. if value.handlers != nil {
  406. t.Fatalf("non-nil handler for TSR route '%s", route)
  407. } else if !value.tsr {
  408. t.Errorf("expected TSR recommendation for route '%s'", route)
  409. }
  410. }
  411. noTsrRoutes := [...]string{
  412. "/",
  413. "/no",
  414. "/no/",
  415. "/_",
  416. "/_/",
  417. "/api/world/abc",
  418. }
  419. for _, route := range noTsrRoutes {
  420. value := tree.getValue(route, nil, false)
  421. if value.handlers != nil {
  422. t.Fatalf("non-nil handler for No-TSR route '%s", route)
  423. } else if value.tsr {
  424. t.Errorf("expected no TSR recommendation for route '%s'", route)
  425. }
  426. }
  427. }
  428. func TestTreeRootTrailingSlashRedirect(t *testing.T) {
  429. tree := &node{}
  430. recv := catchPanic(func() {
  431. tree.addRoute("/:test", fakeHandler("/:test"))
  432. })
  433. if recv != nil {
  434. t.Fatalf("panic inserting test route: %v", recv)
  435. }
  436. value := tree.getValue("/", nil, false)
  437. if value.handlers != nil {
  438. t.Fatalf("non-nil handler")
  439. } else if value.tsr {
  440. t.Errorf("expected no TSR recommendation")
  441. }
  442. }
  443. func TestTreeFindCaseInsensitivePath(t *testing.T) {
  444. tree := &node{}
  445. routes := [...]string{
  446. "/hi",
  447. "/b/",
  448. "/ABC/",
  449. "/search/:query",
  450. "/cmd/:tool/",
  451. "/src/*filepath",
  452. "/x",
  453. "/x/y",
  454. "/y/",
  455. "/y/z",
  456. "/0/:id",
  457. "/0/:id/1",
  458. "/1/:id/",
  459. "/1/:id/2",
  460. "/aa",
  461. "/a/",
  462. "/doc",
  463. "/doc/go_faq.html",
  464. "/doc/go1.html",
  465. "/doc/go/away",
  466. "/no/a",
  467. "/no/b",
  468. }
  469. for _, route := range routes {
  470. recv := catchPanic(func() {
  471. tree.addRoute(route, fakeHandler(route))
  472. })
  473. if recv != nil {
  474. t.Fatalf("panic inserting route '%s': %v", route, recv)
  475. }
  476. }
  477. // Check out == in for all registered routes
  478. // With fixTrailingSlash = true
  479. for _, route := range routes {
  480. out, found := tree.findCaseInsensitivePath(route, true)
  481. if !found {
  482. t.Errorf("Route '%s' not found!", route)
  483. } else if string(out) != route {
  484. t.Errorf("Wrong result for route '%s': %s", route, string(out))
  485. }
  486. }
  487. // With fixTrailingSlash = false
  488. for _, route := range routes {
  489. out, found := tree.findCaseInsensitivePath(route, false)
  490. if !found {
  491. t.Errorf("Route '%s' not found!", route)
  492. } else if string(out) != route {
  493. t.Errorf("Wrong result for route '%s': %s", route, string(out))
  494. }
  495. }
  496. tests := []struct {
  497. in string
  498. out string
  499. found bool
  500. slash bool
  501. }{
  502. {"/HI", "/hi", true, false},
  503. {"/HI/", "/hi", true, true},
  504. {"/B", "/b/", true, true},
  505. {"/B/", "/b/", true, false},
  506. {"/abc", "/ABC/", true, true},
  507. {"/abc/", "/ABC/", true, false},
  508. {"/aBc", "/ABC/", true, true},
  509. {"/aBc/", "/ABC/", true, false},
  510. {"/abC", "/ABC/", true, true},
  511. {"/abC/", "/ABC/", true, false},
  512. {"/SEARCH/QUERY", "/search/QUERY", true, false},
  513. {"/SEARCH/QUERY/", "/search/QUERY", true, true},
  514. {"/CMD/TOOL/", "/cmd/TOOL/", true, false},
  515. {"/CMD/TOOL", "/cmd/TOOL/", true, true},
  516. {"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
  517. {"/x/Y", "/x/y", true, false},
  518. {"/x/Y/", "/x/y", true, true},
  519. {"/X/y", "/x/y", true, false},
  520. {"/X/y/", "/x/y", true, true},
  521. {"/X/Y", "/x/y", true, false},
  522. {"/X/Y/", "/x/y", true, true},
  523. {"/Y/", "/y/", true, false},
  524. {"/Y", "/y/", true, true},
  525. {"/Y/z", "/y/z", true, false},
  526. {"/Y/z/", "/y/z", true, true},
  527. {"/Y/Z", "/y/z", true, false},
  528. {"/Y/Z/", "/y/z", true, true},
  529. {"/y/Z", "/y/z", true, false},
  530. {"/y/Z/", "/y/z", true, true},
  531. {"/Aa", "/aa", true, false},
  532. {"/Aa/", "/aa", true, true},
  533. {"/AA", "/aa", true, false},
  534. {"/AA/", "/aa", true, true},
  535. {"/aA", "/aa", true, false},
  536. {"/aA/", "/aa", true, true},
  537. {"/A/", "/a/", true, false},
  538. {"/A", "/a/", true, true},
  539. {"/DOC", "/doc", true, false},
  540. {"/DOC/", "/doc", true, true},
  541. {"/NO", "", false, true},
  542. {"/DOC/GO", "", false, true},
  543. }
  544. // With fixTrailingSlash = true
  545. for _, test := range tests {
  546. out, found := tree.findCaseInsensitivePath(test.in, true)
  547. if found != test.found || (found && (string(out) != test.out)) {
  548. t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
  549. test.in, string(out), found, test.out, test.found)
  550. return
  551. }
  552. }
  553. // With fixTrailingSlash = false
  554. for _, test := range tests {
  555. out, found := tree.findCaseInsensitivePath(test.in, false)
  556. if test.slash {
  557. if found { // test needs a trailingSlash fix. It must not be found!
  558. t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
  559. }
  560. } else {
  561. if found != test.found || (found && (string(out) != test.out)) {
  562. t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
  563. test.in, string(out), found, test.out, test.found)
  564. return
  565. }
  566. }
  567. }
  568. }
  569. func TestTreeInvalidNodeType(t *testing.T) {
  570. const panicMsg = "invalid node type"
  571. tree := &node{}
  572. tree.addRoute("/", fakeHandler("/"))
  573. tree.addRoute("/:page", fakeHandler("/:page"))
  574. // set invalid node type
  575. tree.children[0].nType = 42
  576. // normal lookup
  577. recv := catchPanic(func() {
  578. tree.getValue("/test", nil, false)
  579. })
  580. if rs, ok := recv.(string); !ok || rs != panicMsg {
  581. t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
  582. }
  583. // case-insensitive lookup
  584. recv = catchPanic(func() {
  585. tree.findCaseInsensitivePath("/test", true)
  586. })
  587. if rs, ok := recv.(string); !ok || rs != panicMsg {
  588. t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
  589. }
  590. }
  591. func TestTreeWildcardConflictEx(t *testing.T) {
  592. conflicts := [...]struct {
  593. route string
  594. segPath string
  595. existPath string
  596. existSegPath string
  597. }{
  598. {"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
  599. {"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
  600. {"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
  601. {"/conxxx", "xxx", `/con:tact`, `:tact`},
  602. {"/conooo/xxx", "ooo", `/con:tact`, `:tact`},
  603. }
  604. for _, conflict := range conflicts {
  605. // I have to re-create a 'tree', because the 'tree' will be
  606. // in an inconsistent state when the loop recovers from the
  607. // panic which threw by 'addRoute' function.
  608. tree := &node{}
  609. routes := [...]string{
  610. "/con:tact",
  611. "/who/are/*you",
  612. "/who/foo/hello",
  613. }
  614. for _, route := range routes {
  615. tree.addRoute(route, fakeHandler(route))
  616. }
  617. recv := catchPanic(func() {
  618. tree.addRoute(conflict.route, fakeHandler(conflict.route))
  619. })
  620. if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'",
  621. conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) {
  622. t.Fatalf("invalid wildcard conflict error (%v)", recv)
  623. }
  624. }
  625. }