Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ func Benchmark404Many(B *testing.B) {
runRequest(B, router, http.MethodGet, "/viewfake")
}

func BenchmarkStaticRouteWithParamFallback(B *testing.B) {
router := New()
router.GET("/users/:id", func(c *Context) {})
router.GET("/users/new", func(c *Context) {})
router.GET("/users/:id/profile", func(c *Context) {})
runRequest(B, router, http.MethodGet, "/users/new")
}

type mockWriter struct {
headers http.Header
}
Expand Down
20 changes: 20 additions & 0 deletions binding/binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,26 @@ func TestPlainBinding(t *testing.T) {
require.NoError(t, p.Bind(req, ptr))
}

func TestPlainBindingBindBody(t *testing.T) {
p := Plain

var s string
require.NoError(t, p.BindBody([]byte("test string"), &s))
assert.Equal(t, "test string", s)

var bs []byte
require.NoError(t, p.BindBody([]byte("test []byte"), &bs))
assert.Equal(t, []byte("test []byte"), bs)

var i int
require.Error(t, p.BindBody([]byte("test fail"), &i))

require.NoError(t, p.BindBody(nil, nil))

var ptr *string
require.NoError(t, p.BindBody(nil, ptr))
}

func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name())

Expand Down
79 changes: 79 additions & 0 deletions codec/json/json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2026 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package json

import (
"bytes"
"encoding/json"
"strings"
"testing"
)

func TestAPI(t *testing.T) {
if API == nil {
t.Fatal("API is nil")
}
if Package == "" {
t.Fatal("Package is empty")
}
}

func TestAPIMarshalAndUnmarshal(t *testing.T) {
type payload struct {
Name string `json:"name"`
}

data, err := API.Marshal(payload{Name: "gin"})
if err != nil {
t.Fatal(err)
}
if string(data) != `{"name":"gin"}` {
t.Fatalf("unexpected marshal output: %s", data)
}

var decoded payload
if err := API.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.Name != "gin" {
t.Fatalf("unexpected decoded payload: %#v", decoded)
}
}

func TestAPIMarshalIndent(t *testing.T) {
data, err := API.MarshalIndent(map[string]string{"name": "gin"}, "", " ")
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(data, []byte("\n ")) {
t.Fatalf("expected indented JSON, got %q", data)
}
}

func TestAPIEncoder(t *testing.T) {
var buf bytes.Buffer
encoder := API.NewEncoder(&buf)
encoder.SetEscapeHTML(false)

if err := encoder.Encode("<gin>"); err != nil {
t.Fatal(err)
}
if got := buf.String(); got != "\"<gin>\"\n" {
t.Fatalf("unexpected encoded JSON: %q", got)
}
}

