lolly/internal/e2e/compression_e2e_test.go
xfy 2cb10eb749 perf(e2e): 并行化 E2E 测试,从 ~2h 降至 ~102s
- testutil: 用 sync.Once 缓存 LollyImageAvailable 结果
- testutil: 原子计数器替代时间戳避免容器名竞态
- testutil: SetupProxyTest 接受 suffix 参数生成独立 Docker 网络
- testutil: CleanupProxyTest 显式调用 network.Remove() 清理
- testutil: 移除死代码 SetupProxyTestEnv/ProxyTestEnv
- testutil: HealthCheckWaitTimeout 30s→15s, DefaultTestTimeout 180s→120s
- e2e: 所有 107 个测试函数添加 t.Parallel()
- e2e: 替换 65 处硬编码 30*time.Second 为常量
- make: test-all 三类测试并行运行,显式 PID wait 收集退出码
- make: test-e2e 添加 -parallel 4 flag

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:19:46 +08:00

274 lines
7.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build e2e
// compression_e2e_test.go - 压缩功能 E2E 测试L3 层,需要 Docker
//
// 测试 lolly 响应压缩功能,包括:
// - Gzip 压缩响应
// - 压缩级别配置
// - Content-Type 过滤
// - Accept-Encoding 协商
//
// 作者xfy
package e2e
import (
"bytes"
"compress/gzip"
"context"
"io"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"rua.plus/lolly/internal/e2e/testutil"
)
// TestE2ECompressionGzip 测试 Gzip 压缩响应。
func TestE2ECompressionGzip(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 创建包含可压缩内容的 HTML 文件
htmlContent := repeatString("<p>Hello World</p>", 100)
tmpDir := t.TempDir()
htmlPath := filepath.Join(tmpDir, "index.html")
require.NoError(t, os.WriteFile(htmlPath, []byte(htmlContent), 0o644))
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
compression:
enabled: true
types:
- "text/html"
- "text/css"
- "application/json"
`
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(config),
testutil.WithExtraMount(tmpDir, "/var/www/html"),
)
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 发送带 Accept-Encoding: gzip 的请求
req, err := http.NewRequest("GET", lolly.HTTPBaseURL()+"/", nil)
require.NoError(t, err)
req.Header.Set("Accept-Encoding", "gzip")
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// 检查响应是否被压缩
encoding := resp.Header.Get("Content-Encoding")
if encoding == "gzip" {
t.Log("Response is gzip compressed")
// 解压并验证内容
gzReader, err := gzip.NewReader(resp.Body)
require.NoError(t, err)
defer gzReader.Close()
body, err := io.ReadAll(gzReader)
require.NoError(t, err)
assert.Contains(t, string(body), "Hello World")
} else {
t.Logf("Response not compressed (Content-Encoding: %s), may be too small", encoding)
}
}
// TestE2ECompressionNoAcceptEncoding 测试不发送 Accept-Encoding 时不压缩。
func TestE2ECompressionNoAcceptEncoding(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
htmlContent := `<html><body><p>Test Content</p></body></html>`
tmpDir := t.TempDir()
htmlPath := filepath.Join(tmpDir, "index.html")
require.NoError(t, os.WriteFile(htmlPath, []byte(htmlContent), 0o644))
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
compression:
enabled: true
`
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(config),
testutil.WithExtraMount(tmpDir, "/var/www/html"),
)
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 不发送 Accept-Encoding
resp, err := client.Get(lolly.HTTPBaseURL() + "/")
require.NoError(t, err)
defer resp.Body.Close()
// 响应不应该被压缩
encoding := resp.Header.Get("Content-Encoding")
assert.NotEqual(t, "gzip", encoding, "Response should not be gzip compressed without Accept-Encoding")
}
// TestE2ECompressionDisabled 测试禁用压缩。
func TestE2ECompressionDisabled(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
htmlContent := repeatString("<p>Hello World</p>", 100)
tmpDir := t.TempDir()
htmlPath := filepath.Join(tmpDir, "index.html")
require.NoError(t, os.WriteFile(htmlPath, []byte(htmlContent), 0o644))
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
compression:
enabled: false
`
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(config),
testutil.WithExtraMount(tmpDir, "/var/www/html"),
)
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 发送带 Accept-Encoding: gzip 的请求
req, err := http.NewRequest("GET", lolly.HTTPBaseURL()+"/", nil)
require.NoError(t, err)
req.Header.Set("Accept-Encoding", "gzip")
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// 响应不应该被压缩
encoding := resp.Header.Get("Content-Encoding")
assert.NotEqual(t, "gzip", encoding, "Response should not be compressed when disabled")
}
// TestE2ECompressionPrecompressed 测试预压缩文件。
func TestE2ECompressionPrecompressed(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 创建原始文件和预压缩的 .gz 文件
originalContent := repeatString("<p>Hello World</p>", 100)
gzContent := gzipCompress(t, originalContent)
tmpDir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "test.js"), []byte(originalContent), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "test.js.gz"), gzContent, 0o644))
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
gzip_static: true
`
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(config),
testutil.WithExtraMount(tmpDir, "/var/www/html"),
)
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 请求预压缩文件
req, err := http.NewRequest("GET", lolly.HTTPBaseURL()+"/test.js", nil)
require.NoError(t, err)
req.Header.Set("Accept-Encoding", "gzip")
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// 如果支持预压缩,应该直接返回 .gz 文件
encoding := resp.Header.Get("Content-Encoding")
t.Logf("Content-Encoding: %s", encoding)
}
// 辅助函数
func repeatString(s string, n int) string {
var buf bytes.Buffer
for range n {
buf.WriteString(s)
}
return buf.String()
}
func gzipCompress(t *testing.T, content string) []byte {
t.Helper()
var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
_, err := gzWriter.Write([]byte(content))
require.NoError(t, err)
require.NoError(t, gzWriter.Close())
return buf.Bytes()
}