Repository: gin-gonic/gin
Pull request #3328 (2022-09-18 16:38:05)

fix: test fmt.Println replace t.Error
- With pull requests:
  - Open your pull request against `master`
  - Your pull request should have no more than two commits, if not you should squash them.
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
  - You should add/modify tests to cover your proposed code changes.
  - If your pull request contains a new feature, please document it on the README.



appleboy: please rebase the master branch.
demoManito: > please rebase the master branch.

@appleboy rebase the master
demoManito: <img width="1003" alt="image" src="https://user-images.githubusercontent.com/27986239/197156974-71b24f92-78ff-4fe1-8b17-341b5d76c874.png">
This error has nothing to do with my modification @appleboy 
codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/3328?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
> Merging [#3328](https://codecov.io/gh/gin-gonic/gin/pull/3328?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (86c114e) into [master](https://codecov.io/gh/gin-gonic/gin/commit/51aea73ba0f125f6cacc3b4b695efdf21d9c634f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (51aea73) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##           master    #3328   +/-   ##
=======================================
  Coverage   98.27%   98.27%           
=======================================
  Files          42       42           
  Lines        3127     3127           
=======================================
  Hits         3073     3073           
  Misses         40       40           
  Partials       14       14           
```

| Flag | Coverage Δ | |
|---|---|---|
|  | `98.27% <ø> (ø)` | |
| go-1.17 | `?` | |
| go-1.18 | `98.17% <ø> (ø)` | |
| go-1.19 | `98.27% <ø> (ø)` | |
| macos-latest | `?` | |
| ubuntu-latest | `98.27% <ø> (?)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#carryforward-flags-in-the-pull-request-comment) to find out more.


Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1bc51a8..a682c8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -113,7 +113,7 @@
 * chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378))
 * Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387))
 * update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321))
-* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
+* remove an unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
 * Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389))
 * Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322))
 * binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450))
diff --git a/README.md b/README.md
index 960c66d..6445e97 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
 
 To install Gin package, you need to install Go and set your Go workspace first.
 
-1. You first need [Go](https://golang.org/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin.
+1. You first need [Go](https://go.dev/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin.
 
 ```sh
 go get -u github.com/gin-gonic/gin
@@ -293,7 +293,7 @@ func main() {
   router := gin.Default()
 
   // Query string parameters are parsed using the existing underlying request object.
-  // The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
+  // The request responds to an url matching:  /welcome?firstname=Jane&lastname=Doe
   router.GET("/welcome", func(c *gin.Context) {
     firstname := c.DefaultQuery("firstname", "Guest")
     lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
@@ -678,7 +678,7 @@ func main() {
 
 To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
 
-Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
+Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
 
 Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
 
@@ -693,7 +693,7 @@ Also, Gin provides two sets of methods for binding:
 
 When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
 
-You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned.
+You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned.
 
 ```go
 // Binding from JSON
@@ -1820,7 +1820,7 @@ Alternatives:
 
 #### Manually
 
-In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown).
+In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown).
 
 ```go
 // +build go1.8
@@ -2096,7 +2096,7 @@ func validate(obj interface{}) error {
 }
 
 // Now we can do this!!!
-// FormA is a external type that we can't modify it's tag
+// FormA is an external type that we can't modify it's tag
 type FormA struct {
   FieldA string `url:"field_a"`
 }
@@ -2116,7 +2116,7 @@ func ListHandler(s *Service) func(ctx *gin.Context) {
 
 ### http2 server push
 
-http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
+http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information.
 
 ```go
 package main
@@ -2364,7 +2364,7 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
 * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
 * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.
 * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.
-* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
+* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares.
 * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
 * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
 * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
diff --git a/gin.go b/gin.go
index a2e2e67..35159d0 100644
--- a/gin.go
+++ b/gin.go
@@ -203,7 +203,7 @@ func New() *Engine {
 	}
 	engine.RouterGroup.engine = engine
 	engine.pool.New = func() any {
-		return engine.allocateContext()
+		return engine.allocateContext(engine.maxParams)
 	}
 	return engine
 }
@@ -225,8 +225,8 @@ func (engine *Engine) Handler() http.Handler {
 	return h2c.NewHandler(engine, h2s)
 }
 
