mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 19:21:52 +08:00
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n
## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池
## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)
## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建
## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
JSON 输出格式、Redis 检测、Context 超时等
## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
666 lines
16 KiB
Go
666 lines
16 KiB
Go
package logging
|
||
|
||
import (
|
||
"fmt"
|
||
"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: PrefixDefault,
|
||
},
|
||
{
|
||
name: "Base级别",
|
||
logFunc: logger.Base,
|
||
message: "base message",
|
||
wantMsg: "base message",
|
||
wantPfx: PrefixDefault,
|
||
},
|
||
{
|
||
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: "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, "[") {
|
||
t.Errorf("输出应该以时间开头: %s", 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": false,
|
||
},
|
||
},
|
||
{
|
||
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 测试时间格式化
|
||
//
|
||
// 验证:不同时长格式化正确(毫秒、秒、分钟、小时)
|
||
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",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
config := &LoggerConfig{
|
||
Level: LevelAll,
|
||
EnableColor: false,
|
||
StartTime: time.Now().Add(-tt.elapsed),
|
||
}
|
||
logger := NewLogger(config)
|
||
capture := &captureOutput{}
|
||
logger.SetCoordinatedOutput(capture.Write)
|
||
|
||
logger.Info("test")
|
||
|
||
output := capture.Get()
|
||
if len(output) != 1 {
|
||
t.Fatalf("期望1条输出,实际%d条", len(output))
|
||
}
|
||
|
||
if !strings.Contains(output[0], tt.wantStr) {
|
||
t.Errorf("时间格式错误\n期望包含: %s\n实际输出: %s",
|
||
tt.wantStr, output[0])
|
||
}
|
||
|
||
t.Logf("✓ %s → %s", tt.name, tt.wantStr)
|
||
})
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 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, "[") {
|
||
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 测试单独级别配置
|
||
//
|
||
// 验证:每个单独级别(LevelDebug, LevelBase等)只输出对应级别
|
||
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": false, "info": false,
|
||
"success": false, "error": false,
|
||
},
|
||
},
|
||
{
|
||
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": false,
|
||
},
|
||
},
|
||
{
|
||
name: "LevelInfo - 仅信息",
|
||
configLevel: LevelInfo,
|
||
testLevels: map[string]func(*Logger, string){
|
||
"base": (*Logger).Base,
|
||
"info": (*Logger).Info,
|
||
},
|
||
wantOutput: map[string]bool{
|
||
"base": false, "info": true,
|
||
},
|
||
},
|
||
{
|
||
name: "LevelSuccess - 仅成功",
|
||
configLevel: LevelSuccess,
|
||
testLevels: map[string]func(*Logger, string){
|
||
"info": (*Logger).Info,
|
||
"success": (*Logger).Success,
|
||
},
|
||
wantOutput: map[string]bool{
|
||
"info": false, "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.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 测试向后兼容性
|
||
//
|
||
// 验证:字符串"debug"作为级别时的行为
|
||
func TestLogger_BackwardCompatibility(t *testing.T) {
|
||
config := &LoggerConfig{
|
||
Level: LogLevel("debug"), // 旧版本可能用字符串
|
||
EnableColor: false,
|
||
ShowProgress: false,
|
||
StartTime: time.Now(),
|
||
LevelColors: GetDefaultLevelColors(),
|
||
}
|
||
logger := NewLogger(config)
|
||
capture := &captureOutput{}
|
||
logger.SetCoordinatedOutput(capture.Write)
|
||
|
||
// 字符串"debug"应该显示所有级别
|
||
logger.Debug("debug msg")
|
||
logger.Info("info msg")
|
||
logger.Error("error msg")
|
||
|
||
output := capture.Get()
|
||
if len(output) != 3 {
|
||
t.Errorf("字符串'debug'应该显示所有级别,期望3条,实际%d条", len(output))
|
||
}
|
||
|
||
t.Logf("✓ 向后兼容测试通过(字符串'debug'显示所有级别)")
|
||
}
|
||
|
||
// 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测试通过")
|
||
}
|