lolly/internal/app/app_test.go
xfy 909bd405d2 feat(converter,app): 添加 nginx 配置导入功能
- 新增 internal/converter/nginx 解析器和转换器
- main.go 添加 --import/-i 参数支持 nginx 配置导入
- app_test.go 添加导入功能相关测试

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 17:12:49 +08:00

1900 lines
46 KiB
Go
Raw 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 !windows
// Package app 提供应用程序功能的测试。
//
// 该文件测试应用程序模块的各项功能,包括:
// - 应用创建和配置
// - 信号处理SIGTERM、SIGHUP、SIGUSR1等
// - 配置重载
// - 日志重开
// - 版本输出
// - 优雅关闭
//
// 作者xfy
package app
import (
"bytes"
"crypto/tls"
"io"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/http2"
"rua.plus/lolly/internal/http3"
"rua.plus/lolly/internal/logging"
"rua.plus/lolly/internal/server"
"rua.plus/lolly/internal/version"
)
// captureStdout 捕获 stdout 输出,返回捕获的内容和恢复函数。
func captureStdout(t *testing.T) (func() string, func()) {
t.Helper()
old := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("创建 pipe 失败: %v", err)
}
os.Stdout = w
// 异步读取管道,避免死锁
var buf bytes.Buffer
done := make(chan struct{})
go func() {
defer close(done)
_, _ = io.Copy(&buf, r)
}()
return func() string {
_ = w.Close()
os.Stdout = old
<-done
return buf.String()
}, func() {
_ = w.Close()
os.Stdout = old
}
}
// captureStderr 捕获 stderr 输出,返回捕获的内容和恢复函数。
func captureStderr(t *testing.T) (func() string, func()) {
t.Helper()
old := os.Stderr
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("创建 pipe 失败: %v", err)
}
os.Stderr = w
// 异步读取管道,避免死锁
var buf bytes.Buffer
done := make(chan struct{})
go func() {
defer close(done)
_, _ = io.Copy(&buf, r)
}()
return func() string {
_ = w.Close()
os.Stderr = old
<-done
return buf.String()
}, func() {
_ = w.Close()
os.Stderr = old
}
}
// TestNewApp 测试 NewApp 构造器。
func TestNewApp(t *testing.T) {
cfgPath := "/path/to/config.yaml"
app := NewApp(cfgPath)
if app.cfgPath != cfgPath {
t.Errorf("cfgPath = %q, want %q", app.cfgPath, cfgPath)
}
if app.cfg != nil {
t.Error("新创建的 App cfg 应为 nil")
}
if app.srv != nil {
t.Error("新创建的 App srv 应为 nil")
}
if app.pidFile != "" {
t.Errorf("pidFile = %q, want empty", app.pidFile)
}
if app.logFile != "" {
t.Errorf("logFile = %q, want empty", app.logFile)
}
}
// TestSetPidFile 测试 SetPidFile setter 方法。
func TestSetPidFile(t *testing.T) {
app := NewApp("/test/config.yaml")
pidPath := "/var/run/lolly.pid"
app.SetPidFile(pidPath)
if app.pidFile != pidPath {
t.Errorf("pidFile = %q, want %q", app.pidFile, pidPath)
}
}
// TestSetLogFile 测试 SetLogFile setter 方法。
func TestSetLogFile(t *testing.T) {
app := NewApp("/test/config.yaml")
logPath := "/var/log/lolly.log"
app.SetLogFile(logPath)
if app.logFile != logPath {
t.Errorf("logFile = %q, want %q", app.logFile, logPath)
}
}
// TestSigName 测试信号名称辅助函数。
func TestSigName(t *testing.T) {
tests := []struct {
name string
expected string
sig syscall.Signal
}{
{
name: "SIGTERM",
sig: syscall.SIGTERM,
expected: "SIGTERM",
},
{
name: "SIGINT",
sig: syscall.SIGINT,
expected: "SIGINT",
},
{
name: "SIGQUIT",
sig: syscall.SIGQUIT,
expected: "SIGQUIT",
},
{
name: "SIGHUP",
sig: syscall.SIGHUP,
expected: "SIGHUP",
},
{
name: "SIGUSR1",
sig: syscall.SIGUSR1,
expected: "SIGUSR1",
},
{
name: "SIGUSR2",
sig: syscall.SIGUSR2,
expected: "SIGUSR2",
},
{
name: "未知信号",
sig: syscall.Signal(999),
expected: "Signal(999)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := sigName(tt.sig)
if result != tt.expected {
t.Errorf("sigName(%d) = %q, want %q", tt.sig, result, tt.expected)
}
})
}
}
func TestRun(t *testing.T) {
tests := []struct {
name string
cfgPath string
outputPath string
importPath string
wantContains string
wantErrContains string
wantExitCode int
genConfig bool
showVersion bool
}{
{
name: "显示版本",
showVersion: true,
wantExitCode: 0,
wantContains: "lolly version",
},
{
name: "生成配置输出到 stdout",
genConfig: true,
outputPath: "",
wantExitCode: 0,
wantContains: "servers:",
},
{
name: "生成配置输出到文件",
genConfig: true,
outputPath: filepath.Join(t.TempDir(), "config.yaml"),
wantExitCode: 0,
wantContains: "配置已写入:",
},
{
name: "配置文件不存在",
cfgPath: filepath.Join(t.TempDir(), "nonexistent.yaml"),
genConfig: false,
showVersion: false,
wantExitCode: 1,
wantErrContains: "加载配置失败",
},
{
name: "generate 与 import 互斥",
genConfig: true,
importPath: "/tmp/nginx.conf",
wantExitCode: 1,
wantErrContains: "mutually exclusive",
},
{
name: "o 参数无 generate 或 import",
outputPath: "output.yaml",
wantExitCode: 1,
wantErrContains: "-o requires",
},
{
name: "导入 nginx 配置文件不存在",
importPath: "/tmp/nginx.conf",
wantExitCode: 1,
wantErrContains: "解析 nginx 配置失败",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getStdout, restoreStdout := captureStdout(t)
getStderr, restoreStderr := captureStderr(t)
exitCode := Run(tt.cfgPath, tt.genConfig, tt.outputPath, tt.importPath, tt.showVersion)
restoreStderr()
restoreStdout()
stdout := getStdout()
stderr := getStderr()
if exitCode != tt.wantExitCode {
t.Errorf("exit code = %d, want %d", exitCode, tt.wantExitCode)
}
if tt.wantContains != "" && !strings.Contains(stdout, tt.wantContains) {
t.Errorf("stdout 应包含 %q, 实际输出: %q", tt.wantContains, stdout)
}
if tt.wantErrContains != "" && !strings.Contains(stderr, tt.wantErrContains) {
t.Errorf("stderr 应包含 %q, 实际输出: %q", tt.wantErrContains, stderr)
}
// 验证生成配置文件的内容
if tt.outputPath != "" && tt.genConfig && exitCode == 0 {
data, err := os.ReadFile(tt.outputPath)
if err != nil {
t.Errorf("读取生成的配置文件失败: %v", err)
} else if !strings.Contains(string(data), "servers:") {
t.Errorf("生成的配置文件应包含 'servers:', 实际内容: %s", string(data)[:100])
}
}
})
}
}
// TestGenerateConfig 测试 generateConfig 函数。
func TestGenerateConfig(t *testing.T) {
t.Run("输出到 stdout", func(t *testing.T) {
getStdout, restoreStdout := captureStdout(t)
exitCode := generateConfig("")
restoreStdout()
stdout := getStdout()
if exitCode != 0 {
t.Errorf("exit code = %d, want 0", exitCode)
}
// 验证输出包含基本配置结构
expectedFields := []string{"servers:", "listen:", "logging:", "performance:", "monitoring:"}
for _, field := range expectedFields {
if !strings.Contains(stdout, field) {
t.Errorf("输出应包含 %q", field)
}
}
})
t.Run("输出到文件", func(t *testing.T) {
tmpDir := t.TempDir()
outputPath := filepath.Join(tmpDir, "test-config.yaml")
getStdout, restoreStdout := captureStdout(t)
exitCode := generateConfig(outputPath)
restoreStdout()
stdout := getStdout()
if exitCode != 0 {
t.Errorf("exit code = %d, want 0", exitCode)
}
if !strings.Contains(stdout, outputPath) {
t.Errorf("stdout 应包含文件路径 %q, 实际输出: %q", outputPath, stdout)
}
// 验证文件存在且内容正确
data, err := os.ReadFile(outputPath)
if err != nil {
t.Fatalf("读取生成的配置文件失败: %v", err)
}
content := string(data)
expectedFields := []string{"servers:", "listen:", "logging:", "performance:", "monitoring:"}
for _, field := range expectedFields {
if !strings.Contains(content, field) {
t.Errorf("配置文件应包含 %q", field)
}
}
})
t.Run("输出到无效路径", func(t *testing.T) {
// Skip if running as root - root can write anywhere
if os.Getuid() == 0 {
t.Skip("Skipping permission test when running as root")
}
// 使用一个无法写入的路径(如根目录下的文件)
invalidPath := "/root/cannot-write-here.yaml"
getStderr, restoreStderr := captureStderr(t)
exitCode := generateConfig(invalidPath)
restoreStderr()
stderr := getStderr()
if exitCode != 1 {
t.Errorf("exit code = %d, want 1", exitCode)
}
if !strings.Contains(stderr, "写入文件失败") {
t.Errorf("stderr 应包含 '写入文件失败', 实际输出: %q", stderr)
}
})
}
// TestPrintVersion 测试 printVersion 函数。
func TestPrintVersion(t *testing.T) {
getStdout, restoreStdout := captureStdout(t)
printVersion()
restoreStdout()
stdout := getStdout()
// 验证版本输出格式
expectedLines := []string{
"lolly version",
"Git:",
"Built:",
"Go:",
"Platform:",
}
for _, line := range expectedLines {
if !strings.Contains(stdout, line) {
t.Errorf("版本输出应包含 %q, 实际输出: %q", line, stdout)
}
}
}
// TestHandleSignal_SIGQUIT 测试 SIGQUIT 信号处理(优雅停止)
func TestHandleSignal_SIGQUIT(t *testing.T) {
// 创建一个简单的 App
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0", // 使用随机端口
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 创建 mock server
app.srv = server.New(app.cfg)
// 测试 SIGQUIT 处理
result := app.handleSignal(syscall.SIGQUIT)
if result != false {
t.Error("Expected handleSignal(SIGQUIT) to return false (stop)")
}
}
// TestHandleSignal_SIGTERM 测试 SIGTERM 信号处理(快速停止)
func TestHandleSignal_SIGTERM(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGTERM)
if result != false {
t.Error("Expected handleSignal(SIGTERM) to return false (stop)")
}
}
// TestHandleSignal_SIGINT 测试 SIGINT 信号处理(快速停止)
func TestHandleSignal_SIGINT(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGINT)
if result != false {
t.Error("Expected handleSignal(SIGINT) to return false (stop)")
}
}
// TestHandleSignal_SIGHUP 测试 SIGHUP 信号处理(重载配置)
func TestHandleSignal_SIGHUP(t *testing.T) {
// 创建临时配置文件
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":8080"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":8080",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
result := app.handleSignal(syscall.SIGHUP)
if result != true {
t.Error("Expected handleSignal(SIGHUP) to return true (continue)")
}
}
// TestHandleSignal_SIGUSR1 测试 SIGUSR1 信号处理(重开日志)
func TestHandleSignal_SIGUSR1(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":8080",
}},
Logging: config.LoggingConfig{
Error: config.ErrorLogConfig{
Level: "info",
},
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
result := app.handleSignal(syscall.SIGUSR1)
if result != true {
t.Error("Expected handleSignal(SIGUSR1) to return true (continue)")
}
}
// TestHandleSignal_Unknown 测试未知信号处理
func TestHandleSignal_Unknown(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":8080",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 使用一个未处理的信号
result := app.handleSignal(syscall.SIGCHLD)
if result != true {
t.Error("Expected handleSignal(unknown) to return true (continue)")
}
}
// TestShutdownHTTP3_NilServer 测试 HTTP/3 服务器为 nil 时关闭
func TestShutdownHTTP3_NilServer(_ *testing.T) {
app := NewApp("")
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 不应 panic
app.shutdownHTTP3()
}
// TestReopenLogs 测试重开日志
func TestReopenLogs(_ *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Logging: config.LoggingConfig{
Error: config.ErrorLogConfig{
Level: "info",
},
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 不应 panic
app.reopenLogs()
}
// TestReloadConfig_FileNotFound 测试重载不存在的配置
func TestReloadConfig_FileNotFound(_ *testing.T) {
app := NewApp("/nonexistent/config.yaml")
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 不应 panic只是记录错误
app.reloadConfig()
}
// TestReloadConfig_Success 测试成功重载配置
func TestReloadConfig_Success(t *testing.T) {
// 创建临时配置文件
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":9090"
logging:
error:
level: "debug"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":8080",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.reloadConfig()
// 验证配置已更新
if app.cfg.Servers[0].Listen != ":9090" {
t.Errorf("Expected listen ':9090', got '%s'", app.cfg.Servers[0].Listen)
}
}
// TestSetupSignalHandlers 测试信号处理设置
func TestSetupSignalHandlers(_ *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
sigChan := make(chan os.Signal, 1)
app.setupSignalHandlers(sigChan)
// 验证信号通道已设置(无法直接验证 signal.Notify但可确认函数执行成功
}
// TestHandleSignal_SIGUSR2 测试 SIGUSR2 信号处理(热升级)
func TestHandleSignal_SIGUSR2(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
app.upgradeMgr = server.NewUpgradeManager(app.srv)
// SIGUSR2 处理应返回 true继续运行
result := app.handleSignal(syscall.SIGUSR2)
if result != true {
t.Error("Expected handleSignal(SIGUSR2) to return true (continue)")
}
}
// TestGracefulUpgrade_NoListener 测试无监听器时的热升级
func TestGracefulUpgrade_NoListener(_ *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
app.upgradeMgr = server.NewUpgradeManager(app.srv)
// 在没有监听器的情况下执行热升级
app.gracefulUpgrade()
// 应记录错误但不 panic
}
// TestVersionVariables 测试版本变量默认值
func TestVersionVariables(t *testing.T) {
if version.Version != "dev" {
t.Errorf("Default Version should be 'dev', got '%s'", version.Version)
}
if version.GitCommit != "unknown" {
t.Errorf("Default GitCommit should be 'unknown', got '%s'", version.GitCommit)
}
if version.GitBranch != "unknown" {
t.Errorf("Default GitBranch should be 'unknown', got '%s'", version.GitBranch)
}
if version.BuildTime != "unknown" {
t.Errorf("Default BuildTime should be 'unknown', got '%s'", version.BuildTime)
}
if version.GoVersion != "unknown" {
t.Errorf("Default GoVersion should be 'unknown', got '%s'", version.GoVersion)
}
if version.BuildPlatform != "unknown" {
t.Errorf("Default BuildPlatform should be 'unknown', got '%s'", version.BuildPlatform)
}
}
// TestAppFields 测试 App 结构体字段初始化
func TestAppFields(t *testing.T) {
app := NewApp("/test/config.yaml")
// 验证初始状态
if app.cfgPath != "/test/config.yaml" {
t.Errorf("cfgPath = %q, want %q", app.cfgPath, "/test/config.yaml")
}
if app.cfg != nil {
t.Error("cfg should be nil initially")
}
if app.srv != nil {
t.Error("srv should be nil initially")
}
if app.http3Srv != nil {
t.Error("http3Srv should be nil initially")
}
if app.streamSrv != nil {
t.Error("streamSrv should be nil initially")
}
if app.upgradeMgr != nil {
t.Error("upgradeMgr should be nil initially")
}
if len(app.listeners) != 0 {
t.Error("listeners should be empty initially")
}
}
// TestShutdownHTTP3_WithServer 测试有 HTTP3 服务器时的关闭
func TestShutdownHTTP3_WithServer(_ *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
}},
HTTP3: config.HTTP3Config{
Enabled: false, // 禁用,避免实际启动
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
// 创建但不启动 http3 服务器
app.http3Srv = nil // 确保为 nil
app.shutdownHTTP3()
// 应正常执行无 panic
}
// TestReopenLogs_WithNilConfig 测试配置为 nil 时重开日志
func TestReopenLogs_WithNilConfig(_ *testing.T) {
app := NewApp("")
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.reopenLogs()
// 应正常执行无 panic
}
// TestReloadConfig_WithValidConfig 测试多次重载配置
func TestReloadConfig_WithValidConfig(t *testing.T) {
tmpDir := t.TempDir()
// 创建第一个配置
cfgPath1 := filepath.Join(tmpDir, "config1.yaml")
cfgContent1 := `
servers:
- listen: ":8080"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath1, []byte(cfgContent1), 0o644); err != nil {
t.Fatalf("Failed to write config1: %v", err)
}
// 创建第二个配置
cfgPath2 := filepath.Join(tmpDir, "config2.yaml")
cfgContent2 := `
servers:
- listen: ":9090"
logging:
error:
level: "debug"
`
if err := os.WriteFile(cfgPath2, []byte(cfgContent2), 0o644); err != nil {
t.Fatalf("Failed to write config2: %v", err)
}
app := NewApp(cfgPath1)
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":7070",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 第一次重载
app.reloadConfig()
if app.cfg.Servers[0].Listen != ":8080" {
t.Errorf("After first reload: listen = %q, want :8080", app.cfg.Servers[0].Listen)
}
// 更改配置路径并重载
app.cfgPath = cfgPath2
app.reloadConfig()
if app.cfg.Servers[0].Listen != ":9090" {
t.Errorf("After second reload: listen = %q, want :9090", app.cfg.Servers[0].Listen)
}
}
// TestHandleSignal_AllSignals 测试所有信号类型
func TestHandleSignal_AllSignals(t *testing.T) {
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":8080"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
tests := []struct {
name string
sig syscall.Signal
wantResult bool
}{
{"SIGQUIT - graceful stop", syscall.SIGQUIT, false},
{"SIGTERM - fast stop", syscall.SIGTERM, false},
{"SIGINT - fast stop", syscall.SIGINT, false},
{"SIGHUP - reload", syscall.SIGHUP, true},
{"SIGUSR1 - reopen logs", syscall.SIGUSR1, true},
{"SIGUSR2 - upgrade", syscall.SIGUSR2, true},
{"SIGCHLD - unknown", syscall.SIGCHLD, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := NewApp(cfgPath)
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
app.upgradeMgr = server.NewUpgradeManager(app.srv)
result := app.handleSignal(tt.sig)
if result != tt.wantResult {
t.Errorf("handleSignal(%v) = %v, want %v", tt.sig, result, tt.wantResult)
}
})
}
}
// TestHandleSignal_NilConfig 测试信号处理时配置为 nil 的防御性检查
func TestHandleSignal_NilConfig(t *testing.T) {
tests := []struct {
name string
sig syscall.Signal
wantResult bool
}{
{"SIGQUIT with nil config", syscall.SIGQUIT, false},
{"SIGTERM with nil config", syscall.SIGTERM, false},
{"SIGINT with nil config", syscall.SIGINT, false},
{"SIGHUP with nil config", syscall.SIGHUP, true},
{"SIGUSR1 with nil config", syscall.SIGUSR1, true},
{"SIGUSR2 with nil config", syscall.SIGUSR2, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := NewApp("")
// 故意不设置 cfg保持 nil
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(config.DefaultConfig())
app.upgradeMgr = server.NewUpgradeManager(app.srv)
result := app.handleSignal(tt.sig)
if result != tt.wantResult {
t.Errorf("handleSignal(%v) with nil config = %v, want %v", tt.sig, result, tt.wantResult)
}
})
}
}
// TestHandleSignal_TimeoutDefaults 测试信号处理中超时默认值
func TestHandleSignal_TimeoutDefaults(t *testing.T) {
t.Run("SIGQUIT with zero graceful timeout", func(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
GracefulTimeout: 0, // 使用默认值
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGQUIT)
// 验证函数正常执行,不 panic
if result != false {
t.Error("Expected false for SIGQUIT")
}
})
t.Run("SIGTERM with zero fast timeout", func(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
FastTimeout: 0, // 使用默认值
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGTERM)
if result != false {
t.Error("Expected false for SIGTERM")
}
})
t.Run("SIGQUIT with negative graceful timeout", func(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
GracefulTimeout: -1 * time.Second, // 负数也使用默认值
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGQUIT)
if result != false {
t.Error("Expected false for SIGQUIT")
}
})
t.Run("SIGTERM with negative fast timeout", func(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
FastTimeout: -1 * time.Second,
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGTERM)
if result != false {
t.Error("Expected false for SIGTERM")
}
})
}
// TestGracefulUpgrade_NilServer 测试服务器为 nil 时的热升级
func TestGracefulUpgrade_NilServer(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 故意不设置 srv保持 nil
app.gracefulUpgrade()
// 应记录错误但不 panic
}
// TestShutdownHTTP2_WithServer 测试有 HTTP/2 服务器时的关闭
func TestShutdownHTTP2_WithServer(t *testing.T) {
t.Run("nil server", func(t *testing.T) {
app := NewApp("")
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.http2Srv = nil
app.shutdownHTTP2()
// 应正常执行无 panic
})
t.Run("with stopped server", func(t *testing.T) {
app := NewApp("")
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 创建一个 HTTP/2 服务器(不启动)
h2Cfg := &config.HTTP2Config{
MaxConcurrentStreams: 250,
}
h2Srv, err := http2.NewServer(h2Cfg, func(_ *fasthttp.RequestCtx) {}, nil)
if err != nil {
t.Fatalf("Failed to create HTTP/2 server: %v", err)
}
app.http2Srv = h2Srv
app.shutdownHTTP2()
// 应正常执行无 panic
})
}
// TestShutdownHTTP3_WithActualServer 测试有实际 HTTP/3 服务器时的关闭
func TestShutdownHTTP3_WithActualServer(t *testing.T) {
t.Run("with stopped server", func(t *testing.T) {
app := NewApp("")
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 创建一个 HTTP/3 服务器(不启动)
h3Cfg := &config.HTTP3Config{
Listen: ":0",
}
// 创建一个简单的 TLS 配置用于测试
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{},
}
h3Srv, err := http3.NewServer(h3Cfg, func(_ *fasthttp.RequestCtx) {}, tlsConfig)
if err != nil {
// 如果创建失败(例如缺少证书),跳过测试
t.Skipf("Failed to create HTTP/3 server: %v", err)
}
app.http3Srv = h3Srv
app.shutdownHTTP3()
// 应正常执行无 panic
})
}
// TestHandleSignal_SignalTypeAssertion 测试信号类型断言失败的情况
func TestHandleSignal_SignalTypeAssertion(t *testing.T) {
// 创建一个自定义的 os.Signal 实现来测试类型断言失败路径
customSignal := customSig{}
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
// handleSignal 期望 syscall.Signal传入自定义信号会触发类型断言失败
// 注意:由于 handleSignal 的 switch 语句会匹配具体信号,自定义信号会走到 default 分支
// 所以我们需要用一个匹配 SIGTERM/SIGINT case 但类型断言会失败的信号
// 实际上这种场景很难构造,因为 switch case 已经限定了信号类型
// 验证 customSignal 走到 default 分支
result := app.handleSignal(customSignal)
if result != true {
t.Error("Expected true for unknown signal")
}
}
// customSig 实现自定义信号类型用于测试
type customSig struct{}
func (customSig) String() string { return "custom" }
func (customSig) Signal() {}
// TestHandleSignal_PositiveTimeout 测试信号处理中使用配置的超时值
func TestHandleSignal_PositiveTimeout(t *testing.T) {
t.Run("SIGQUIT with positive graceful timeout", func(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
GracefulTimeout: 5 * time.Second, // 正数超时值
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGQUIT)
if result != false {
t.Error("Expected false for SIGQUIT")
}
})
t.Run("SIGTERM with positive fast timeout", func(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
FastTimeout: 3 * time.Second, // 正数超时值
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
result := app.handleSignal(syscall.SIGTERM)
if result != false {
t.Error("Expected false for SIGTERM")
}
})
}
// TestGracefulUpgrade_PositiveTimeout 测试热升级时使用配置的超时值
func TestGracefulUpgrade_PositiveTimeout(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
GracefulTimeout: 5 * time.Second, // 正数超时值
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
app.upgradeMgr = server.NewUpgradeManager(app.srv)
// 无监听器时会失败,但会使用配置的超时值
app.gracefulUpgrade()
// 应正常执行无 panic
}
// TestGracefulUpgrade_ZeroTimeout 测试热升级时使用零超时值(使用默认值)
func TestGracefulUpgrade_ZeroTimeout(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Shutdown: config.ShutdownConfig{
GracefulTimeout: 0, // 零值,使用默认 30s
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
app.upgradeMgr = server.NewUpgradeManager(app.srv)
app.gracefulUpgrade()
// 应正常执行无 panic
}
// TestSetPidFileAndWrite 测试 PID 文件设置和写入
func TestSetPidFileAndWrite(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
tmpDir := t.TempDir()
pidPath := filepath.Join(tmpDir, "test.pid")
app.SetPidFile(pidPath)
if app.pidFile != pidPath {
t.Errorf("pidFile = %q, want %q", app.pidFile, pidPath)
}
// 测试升级管理器写入 PID
app.upgradeMgr = server.NewUpgradeManager(app.srv)
app.upgradeMgr.SetPidFile(pidPath)
if err := app.upgradeMgr.WritePid(); err != nil {
t.Errorf("WritePid failed: %v", err)
}
// 验证 PID 文件已创建
if _, err := os.Stat(pidPath); os.IsNotExist(err) {
t.Error("PID file was not created")
}
}
// TestApp_LoggerOperations 测试 App 日志操作
func TestApp_LoggerOperations(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":8080"}},
Logging: config.LoggingConfig{
Error: config.ErrorLogConfig{
Level: "debug",
},
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 测试各种日志方法
app.logger.LogStartup("测试启动", map[string]string{"key": "value"})
app.logger.LogShutdown("测试关闭")
app.logger.LogSignal("SIGTERM", "测试信号")
app.logger.Info().Msg("测试信息")
app.logger.Error().Msg("测试错误")
}
// TestApp_Variables 测试全局变量加载
func TestApp_Variables(t *testing.T) {
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":8080"
variables:
set:
APP_NAME: "lolly"
DEBUG: "true"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
app.cfg = config.DefaultConfig()
app.cfg.Variables.Set = map[string]string{
"TEST_VAR": "test_value",
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 验证变量配置存在
if len(app.cfg.Variables.Set) != 1 {
t.Errorf("Expected 1 variable, got %d", len(app.cfg.Variables.Set))
}
}
// TestHandleSignal_AllSignalsWithServer 测试所有信号与服务器
func TestHandleSignal_AllSignalsWithServer(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
SSL: config.SSLConfig{
HTTP2: config.HTTP2Config{
Enabled: false,
},
},
}},
HTTP3: config.HTTP3Config{
Enabled: false,
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
app.upgradeMgr = server.NewUpgradeManager(app.srv)
// 测试所有信号
signals := []struct {
sig syscall.Signal
wantResult bool
}{
{syscall.SIGQUIT, false},
{syscall.SIGTERM, false},
{syscall.SIGINT, false},
{syscall.SIGHUP, true},
{syscall.SIGUSR1, true},
{syscall.SIGUSR2, true},
}
for _, tc := range signals {
result := app.handleSignal(tc.sig)
if result != tc.wantResult {
t.Errorf("handleSignal(%v) = %v, want %v", tc.sig, result, tc.wantResult)
}
}
}
// TestGenerateConfig_ErrorCase 测试生成配置时的错误情况
// 注意config.GenerateConfigYAML 正常情况下不会失败,
// 但我们测试文件写入失败的情况
func TestGenerateConfig_ErrorCase(t *testing.T) {
t.Run("写入无效路径", func(t *testing.T) {
getStderr, restoreStderr := captureStderr(t)
exitCode := generateConfig("/nonexistent/dir/config.yaml")
restoreStderr()
stderr := getStderr()
if exitCode != 1 {
t.Errorf("exit code = %d, want 1", exitCode)
}
if !strings.Contains(stderr, "写入文件失败") {
t.Errorf("stderr 应包含 '写入文件失败', 实际输出: %q", stderr)
}
})
}
// TestApp_ResolverEnabled 测试启用 DNS 解析器
func TestApp_ResolverEnabled(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Resolver: config.ResolverConfig{
Enabled: true,
Addresses: []string{"8.8.8.8:53"},
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 验证配置
if !app.cfg.Resolver.Enabled {
t.Error("Resolver should be enabled")
}
if len(app.cfg.Resolver.Addresses) != 1 {
t.Errorf("Expected 1 resolver address, got %d", len(app.cfg.Resolver.Addresses))
}
}
// TestApp_MultiServerMode 测试多服务器模式
func TestApp_MultiServerMode(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{
{Listen: ":8080", Name: "server1"},
{Listen: ":8081", Name: "server2"},
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 验证多服务器配置
if len(app.cfg.Servers) != 2 {
t.Errorf("Expected 2 servers, got %d", len(app.cfg.Servers))
}
mode := app.cfg.GetMode()
if mode != config.ServerModeMultiServer {
t.Errorf("Expected multi-server mode, got %v", mode)
}
}
// TestApp_StreamConfig 测试 Stream 配置
func TestApp_StreamConfig(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
Stream: []config.StreamConfig{
{
Listen: ":9090",
Protocol: "tcp",
Upstream: config.StreamUpstream{
Targets: []config.StreamTarget{
{Addr: "127.0.0.1:9091", Weight: 1},
},
LoadBalance: "round_robin",
},
},
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 验证 Stream 配置
if len(app.cfg.Stream) != 1 {
t.Errorf("Expected 1 stream config, got %d", len(app.cfg.Stream))
}
}
// TestApp_HTTP3Config 测试 HTTP/3 配置
func TestApp_HTTP3Config(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
SSL: config.SSLConfig{
Cert: "/path/to/cert.pem",
Key: "/path/to/key.pem",
},
}},
HTTP3: config.HTTP3Config{
Enabled: true,
Listen: ":443",
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 验证 HTTP/3 配置
if !app.cfg.HTTP3.Enabled {
t.Error("HTTP/3 should be enabled")
}
}
// TestApp_HTTP2Config 测试 HTTP/2 配置
func TestApp_HTTP2Config(t *testing.T) {
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{
Listen: ":0",
SSL: config.SSLConfig{
Cert: "/path/to/cert.pem",
Key: "/path/to/key.pem",
HTTP2: config.HTTP2Config{
Enabled: true,
MaxConcurrentStreams: 100,
PushEnabled: true,
},
},
}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 验证 HTTP/2 配置
if !app.cfg.Servers[0].SSL.HTTP2.Enabled {
t.Error("HTTP/2 should be enabled")
}
}
// TestGracefulUpgrade_GetExecutableError 测试获取可执行文件路径失败的情况
// 注意os.Executable 正常情况下不会失败,此测试验证错误处理路径存在
func TestGracefulUpgrade_GetExecutableError(t *testing.T) {
// 此测试验证代码路径存在,但实际很难触发 os.Executable 失败
app := NewApp("")
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":0"}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
app.srv = server.New(app.cfg)
app.upgradeMgr = server.NewUpgradeManager(app.srv)
// 正常情况下会获取到可执行文件路径,但无监听器会失败
app.gracefulUpgrade()
// 验证不会 panic
}
// TestReloadConfig_UpdateLogger 测试重载配置后更新日志器
func TestReloadConfig_UpdateLogger(t *testing.T) {
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":9090"
logging:
error:
level: "debug"
format: "json"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":8080"}},
Logging: config.LoggingConfig{
Error: config.ErrorLogConfig{Level: "info"},
Format: "text",
},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 重载配置
app.reloadConfig()
// 验证配置已更新
if app.cfg.Servers[0].Listen != ":9090" {
t.Errorf("Expected listen ':9090', got '%s'", app.cfg.Servers[0].Listen)
}
// 验证日志器已更新(通过新配置创建)
if app.cfg.Logging.Format != "json" {
t.Errorf("Expected format 'json', got '%s'", app.cfg.Logging.Format)
}
}
// TestHandleSignal_SIGHUP_WithValidConfigFile 测试 SIGHUP 重载有效配置文件
func TestHandleSignal_SIGHUP_WithValidConfigFile(t *testing.T) {
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":7070"
logging:
error:
level: "debug"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
app.cfg = &config.Config{
Servers: []config.ServerConfig{{Listen: ":8080"}},
}
app.logger = logging.NewAppLogger(&config.LoggingConfig{})
// 发送 SIGHUP 信号
result := app.handleSignal(syscall.SIGHUP)
if result != true {
t.Error("Expected handleSignal(SIGHUP) to return true")
}
// 验证配置已更新
if app.cfg.Servers[0].Listen != ":7070" {
t.Errorf("Expected listen ':7070', got '%s'", app.cfg.Servers[0].Listen)
}
}
// TestApp_Run_WithValidConfig 测试 App.Run 加载有效配置
// 注意:此测试验证配置加载路径,但由于服务器启动会阻塞,
// 我们通过子进程测试或使用 short 标志跳过完整运行
func TestApp_Run_WithValidConfig(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
// 在 goroutine 中启动服务器
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
// 等待一小段时间让服务器启动
time.Sleep(100 * time.Millisecond)
// 验证配置已加载
if app.cfg == nil {
t.Error("Config should be loaded")
}
// 验证服务器已创建
if app.srv == nil {
t.Error("Server should be created")
}
// 验证 logger 已创建
if app.logger == nil {
t.Error("Logger should be created")
}
// 验证升级管理器已创建
if app.upgradeMgr == nil {
t.Error("Upgrade manager should be created")
}
// 发送 SIGTERM 信号停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_WithVariables 测试 App.Run 加载全局变量
func TestApp_Run_WithVariables(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
variables:
set:
TEST_VAR: "test_value"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证配置已加载且包含变量
if app.cfg == nil {
t.Error("Config should be loaded")
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_WithResolver 测试 App.Run 启用 DNS 解析器
func TestApp_Run_WithResolver(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
resolver:
enabled: true
addresses:
- "8.8.8.8:53"
ipv4: true
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证 DNS 解析器已创建
if app.resv == nil {
t.Error("Resolver should be created when enabled")
}
// 验证服务器已创建
if app.srv == nil {
t.Error("Server should be created")
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_MultiServer 测试 App.Run 多服务器模式
func TestApp_Run_MultiServer(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
name: "server1"
- listen: ":0"
name: "server2"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证多服务器配置
if app.cfg == nil {
t.Error("Config should be loaded")
}
if len(app.cfg.Servers) != 2 {
t.Errorf("Expected 2 servers, got %d", len(app.cfg.Servers))
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_WithPidFile 测试 App.Run 设置 PID 文件
func TestApp_Run_WithPidFile(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
pidPath := filepath.Join(tmpDir, "lolly.pid")
app := NewApp(cfgPath)
app.SetPidFile(pidPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证 PID 文件已创建
if _, err := os.Stat(pidPath); os.IsNotExist(err) {
t.Error("PID file should be created")
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_WithStreamConfig 测试 App.Run 配置 Stream 服务器
func TestApp_Run_WithStreamConfig(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
stream:
- listen: ":19090"
protocol: tcp
upstream:
targets:
- addr: "127.0.0.1:19091"
weight: 1
load_balance: round_robin
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(150 * time.Millisecond)
// 验证 Stream 服务器已创建
if app.streamSrv == nil {
t.Error("Stream server should be created when configured")
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_GracefulUpgradeMode 测试 App.Run 热升级子进程模式
func TestApp_Run_GracefulUpgradeMode(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
// 设置热升级模式环境变量
t.Setenv("GRACEFUL_UPGRADE", "1")
app := NewApp(cfgPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证升级管理器已创建
if app.upgradeMgr == nil {
t.Error("Upgrade manager should be created in graceful upgrade mode")
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_WithLogFile 测试 App.Run 设置日志文件
func TestApp_Run_WithLogFile(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
logPath := filepath.Join(tmpDir, "lolly.log")
app := NewApp(cfgPath)
app.SetLogFile(logPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证日志文件路径已设置
if app.logFile != logPath {
t.Errorf("logFile = %q, want %q", app.logFile, logPath)
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_WithZeroTimeout 测试 App.Run 使用零超时配置(会使用默认值)
func TestApp_Run_WithZeroTimeout(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
shutdown:
graceful_timeout: 0s
fast_timeout: 0s
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证配置已加载
if app.cfg == nil {
t.Error("Config should be loaded")
}
// 停止服务器(会使用默认超时值)
app.srv.StopWithTimeout(1 * time.Second)
}
// TestApp_Run_WithExplicitTimeout 测试 App.Run 使用显式超时配置
func TestApp_Run_WithExplicitTimeout(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "config.yaml")
cfgContent := `
servers:
- listen: ":0"
shutdown:
graceful_timeout: 10s
fast_timeout: 5s
logging:
error:
level: "info"
`
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
app := NewApp(cfgPath)
done := make(chan int, 1)
go func() {
done <- app.Run()
}()
time.Sleep(100 * time.Millisecond)
// 验证配置已加载
if app.cfg == nil {
t.Error("Config should be loaded")
}
// 验证超时值
if app.cfg.Shutdown.GracefulTimeout != 10*time.Second {
t.Errorf("GracefulTimeout = %v, want 10s", app.cfg.Shutdown.GracefulTimeout)
}
// 停止服务器
app.srv.StopWithTimeout(1 * time.Second)
}