mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-02-09 10:19:19 +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)
238 lines
6.3 KiB
Go
238 lines
6.3 KiB
Go
package core
|
||
|
||
/*
|
||
adaptive_pool_test.go - AdaptivePool 高价值测试
|
||
|
||
测试重点:
|
||
1. 并发安全 - 多goroutine同时调整不崩溃
|
||
2. 降级逻辑 - 资源耗尽率高时正确减少线程
|
||
3. 恢复逻辑 - 资源耗尽率低时正确增加线程
|
||
4. 边界条件 - 不超过minSize/maxSize
|
||
|
||
不测试:
|
||
- 简单的getter方法(太简单,不值得)
|
||
- ants库本身的正确性(库作者负责)
|
||
*/
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/shadow1ng/fscan/common"
|
||
)
|
||
|
||
// =============================================================================
|
||
// 场景1:降级逻辑测试(高价值)
|
||
// =============================================================================
|
||
|
||
// TestAdaptivePool_DowngradeOnHighExhaustion 验证资源耗尽率高时降低线程数
|
||
// 这是个核心业务逻辑:耗尽率 > 10% 时应该减少线程
|
||
func TestAdaptivePool_DowngradeOnHighExhaustion(t *testing.T) {
|
||
state := common.NewState()
|
||
|
||
pool, err := NewAdaptivePool(100, func(interface{}) {}, state)
|
||
if err != nil {
|
||
t.Fatalf("创建线程池失败: %v", err)
|
||
}
|
||
defer pool.Release()
|
||
|
||
initialCap := pool.Cap()
|
||
|
||
// 模拟高资源耗尽率:20% 的包都失败了
|
||
// 需要至少100个样本才会触发调整
|
||
for i := 0; i < 200; i++ {
|
||
state.IncrementPacketCount()
|
||
if i < 40 { // 前40个失败(20%)
|
||
state.IncrementResourceExhaustedCount()
|
||
}
|
||
}
|
||
|
||
// 触发调整:提交足够多的任务让maybeAdjust被调用
|
||
for i := 0; i < 20; i++ {
|
||
_ = pool.Invoke(nil)
|
||
time.Sleep(time.Millisecond * 10) // 等待异步调整
|
||
}
|
||
|
||
// 等待调整完成
|
||
time.Sleep(time.Millisecond * 50)
|
||
|
||
finalCap := pool.Cap()
|
||
|
||
// 验证:线程数应该减少
|
||
if finalCap >= initialCap {
|
||
t.Errorf("应该降级: 初始 %d, 最终 %d", initialCap, finalCap)
|
||
}
|
||
|
||
// 验证:不应该降到minSize以下
|
||
minSize := initialCap / 4
|
||
if minSize < 10 {
|
||
minSize = 10
|
||
}
|
||
if finalCap < minSize {
|
||
t.Errorf("降到minSize以下: %d < %d", finalCap, minSize)
|
||
}
|
||
|
||
t.Logf("降级成功: %d -> %d (min=%d)", initialCap, finalCap, minSize)
|
||
}
|
||
|
||
// =============================================================================
|
||
// 场景3:恢复逻辑测试(高价值)
|
||
// =============================================================================
|
||
|
||
// TestAdaptivePool_NoRecoveryOnLowExhaustion 验证低耗尽率时不升级
|
||
// 防止线程数盲目增长
|
||
func TestAdaptivePool_NoRecoveryOnLowExhaustion(t *testing.T) {
|
||
state := common.NewState()
|
||
|
||
pool, err := NewAdaptivePool(50, func(interface{}) {}, state)
|
||
if err != nil {
|
||
t.Fatalf("创建线程池失败: %v", err)
|
||
}
|
||
defer pool.Release()
|
||
|
||
// 先降到minSize
|
||
for i := 0; i < 500; i++ {
|
||
state.IncrementPacketCount()
|
||
state.IncrementResourceExhaustedCount() // 100% 耗尽
|
||
}
|
||
|
||
for i := 0; i < 20; i++ {
|
||
_ = pool.Invoke(nil)
|
||
}
|
||
time.Sleep(time.Millisecond * 50)
|
||
|
||
reducedCap := pool.Cap()
|
||
|
||
// 现在模拟低耗尽率:只有1%失败
|
||
for i := 0; i < 500; i++ {
|
||
state.IncrementPacketCount()
|
||
if i%100 == 0 { // 只有5个失败(1%)
|
||
state.IncrementResourceExhaustedCount()
|
||
}
|
||
}
|
||
|
||
for i := 0; i < 20; i++ {
|
||
_ = pool.Invoke(nil)
|
||
}
|
||
time.Sleep(time.Millisecond * 50)
|
||
|
||
finalCap := pool.Cap()
|
||
|
||
// 验证:即使耗尽率低,也不应该立即恢复(保守策略)
|
||
// 或者即使恢复,也很有限
|
||
if finalCap > reducedCap+5 {
|
||
t.Logf("恢复行为: %d -> %d", reducedCap, finalCap)
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 场景4:边界条件测试(中价值)
|
||
// =============================================================================
|
||
|
||
// TestAdaptivePool_MinSizeBoundary 验证不会降到minSize以下
|
||
func TestAdaptivePool_MinSizeBoundary(t *testing.T) {
|
||
state := common.NewState()
|
||
|
||
// 创建小线程池,minSize会是10
|
||
pool, err := NewAdaptivePool(40, func(interface{}) {}, state)
|
||
if err != nil {
|
||
t.Fatalf("创建线程池失败: %v", err)
|
||
}
|
||
defer pool.Release()
|
||
|
||
// 模拟极端的资源耗尽:100%失败
|
||
for i := 0; i < 1000; i++ {
|
||
state.IncrementPacketCount()
|
||
state.IncrementResourceExhaustedCount()
|
||
}
|
||
|
||
// 触发多次调整
|
||
for i := 0; i < 50; i++ {
|
||
_ = pool.Invoke(nil)
|
||
time.Sleep(time.Millisecond)
|
||
}
|
||
|
||
finalCap := pool.Cap()
|
||
|
||
// 验证:不应该低于10
|
||
if finalCap < 10 {
|
||
t.Errorf("线程数 < 10: %d", finalCap)
|
||
}
|
||
|
||
t.Logf("最小边界测试通过: cap=%d", finalCap)
|
||
}
|
||
|
||
// =============================================================================
|
||
// 场景5:样本不足测试(低价值但重要)
|
||
// =============================================================================
|
||
|
||
// TestAdaptivePool_NotEnoughSamples 验证样本不足时不调整
|
||
// 防止基于小样本做错误决策
|
||
func TestAdaptivePool_NotEnoughSamples(t *testing.T) {
|
||
state := common.NewState()
|
||
|
||
pool, err := NewAdaptivePool(100, func(interface{}) {}, state)
|
||
if err != nil {
|
||
t.Fatalf("创建线程池失败: %v", err)
|
||
}
|
||
defer pool.Release()
|
||
|
||
initialCap := pool.Cap()
|
||
|
||
// 只增加少量样本(<100),不足以触发调整
|
||
for i := 0; i < 50; i++ {
|
||
state.IncrementPacketCount()
|
||
state.IncrementResourceExhaustedCount() // 即使100%失败也不调整
|
||
}
|
||
|
||
// 提交任务
|
||
for i := 0; i < 10; i++ {
|
||
_ = pool.Invoke(nil)
|
||
}
|
||
time.Sleep(time.Millisecond * 50)
|
||
|
||
finalCap := pool.Cap()
|
||
|
||
// 验证:样本不足时不应该调整
|
||
if finalCap != initialCap {
|
||
t.Errorf("样本不足时不应该调整: %d -> %d", initialCap, finalCap)
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 辅助函数
|
||
// =============================================================================
|
||
|
||
// TestAdaptivePool_Wait 验证Wait方法正确等待所有任务完成
|
||
func TestAdaptivePool_Wait(t *testing.T) {
|
||
state := common.NewState()
|
||
|
||
pool, err := NewAdaptivePool(10, func(interface{}) {
|
||
time.Sleep(time.Millisecond * 50)
|
||
}, state)
|
||
if err != nil {
|
||
t.Fatalf("创建线程池失败: %v", err)
|
||
}
|
||
defer pool.Release()
|
||
|
||
// 提交任务
|
||
for i := 0; i < 20; i++ {
|
||
_ = pool.Invoke(nil)
|
||
}
|
||
|
||
// Wait应该在所有任务完成后返回
|
||
start := time.Now()
|
||
pool.Wait()
|
||
duration := time.Since(start)
|
||
|
||
// 20个任务,每个50ms,10个线程,应该约100ms完成
|
||
if duration < 80*time.Millisecond {
|
||
t.Logf("Wait提前返回?可能测试有问题: %v", duration)
|
||
}
|
||
if duration > 200*time.Millisecond {
|
||
t.Errorf("Wait耗时过长: %v", duration)
|
||
}
|
||
|
||
t.Logf("Wait测试通过: %v", duration)
|
||
}
|