func TestAPIDecoder(t *testing.T) {
decoder := API.NewDecoder(strings.NewReader(`{"known": 1, "extra": 2}`))
decoder.UseNumber()
decoder.DisallowUnknownFields()

var dst struct {
Known json.Number `json:"known"`
}
if err := decoder.Decode(&dst); err == nil {
t.Fatal("expected unknown field error")
}
}
48 changes: 48 additions & 0 deletions ginS/gins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import (
"html/template"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func init() {
Expand Down Expand Up @@ -214,6 +217,24 @@ func TestSetHTMLTemplate(t *testing.T) {
assert.NotNil(t, engine())
}

func TestLoadHTMLGlob(t *testing.T) {
LoadHTMLGlob("../testdata/template/*")

assert.NotNil(t, engine())
}

func TestLoadHTMLFiles(t *testing.T) {
LoadHTMLFiles("../testdata/template/hello.tmpl", "../testdata/template/raw.tmpl")

assert.NotNil(t, engine())
}

func TestLoadHTMLFS(t *testing.T) {
LoadHTMLFS(http.Dir("../testdata"), "template/hello.tmpl", "template/raw.tmpl")

assert.NotNil(t, engine())
}

func TestStaticFile(t *testing.T) {
StaticFile("/static-file", "../testdata/test_file.txt")

Expand All @@ -224,6 +245,33 @@ func TestStaticFile(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
}

func TestRunReturnsListenError(t *testing.T) {
err := Run("127.0.0.1:bad-port")

require.Error(t, err)
}

func TestRunTLSReturnsListenError(t *testing.T) {
err := RunTLS("127.0.0.1:bad-port", "../testdata/certificate/cert.pem", "../testdata/certificate/key.pem")

require.Error(t, err)
}

func TestRunUnixReturnsListenError(t *testing.T) {
err := RunUnix(filepath.Join(t.TempDir(), "missing", "gin.sock"))

require.Error(t, err)
}

func TestRunFdReturnsListenerError(t *testing.T) {
file, err := os.CreateTemp(t.TempDir(), "gin-fd")
require.NoError(t, err)

err = RunFd(int(file.Fd()))

require.Error(t, err)
}

func TestStatic(t *testing.T) {
Static("/static-dir", "../testdata")

Expand Down
42 changes: 21 additions & 21 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,41 +417,38 @@ type skippedNode struct {
// given path.
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
var globalParamsCount int16
var skipStatic bool

walk: // Outer loop for walking the tree
for {
prefix := n.path
if len(path) > len(prefix) {
if path[:len(prefix)] == prefix {
fullPath := path
path = path[len(prefix):]

// Try all the non-wildcard children first by matching the indices
idxc := path[0]
for i, c := range []byte(n.indices) {
if c == idxc {
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
if n.wildChild {
index := len(*skippedNodes)
*skippedNodes = (*skippedNodes)[:index+1]
(*skippedNodes)[index] = skippedNode{
path: prefix + path,
node: &node{
path: n.path,
wildChild: n.wildChild,
nType: n.nType,
priority: n.priority,
children: n.children,
handlers: n.handlers,
fullPath: n.fullPath,
},
paramsCount: globalParamsCount,
if !skipStatic {
for i, c := range []byte(n.indices) {
if c == idxc {
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
if n.wildChild {
index := len(*skippedNodes)
*skippedNodes = (*skippedNodes)[:index+1]
(*skippedNodes)[index] = skippedNode{
path: fullPath,
node: n,
paramsCount: globalParamsCount,
}
}
}

n = n.children[i]
continue walk
n = n.children[i]
continue walk
}
}
}
skipStatic = false

if !n.wildChild {
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
Expand All @@ -467,6 +464,7 @@ walk: // Outer loop for walking the tree
*value.params = (*value.params)[:skippedNode.paramsCount]
}
globalParamsCount = skippedNode.paramsCount
skipStatic = true
continue walk
}
}
Expand Down Expand Up @@ -598,6 +596,7 @@ walk: // Outer loop for walking the tree
*value.params = (*value.params)[:skippedNode.paramsCount]
}
globalParamsCount = skippedNode.paramsCount
skipStatic = true
continue walk
}
}
Expand Down Expand Up @@ -655,6 +654,7 @@ walk: // Outer loop for walking the tree
*value.params = (*value.params)[:skippedNode.paramsCount]
}
globalParamsCount = skippedNode.paramsCount
skipStatic = true
continue walk
}
}
Expand Down
34 changes: 34 additions & 0 deletions tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,40 @@ func TestTreeExpandParamsCapacity(t *testing.T) {
}
}

func TestTreeFindCaseInsensitivePathWithWildcardChildAndBufferedRune(t *testing.T) {
tree := &node{
path: "/",
indices: "x",
wildChild: true,
children: []*node{
{path: "x", handlers: fakeHandler("/x"), fullPath: "/x"},
{path: ":id", nType: param, handlers: fakeHandler("/:id"), fullPath: "/:id"},
},
}

out := tree.findCaseInsensitivePathRec("/x", nil, [4]byte{0, 'x'}, false)
if string(out) != "/x" {
t.Fatalf("Wrong result: got %s, want /x", string(out))
}
}

func TestTreeFindCaseInsensitivePathWithWildcardChildAndUppercaseStatic(t *testing.T) {
tree := &node{
path: "/",
indices: "A",
wildChild: true,
children: []*node{
{path: "A", handlers: fakeHandler("/A"), fullPath: "/A"},
{path: ":id", nType: param, handlers: fakeHandler("/:id"), fullPath: "/:id"},
},
}

out := tree.findCaseInsensitivePathRec("/a", nil, [4]byte{}, false)
if string(out) != "/A" {
t.Fatalf("Wrong result: got %s, want /A", string(out))
}
}

func TestTreeWildcardConflictEx(t *testing.T) {
conflicts := [...]struct {
route string
Expand Down