github.com/cloudwego/hertz@v0.9.3/pkg/route/tree_test.go (about)

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