1. Cross-platform uSockets configuration - Use @VMODROOT for portable library paths - Support macOS (arm64/x86_64), Windows, and Linux - Auto-detect Homebrew paths for libuv on macOS 2. Route cache optimization (3x performance boost) - Replace LRU cache with FastRouteCache for dynamic routes - Zero-overhead cache lookup (no TTL check, no LRU reordering) - Dynamic route performance: 30k -> 97k RPS 3. New test files - Go-based integration tests for picoev and uSockets - FastCache optimization validation tests - Benchmark comparison tools
269 lines
7.0 KiB
Go
269 lines
7.0 KiB
Go
// uSockets 集成测试 (Go 版本)
|
|
//
|
|
// 使用方法:
|
|
// 1. 先启动测试服务器: v -enable-globals run v-hono/tests/test_usockets_server.v
|
|
// 2. 运行测试: go run v-hono/tests/test_usockets_integration.go
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const baseURL = "http://127.0.0.1:9998"
|
|
|
|
type TestStats struct {
|
|
total int
|
|
passed int
|
|
failed int
|
|
errors []string
|
|
}
|
|
|
|
func (s *TestStats) run(name string, testFn func() bool) {
|
|
s.total++
|
|
fmt.Printf(" 🧪 %s... ", name)
|
|
|
|
if testFn() {
|
|
s.passed++
|
|
fmt.Println("✅")
|
|
} else {
|
|
s.failed++
|
|
s.errors = append(s.errors, name)
|
|
fmt.Println("❌")
|
|
}
|
|
}
|
|
|
|
func (s *TestStats) summary() {
|
|
fmt.Println()
|
|
fmt.Println("═══════════════════════════════════════════════════════════════")
|
|
fmt.Printf("📊 测试结果: %d/%d 通过\n", s.passed, s.total)
|
|
|
|
if s.failed > 0 {
|
|
fmt.Println("❌ 失败的测试:")
|
|
for _, err := range s.errors {
|
|
fmt.Printf(" - %s\n", err)
|
|
}
|
|
} else {
|
|
fmt.Println("🎉 所有测试通过!uSockets 后端验证成功!")
|
|
}
|
|
fmt.Println("═══════════════════════════════════════════════════════════════")
|
|
}
|
|
|
|
func main() {
|
|
fmt.Println("╔═══════════════════════════════════════════════════════════════╗")
|
|
fmt.Println("║ uSockets 集成测试 (Go) ║")
|
|
fmt.Println("╚═══════════════════════════════════════════════════════════════╝")
|
|
fmt.Println()
|
|
|
|
// 验证服务器是否运行
|
|
fmt.Println("🔍 检查 uSockets 测试服务器...")
|
|
if !checkServerReady() {
|
|
fmt.Println("❌ 服务器未运行")
|
|
fmt.Println(" 请先启动: v -enable-globals run v-hono/tests/test_usockets_server.v")
|
|
return
|
|
}
|
|
fmt.Println("✅ 服务器已就绪")
|
|
fmt.Println()
|
|
|
|
stats := &TestStats{}
|
|
|
|
// 1. 基本路由测试
|
|
fmt.Println("📦 1. 基本路由测试")
|
|
stats.run("GET /", testGetRoot)
|
|
stats.run("GET /health", testGetHealth)
|
|
fmt.Println()
|
|
|
|
// 2. JSON 响应测试
|
|
fmt.Println("📦 2. JSON 响应测试")
|
|
stats.run("GET /api/json", testJSONResponse)
|
|
fmt.Println()
|
|
|
|
// 3. 动态路由测试
|
|
fmt.Println("📦 3. 动态路由测试")
|
|
stats.run("GET /api/users/:id", testSingleParam)
|
|
stats.run("GET /api/users/:user_id/posts/:post_id", testMultiParams)
|
|
fmt.Println()
|
|
|
|
// 4. 查询参数测试
|
|
fmt.Println("📦 4. 查询参数测试")
|
|
stats.run("GET /api/search?q=hello", testQueryParam)
|
|
fmt.Println()
|
|
|
|
// 5. 404 测试
|
|
fmt.Println("📦 5. 404 测试")
|
|
stats.run("404 Not Found", testNotFound)
|
|
fmt.Println()
|
|
|
|
// 6. Keep-Alive 测试
|
|
fmt.Println("📦 6. Keep-Alive 连接测试")
|
|
stats.run("Keep-Alive 连接复用", testKeepAlive)
|
|
fmt.Println()
|
|
|
|
// 7. 性能测试
|
|
fmt.Println("📦 7. 性能测试")
|
|
stats.run("吞吐量测试", testThroughput)
|
|
fmt.Println()
|
|
|
|
// 8. 高并发基准测试
|
|
fmt.Println("📦 8. Keep-Alive 基准测试")
|
|
testBenchmark()
|
|
fmt.Println()
|
|
|
|
// 输出总结
|
|
stats.summary()
|
|
}
|
|
|
|
func checkServerReady() bool {
|
|
client := &http.Client{Timeout: 2 * time.Second}
|
|
for i := 0; i < 5; i++ {
|
|
resp, err := client.Get(baseURL + "/health")
|
|
if err == nil && resp.StatusCode == 200 {
|
|
resp.Body.Close()
|
|
return true
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func testGetRoot() bool {
|
|
resp, body := doGet("/")
|
|
return resp != nil && resp.StatusCode == 200 && strings.Contains(body, "Hello from uSockets")
|
|
}
|
|
|
|
func testGetHealth() bool {
|
|
resp, body := doGet("/health")
|
|
return resp != nil && resp.StatusCode == 200 && body == "OK"
|
|
}
|
|
|
|
func testJSONResponse() bool {
|
|
resp, body := doGet("/api/json")
|
|
if resp == nil {
|
|
return false
|
|
}
|
|
contentType := resp.Header.Get("Content-Type")
|
|
return resp.StatusCode == 200 && strings.Contains(contentType, "application/json") && strings.Contains(body, "Hello JSON")
|
|
}
|
|
|
|
func testSingleParam() bool {
|
|
resp, body := doGet("/api/users/123")
|
|
return resp != nil && resp.StatusCode == 200 && strings.Contains(body, "123")
|
|
}
|
|
|
|
func testMultiParams() bool {
|
|
resp, body := doGet("/api/users/100/posts/200")
|
|
return resp != nil && resp.StatusCode == 200 && strings.Contains(body, "100") && strings.Contains(body, "200")
|
|
}
|
|
|
|
func testQueryParam() bool {
|
|
resp, body := doGet("/api/search?q=hello")
|
|
return resp != nil && resp.StatusCode == 200 && strings.Contains(body, "hello")
|
|
}
|
|
|
|
func testNotFound() bool {
|
|
resp, _ := doGet("/nonexistent")
|
|
return resp != nil && resp.StatusCode == 404
|
|
}
|
|
|
|
func testKeepAlive() bool {
|
|
transport := &http.Transport{
|
|
MaxIdleConns: 10,
|
|
IdleConnTimeout: 30 * time.Second,
|
|
DisableKeepAlives: false,
|
|
}
|
|
client := &http.Client{Transport: transport, Timeout: 5 * time.Second}
|
|
|
|
for i := 0; i < 5; i++ {
|
|
resp, err := client.Get(baseURL + "/health")
|
|
if err != nil || resp.StatusCode != 200 {
|
|
return false
|
|
}
|
|
io.Copy(io.Discard, resp.Body)
|
|
resp.Body.Close()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func testThroughput() bool {
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
requests := 100
|
|
success := 0
|
|
|
|
start := time.Now()
|
|
for i := 0; i < requests; i++ {
|
|
resp, err := client.Get(baseURL + "/health")
|
|
if err == nil && resp.StatusCode == 200 {
|
|
success++
|
|
io.Copy(io.Discard, resp.Body)
|
|
resp.Body.Close()
|
|
}
|
|
}
|
|
elapsed := time.Since(start)
|
|
|
|
rps := float64(success) * 1000.0 / float64(elapsed.Milliseconds())
|
|
fmt.Printf("(%.0f req/s) ", rps)
|
|
|
|
return success >= requests*9/10
|
|
}
|
|
|
|
func testBenchmark() {
|
|
requests := 5000
|
|
|
|
conn, err := net.Dial("tcp", "127.0.0.1:9998")
|
|
if err != nil {
|
|
fmt.Println(" ❌ 连接失败:", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
// 预热
|
|
for i := 0; i < 100; i++ {
|
|
fmt.Fprintf(conn, "GET /health HTTP/1.1\r\nHost: 127.0.0.1:9998\r\nConnection: keep-alive\r\n\r\n")
|
|
buf := make([]byte, 256)
|
|
conn.Read(buf)
|
|
}
|
|
|
|
// 正式测试
|
|
start := time.Now()
|
|
success := 0
|
|
|
|
for i := 0; i < requests; i++ {
|
|
_, err := fmt.Fprintf(conn, "GET /health HTTP/1.1\r\nHost: 127.0.0.1:9998\r\nConnection: keep-alive\r\n\r\n")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
buf := make([]byte, 256)
|
|
_, err = conn.Read(buf)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
success++
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
elapsedMs := float64(elapsed.Milliseconds())
|
|
rps := float64(success) * 1000.0 / elapsedMs
|
|
avgUs := elapsedMs * 1000.0 / float64(success)
|
|
|
|
fmt.Printf(" 成功请求: %d/%d\n", success, requests)
|
|
fmt.Printf(" 吞吐量: %.0f req/s\n", rps)
|
|
fmt.Printf(" 平均延迟: %.2fμs\n", avgUs)
|
|
}
|
|
|
|
func doGet(path string) (*http.Response, string) {
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
resp, err := client.Get(baseURL + path)
|
|
if err != nil {
|
|
return nil, ""
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return resp, string(body)
|
|
}
|