From 300894002503193b65a28381193b536373bc1d8f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 6 Sep 2023 16:52:22 +0200 Subject: [PATCH] Improved test coverage --- README.md | 5 ++++- Router.go | 30 ++++++++++---------------- Router_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ Tree.go | 12 ++++++----- treeNode.go | 46 --------------------------------------- 5 files changed, 80 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index b955ed4..382017a 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,13 @@ data := router.LookupNoAlloc("GET", "/users/42", func(key string, value string) PASS: TestStatic PASS: TestParameter PASS: TestWildcard +PASS: TestMap PASS: TestMethods PASS: TestGitHub +PASS: TestTrailingSlash PASS: TestOverwrite -coverage: 82.5% of statements +PASS: TestInvalidMethod +coverage: 99.1% of statements ``` ## Benchmarks diff --git a/Router.go b/Router.go index df0ae32..d3116a5 100644 --- a/Router.go +++ b/Router.go @@ -1,7 +1,5 @@ package router -import "os" - // Router is a high-performance router. type Router[T any] struct { get Tree[T] @@ -46,23 +44,17 @@ func (router *Router[T]) LookupNoAlloc(method string, path string, addParameter return tree.LookupNoAlloc(path, addParameter) } -// Bind traverses all trees and calls the given function on every node. -func (router *Router[T]) Bind(transform func(T) T) { - router.get.Bind(transform) - router.post.Bind(transform) - router.delete.Bind(transform) - router.put.Bind(transform) - router.patch.Bind(transform) - router.head.Bind(transform) - router.connect.Bind(transform) - router.trace.Bind(transform) - router.options.Bind(transform) -} - -// Print shows a pretty print of all the routes. -func (router *Router[T]) Print(method string) { - tree := router.selectTree(method) - tree.root.PrettyPrint(os.Stdout) +// Map traverses all trees and calls the given function on every node. +func (router *Router[T]) Map(transform func(T) T) { + router.get.Map(transform) + router.post.Map(transform) + router.delete.Map(transform) + router.put.Map(transform) + router.patch.Map(transform) + router.head.Map(transform) + router.connect.Map(transform) + router.trace.Map(transform) + router.options.Map(transform) } // selectTree returns the tree by the given HTTP method. diff --git a/Router_test.go b/Router_test.go index 355c563..cc93b46 100644 --- a/Router_test.go +++ b/Router_test.go @@ -1,6 +1,7 @@ package router_test import ( + "strings" "testing" "git.akyoto.dev/go/assert" @@ -60,6 +61,7 @@ func TestWildcard(t *testing.T) { r := router.New[string]() r.Add("GET", "/", "Front page") r.Add("GET", "/:post", "Blog post") + r.Add("GET", "/*any", "Wildcard") r.Add("GET", "/users/:id", "Parameter") r.Add("GET", "/images/static", "Static") r.Add("GET", "/images/*path", "Wildcard") @@ -80,6 +82,9 @@ func TestWildcard(t *testing.T) { assert.Equal(t, params[0].Value, "42") assert.Equal(t, data, "Parameter") + data, _ = r.Lookup("GET", "/users/42/test.txt") + assert.Equal(t, data, "Wildcard") + data, params = r.Lookup("GET", "/images/static") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Static") @@ -103,6 +108,34 @@ func TestWildcard(t *testing.T) { assert.Equal(t, data, "Wildcard") } +func TestMap(t *testing.T) { + r := router.New[string]() + r.Add("GET", "/hello", "Hello") + r.Add("GET", "/world", "World") + r.Add("GET", "/user/:user", "User") + r.Add("GET", "/*path", "Path") + + r.Map(func(data string) string { + return strings.Repeat(data, 2) + }) + + data, params := r.Lookup("GET", "/hello") + assert.Equal(t, len(params), 0) + assert.Equal(t, data, "HelloHello") + + data, params = r.Lookup("GET", "/world") + assert.Equal(t, len(params), 0) + assert.Equal(t, data, "WorldWorld") + + data, params = r.Lookup("GET", "/user/123") + assert.Equal(t, len(params), 1) + assert.Equal(t, data, "UserUser") + + data, params = r.Lookup("GET", "/test.txt") + assert.Equal(t, len(params), 1) + assert.Equal(t, data, "PathPath") +} + func TestMethods(t *testing.T) { methods := []string{ "GET", @@ -145,6 +178,20 @@ func TestGitHub(t *testing.T) { } } +func TestTrailingSlash(t *testing.T) { + r := router.New[string]() + r.Add("GET", "/hello", "Hello 1") + r.Add("GET", "/hello/", "Hello 2") + + data, params := r.Lookup("GET", "/hello") + assert.Equal(t, len(params), 0) + assert.Equal(t, data, "Hello 1") + + data, params = r.Lookup("GET", "/hello/") + assert.Equal(t, len(params), 0) + assert.Equal(t, data, "Hello 2") +} + func TestOverwrite(t *testing.T) { r := router.New[string]() r.Add("GET", "/", "1") @@ -158,6 +205,17 @@ func TestOverwrite(t *testing.T) { assert.Equal(t, data, "5") } +func TestInvalidMethod(t *testing.T) { + defer func() { + if recover() == nil { + t.FailNow() + } + }() + + r := router.New[string]() + r.Add("?", "/hello", "Hello") +} + func TestMemoryUsage(t *testing.T) { escape := func(a any) {} diff --git a/Tree.go b/Tree.go index 3f150a3..3193d8a 100644 --- a/Tree.go +++ b/Tree.go @@ -130,6 +130,8 @@ begin: return node.data } + // node: /|*any + // path: /|image.png if lastWildcard != nil { addParameter(lastWildcard.prefix, path[lastWildcardOffset:]) return lastWildcard.data @@ -193,9 +195,9 @@ begin: // node: /|*any // path: /|image.png - if node.wildcard != nil { - addParameter(node.wildcard.prefix, path[i:]) - return node.wildcard.data + if lastWildcard != nil { + addParameter(lastWildcard.prefix, path[lastWildcardOffset:]) + return lastWildcard.data } return empty @@ -217,8 +219,8 @@ begin: } } -// Bind binds all handlers to a new one provided by the callback. -func (tree *Tree[T]) Bind(transform func(T) T) { +// Map binds all handlers to a new one provided by the callback. +func (tree *Tree[T]) Map(transform func(T) T) { tree.root.each(func(node *treeNode[T]) { node.data = transform(node.data) }) diff --git a/treeNode.go b/treeNode.go index 18af73c..ab430a6 100644 --- a/treeNode.go +++ b/treeNode.go @@ -1,8 +1,6 @@ package router import ( - "fmt" - "io" "strings" ) @@ -283,47 +281,3 @@ func (node *treeNode[T]) each(callback func(*treeNode[T])) { node.wildcard.each(callback) } } - -// PrettyPrint prints a human-readable form of the tree to the given writer. -func (node *treeNode[T]) PrettyPrint(writer io.Writer) { - node.prettyPrint(writer, -1) -} - -// prettyPrint is the underlying pretty printer. -func (node *treeNode[T]) prettyPrint(writer io.Writer, level int) { - prefix := "" - - if level >= 0 { - prefix = strings.Repeat(" ", level) + "|_ " - } - - switch node.kind { - case ':': - prefix += ":" - case '*': - prefix += "*" - } - - fmt.Fprintf(writer, "%s%s [%v]\n", prefix, node.prefix, node.data) - - for _, child := range node.children { - if child == nil { - continue - } - - child.prettyPrint(writer, level+1) - } - - if node.parameter != nil { - node.parameter.prettyPrint(writer, level+1) - } - - if node.wildcard != nil { - node.wildcard.prettyPrint(writer, level+1) - } -} - -// String returns the node prefix. -func (node *treeNode[T]) String() string { - return node.prefix -}