package logging import ( "fmt" "os" "path/filepath" "strings" "sync" "testing" "time" ) /* logger_test.go - 日志系统测试 测试目标:Logger核心功能 价值:日志是程序的眼睛,错误会导致: - 关键信息丢失(用户看不到错误) - 性能问题(并发日志混乱) - 调试困难(时间格式错误) "日志不是可选功能。日志丢失或错误,等于程序在撒谎。 测试必须验证:过滤正确、格式正确、并发安全。" */ // ============================================================================= // 测试辅助函数 // ============================================================================= // captureOutput 捕获日志输出(不污染控制台) type captureOutput struct { mu sync.Mutex output []string } func (c *captureOutput) Write(msg string) { c.mu.Lock() defer c.mu.Unlock() c.output = append(c.output, msg) } func (c *captureOutput) Get() []string { c.mu.Lock() defer c.mu.Unlock() result := make([]string, len(c.output)) copy(result, c.output) return result } func (c *captureOutput) Clear() { c.mu.Lock() defer c.mu.Unlock() c.output = nil } // createTestLogger 创建测试用Logger(捕获输出) func createTestLogger(level LogLevel, enableColor bool) (*Logger, *captureOutput) { capture := &captureOutput{} config := &LoggerConfig{ Level: level, EnableColor: enableColor, SlowOutput: false, // 测试时禁用慢速输出 ShowProgress: false, StartTime: time.Now(), LevelColors: GetDefaultLevelColors(), } logger := NewLogger(config) logger.SetCoordinatedOutput(capture.Write) return logger, capture } // ============================================================================= // Logger - 基础功能测试 // ============================================================================= // TestNewLogger_DefaultConfig 测试默认配置 func TestNewLogger_DefaultConfig(t *testing.T) { // nil配置应该使用默认值 logger := NewLogger(nil) if logger == nil { t.Fatal("NewLogger(nil) 应该返回有效的logger") } if logger.config == nil { t.Error("config不应为nil(应使用默认配置)") } if logger.config.Level != DefaultLevel { t.Errorf("默认Level = %v, want %v", logger.config.Level, DefaultLevel) } if !logger.initialized { t.Error("logger应该已初始化") } t.Logf("✓ 默认配置测试通过") } // TestNewLogger_CustomConfig 测试自定义配置 func TestNewLogger_CustomConfig(t *testing.T) { config := &LoggerConfig{ Level: LevelError, EnableColor: false, SlowOutput: true, ShowProgress: false, StartTime: time.Now(), LevelColors: GetDefaultLevelColors(), } logger := NewLogger(config) if logger.config.Level != LevelError { t.Errorf("Level = %v, want %v", logger.config.Level, LevelError) } if logger.config.EnableColor { t.Error("EnableColor应该为false") } t.Logf("✓ 自定义配置测试通过") } // TestLogger_AllLevels 测试所有日志级别 // // 验证:每个级别都能正确输出 func TestLogger_AllLevels(t *testing.T) { logger, capture := createTestLogger(LevelAll, false) tests := []struct { name string logFunc func(string) message string wantMsg string wantPfx string }{ { name: "Debug级别", logFunc: logger.Debug, message: "debug message", wantMsg: "debug message", wantPfx: PrefixDebug, }, { name: "Base级别", logFunc: logger.Base, message: "base message", wantMsg: "base message", wantPfx: PrefixInfo, // Base 已废弃,默认使用 Info 前缀 }, { name: "Info级别", logFunc: logger.Info, message: "info message", wantMsg: "info message", wantPfx: PrefixInfo, }, { name: "Success级别", logFunc: logger.Success, message: "success message", wantMsg: "success message", wantPfx: PrefixSuccess, }, { name: "Vuln级别", logFunc: logger.Vuln, message: "vuln message", wantMsg: "vuln message", wantPfx: PrefixVuln, }, { name: "Error级别", logFunc: logger.Error, message: "error message", wantMsg: "error message", wantPfx: PrefixError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { capture.Clear() tt.logFunc(tt.message) output := capture.Get() if len(output) != 1 { t.Fatalf("期望1条输出,实际%d条", len(output)) } msg := output[0] if !strings.Contains(msg, tt.wantMsg) { t.Errorf("输出缺少消息: %s\n实际: %s", tt.wantMsg, msg) } if !strings.Contains(msg, tt.wantPfx) { t.Errorf("输出缺少前缀: %s\n实际: %s", tt.wantPfx, msg) } // 验证输出格式:前缀 + 空格 + 消息 if !strings.HasPrefix(msg, tt.wantPfx) { t.Errorf("输出应该以前缀开头: %s\n实际: %s", tt.wantPfx, msg) } t.Logf("✓ %s 输出正确: %s", tt.name, msg) }) } } // ============================================================================= // Logger - 级别过滤测试 // ============================================================================= // TestLogger_LevelFiltering 测试日志级别过滤 // // 验证:不同级别配置下,只输出对应级别的日志 func TestLogger_LevelFiltering(t *testing.T) { tests := []struct { name string configLevel LogLevel logLevels map[string]func(*Logger, string) wantOutput map[string]bool // true表示应该输出 }{ { name: "LevelAll - 显示所有", configLevel: LevelAll, logLevels: map[string]func(*Logger, string){ "debug": (*Logger).Debug, "base": (*Logger).Base, "info": (*Logger).Info, "success": (*Logger).Success, "error": (*Logger).Error, }, wantOutput: map[string]bool{ "debug": true, "base": true, "info": true, "success": true, "error": true, }, }, { name: "LevelError - 仅错误", configLevel: LevelError, logLevels: map[string]func(*Logger, string){ "info": (*Logger).Info, "error": (*Logger).Error, }, wantOutput: map[string]bool{ "info": false, "error": true, }, }, { name: "LevelInfoSuccess - 信息和成功", configLevel: LevelInfoSuccess, logLevels: map[string]func(*Logger, string){ "base": (*Logger).Base, "info": (*Logger).Info, "success": (*Logger).Success, "error": (*Logger).Error, }, wantOutput: map[string]bool{ "base": false, "info": true, "success": true, "error": true, // Error 始终显示(层级设计) }, }, { name: "LevelBaseInfoSuccess - 基础、信息和成功", configLevel: LevelBaseInfoSuccess, logLevels: map[string]func(*Logger, string){ "debug": (*Logger).Debug, "base": (*Logger).Base, "info": (*Logger).Info, "success": (*Logger).Success, }, wantOutput: map[string]bool{ "debug": false, "base": true, "info": true, "success": true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger, capture := createTestLogger(tt.configLevel, false) for levelName, logFunc := range tt.logLevels { capture.Clear() logFunc(logger, levelName+" message") output := capture.Get() shouldOutput := tt.wantOutput[levelName] if shouldOutput && len(output) == 0 { t.Errorf("%s: 应该输出但没有输出", levelName) } if !shouldOutput && len(output) > 0 { t.Errorf("%s: 不应该输出但输出了: %v", levelName, output) } } t.Logf("✓ %s 过滤测试通过", tt.name) }) } } // ============================================================================= // Logger - 时间格式化测试 // ============================================================================= // TestLogger_TimeFormatting 测试时间格式化函数 // // 验证:formatElapsedTime 对不同时长格式化正确(毫秒、秒、分钟、小时) func TestLogger_TimeFormatting(t *testing.T) { tests := []struct { name string elapsed time.Duration wantStr string }{ { name: "0毫秒", elapsed: 0, wantStr: "0ms", }, { name: "500毫秒", elapsed: 500 * time.Millisecond, wantStr: "500ms", }, { name: "999毫秒", elapsed: 999 * time.Millisecond, wantStr: "999ms", }, { name: "1秒", elapsed: 1 * time.Second, wantStr: "1.0s", }, { name: "30秒", elapsed: 30 * time.Second, wantStr: "30.0s", }, { name: "59秒", elapsed: 59 * time.Second, wantStr: "59.0s", }, { name: "1分钟", elapsed: 1 * time.Minute, wantStr: "1m0s", }, { name: "5分30秒", elapsed: 5*time.Minute + 30*time.Second, wantStr: "5m30s", }, { name: "59分59秒", elapsed: 59*time.Minute + 59*time.Second, wantStr: "59m59s", }, { name: "1小时", elapsed: 1 * time.Hour, wantStr: "1h0m0s", }, { name: "2小时30分45秒", elapsed: 2*time.Hour + 30*time.Minute + 45*time.Second, wantStr: "2h30m45s", }, } // 直接测试 formatElapsedTime 函数 for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger := NewLogger(nil) result := logger.formatElapsedTime(tt.elapsed) if result != tt.wantStr { t.Errorf("时间格式错误\n期望: %s\n实际: %s", tt.wantStr, result) } t.Logf("✓ %s → %s", tt.name, result) }) } } // ============================================================================= // Logger - 并发安全测试 // ============================================================================= // TestLogger_ConcurrentLogging 测试并发日志输出 // // 验证:多个goroutine同时写日志不会panic或丢失 func TestLogger_ConcurrentLogging(t *testing.T) { logger, capture := createTestLogger(LevelAll, false) numGoroutines := 100 logsPerGoroutine := 10 totalLogs := numGoroutines * logsPerGoroutine var wg sync.WaitGroup wg.Add(numGoroutines) // 并发写入不同级别的日志 for i := 0; i < numGoroutines; i++ { go func(id int) { defer wg.Done() for j := 0; j < logsPerGoroutine; j++ { msg := fmt.Sprintf("goroutine-%d-log-%d", id, j) // 随机使用不同级别 switch j % 5 { case 0: logger.Debug(msg) case 1: logger.Info(msg) case 2: logger.Success(msg) case 3: logger.Error(msg) case 4: logger.Base(msg) } } }(i) } wg.Wait() // 验证输出数量 output := capture.Get() if len(output) != totalLogs { t.Errorf("期望%d条日志,实际%d条(数据丢失或重复)", totalLogs, len(output)) } // 验证每条日志格式正确(前缀可能是 "[" 或空格) for i, line := range output { if !strings.HasPrefix(line, "[") && !strings.HasPrefix(line, " ") { t.Errorf("第%d条日志格式错误: %s", i+1, line) break } } t.Logf("✓ 并发日志测试通过(%d个goroutine,共%d条日志)", numGoroutines, totalLogs) } // TestLogger_NoCoordinatedOutput 测试无协调输出的情况 // // 验证:coordinatedOutput为nil时,使用fmt.Println(不会panic) func TestLogger_NoCoordinatedOutput(t *testing.T) { config := &LoggerConfig{ Level: LevelAll, EnableColor: false, StartTime: time.Now(), } logger := NewLogger(config) // 不设置 coordinatedOutput // 应该不会panic(会使用fmt.Println) defer func() { if r := recover(); r != nil { t.Errorf("不应该panic: %v", r) } }() logger.Info("test message") t.Logf("✓ 无协调输出测试通过(使用fmt.Println)") } // ============================================================================= // Logger - 高级功能测试(提升覆盖率) // ============================================================================= // TestLogger_SingleLevels 测试单独级别配置 // // 验证:层级过滤 - 设置一个级别后,显示该级别及以上的日志,Error始终显示 func TestLogger_SingleLevels(t *testing.T) { tests := []struct { name string configLevel LogLevel testLevels map[string]func(*Logger, string) wantOutput map[string]bool }{ { name: "LevelDebug - 显示所有", configLevel: LevelDebug, testLevels: map[string]func(*Logger, string){ "debug": (*Logger).Debug, "base": (*Logger).Base, "info": (*Logger).Info, "success": (*Logger).Success, "error": (*Logger).Error, }, wantOutput: map[string]bool{ "debug": true, "base": true, "info": true, "success": true, "error": true, // 层级过滤:Debug(0)及以上全显示 }, }, { name: "LevelBase - 基础及以上", configLevel: LevelBase, testLevels: map[string]func(*Logger, string){ "debug": (*Logger).Debug, "base": (*Logger).Base, "info": (*Logger).Info, }, wantOutput: map[string]bool{ "debug": false, "base": true, "info": true, // 层级过滤:Base(1)及以上 }, }, { name: "LevelInfo - 信息及以上", configLevel: LevelInfo, testLevels: map[string]func(*Logger, string){ "base": (*Logger).Base, "info": (*Logger).Info, }, wantOutput: map[string]bool{ "base": false, "info": true, // 层级过滤:Info(2)及以上 }, }, { name: "LevelSuccess - 成功及以上", configLevel: LevelSuccess, testLevels: map[string]func(*Logger, string){ "info": (*Logger).Info, "success": (*Logger).Success, }, wantOutput: map[string]bool{ "info": false, "success": true, // 层级过滤:Success(3)及以上 }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger, capture := createTestLogger(tt.configLevel, false) for levelName, logFunc := range tt.testLevels { capture.Clear() logFunc(logger, levelName+" message") output := capture.Get() shouldOutput := tt.wantOutput[levelName] if shouldOutput && len(output) == 0 { t.Errorf("%s: 应该输出但没有输出", levelName) } if !shouldOutput && len(output) > 0 { t.Errorf("%s: 不应该输出但输出了: %v", levelName, output) } } t.Logf("✓ %s 测试通过", tt.name) }) } } // TestLogger_ColorOutput 测试颜色输出 // // 验证:EnableColor开关正确控制颜色输出 func TestLogger_ColorOutput(t *testing.T) { t.Run("禁用颜色", func(t *testing.T) { logger, capture := createTestLogger(LevelAll, false) logger.Info("test") output := capture.Get() if len(output) == 0 { t.Fatal("应该有输出") } // 无颜色时,输出就是纯文本 if strings.Contains(output[0], "\033[") { t.Error("禁用颜色时不应该包含ANSI转义序列") } t.Logf("✓ 禁用颜色测试通过") }) t.Run("启用颜色", func(t *testing.T) { logger, capture := createTestLogger(LevelAll, true) logger.Info("test") output := capture.Get() if len(output) == 0 { t.Fatal("应该有输出") } // 启用颜色时,输出可能包含颜色(取决于终端支持) // 但不会panic t.Logf("✓ 启用颜色测试通过: %s", output[0]) }) } // TestLogger_BackwardCompatibility 测试向后兼容性 // // 验证:LevelAll 等同于 LevelDebug,显示所有级别 func TestLogger_BackwardCompatibility(t *testing.T) { config := &LoggerConfig{ Level: LevelAll, // LevelAll 是 LevelDebug 的别名 EnableColor: false, ShowProgress: false, StartTime: time.Now(), LevelColors: GetDefaultLevelColors(), } logger := NewLogger(config) capture := &captureOutput{} logger.SetCoordinatedOutput(capture.Write) // LevelAll 应该显示所有级别 logger.Debug("debug msg") logger.Info("info msg") logger.Error("error msg") output := capture.Get() if len(output) != 3 { t.Errorf("LevelAll应该显示所有级别,期望3条,实际%d条", len(output)) } t.Logf("✓ 向后兼容测试通过(LevelAll显示所有级别)") } // TestLogger_Initialize 测试初始化标记 // // 验证:Initialize方法正确设置initialized标志 func TestLogger_Initialize(t *testing.T) { config := &LoggerConfig{ Level: LevelAll, EnableColor: false, ShowProgress: false, StartTime: time.Now(), LevelColors: GetDefaultLevelColors(), } // 手动创建logger,跳过NewLogger中的自动初始化 logger := &Logger{ config: config, initialized: false, // 明确设置为false } // 验证初始状态 if logger.initialized { t.Error("新创建的logger不应该已初始化") } // 调用Initialize logger.Initialize() // 验证已初始化 if !logger.initialized { t.Error("调用Initialize后应该已初始化") } t.Logf("✓ Initialize测试通过") } func TestLogger_CloseClosesDebugFile(t *testing.T) { path := filepath.Join(t.TempDir(), "debug.log") logger := NewLogger(&LoggerConfig{ Level: LevelAll, EnableColor: false, ShowProgress: false, StartTime: time.Now(), LevelColors: GetDefaultLevelColors(), DebugLogFile: path, }) if logger.debugFile == nil { t.Fatal("debug file should be opened") } logger.Info("debug file line") logger.Close() if logger.debugFile != nil { t.Fatal("debug file should be nil after Close") } content, err := os.ReadFile(path) if err != nil { t.Fatalf("read debug file: %v", err) } if !strings.Contains(string(content), "debug file line") { t.Fatalf("debug file content = %q", string(content)) } logger.Close() }