mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-02-09 02:09:17 +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)
367 lines
9.6 KiB
Go
367 lines
9.6 KiB
Go
package common
|
||
|
||
import (
|
||
"reflect"
|
||
"testing"
|
||
)
|
||
|
||
/*
|
||
parse_test.go - 解析工具函数测试
|
||
|
||
测试目标:RemoveDuplicate 函数
|
||
价值:去重逻辑影响用户输入处理,错误会导致:
|
||
- 重复扫描同一目标(性能浪费)
|
||
- 顺序错乱(某些场景依赖顺序)
|
||
|
||
"去重是经典算法问题。保留顺序、处理空值、全重复——
|
||
这些都是真实场景,必须验证。代码很简单,但边界情况会咬人。"
|
||
*/
|
||
|
||
// =============================================================================
|
||
// RemoveDuplicate - 切片去重测试
|
||
// =============================================================================
|
||
|
||
// TestRemoveDuplicate_BasicCases 测试基本去重功能
|
||
func TestRemoveDuplicate_BasicCases(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input []string
|
||
expected []string
|
||
}{
|
||
{
|
||
name: "无重复元素",
|
||
input: []string{"a", "b", "c"},
|
||
expected: []string{"a", "b", "c"},
|
||
},
|
||
{
|
||
name: "有重复元素-保留首次出现",
|
||
input: []string{"a", "b", "a", "c"},
|
||
expected: []string{"a", "b", "c"},
|
||
},
|
||
{
|
||
name: "连续重复",
|
||
input: []string{"a", "a", "b", "b", "c", "c"},
|
||
expected: []string{"a", "b", "c"},
|
||
},
|
||
{
|
||
name: "全部重复",
|
||
input: []string{"a", "a", "a", "a"},
|
||
expected: []string{"a"},
|
||
},
|
||
{
|
||
name: "空切片",
|
||
input: []string{},
|
||
expected: []string{},
|
||
},
|
||
{
|
||
name: "单个元素",
|
||
input: []string{"a"},
|
||
expected: []string{"a"},
|
||
},
|
||
{
|
||
name: "两个相同元素",
|
||
input: []string{"a", "a"},
|
||
expected: []string{"a"},
|
||
},
|
||
{
|
||
name: "两个不同元素",
|
||
input: []string{"a", "b"},
|
||
expected: []string{"a", "b"},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := RemoveDuplicate(tt.input)
|
||
if !reflect.DeepEqual(result, tt.expected) {
|
||
t.Errorf("RemoveDuplicate(%v) = %v, want %v",
|
||
tt.input, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestRemoveDuplicate_OrderPreservation 测试顺序保留
|
||
func TestRemoveDuplicate_OrderPreservation(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input []string
|
||
expected []string
|
||
note string
|
||
}{
|
||
{
|
||
name: "保留首次出现顺序",
|
||
input: []string{"b", "a", "c", "a", "b"},
|
||
expected: []string{"b", "a", "c"},
|
||
note: "b先出现,应该在a前面",
|
||
},
|
||
{
|
||
name: "后续重复不影响顺序",
|
||
input: []string{"1", "2", "3", "2", "1"},
|
||
expected: []string{"1", "2", "3"},
|
||
note: "保持1,2,3的原始顺序",
|
||
},
|
||
{
|
||
name: "数字字符串顺序",
|
||
input: []string{"10", "2", "3", "2", "10"},
|
||
expected: []string{"10", "2", "3"},
|
||
note: "按出现顺序,不按数值排序",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := RemoveDuplicate(tt.input)
|
||
if !reflect.DeepEqual(result, tt.expected) {
|
||
t.Errorf("RemoveDuplicate(%v) = %v, want %v\nNote: %s",
|
||
tt.input, result, tt.expected, tt.note)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestRemoveDuplicate_EdgeCases 测试边界情况
|
||
func TestRemoveDuplicate_EdgeCases(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input []string
|
||
expected []string
|
||
}{
|
||
{
|
||
name: "nil切片",
|
||
input: nil,
|
||
expected: nil,
|
||
},
|
||
{
|
||
name: "空字符串元素",
|
||
input: []string{"", "a", "", "b"},
|
||
expected: []string{"", "a", "b"},
|
||
},
|
||
{
|
||
name: "全是空字符串",
|
||
input: []string{"", "", ""},
|
||
expected: []string{""},
|
||
},
|
||
{
|
||
name: "包含空格的字符串",
|
||
input: []string{" ", "a", " ", "b"},
|
||
expected: []string{" ", "a", "b"},
|
||
},
|
||
{
|
||
name: "相似但不同的字符串",
|
||
input: []string{"a", "A", "a", "A"},
|
||
expected: []string{"a", "A"},
|
||
},
|
||
{
|
||
name: "长字符串",
|
||
input: []string{"very long string 1", "short", "very long string 1"},
|
||
expected: []string{"very long string 1", "short"},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := RemoveDuplicate(tt.input)
|
||
if !reflect.DeepEqual(result, tt.expected) {
|
||
t.Errorf("RemoveDuplicate(%v) = %v, want %v",
|
||
tt.input, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestRemoveDuplicate_ProductionScenarios 测试生产环境真实场景
|
||
func TestRemoveDuplicate_ProductionScenarios(t *testing.T) {
|
||
t.Run("IP地址去重", func(t *testing.T) {
|
||
// 用户可能输入重复的IP
|
||
input := []string{"192.168.1.1", "192.168.1.2", "192.168.1.1", "192.168.1.3"}
|
||
expected := []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"}
|
||
result := RemoveDuplicate(input)
|
||
if !reflect.DeepEqual(result, expected) {
|
||
t.Errorf("IP去重失败: got %v, want %v", result, expected)
|
||
}
|
||
})
|
||
|
||
t.Run("域名去重", func(t *testing.T) {
|
||
// 用户可能从文件读取重复域名
|
||
input := []string{"example.com", "test.com", "example.com", "demo.com", "test.com"}
|
||
expected := []string{"example.com", "test.com", "demo.com"}
|
||
result := RemoveDuplicate(input)
|
||
if !reflect.DeepEqual(result, expected) {
|
||
t.Errorf("域名去重失败: got %v, want %v", result, expected)
|
||
}
|
||
})
|
||
|
||
t.Run("端口列表去重", func(t *testing.T) {
|
||
// 合并多个端口列表
|
||
input := []string{"80", "443", "8080", "80", "443", "3306"}
|
||
expected := []string{"80", "443", "8080", "3306"}
|
||
result := RemoveDuplicate(input)
|
||
if !reflect.DeepEqual(result, expected) {
|
||
t.Errorf("端口去重失败: got %v, want %v", result, expected)
|
||
}
|
||
})
|
||
|
||
t.Run("用户名去重", func(t *testing.T) {
|
||
// 字典文件可能有重复
|
||
input := []string{"admin", "root", "admin", "user", "root", "test"}
|
||
expected := []string{"admin", "root", "user", "test"}
|
||
result := RemoveDuplicate(input)
|
||
if !reflect.DeepEqual(result, expected) {
|
||
t.Errorf("用户名去重失败: got %v, want %v", result, expected)
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestRemoveDuplicate_Performance 测试大规模数据性能
|
||
func TestRemoveDuplicate_Performance(t *testing.T) {
|
||
t.Run("1000个元素-50%重复", func(t *testing.T) {
|
||
// 构造测试数据:1000个元素,500个唯一
|
||
input := make([]string, 1000)
|
||
for i := 0; i < 1000; i++ {
|
||
input[i] = string(rune('A' + i%500))
|
||
}
|
||
|
||
result := RemoveDuplicate(input)
|
||
|
||
// 验证结果长度
|
||
if len(result) != 500 {
|
||
t.Errorf("去重后应该有500个唯一元素,实际 %d", len(result))
|
||
}
|
||
|
||
// 验证无重复
|
||
seen := make(map[string]bool)
|
||
for _, item := range result {
|
||
if seen[item] {
|
||
t.Errorf("结果中仍有重复元素: %s", item)
|
||
break
|
||
}
|
||
seen[item] = true
|
||
}
|
||
})
|
||
|
||
t.Run("大规模-全不重复", func(t *testing.T) {
|
||
// 10000个唯一元素
|
||
input := make([]string, 10000)
|
||
for i := 0; i < 10000; i++ {
|
||
input[i] = string(rune(i))
|
||
}
|
||
|
||
result := RemoveDuplicate(input)
|
||
|
||
if len(result) != 10000 {
|
||
t.Errorf("全不重复应该保持原长度,期望10000,实际 %d", len(result))
|
||
}
|
||
})
|
||
|
||
t.Run("大规模-全重复", func(t *testing.T) {
|
||
// 10000个相同元素
|
||
input := make([]string, 10000)
|
||
for i := 0; i < 10000; i++ {
|
||
input[i] = "duplicate"
|
||
}
|
||
|
||
result := RemoveDuplicate(input)
|
||
|
||
if len(result) != 1 {
|
||
t.Errorf("全重复应该只剩1个元素,实际 %d", len(result))
|
||
}
|
||
if result[0] != "duplicate" {
|
||
t.Errorf("结果应该是 'duplicate',实际 %q", result[0])
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestRemoveDuplicate_ReturnValue 测试返回值特性
|
||
func TestRemoveDuplicate_ReturnValue(t *testing.T) {
|
||
t.Run("不修改原始切片", func(t *testing.T) {
|
||
input := []string{"a", "b", "a"}
|
||
inputCopy := make([]string, len(input))
|
||
copy(inputCopy, input)
|
||
|
||
_ = RemoveDuplicate(input)
|
||
|
||
// 验证原始切片未被修改
|
||
if !reflect.DeepEqual(input, inputCopy) {
|
||
t.Errorf("RemoveDuplicate修改了原始切片\n原始: %v\n修改后: %v", inputCopy, input)
|
||
}
|
||
})
|
||
|
||
t.Run("返回新切片", func(t *testing.T) {
|
||
input := []string{"a", "b", "c"}
|
||
result := RemoveDuplicate(input)
|
||
|
||
// 修改result不应影响input
|
||
result[0] = "modified"
|
||
if input[0] == "modified" {
|
||
t.Error("返回的切片与输入共享内存")
|
||
}
|
||
})
|
||
|
||
t.Run("空输入返回空切片", func(t *testing.T) {
|
||
result := RemoveDuplicate([]string{})
|
||
if result == nil {
|
||
t.Error("空输入应返回空切片,而非nil")
|
||
}
|
||
if len(result) != 0 {
|
||
t.Errorf("空输入返回切片长度应为0,实际 %d", len(result))
|
||
}
|
||
})
|
||
|
||
t.Run("单元素不分配新内存", func(t *testing.T) {
|
||
input := []string{"a"}
|
||
result := RemoveDuplicate(input)
|
||
|
||
// 根据实现,单元素直接返回原切片(优化)
|
||
// 这是实现细节,测试是否返回相同引用
|
||
if &input[0] != &result[0] {
|
||
t.Log("单元素时返回了新切片(也是正确的实现)")
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestRemoveDuplicate_SpecialCharacters 测试特殊字符处理
|
||
func TestRemoveDuplicate_SpecialCharacters(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input []string
|
||
expected []string
|
||
}{
|
||
{
|
||
name: "包含换行符",
|
||
input: []string{"a\n", "b", "a\n"},
|
||
expected: []string{"a\n", "b"},
|
||
},
|
||
{
|
||
name: "包含制表符",
|
||
input: []string{"a\t", "b", "a\t"},
|
||
expected: []string{"a\t", "b"},
|
||
},
|
||
{
|
||
name: "Unicode字符",
|
||
input: []string{"你好", "world", "你好", "世界"},
|
||
expected: []string{"你好", "world", "世界"},
|
||
},
|
||
{
|
||
name: "特殊符号",
|
||
input: []string{"!@#", "$%^", "!@#"},
|
||
expected: []string{"!@#", "$%^"},
|
||
},
|
||
{
|
||
name: "路径字符串",
|
||
input: []string{"/root/test", "/home/user", "/root/test"},
|
||
expected: []string{"/root/test", "/home/user"},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := RemoveDuplicate(tt.input)
|
||
if !reflect.DeepEqual(result, tt.expected) {
|
||
t.Errorf("RemoveDuplicate(%v) = %v, want %v",
|
||
tt.input, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|