"Fossies" - the Fresh Open Source Software Archive

Member "gin-1.7.7/tree_test.go" (24 Nov 2021, 30672 Bytes) of package /linux/www/gin-1.7.7.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Go source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "tree_test.go": 1.7.6_vs_1.7.7.

    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 gin
    6 
    7 import (
    8     "fmt"
    9     "reflect"
   10     "regexp"
   11     "strings"
   12     "testing"
   13 )
   14 
   15 // Used as a workaround since we can't compare functions or their addresses
   16 var fakeHandlerValue string
   17 
   18 func fakeHandler(val string) HandlersChain {
   19     return HandlersChain{func(c *Context) {
   20         fakeHandlerValue = val
   21     }}
   22 }
   23 
   24 type testRequests []struct {
   25     path       string
   26     nilHandler bool
   27     route      string
   28     ps         Params
   29 }
   30 
   31 func getParams() *Params {
   32     ps := make(Params, 0, 20)
   33     return &ps
   34 }
   35 
   36 func getSkippedNodes() *[]skippedNode {
   37     ps := make([]skippedNode, 0, 20)
   38     return &ps
   39 }
   40 
   41 func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
   42     unescape := false
   43     if len(unescapes) >= 1 {
   44         unescape = unescapes[0]
   45     }
   46 
   47     for _, request := range requests {
   48         value := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape)
   49 
   50         if value.handlers == nil {
   51             if !request.nilHandler {
   52                 t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
   53             }
   54         } else if request.nilHandler {
   55             t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
   56         } else {
   57             value.handlers[0](nil)
   58             if fakeHandlerValue != request.route {
   59                 t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
   60             }
   61         }
   62 
   63         if value.params != nil {
   64             if !reflect.DeepEqual(*value.params, request.ps) {
   65                 t.Errorf("Params mismatch for route '%s'", request.path)
   66             }
   67         }
   68 
   69     }
   70 }
   71 
   72 func checkPriorities(t *testing.T, n *node) uint32 {
   73     var prio uint32
   74     for i := range n.children {
   75         prio += checkPriorities(t, n.children[i])
   76     }
   77 
   78     if n.handlers != nil {
   79         prio++
   80     }
   81 
   82     if n.priority != prio {
   83         t.Errorf(
   84             "priority mismatch for node '%s': is %d, should be %d",
   85             n.path, n.priority, prio,
   86         )
   87     }
   88 
   89     return prio
   90 }
   91 
   92 func TestCountParams(t *testing.T) {
   93     if countParams("/path/:param1/static/*catch-all") != 2 {
   94         t.Fail()
   95     }
   96     if countParams(strings.Repeat("/:param", 256)) != 256 {
   97         t.Fail()
   98     }
   99 }
  100 
  101 func TestTreeAddAndGet(t *testing.T) {
  102     tree := &node{}
  103 
  104     routes := [...]string{
  105         "/hi",
  106         "/contact",
  107         "/co",
  108         "/c",
  109         "/a",
  110         "/ab",
  111         "/doc/",
  112         "/doc/go_faq.html",
  113         "/doc/go1.html",
  114         "/α",
  115         "/β",
  116     }
  117     for _, route := range routes {
  118         tree.addRoute(route, fakeHandler(route))
  119     }
  120 
  121     checkRequests(t, tree, testRequests{
  122         {"/a", false, "/a", nil},
  123         {"/", true, "", nil},
  124         {"/hi", false, "/hi", nil},
  125         {"/contact", false, "/contact", nil},
  126         {"/co", false, "/co", nil},
  127         {"/con", true, "", nil},  // key mismatch
  128         {"/cona", true, "", nil}, // key mismatch
  129         {"/no", true, "", nil},   // no matching child
  130         {"/ab", false, "/ab", nil},
  131         {"/α", false, "/α", nil},
  132         {"/β", false, "/β", nil},
  133     })
  134 
  135     checkPriorities(t, tree)
  136 }
  137 
  138 func TestTreeWildcard(t *testing.T) {
  139     tree := &node{}
  140 
  141     routes := [...]string{
  142         "/",
  143         "/cmd/:tool/",
  144         "/cmd/:tool/:sub",
  145         "/cmd/whoami",
  146         "/cmd/whoami/root",
  147         "/cmd/whoami/root/",
  148         "/src/*filepath",
  149         "/search/",
  150         "/search/:query",
  151         "/search/gin-gonic",
  152         "/search/google",
  153         "/user_:name",
  154         "/user_:name/about",
  155         "/files/:dir/*filepath",
  156         "/doc/",
  157         "/doc/go_faq.html",
  158         "/doc/go1.html",
  159         "/info/:user/public",
  160         "/info/:user/project/:project",
  161         "/info/:user/project/golang",
  162         "/aa/*xx",
  163         "/ab/*xx",
  164         "/:cc",
  165         "/c1/:dd/e",
  166         "/c1/:dd/e1",
  167         "/:cc/cc",
  168         "/:cc/:dd/ee",
  169         "/:cc/:dd/:ee/ff",
  170         "/:cc/:dd/:ee/:ff/gg",
  171         "/:cc/:dd/:ee/:ff/:gg/hh",
  172         "/get/test/abc/",
  173         "/get/:param/abc/",
  174         "/something/:paramname/thirdthing",
  175         "/something/secondthing/test",
  176         "/get/abc",
  177         "/get/:param",
  178         "/get/abc/123abc",
  179         "/get/abc/:param",
  180         "/get/abc/123abc/xxx8",
  181         "/get/abc/123abc/:param",
  182         "/get/abc/123abc/xxx8/1234",
  183         "/get/abc/123abc/xxx8/:param",
  184         "/get/abc/123abc/xxx8/1234/ffas",
  185         "/get/abc/123abc/xxx8/1234/:param",
  186         "/get/abc/123abc/xxx8/1234/kkdd/12c",
  187         "/get/abc/123abc/xxx8/1234/kkdd/:param",
  188         "/get/abc/:param/test",
  189         "/get/abc/123abd/:param",
  190         "/get/abc/123abddd/:param",
  191         "/get/abc/123/:param",
  192         "/get/abc/123abg/:param",
  193         "/get/abc/123abf/:param",
  194         "/get/abc/123abfff/:param",
  195     }
  196     for _, route := range routes {
  197         tree.addRoute(route, fakeHandler(route))
  198     }
  199 
  200     checkRequests(t, tree, testRequests{
  201         {"/", false, "/", nil},
  202         {"/cmd/test", true, "/cmd/:tool/", Params{Param{"tool", "test"}}},
  203         {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
  204         {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}},
  205         {"/cmd/who", true, "/cmd/:tool/", Params{Param{"tool", "who"}}},
  206         {"/cmd/who/", false, "/cmd/:tool/", Params{Param{"tool", "who"}}},
  207         {"/cmd/whoami", false, "/cmd/whoami", nil},
  208         {"/cmd/whoami/", true, "/cmd/whoami", nil},
  209         {"/cmd/whoami/r", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
  210         {"/cmd/whoami/r/", true, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
  211         {"/cmd/whoami/root", false, "/cmd/whoami/root", nil},
  212         {"/cmd/whoami/root/", false, "/cmd/whoami/root/", nil},
  213         {"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}},
  214         {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
  215         {"/search/", false, "/search/", nil},
  216         {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
  217         {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
  218         {"/search/gin", false, "/search/:query", Params{Param{"query", "gin"}}},
  219         {"/search/gin-gonic", false, "/search/gin-gonic", nil},
  220         {"/search/google", false, "/search/google", nil},
  221         {"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}},
  222         {"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}},
  223         {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}},
  224         {"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}},
  225         {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
  226         {"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}},
  227         {"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}},
  228         {"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}},
  229         {"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}},
  230         // * Error with argument being intercepted
  231         // new PR handle (/all /all/cc /a/cc)
  232         // fix PR: https://github.com/gin-gonic/gin/pull/2796
  233         {"/all", false, "/:cc", Params{Param{Key: "cc", Value: "all"}}},
  234         {"/d", false, "/:cc", Params{Param{Key: "cc", Value: "d"}}},
  235         {"/ad", false, "/:cc", Params{Param{Key: "cc", Value: "ad"}}},
  236         {"/dd", false, "/:cc", Params{Param{Key: "cc", Value: "dd"}}},
  237         {"/dddaa", false, "/:cc", Params{Param{Key: "cc", Value: "dddaa"}}},
  238         {"/aa", false, "/:cc", Params{Param{Key: "cc", Value: "aa"}}},
  239         {"/aaa", false, "/:cc", Params{Param{Key: "cc", Value: "aaa"}}},
  240         {"/aaa/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "aaa"}}},
  241         {"/ab", false, "/:cc", Params{Param{Key: "cc", Value: "ab"}}},
  242         {"/abb", false, "/:cc", Params{Param{Key: "cc", Value: "abb"}}},
  243         {"/abb/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "abb"}}},
  244         {"/allxxxx", false, "/:cc", Params{Param{Key: "cc", Value: "allxxxx"}}},
  245         {"/alldd", false, "/:cc", Params{Param{Key: "cc", Value: "alldd"}}},
  246         {"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "all"}}},
  247         {"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "a"}}},
  248         {"/c1/d/e", false, "/c1/:dd/e", Params{Param{Key: "dd", Value: "d"}}},
  249         {"/c1/d/e1", false, "/c1/:dd/e1", Params{Param{Key: "dd", Value: "d"}}},
  250         {"/c1/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c1"}, Param{Key: "dd", Value: "d"}}},
  251         {"/cc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "cc"}}},
  252         {"/ccc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ccc"}}},
  253         {"/deedwjfs/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "deedwjfs"}}},
  254         {"/acllcc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "acllcc"}}},
  255         {"/get/test/abc/", false, "/get/test/abc/", nil},
  256         {"/get/te/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "te"}}},
  257         {"/get/testaa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "testaa"}}},
  258         {"/get/xx/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "xx"}}},
  259         {"/get/tt/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "tt"}}},
  260         {"/get/a/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "a"}}},
  261         {"/get/t/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "t"}}},
  262         {"/get/aa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "aa"}}},
  263         {"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}},
  264         {"/something/secondthing/test", false, "/something/secondthing/test", nil},
  265         {"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}},
  266         {"/something/secondthingaaaa/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "secondthingaaaa"}}},
  267         {"/something/se/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "se"}}},
  268         {"/something/s/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "s"}}},
  269         {"/c/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}}},
  270         {"/c/d/e/ff", false, "/:cc/:dd/:ee/ff", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}}},
  271         {"/c/d/e/f/gg", false, "/:cc/:dd/:ee/:ff/gg", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}}},
  272         {"/c/d/e/f/g/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}, Param{Key: "gg", Value: "g"}}},
  273         {"/cc/dd/ee/ff/gg/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "cc"}, Param{Key: "dd", Value: "dd"}, Param{Key: "ee", Value: "ee"}, Param{Key: "ff", Value: "ff"}, Param{Key: "gg", Value: "gg"}}},
  274         {"/get/abc", false, "/get/abc", nil},
  275         {"/get/a", false, "/get/:param", Params{Param{Key: "param", Value: "a"}}},
  276         {"/get/abz", false, "/get/:param", Params{Param{Key: "param", Value: "abz"}}},
  277         {"/get/12a", false, "/get/:param", Params{Param{Key: "param", Value: "12a"}}},
  278         {"/get/abcd", false, "/get/:param", Params{Param{Key: "param", Value: "abcd"}}},
  279         {"/get/abc/123abc", false, "/get/abc/123abc", nil},
  280         {"/get/abc/12", false, "/get/abc/:param", Params{Param{Key: "param", Value: "12"}}},
  281         {"/get/abc/123ab", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123ab"}}},
  282         {"/get/abc/xyz", false, "/get/abc/:param", Params{Param{Key: "param", Value: "xyz"}}},
  283         {"/get/abc/123abcddxx", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123abcddxx"}}},
  284         {"/get/abc/123abc/xxx8", false, "/get/abc/123abc/xxx8", nil},
  285         {"/get/abc/123abc/x", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "x"}}},
  286         {"/get/abc/123abc/xxx", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx"}}},
  287         {"/get/abc/123abc/abc", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "abc"}}},
  288         {"/get/abc/123abc/xxx8xxas", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx8xxas"}}},
  289         {"/get/abc/123abc/xxx8/1234", false, "/get/abc/123abc/xxx8/1234", nil},
  290         {"/get/abc/123abc/xxx8/1", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1"}}},
  291         {"/get/abc/123abc/xxx8/123", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "123"}}},
  292         {"/get/abc/123abc/xxx8/78k", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "78k"}}},
  293         {"/get/abc/123abc/xxx8/1234xxxd", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1234xxxd"}}},
  294         {"/get/abc/123abc/xxx8/1234/ffas", false, "/get/abc/123abc/xxx8/1234/ffas", nil},
  295         {"/get/abc/123abc/xxx8/1234/f", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "f"}}},
  296         {"/get/abc/123abc/xxx8/1234/ffa", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffa"}}},
  297         {"/get/abc/123abc/xxx8/1234/kka", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "kka"}}},
  298         {"/get/abc/123abc/xxx8/1234/ffas321", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffas321"}}},
  299         {"/get/abc/123abc/xxx8/1234/kkdd/12c", false, "/get/abc/123abc/xxx8/1234/kkdd/12c", nil},
  300         {"/get/abc/123abc/xxx8/1234/kkdd/1", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "1"}}},
  301         {"/get/abc/123abc/xxx8/1234/kkdd/12", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12"}}},
  302         {"/get/abc/123abc/xxx8/1234/kkdd/12b", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12b"}}},
  303         {"/get/abc/123abc/xxx8/1234/kkdd/34", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "34"}}},
  304         {"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12c2e3"}}},
  305         {"/get/abc/12/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "12"}}},
  306         {"/get/abc/123abdd/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdd"}}},
  307         {"/get/abc/123abdddf/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdddf"}}},
  308         {"/get/abc/123ab/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123ab"}}},
  309         {"/get/abc/123abgg/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abgg"}}},
  310         {"/get/abc/123abff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abff"}}},
  311         {"/get/abc/123abffff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abffff"}}},
  312         {"/get/abc/123abd/test", false, "/get/abc/123abd/:param", Params{Param{Key: "param", Value: "test"}}},
  313         {"/get/abc/123abddd/test", false, "/get/abc/123abddd/:param", Params{Param{Key: "param", Value: "test"}}},
  314         {"/get/abc/123/test22", false, "/get/abc/123/:param", Params{Param{Key: "param", Value: "test22"}}},
  315         {"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}},
  316         {"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}},
  317         {"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}},
  318     })
  319 
  320     checkPriorities(t, tree)
  321 }
  322 
  323 func TestUnescapeParameters(t *testing.T) {
  324     tree := &node{}
  325 
  326     routes := [...]string{
  327         "/",
  328         "/cmd/:tool/:sub",
  329         "/cmd/:tool/",
  330         "/src/*filepath",
  331         "/search/:query",
  332         "/files/:dir/*filepath",
  333         "/info/:user/project/:project",
  334         "/info/:user",
  335     }
  336     for _, route := range routes {
  337         tree.addRoute(route, fakeHandler(route))
  338     }
  339 
  340     unescape := true
  341     checkRequests(t, tree, testRequests{
  342         {"/", false, "/", nil},
  343         {"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}},
  344         {"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}},
  345         {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
  346         {"/src/some/file+test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file test.png"}}},
  347         {"/src/some/file++++%%%%test.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file++++%%%%test.png"}}},
  348         {"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file/test.png"}}},
  349         {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng in ünìcodé"}}},
  350         {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
  351         {"/info/slash%2Fgordon", false, "/info/:user", Params{Param{Key: "user", Value: "slash/gordon"}}},
  352         {"/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"}}},
  353         {"/info/slash%%%%", false, "/info/:user", Params{Param{Key: "user", Value: "slash%%%%"}}},
  354         {"/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"}}},
  355     }, unescape)
  356 
  357     checkPriorities(t, tree)
  358 }
  359 
  360 func catchPanic(testFunc func()) (recv interface{}) {
  361     defer func() {
  362         recv = recover()
  363     }()
  364 
  365     testFunc()
  366     return
  367 }
  368 
  369 type testRoute struct {
  370     path     string
  371     conflict bool
  372 }
  373 
  374 func testRoutes(t *testing.T, routes []testRoute) {
  375     tree := &node{}
  376 
  377     for _, route := range routes {
  378         recv := catchPanic(func() {
  379             tree.addRoute(route.path, nil)
  380         })
  381 
  382         if route.conflict {
  383             if recv == nil {
  384                 t.Errorf("no panic for conflicting route '%s'", route.path)
  385             }
  386         } else if recv != nil {
  387             t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
  388         }
  389     }
  390 }
  391 
  392 func TestTreeWildcardConflict(t *testing.T) {
  393     routes := []testRoute{
  394         {"/cmd/:tool/:sub", false},
  395         {"/cmd/vet", false},
  396         {"/foo/bar", false},
  397         {"/foo/:name", false},
  398         {"/foo/:names", true},
  399         {"/cmd/*path", true},
  400         {"/cmd/:badvar", true},
  401         {"/cmd/:tool/names", false},
  402         {"/cmd/:tool/:badsub/details", true},
  403         {"/src/*filepath", false},
  404         {"/src/:file", true},
  405         {"/src/static.json", true},
  406         {"/src/*filepathx", true},
  407         {"/src/", true},
  408         {"/src/foo/bar", true},
  409         {"/src1/", false},
  410         {"/src1/*filepath", true},
  411         {"/src2*filepath", true},
  412         {"/src2/*filepath", false},
  413         {"/search/:query", false},
  414         {"/search/valid", false},
  415         {"/user_:name", false},
  416         {"/user_x", false},
  417         {"/user_:name", false},
  418         {"/id:id", false},
  419         {"/id/:id", false},
  420     }
  421     testRoutes(t, routes)
  422 }
  423 
  424 func TestCatchAllAfterSlash(t *testing.T) {
  425     routes := []testRoute{
  426         {"/non-leading-*catchall", true},
  427     }
  428     testRoutes(t, routes)
  429 }
  430 
  431 func TestTreeChildConflict(t *testing.T) {
  432     routes := []testRoute{
  433         {"/cmd/vet", false},
  434         {"/cmd/:tool", false},
  435         {"/cmd/:tool/:sub", false},
  436         {"/cmd/:tool/misc", false},
  437         {"/cmd/:tool/:othersub", true},
  438         {"/src/AUTHORS", false},
  439         {"/src/*filepath", true},
  440         {"/user_x", false},
  441         {"/user_:name", false},
  442         {"/id/:id", false},
  443         {"/id:id", false},
  444         {"/:id", false},
  445         {"/*filepath", true},
  446     }
  447     testRoutes(t, routes)
  448 }
  449 
  450 func TestTreeDupliatePath(t *testing.T) {
  451     tree := &node{}
  452 
  453     routes := [...]string{
  454         "/",
  455         "/doc/",
  456         "/src/*filepath",
  457         "/search/:query",
  458         "/user_:name",
  459     }
  460     for _, route := range routes {
  461         recv := catchPanic(func() {
  462             tree.addRoute(route, fakeHandler(route))
  463         })
  464         if recv != nil {
  465             t.Fatalf("panic inserting route '%s': %v", route, recv)
  466         }
  467 
  468         // Add again
  469         recv = catchPanic(func() {
  470             tree.addRoute(route, nil)
  471         })
  472         if recv == nil {
  473             t.Fatalf("no panic while inserting duplicate route '%s", route)
  474         }
  475     }
  476 
  477     //printChildren(tree, "")
  478 
  479     checkRequests(t, tree, testRequests{
  480         {"/", false, "/", nil},
  481         {"/doc/", false, "/doc/", nil},
  482         {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
  483         {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
  484         {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
  485     })
  486 }
  487 
  488 func TestEmptyWildcardName(t *testing.T) {
  489     tree := &node{}
  490 
  491     routes := [...]string{
  492         "/user:",
  493         "/user:/",
  494         "/cmd/:/",
  495         "/src/*",
  496     }
  497     for _, route := range routes {
  498         recv := catchPanic(func() {
  499             tree.addRoute(route, nil)
  500         })
  501         if recv == nil {
  502             t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
  503         }
  504     }
  505 }
  506 
  507 func TestTreeCatchAllConflict(t *testing.T) {
  508     routes := []testRoute{
  509         {"/src/*filepath/x", true},
  510         {"/src2/", false},
  511         {"/src2/*filepath/x", true},
  512         {"/src3/*filepath", false},
  513         {"/src3/*filepath/x", true},
  514     }
  515     testRoutes(t, routes)
  516 }
  517 
  518 func TestTreeCatchAllConflictRoot(t *testing.T) {
  519     routes := []testRoute{
  520         {"/", false},
  521         {"/*filepath", true},
  522     }
  523     testRoutes(t, routes)
  524 }
  525 
  526 func TestTreeCatchMaxParams(t *testing.T) {
  527     tree := &node{}
  528     var route = "/cmd/*filepath"
  529     tree.addRoute(route, fakeHandler(route))
  530 }
  531 
  532 func TestTreeDoubleWildcard(t *testing.T) {
  533     const panicMsg = "only one wildcard per path segment is allowed"
  534 
  535     routes := [...]string{
  536         "/:foo:bar",
  537         "/:foo:bar/",
  538         "/:foo*bar",
  539     }
  540 
  541     for _, route := range routes {
  542         tree := &node{}
  543         recv := catchPanic(func() {
  544             tree.addRoute(route, nil)
  545         })
  546 
  547         if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {
  548             t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
  549         }
  550     }
  551 }
  552 
  553 /*func TestTreeDuplicateWildcard(t *testing.T) {
  554     tree := &node{}
  555     routes := [...]string{
  556         "/:id/:name/:id",
  557     }
  558     for _, route := range routes {
  559         ...
  560     }
  561 }*/
  562 
  563 func TestTreeTrailingSlashRedirect(t *testing.T) {
  564     tree := &node{}
  565 
  566     routes := [...]string{
  567         "/hi",
  568         "/b/",
  569         "/search/:query",
  570         "/cmd/:tool/",
  571         "/src/*filepath",
  572         "/x",
  573         "/x/y",
  574         "/y/",
  575         "/y/z",
  576         "/0/:id",
  577         "/0/:id/1",
  578         "/1/:id/",
  579         "/1/:id/2",
  580         "/aa",
  581         "/a/",
  582         "/admin",
  583         "/admin/:category",
  584         "/admin/:category/:page",
  585         "/doc",
  586         "/doc/go_faq.html",
  587         "/doc/go1.html",
  588         "/no/a",
  589         "/no/b",
  590         "/api/:page/:name",
  591         "/api/hello/:name/bar/",
  592         "/api/bar/:name",
  593         "/api/baz/foo",
  594         "/api/baz/foo/bar",
  595         "/blog/:p",
  596         "/posts/:b/:c",
  597         "/posts/b/:c/d/",
  598     }
  599     for _, route := range routes {
  600         recv := catchPanic(func() {
  601             tree.addRoute(route, fakeHandler(route))
  602         })
  603         if recv != nil {
  604             t.Fatalf("panic inserting route '%s': %v", route, recv)
  605         }
  606     }
  607 
  608     tsrRoutes := [...]string{
  609         "/hi/",
  610         "/b",
  611         "/search/gopher/",
  612         "/cmd/vet",
  613         "/src",
  614         "/x/",
  615         "/y",
  616         "/0/go/",
  617         "/1/go",
  618         "/a",
  619         "/admin/",
  620         "/admin/config/",
  621         "/admin/config/permissions/",
  622         "/doc/",
  623         "/admin/static/",
  624         "/admin/cfg/",
  625         "/admin/cfg/users/",
  626         "/api/hello/x/bar",
  627         "/api/baz/foo/",
  628         "/api/baz/bax/",
  629         "/api/bar/huh/",
  630         "/api/baz/foo/bar/",
  631         "/api/world/abc/",
  632         "/blog/pp/",
  633         "/posts/b/c/d",
  634     }
  635 
  636     for _, route := range tsrRoutes {
  637         value := tree.getValue(route, nil, getSkippedNodes(), false)
  638         if value.handlers != nil {
  639             t.Fatalf("non-nil handler for TSR route '%s", route)
  640         } else if !value.tsr {
  641             t.Errorf("expected TSR recommendation for route '%s'", route)
  642         }
  643     }
  644 
  645     noTsrRoutes := [...]string{
  646         "/",
  647         "/no",
  648         "/no/",
  649         "/_",
  650         "/_/",
  651         "/api",
  652         "/api/",
  653         "/api/hello/x/foo",
  654         "/api/baz/foo/bad",
  655         "/foo/p/p",
  656     }
  657     for _, route := range noTsrRoutes {
  658         value := tree.getValue(route, nil, getSkippedNodes(), false)
  659         if value.handlers != nil {
  660             t.Fatalf("non-nil handler for No-TSR route '%s", route)
  661         } else if value.tsr {
  662             t.Errorf("expected no TSR recommendation for route '%s'", route)
  663         }
  664     }
  665 }
  666 
  667 func TestTreeRootTrailingSlashRedirect(t *testing.T) {
  668     tree := &node{}
  669 
  670     recv := catchPanic(func() {
  671         tree.addRoute("/:test", fakeHandler("/:test"))
  672     })
  673     if recv != nil {
  674         t.Fatalf("panic inserting test route: %v", recv)
  675     }
  676 
  677     value := tree.getValue("/", nil, getSkippedNodes(), false)
  678     if value.handlers != nil {
  679         t.Fatalf("non-nil handler")
  680     } else if value.tsr {
  681         t.Errorf("expected no TSR recommendation")
  682     }
  683 }
  684 
  685 func TestTreeFindCaseInsensitivePath(t *testing.T) {
  686     tree := &node{}
  687 
  688     longPath := "/l" + strings.Repeat("o", 128) + "ng"
  689     lOngPath := "/l" + strings.Repeat("O", 128) + "ng/"
  690 
  691     routes := [...]string{
  692         "/hi",
  693         "/b/",
  694         "/ABC/",
  695         "/search/:query",
  696         "/cmd/:tool/",
  697         "/src/*filepath",
  698         "/x",
  699         "/x/y",
  700         "/y/",
  701         "/y/z",
  702         "/0/:id",
  703         "/0/:id/1",
  704         "/1/:id/",
  705         "/1/:id/2",
  706         "/aa",
  707         "/a/",
  708         "/doc",
  709         "/doc/go_faq.html",
  710         "/doc/go1.html",
  711         "/doc/go/away",
  712         "/no/a",
  713         "/no/b",
  714         "/Π",
  715         "/u/apfêl/",
  716         "/u/äpfêl/",
  717         "/u/öpfêl",
  718         "/v/Äpfêl/",
  719         "/v/Öpfêl",
  720         "/w/♬",  // 3 byte
  721         "/w/♭/", // 3 byte, last byte differs
  722         "/w/𠜎",  // 4 byte
  723         "/w/𠜏/", // 4 byte
  724         longPath,
  725     }
  726 
  727     for _, route := range routes {
  728         recv := catchPanic(func() {
  729             tree.addRoute(route, fakeHandler(route))
  730         })
  731         if recv != nil {
  732             t.Fatalf("panic inserting route '%s': %v", route, recv)
  733         }
  734     }
  735 
  736     // Check out == in for all registered routes
  737     // With fixTrailingSlash = true
  738     for _, route := range routes {
  739         out, found := tree.findCaseInsensitivePath(route, true)
  740         if !found {
  741             t.Errorf("Route '%s' not found!", route)
  742         } else if string(out) != route {
  743             t.Errorf("Wrong result for route '%s': %s", route, string(out))
  744         }
  745     }
  746     // With fixTrailingSlash = false
  747     for _, route := range routes {
  748         out, found := tree.findCaseInsensitivePath(route, false)
  749         if !found {
  750             t.Errorf("Route '%s' not found!", route)
  751         } else if string(out) != route {
  752             t.Errorf("Wrong result for route '%s': %s", route, string(out))
  753         }
  754     }
  755 
  756     tests := []struct {
  757         in    string
  758         out   string
  759         found bool
  760         slash bool
  761     }{
  762         {"/HI", "/hi", true, false},
  763         {"/HI/", "/hi", true, true},
  764         {"/B", "/b/", true, true},
  765         {"/B/", "/b/", true, false},
  766         {"/abc", "/ABC/", true, true},
  767         {"/abc/", "/ABC/", true, false},
  768         {"/aBc", "/ABC/", true, true},
  769         {"/aBc/", "/ABC/", true, false},
  770         {"/abC", "/ABC/", true, true},
  771         {"/abC/", "/ABC/", true, false},
  772         {"/SEARCH/QUERY", "/search/QUERY", true, false},
  773         {"/SEARCH/QUERY/", "/search/QUERY", true, true},
  774         {"/CMD/TOOL/", "/cmd/TOOL/", true, false},
  775         {"/CMD/TOOL", "/cmd/TOOL/", true, true},
  776         {"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
  777         {"/x/Y", "/x/y", true, false},
  778         {"/x/Y/", "/x/y", true, true},
  779         {"/X/y", "/x/y", true, false},
  780         {"/X/y/", "/x/y", true, true},
  781         {"/X/Y", "/x/y", true, false},
  782         {"/X/Y/", "/x/y", true, true},
  783         {"/Y/", "/y/", true, false},
  784         {"/Y", "/y/", true, true},
  785         {"/Y/z", "/y/z", true, false},
  786         {"/Y/z/", "/y/z", true, true},
  787         {"/Y/Z", "/y/z", true, false},
  788         {"/Y/Z/", "/y/z", true, true},
  789         {"/y/Z", "/y/z", true, false},
  790         {"/y/Z/", "/y/z", true, true},
  791         {"/Aa", "/aa", true, false},
  792         {"/Aa/", "/aa", true, true},
  793         {"/AA", "/aa", true, false},
  794         {"/AA/", "/aa", true, true},
  795         {"/aA", "/aa", true, false},
  796         {"/aA/", "/aa", true, true},
  797         {"/A/", "/a/", true, false},
  798         {"/A", "/a/", true, true},
  799         {"/DOC", "/doc", true, false},
  800         {"/DOC/", "/doc", true, true},
  801         {"/NO", "", false, true},
  802         {"/DOC/GO", "", false, true},
  803         {"/π", "/Π", true, false},
  804         {"/π/", "/Π", true, true},
  805         {"/u/ÄPFÊL/", "/u/äpfêl/", true, false},
  806         {"/u/ÄPFÊL", "/u/äpfêl/", true, true},
  807         {"/u/ÖPFÊL/", "/u/öpfêl", true, true},
  808         {"/u/ÖPFÊL", "/u/öpfêl", true, false},
  809         {"/v/äpfêL/", "/v/Äpfêl/", true, false},
  810         {"/v/äpfêL", "/v/Äpfêl/", true, true},
  811         {"/v/öpfêL/", "/v/Öpfêl", true, true},
  812         {"/v/öpfêL", "/v/Öpfêl", true, false},
  813         {"/w/♬/", "/w/♬", true, true},
  814         {"/w/♭", "/w/♭/", true, true},
  815         {"/w/𠜎/", "/w/𠜎", true, true},
  816         {"/w/𠜏", "/w/𠜏/", true, true},
  817         {lOngPath, longPath, true, true},
  818     }
  819     // With fixTrailingSlash = true
  820     for _, test := range tests {
  821         out, found := tree.findCaseInsensitivePath(test.in, true)
  822         if found != test.found || (found && (string(out) != test.out)) {
  823             t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
  824                 test.in, string(out), found, test.out, test.found)
  825             return
  826         }
  827     }
  828     // With fixTrailingSlash = false
  829     for _, test := range tests {
  830         out, found := tree.findCaseInsensitivePath(test.in, false)
  831         if test.slash {
  832             if found { // test needs a trailingSlash fix. It must not be found!
  833                 t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
  834             }
  835         } else {
  836             if found != test.found || (found && (string(out) != test.out)) {
  837                 t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
  838                     test.in, string(out), found, test.out, test.found)
  839                 return
  840             }
  841         }
  842     }
  843 }
  844 
  845 func TestTreeInvalidNodeType(t *testing.T) {
  846     const panicMsg = "invalid node type"
  847 
  848     tree := &node{}
  849     tree.addRoute("/", fakeHandler("/"))
  850     tree.addRoute("/:page", fakeHandler("/:page"))
  851 
  852     // set invalid node type
  853     tree.children[0].nType = 42
  854 
  855     // normal lookup
  856     recv := catchPanic(func() {
  857         tree.getValue("/test", nil, getSkippedNodes(), false)
  858     })
  859     if rs, ok := recv.(string); !ok || rs != panicMsg {
  860         t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
  861     }
  862 
  863     // case-insensitive lookup
  864     recv = catchPanic(func() {
  865         tree.findCaseInsensitivePath("/test", true)
  866     })
  867     if rs, ok := recv.(string); !ok || rs != panicMsg {
  868         t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
  869     }
  870 }
  871 
  872 func TestTreeInvalidParamsType(t *testing.T) {
  873     tree := &node{}
  874     tree.wildChild = true
  875     tree.children = append(tree.children, &node{})
  876     tree.children[0].nType = 2
  877 
  878     // set invalid Params type
  879     params := make(Params, 0, 0)
  880 
  881     // try to trigger slice bounds out of range with capacity 0
  882     tree.getValue("/test", &params, getSkippedNodes(), false)
  883 }
  884 
  885 func TestTreeWildcardConflictEx(t *testing.T) {
  886     conflicts := [...]struct {
  887         route        string
  888         segPath      string
  889         existPath    string
  890         existSegPath string
  891     }{
  892         {"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
  893         {"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
  894         {"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
  895         {"/con:nection", ":nection", `/con:tact`, `:tact`},
  896     }
  897 
  898     for _, conflict := range conflicts {
  899         // I have to re-create a 'tree', because the 'tree' will be
  900         // in an inconsistent state when the loop recovers from the
  901         // panic which threw by 'addRoute' function.
  902         tree := &node{}
  903         routes := [...]string{
  904             "/con:tact",
  905             "/who/are/*you",
  906             "/who/foo/hello",
  907         }
  908 
  909         for _, route := range routes {
  910             tree.addRoute(route, fakeHandler(route))
  911         }
  912 
  913         recv := catchPanic(func() {
  914             tree.addRoute(conflict.route, fakeHandler(conflict.route))
  915         })
  916 
  917         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)) {
  918             t.Fatalf("invalid wildcard conflict error (%v)", recv)
  919         }
  920     }
  921 }