Files
sux/sux_test.go
Darko Luketic 045ac8b5a3 Implement performance optimizations v1.1.0
- Replace map-based Params with slice-based structure for 25-40% reduction in allocations
- Implement zero-allocation path parsing with pre-allocation
- Add enhanced memory pooling for parameters, segments, and string builders
- Optimize wildcard parameter handling with string builders
- Add comprehensive performance benchmarks
- Achieve 14-49% performance improvements across all route types
- Maintain full API compatibility

Performance improvements:
- Static routes: 14.6% faster (682.1 vs 798.5 ns/op)
- Parameter routes: 24.7% faster (868.9 vs 1154 ns/op)
- Wildcard routes: 41.6% faster (979.3 vs 1676 ns/op)
- Multiple parameters: 41.5% faster (1026 vs 1753 ns/op)
- Middleware: 49.0% faster (1930 vs 3782 ns/op)
- Route groups: 49.5% faster (1442 vs 2855 ns/op)
- Large router: 24.5% faster (833.0 vs 1103 ns/op)
2025-10-26 18:06:15 +01:00

330 lines
8.1 KiB
Go

package sux
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestBasicRouting(t *testing.T) {
router := New()
router.GET("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("home"))
})
router.GET("/about", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("about"))
})
// Test home route
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
if w.Body.String() != "home" {
t.Errorf("Expected body 'home', got '%s'", w.Body.String())
}
// Test about route
req = httptest.NewRequest("GET", "/about", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
if w.Body.String() != "about" {
t.Errorf("Expected body 'about', got '%s'", w.Body.String())
}
}
func TestParameterRouting(t *testing.T) {
router := New()
router.GET("/users/:id", func(w http.ResponseWriter, r *http.Request) {
params := ParamsFromContext(r)
if params.Len() == 0 {
w.WriteHeader(http.StatusInternalServerError)
return
}
id := params.Get("id")
w.WriteHeader(http.StatusOK)
w.Write([]byte("user " + id))
})
// Test parameter route
req := httptest.NewRequest("GET", "/users/123", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
if w.Body.String() != "user 123" {
t.Errorf("Expected body 'user 123', got '%s'", w.Body.String())
}
}
func TestWildcardRouting(t *testing.T) {
router := New()
router.GET("/files/*path", func(w http.ResponseWriter, r *http.Request) {
params := ParamsFromContext(r)
if params.Len() == 0 {
w.WriteHeader(http.StatusInternalServerError)
return
}
path := params.Get("path")
w.WriteHeader(http.StatusOK)
w.Write([]byte("file: " + path))
})
// Test wildcard route
req := httptest.NewRequest("GET", "/files/docs/readme.txt", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
if w.Body.String() != "file: docs/readme.txt" {
t.Errorf("Expected body 'file: docs/readme.txt', got '%s'", w.Body.String())
}
}
func TestMethodNotAllowed(t *testing.T) {
router := New()
router.GET("/resource", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("GET resource"))
})
// Test POST to GET-only route
req := httptest.NewRequest("POST", "/resource", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("Expected status 405, got %d", w.Code)
}
}
func TestNotFound(t *testing.T) {
router := New()
router.GET("/existing", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
// Test non-existent route
req := httptest.NewRequest("GET", "/nonexistent", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("Expected status 404, got %d", w.Code)
}
}
func TestMiddleware(t *testing.T) {
router := New()
// Add middleware that sets a header
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Middleware", "applied")
next.ServeHTTP(w, r)
})
})
router.GET("/test", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("test"))
})
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
if w.Header().Get("X-Middleware") != "applied" {
t.Errorf("Expected middleware header, got '%s'", w.Header().Get("X-Middleware"))
}
}
func TestRouteGroups(t *testing.T) {
router := New()
// Create API group with middleware
api := router.Group("/api", func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-API-Version", "v1")
next.ServeHTTP(w, r)
})
})
api.GET("/users", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("users list"))
})
// Test grouped route
req := httptest.NewRequest("GET", "/api/users", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
if w.Body.String() != "users list" {
t.Errorf("Expected body 'users list', got '%s'", w.Body.String())
}
if w.Header().Get("X-API-Version") != "v1" {
t.Errorf("Expected API version header, got '%s'", w.Header().Get("X-API-Version"))
}
}
func TestCustomHandlers(t *testing.T) {
router := New()
// Custom 404 handler
router.NotFound(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("custom 404"))
})
// Custom 405 handler
router.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("custom 405"))
})
router.GET("/existing", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
// Test custom 404
req := httptest.NewRequest("GET", "/nonexistent", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("Expected status 404, got %d", w.Code)
}
if w.Body.String() != "custom 404" {
t.Errorf("Expected body 'custom 404', got '%s'", w.Body.String())
}
// Test custom 405
req = httptest.NewRequest("POST", "/existing", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("Expected status 405, got %d", w.Code)
}
if w.Body.String() != "custom 405" {
t.Errorf("Expected body 'custom 405', got '%s'", w.Body.String())
}
}
func TestMultipleParameters(t *testing.T) {
router := New()
router.GET("/users/:userId/posts/:postId", func(w http.ResponseWriter, r *http.Request) {
params := ParamsFromContext(r)
if params.Len() == 0 {
w.WriteHeader(http.StatusInternalServerError)
return
}
userId := params.Get("userId")
postId := params.Get("postId")
w.WriteHeader(http.StatusOK)
w.Write([]byte("user " + userId + " post " + postId))
})
req := httptest.NewRequest("GET", "/users/123/posts/456", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
if w.Body.String() != "user 123 post 456" {
t.Errorf("Expected body 'user 123 post 456', got '%s'", w.Body.String())
}
}
func TestThreadSafety(t *testing.T) {
router := New()
router.GET("/test", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("test"))
})
// Create multiple goroutines to test concurrent access
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func() {
for j := 0; j < 100; j++ {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
}
done <- true
}()
}
// Wait for all goroutines to complete
for i := 0; i < 10; i++ {
<-done
}
}
func TestRouterInstanceIsolation(t *testing.T) {
// Create two separate router instances
router1 := New()
router2 := New()
router1.GET("/route", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("router1"))
})
router2.GET("/route", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("router2"))
})
// Test router1
req := httptest.NewRequest("GET", "/route", nil)
w := httptest.NewRecorder()
router1.ServeHTTP(w, req)
if w.Body.String() != "router1" {
t.Errorf("Expected body 'router1', got '%s'", w.Body.String())
}
// Test router2
w = httptest.NewRecorder()
router2.ServeHTTP(w, req)
if w.Body.String() != "router2" {
t.Errorf("Expected body 'router2', got '%s'", w.Body.String())
}
}