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", &params, false)
   763  }