300 lines
6.7 KiB
Go
300 lines
6.7 KiB
Go
package router
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
|
|
assert "git.sharkk.net/Go/Assert"
|
|
)
|
|
|
|
func handler(t *testing.T, expectedParams []string) Handler {
|
|
return func(params []string) {
|
|
assert.Equal(t, len(params), len(expectedParams))
|
|
for i, expected := range expectedParams {
|
|
assert.Equal(t, params[i], expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRootPath(t *testing.T) {
|
|
r := New()
|
|
r.Get("/", handler(t, nil))
|
|
|
|
h, params, found := r.Lookup("GET", "/")
|
|
assert.True(t, found)
|
|
h(params)
|
|
}
|
|
|
|
func TestStaticPath(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/all", handler(t, nil))
|
|
|
|
h, params, found := r.Lookup("GET", "/users/all")
|
|
assert.True(t, found)
|
|
h(params)
|
|
}
|
|
|
|
func TestSingleParameter(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/[id]", handler(t, []string{"123"}))
|
|
|
|
h, params, found := r.Lookup("GET", "/users/123")
|
|
assert.True(t, found)
|
|
h(params)
|
|
}
|
|
|
|
func TestMultipleParameters(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/[id]/posts/[postId]", handler(t, []string{"123", "456"}))
|
|
|
|
h, params, found := r.Lookup("GET", "/users/123/posts/456")
|
|
assert.True(t, found)
|
|
h(params)
|
|
}
|
|
|
|
func TestNonExistentPath(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/[id]", handler(t, nil))
|
|
|
|
_, _, found := r.Lookup("GET", "/posts/123")
|
|
assert.False(t, found)
|
|
}
|
|
|
|
func TestWrongMethod(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/[id]", handler(t, nil))
|
|
|
|
_, _, found := r.Lookup("POST", "/users/123")
|
|
assert.False(t, found)
|
|
}
|
|
|
|
func TestTrailingSlash(t *testing.T) {
|
|
r := New()
|
|
r.Get("/users/[id]", handler(t, []string{"123"}))
|
|
|
|
h, params, found := r.Lookup("GET", "/users/123/")
|
|
assert.True(t, found)
|
|
h(params)
|
|
}
|
|
|
|
func TestDifferentMethods(t *testing.T) {
|
|
r := New()
|
|
|
|
r.Get("/test", handler(t, nil))
|
|
r.Post("/test", handler(t, nil))
|
|
r.Put("/test", handler(t, nil))
|
|
r.Patch("/test", handler(t, nil))
|
|
r.Delete("/test", handler(t, nil))
|
|
|
|
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
|
|
for _, method := range methods {
|
|
t.Run(method, func(t *testing.T) {
|
|
_, _, found := r.Lookup(method, "/test")
|
|
assert.True(t, found)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWildcardPath(t *testing.T) {
|
|
r := New()
|
|
|
|
t.Run("simple wildcard", func(t *testing.T) {
|
|
err := r.Get("/files/*path", handler(t, []string{"docs/report.pdf"}))
|
|
assert.Nil(t, err)
|
|
|
|
h, params, found := r.Lookup("GET", "/files/docs/report.pdf")
|
|
assert.True(t, found)
|
|
h(params)
|
|
})
|
|
|
|
t.Run("wildcard with empty path", func(t *testing.T) {
|
|
err := r.Get("/download/*filepath", handler(t, []string{""}))
|
|
assert.Nil(t, err)
|
|
|
|
h, params, found := r.Lookup("GET", "/download/")
|
|
assert.True(t, found)
|
|
h(params)
|
|
})
|
|
|
|
t.Run("wildcard with parameter", func(t *testing.T) {
|
|
err := r.Get("/users/[id]/*action", handler(t, []string{"123", "settings/profile/avatar"}))
|
|
assert.Nil(t, err)
|
|
|
|
h, params, found := r.Lookup("GET", "/users/123/settings/profile/avatar")
|
|
assert.True(t, found)
|
|
h(params)
|
|
})
|
|
|
|
t.Run("multiple wildcards not allowed", func(t *testing.T) {
|
|
err := r.Get("/api/*version/*path", handler(t, nil))
|
|
assert.NotNil(t, err)
|
|
})
|
|
|
|
t.Run("non-last wildcard not allowed", func(t *testing.T) {
|
|
err := r.Get("/api/*version/users", handler(t, nil))
|
|
assert.NotNil(t, err)
|
|
})
|
|
}
|
|
|
|
// Benchmarks
|
|
func BenchmarkRouterLookup(b *testing.B) {
|
|
r := New()
|
|
h := func(params []string) {}
|
|
|
|
// Setup routes for benchmarking
|
|
r.Get("/", h)
|
|
r.Get("/users/all", h)
|
|
r.Get("/users/[id]", h)
|
|
r.Get("/users/[id]/posts/[postId]", h)
|
|
|
|
b.Run("root", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/")
|
|
}
|
|
})
|
|
|
|
b.Run("static", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/all")
|
|
}
|
|
})
|
|
|
|
b.Run("single_param", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/123")
|
|
}
|
|
})
|
|
|
|
b.Run("multi_param", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/123/posts/456")
|
|
}
|
|
})
|
|
|
|
b.Run("not_found", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/nonexistent/path")
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkParallelLookup(b *testing.B) {
|
|
r := New()
|
|
h := func(params []string) {}
|
|
|
|
r.Get("/users/[id]", h)
|
|
r.Get("/posts/[id]/comments", h)
|
|
r.Get("/products/[category]/[id]", h)
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
i := 0
|
|
for pb.Next() {
|
|
switch i % 3 {
|
|
case 0:
|
|
r.Lookup("GET", "/users/123")
|
|
case 1:
|
|
r.Lookup("GET", "/posts/456/comments")
|
|
case 2:
|
|
r.Lookup("GET", "/products/electronics/789")
|
|
}
|
|
i++
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkWildcardLookup(b *testing.B) {
|
|
r := New()
|
|
h := func(params []string) {}
|
|
|
|
// Setup routes for benchmarking
|
|
r.Get("/files/*path", h)
|
|
r.Get("/users/[id]/*action", h)
|
|
|
|
b.Run("simple_wildcard", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/files/documents/reports/2024/q1.pdf")
|
|
}
|
|
})
|
|
|
|
b.Run("wildcard_with_param", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r.Lookup("GET", "/users/123/settings/profile/avatar")
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkComparison(b *testing.B) {
|
|
// Custom router setup
|
|
customRouter := New()
|
|
customRouter.Get("/", func(params []string) {})
|
|
customRouter.Get("/users/all", func(params []string) {})
|
|
customRouter.Get("/users/[id]", func(params []string) {})
|
|
customRouter.Get("/users/[id]/posts/[postId]", func(params []string) {})
|
|
|
|
// Standard mux setup
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
|
|
mux.HandleFunc("/users/all", func(w http.ResponseWriter, r *http.Request) {})
|
|
mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {}) // Best equivalent for dynamic routes
|
|
|
|
// Root path
|
|
b.Run("root_path", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
req, _ := http.NewRequest("GET", "/", nil)
|
|
for i := 0; i < b.N; i++ {
|
|
mux.ServeHTTP(nil, req)
|
|
}
|
|
})
|
|
})
|
|
|
|
// Static path
|
|
b.Run("static_path", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/users/all")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
req, _ := http.NewRequest("GET", "/users/all", nil)
|
|
for i := 0; i < b.N; i++ {
|
|
mux.ServeHTTP(nil, req)
|
|
}
|
|
})
|
|
})
|
|
|
|
// Dynamic path
|
|
b.Run("dynamic_path", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/users/123")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
req, _ := http.NewRequest("GET", "/users/123", nil)
|
|
for i := 0; i < b.N; i++ {
|
|
mux.ServeHTTP(nil, req)
|
|
}
|
|
})
|
|
})
|
|
|
|
// Not found
|
|
b.Run("not_found", func(b *testing.B) {
|
|
b.Run("custom", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
customRouter.Lookup("GET", "/nonexistent/path")
|
|
}
|
|
})
|
|
b.Run("mux", func(b *testing.B) {
|
|
req, _ := http.NewRequest("GET", "/nonexistent/path", nil)
|
|
for i := 0; i < b.N; i++ {
|
|
mux.ServeHTTP(nil, req)
|
|
}
|
|
})
|
|
})
|
|
}
|