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