-func (engine *Engine) allocateContext() *Context {
-	v := make(Params, 0, engine.maxParams)
+func (engine *Engine) allocateContext(maxParams uint16) *Context {
+	v := make(Params, 0, maxParams)
 	skippedNodes := make([]skippedNode, 0, engine.maxSections)
 	return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
 }
diff --git a/tree.go b/tree.go
index 956bf4d..3f34b8e 100644
--- a/tree.go
+++ b/tree.go
@@ -107,7 +107,8 @@ func countSections(path string) uint16 {
 type nodeType uint8
 
 const (
-	root nodeType = iota + 1
+	static nodeType = iota
+	root
 	param
 	catchAll
 )
@@ -173,6 +174,7 @@ walk:
 			child := node{
 				path:      n.path[i:],
 				wildChild: n.wildChild,
+				nType:     static,
 				indices:   n.indices,
 				children:  n.children,
 				handlers:  n.handlers,
@@ -604,6 +606,11 @@ walk: // Outer loop for walking the tree
 				return
 			}
 
+			if path == "/" && n.nType == static {
+				value.tsr = true
+				return
+			}
+
 			// No handle found. Check if a handle for this path + a
 			// trailing slash exists for trailing slash recommendation
 			for i, c := range []byte(n.indices) {


Test patch:
diff --git a/context_test.go b/context_test.go
index cc55cb0..85e0a61 100644
--- a/context_test.go
+++ b/context_test.go
@@ -146,7 +146,7 @@ func TestSaveUploadedCreateFailed(t *testing.T) {
 
 func TestContextReset(t *testing.T) {
 	router := New()
-	c := router.allocateContext()
+	c := router.allocateContext(0)
 	assert.Equal(t, c.engine, router)
 
 	c.index = 2
@@ -2354,3 +2354,17 @@ func TestContextAddParam(t *testing.T) {
 	assert.Equal(t, ok, true)
 	assert.Equal(t, value, v)
 }
+
+func TestCreateTestContextWithRouteParams(t *testing.T) {
+	w := httptest.NewRecorder()
+	engine := New()
+	engine.GET("/:action/:name", func(ctx *Context) {
+		ctx.String(http.StatusOK, "%s %s", ctx.Param("action"), ctx.Param("name"))
+	})
+	c := CreateTestContextOnly(w, engine)
+	c.Request, _ = http.NewRequest(http.MethodGet, "/hello/gin", nil)
+	engine.HandleContext(c)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Equal(t, "hello gin", w.Body.String())
+}
diff --git a/gin_test.go b/gin_test.go
index 4fac677..5ab2430 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -73,7 +73,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -83,7 +83,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
 func TestH2c(t *testing.T) {
 	ln, err := net.Listen("tcp", "127.0.0.1:0")
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 	r := Default()
 	r.UseH2C = true
@@ -93,7 +93,7 @@ func TestH2c(t *testing.T) {
 	go func() {
 		err := http.Serve(ln, r.Handler())
 		if err != nil {
-			fmt.Println(err)
+			t.Log(err)
 		}
 	}()
 	defer ln.Close()
@@ -111,7 +111,7 @@ func TestH2c(t *testing.T) {
 
 	res, err := httpClient.Get(url)
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -131,7 +131,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -151,7 +151,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -178,7 +178,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) {
 	client := &http.Client{Transport: tr}
 	res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -198,7 +198,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -229,7 +229,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -249,7 +249,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -269,7 +269,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -296,7 +296,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) {
 	client := &http.Client{Transport: tr}
 	res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
@@ -316,7 +316,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
 
 	res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
 	if err != nil {
-		fmt.Println(err)
+		t.Error(err)
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
diff --git a/test_helpers.go b/test_helpers.go
index b3be93b..7508c5c 100644
--- a/test_helpers.go
+++ b/test_helpers.go
@@ -9,7 +9,15 @@ import "net/http"
 // CreateTestContext returns a fresh engine and context for testing purposes
 func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
 	r = New()
-	c = r.allocateContext()
+	c = r.allocateContext(0)
+	c.reset()
+	c.writermem.reset(w)
+	return
+}
+
+// CreateTestContextOnly returns a fresh context base on the engine for testing purposes
+func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
+	c = r.allocateContext(r.maxParams)
 	c.reset()
 	c.writermem.reset(w)
 	return
diff --git a/tree_test.go b/tree_test.go
index 085b580..2005738 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -684,6 +684,26 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
 	}
 }
 
+func TestRedirectTrailingSlash(t *testing.T) {
+	var data = []struct {
+		path string
+	}{
+		{"/hello/:name"},
+		{"/hello/:name/123"},
+		{"/hello/:name/234"},
+	}
+
+	node := &node{}
+	for _, item := range data {
+		node.addRoute(item.path, fakeHandler("test"))
+	}
+
+	value := node.getValue("/hello/abx/", nil, getSkippedNodes(), false)
+	if value.tsr != true {
+		t.Fatalf("want true, is false")
+	}
+}
+
 func TestTreeFindCaseInsensitivePath(t *testing.T) {
 	tree := &node{}