mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-26 21:21:53 +08:00
test: 补充单元测试覆盖率 29.9% → 36.6%
新建 18 个测试文件,追加 30 个已有测试文件,覆盖协议解析、 错误分类、CEL 表达式求值、YAML 反序列化、字节编码等纯函数。
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
package parsers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -166,3 +168,777 @@ func (s *closeTrackingSource) Close() error {
|
||||
s.closed = true
|
||||
return s.err
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// newHostSource 分支覆盖
|
||||
// =============================================================================
|
||||
|
||||
// TestNewHostSource_Shortcuts 验证 192/172/10 快捷方式展开为正确 CIDR
|
||||
func TestNewHostSource_Shortcuts(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
wantFirst string
|
||||
}{
|
||||
{"192", "192.168.0.1"},
|
||||
{"172", "172.16.0.1"},
|
||||
{"10", "10.0.0.1"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.input, func(t *testing.T) {
|
||||
src, err := newHostSource(c.input)
|
||||
if err != nil {
|
||||
t.Fatalf("newHostSource(%q) error = %v", c.input, err)
|
||||
}
|
||||
defer src.Close()
|
||||
host, ok, err := src.Next()
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("Next() = %q/%v/%v", host, ok, err)
|
||||
}
|
||||
if host != c.wantFirst {
|
||||
t.Errorf("first host = %q, 期望 %q", host, c.wantFirst)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostSource_CIDRBranch 验证含 "/" 走 CIDR 分支
|
||||
func TestNewHostSource_CIDRBranch(t *testing.T) {
|
||||
src, err := newHostSource("10.0.0.0/30")
|
||||
if err != nil {
|
||||
t.Fatalf("newHostSource CIDR error = %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
host, ok, _ := src.Next()
|
||||
if !ok || host != "10.0.0.1" {
|
||||
t.Errorf("CIDR first host = %q, 期望 10.0.0.1", host)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostSource_InvalidCIDR 无效 CIDR 返回错误
|
||||
func TestNewHostSource_InvalidCIDR(t *testing.T) {
|
||||
_, err := newHostSource("999.0.0.0/24")
|
||||
if err == nil {
|
||||
t.Error("无效 CIDR 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostSource_RangeBranch 验证 a-b 格式走 range 分支
|
||||
func TestNewHostSource_RangeBranch(t *testing.T) {
|
||||
src, err := newHostSource("192.168.1.5-192.168.1.7")
|
||||
if err != nil {
|
||||
t.Fatalf("newHostSource range error = %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
var got []string
|
||||
for {
|
||||
h, ok, err := src.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("Next() error = %v", err)
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
got = append(got, h)
|
||||
}
|
||||
want := []string{"192.168.1.5", "192.168.1.6", "192.168.1.7"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("range hosts = %v, 期望 %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostSource_RangeShortTail 验证短尾写法 x.x.x.a-b
|
||||
func TestNewHostSource_RangeShortTail(t *testing.T) {
|
||||
src, err := newHostSource("10.0.0.3-5")
|
||||
if err != nil {
|
||||
t.Fatalf("newHostSource short-tail range error = %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
var got []string
|
||||
for {
|
||||
h, ok, err := src.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("Next() error = %v", err)
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
got = append(got, h)
|
||||
}
|
||||
want := []string{"10.0.0.3", "10.0.0.4", "10.0.0.5"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("short-tail range = %v, 期望 %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostSource_SingleHost 验证普通主机名走 singleHostSource 分支
|
||||
func TestNewHostSource_SingleHost(t *testing.T) {
|
||||
src, err := newHostSource("example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("newHostSource single error = %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
host, ok, err := src.Next()
|
||||
if err != nil || !ok || host != "example.com" {
|
||||
t.Errorf("single host = %q/%v/%v, 期望 example.com/true/nil", host, ok, err)
|
||||
}
|
||||
// 第二次应该耗尽
|
||||
_, ok, _ = src.Next()
|
||||
if ok {
|
||||
t.Error("singleHostSource 第二次 Next 应返回 ok=false")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// hostMatcher.add 分支覆盖
|
||||
// =============================================================================
|
||||
|
||||
// TestHostMatcherAdd_192Shortcut 验证 add("192") 展开为 192.168.0.0/16
|
||||
func TestHostMatcherAdd_192Shortcut(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("192"); err != nil {
|
||||
t.Fatalf("add(192) error = %v", err)
|
||||
}
|
||||
if !m.match("192.168.1.100") {
|
||||
t.Error("192.168.1.100 应命中 192.168.0.0/16")
|
||||
}
|
||||
if m.match("10.0.0.1") {
|
||||
t.Error("10.0.0.1 不应命中")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_172Shortcut 验证 add("172")
|
||||
func TestHostMatcherAdd_172Shortcut(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("172"); err != nil {
|
||||
t.Fatalf("add(172) error = %v", err)
|
||||
}
|
||||
if !m.match("172.16.0.1") {
|
||||
t.Error("172.16.0.1 应命中 172.16.0.0/12")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_10Shortcut 验证 add("10")
|
||||
func TestHostMatcherAdd_10Shortcut(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("10"); err != nil {
|
||||
t.Fatalf("add(10) error = %v", err)
|
||||
}
|
||||
if !m.match("10.1.2.3") {
|
||||
t.Error("10.1.2.3 应命中 10.0.0.0/8")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_CIDR 验证 add 处理 CIDR 字符串
|
||||
func TestHostMatcherAdd_CIDR(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("192.168.5.0/24"); err != nil {
|
||||
t.Fatalf("add CIDR error = %v", err)
|
||||
}
|
||||
if !m.match("192.168.5.10") {
|
||||
t.Error("192.168.5.10 应命中 /24")
|
||||
}
|
||||
if m.match("192.168.6.10") {
|
||||
t.Error("192.168.6.10 不应命中")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_Range 验证 add 处理 a-b 范围
|
||||
func TestHostMatcherAdd_Range(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("10.0.0.10-10.0.0.20"); err != nil {
|
||||
t.Fatalf("add range error = %v", err)
|
||||
}
|
||||
if !m.match("10.0.0.15") {
|
||||
t.Error("10.0.0.15 应命中范围")
|
||||
}
|
||||
if m.match("10.0.0.9") || m.match("10.0.0.21") {
|
||||
t.Error("边界外不应命中")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_ExactHost 验证 add 处理普通主机名(exact 分支)
|
||||
func TestHostMatcherAdd_ExactHost(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("myhost.local"); err != nil {
|
||||
t.Fatalf("add exact error = %v", err)
|
||||
}
|
||||
if !m.match("myhost.local") {
|
||||
t.Error("exact 主机名应命中")
|
||||
}
|
||||
if m.match("other.local") {
|
||||
t.Error("其他主机名不应命中")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_MultipleComma 验证逗号分隔多个值
|
||||
func TestHostMatcherAdd_MultipleComma(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("host1.com, host2.com, 192.168.1.0/30"); err != nil {
|
||||
t.Fatalf("add comma-separated error = %v", err)
|
||||
}
|
||||
if !m.match("host1.com") || !m.match("host2.com") || !m.match("192.168.1.1") {
|
||||
t.Error("逗号分隔的值应全部命中")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_EmptyEntry 逗号中间空串不报错
|
||||
func TestHostMatcherAdd_EmptyEntry(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add(",,,"); err != nil {
|
||||
t.Fatalf("全空逗号不应报错: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostMatcherAdd_InvalidCIDR 无效 CIDR 返回 error
|
||||
func TestHostMatcherAdd_InvalidCIDR(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.add("999.0.0.0/8"); err == nil {
|
||||
t.Error("无效 CIDR 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// fileHostSource.Next 分支覆盖
|
||||
// =============================================================================
|
||||
|
||||
// TestFileHostSourceNext_SkipsEmptyAndComments 验证空行和注释行被跳过
|
||||
func TestFileHostSourceNext_SkipsEmptyAndComments(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := dir + "/hosts.txt"
|
||||
content := "\n# this is a comment\n\n \n10.0.0.1\n# another comment\n10.0.0.2\n"
|
||||
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile error = %v", err)
|
||||
}
|
||||
|
||||
iter, err := NewHostIterator("", path)
|
||||
if err != nil {
|
||||
t.Fatalf("NewHostIterator error = %v", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
batch, err := iter.NextBatch(context.Background(), 10)
|
||||
if err != nil {
|
||||
t.Fatalf("NextBatch error = %v", err)
|
||||
}
|
||||
want := []string{"10.0.0.1", "10.0.0.2"}
|
||||
if !reflect.DeepEqual(batch, want) {
|
||||
t.Errorf("batch = %v, 期望 %v", batch, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFileHostSourceNext_MultipleSources 验证文件中每行多个 host(逗号分隔)走 multiHostSource 分支
|
||||
func TestFileHostSourceNext_MultipleSources(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := dir + "/hosts.txt"
|
||||
// 一行两个 host,触发 multiHostSource 分支
|
||||
content := "10.0.0.1,10.0.0.2\n10.0.0.3\n"
|
||||
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile error = %v", err)
|
||||
}
|
||||
|
||||
iter, err := NewHostIterator("", path)
|
||||
if err != nil {
|
||||
t.Fatalf("NewHostIterator error = %v", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
batch, err := iter.NextBatch(context.Background(), 10)
|
||||
if err != nil {
|
||||
t.Fatalf("NextBatch error = %v", err)
|
||||
}
|
||||
want := []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}
|
||||
if !reflect.DeepEqual(batch, want) {
|
||||
t.Errorf("batch = %v, 期望 %v", batch, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFileHostSourceNext_InvalidLineSkipped 无效行(解析失败)被跳过不报错
|
||||
func TestFileHostSourceNext_InvalidLineSkipped(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := dir + "/hosts.txt"
|
||||
// 包含无效 CIDR,应被跳过
|
||||
content := "999.0.0.0/8\n10.0.0.1\n"
|
||||
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile error = %v", err)
|
||||
}
|
||||
|
||||
iter, err := NewHostIterator("", path)
|
||||
if err != nil {
|
||||
t.Fatalf("NewHostIterator error = %v", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
batch, err := iter.NextBatch(context.Background(), 10)
|
||||
if err != nil {
|
||||
t.Fatalf("NextBatch error = %v", err)
|
||||
}
|
||||
// 无效行被跳过,只返回有效行
|
||||
if len(batch) != 1 || batch[0] != "10.0.0.1" {
|
||||
t.Errorf("batch = %v, 期望 [10.0.0.1]", batch)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NewHostIterator 错误路径
|
||||
// =============================================================================
|
||||
|
||||
// TestNewHostIterator_InvalidFilename 不存在的文件应返回 error
|
||||
func TestNewHostIterator_InvalidFilename(t *testing.T) {
|
||||
_, err := NewHostIterator("", "/nonexistent/path/hosts.txt")
|
||||
if err == nil {
|
||||
t.Error("不存在的文件应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostIterator_InvalidHost host 解析失败时应返回 error(并关闭已打开的文件 source)
|
||||
func TestNewHostIterator_InvalidHost_WithFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := dir + "/hosts.txt"
|
||||
if err := os.WriteFile(path, []byte("10.0.0.1\n"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
// 无效 CIDR 会让 newHostSources 失败
|
||||
_, err := NewHostIterator("999.0.0.0/8", path)
|
||||
if err == nil {
|
||||
t.Error("无效 host 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostIterator_InvalidExclude exclude 参数无效时应返回 error
|
||||
func TestNewHostIterator_InvalidExclude(t *testing.T) {
|
||||
_, err := NewHostIterator("10.0.0.1", "", "999.0.0.0/8")
|
||||
if err == nil {
|
||||
t.Error("无效 exclude 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostIterator_EmptyExcludeSkipped 空白 exclude 条目应被跳过,不报错
|
||||
func TestNewHostIterator_EmptyExcludeSkipped(t *testing.T) {
|
||||
iter, err := NewHostIterator("10.0.0.1", "", " ", "")
|
||||
if err != nil {
|
||||
t.Fatalf("空白 exclude 不应报错: %v", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
host, ok, err := iter.Next()
|
||||
if err != nil || !ok || host != "10.0.0.1" {
|
||||
t.Errorf("Next() = %q/%v/%v", host, ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Close 路径
|
||||
// =============================================================================
|
||||
|
||||
// TestClose_Nil nil HostIterator Close 不 panic
|
||||
func TestClose_Nil(t *testing.T) {
|
||||
var it *HostIterator
|
||||
if err := it.Close(); err != nil {
|
||||
t.Errorf("nil Close 应返回 nil, 得到 %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestClose_WithCurrent 有 current source 时 Close 应关闭它
|
||||
func TestClose_WithCurrent(t *testing.T) {
|
||||
src := &closeTrackingSource{}
|
||||
it := &HostIterator{current: src}
|
||||
if err := it.Close(); err != nil {
|
||||
t.Errorf("Close error = %v", err)
|
||||
}
|
||||
if !src.closed {
|
||||
t.Error("current source 应被关闭")
|
||||
}
|
||||
if it.current != nil {
|
||||
t.Error("Close 后 current 应为 nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestClose_SourcesError Close 中 source 返回 error 应被记录
|
||||
func TestClose_SourcesError(t *testing.T) {
|
||||
errSrc := &closeTrackingSource{err: errors.New("close error")}
|
||||
it := &HostIterator{sources: []hostSource{errSrc}}
|
||||
err := it.Close()
|
||||
if err == nil {
|
||||
t.Error("source Close 失败时应返回 error")
|
||||
}
|
||||
if !errSrc.closed {
|
||||
t.Error("出错的 source 也应被调用 Close")
|
||||
}
|
||||
}
|
||||
|
||||
// TestClose_CurrentErrorThenSources current Close 报错,后续 source Close 成功,返回 current 的 error
|
||||
func TestClose_CurrentErrorThenSources(t *testing.T) {
|
||||
currentSrc := &closeTrackingSource{err: errors.New("current close error")}
|
||||
otherSrc := &closeTrackingSource{}
|
||||
it := &HostIterator{
|
||||
current: currentSrc,
|
||||
sources: []hostSource{otherSrc},
|
||||
}
|
||||
err := it.Close()
|
||||
if err == nil {
|
||||
t.Error("应返回 current 的 error")
|
||||
}
|
||||
if !currentSrc.closed || !otherSrc.closed {
|
||||
t.Error("两个 source 都应被关闭")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Next 错误路径
|
||||
// =============================================================================
|
||||
|
||||
// errorSource 让 Next() 返回 error
|
||||
type errorSource struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *errorSource) Next() (string, bool, error) { return "", false, s.err }
|
||||
func (s *errorSource) Close() error { return nil }
|
||||
|
||||
// errorOnCloseSource Next 返回 ok=false,Close 返回 error
|
||||
type errorOnCloseSource struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *errorOnCloseSource) Next() (string, bool, error) { return "", false, nil }
|
||||
func (s *errorOnCloseSource) Close() error { return s.err }
|
||||
|
||||
// TestNext_SourceNextError source.Next() 返回 error 时 iter.Next 应透传
|
||||
func TestNext_SourceNextError(t *testing.T) {
|
||||
it := &HostIterator{
|
||||
sources: []hostSource{&errorSource{err: errors.New("next error")}},
|
||||
}
|
||||
_, _, err := it.Next()
|
||||
if err == nil {
|
||||
t.Error("source Next error 应透传")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNext_SourceCloseError 源耗尽时 Close 报错应透传
|
||||
func TestNext_SourceCloseError(t *testing.T) {
|
||||
it := &HostIterator{
|
||||
sources: []hostSource{&errorOnCloseSource{err: errors.New("close error")}},
|
||||
}
|
||||
_, _, err := it.Next()
|
||||
if err == nil {
|
||||
t.Error("source 耗尽时 Close error 应透传")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NextBatch 边界条件
|
||||
// =============================================================================
|
||||
|
||||
// TestNextBatch_ZeroSize size=0 应使用 DefaultHostBatchSize(实际受源数量限制)
|
||||
func TestNextBatch_ZeroSize(t *testing.T) {
|
||||
iter, err := NewHostIterator("10.0.0.1", "")
|
||||
if err != nil {
|
||||
t.Fatalf("NewHostIterator: %v", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
// size=0 触发默认 DefaultHostBatchSize 分支,源只有一个 host
|
||||
batch, err := iter.NextBatch(context.Background(), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("NextBatch(0) error = %v", err)
|
||||
}
|
||||
if len(batch) != 1 || batch[0] != "10.0.0.1" {
|
||||
t.Errorf("batch = %v, 期望 [10.0.0.1]", batch)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNextBatch_NegativeSize size<0 也应使用默认值
|
||||
func TestNextBatch_NegativeSize(t *testing.T) {
|
||||
iter, err := NewHostIterator("10.0.0.2", "")
|
||||
if err != nil {
|
||||
t.Fatalf("NewHostIterator: %v", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
batch, err := iter.NextBatch(context.Background(), -1)
|
||||
if err != nil {
|
||||
t.Fatalf("NextBatch(-1) error = %v", err)
|
||||
}
|
||||
if len(batch) != 1 || batch[0] != "10.0.0.2" {
|
||||
t.Errorf("batch = %v, 期望 [10.0.0.2]", batch)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNextBatch_ContextCancelled context 取消应立即返回
|
||||
func TestNextBatch_ContextCancelled(t *testing.T) {
|
||||
iter, err := NewHostIterator("10.0.0.0/8", "")
|
||||
if err != nil {
|
||||
t.Fatalf("NewHostIterator: %v", err)
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // 立即取消
|
||||
|
||||
_, err = iter.NextBatch(ctx, 100)
|
||||
if err == nil {
|
||||
t.Error("已取消的 context 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNextBatch_DeduplicatesHosts 重复 host 只保留一个
|
||||
func TestNextBatch_DeduplicatesHosts(t *testing.T) {
|
||||
// 两个相同的单 host source
|
||||
it := &HostIterator{
|
||||
sources: []hostSource{
|
||||
&singleHostSource{host: "10.0.0.1"},
|
||||
&singleHostSource{host: "10.0.0.1"},
|
||||
},
|
||||
}
|
||||
batch, err := it.NextBatch(context.Background(), 10)
|
||||
if err != nil {
|
||||
t.Fatalf("NextBatch error = %v", err)
|
||||
}
|
||||
if len(batch) != 1 || batch[0] != "10.0.0.1" {
|
||||
t.Errorf("batch = %v, 期望去重为 [10.0.0.1]", batch)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNextBatch_NextError Next 报错时应透传
|
||||
func TestNextBatch_NextError(t *testing.T) {
|
||||
it := &HostIterator{
|
||||
sources: []hostSource{&errorSource{err: errors.New("iter error")}},
|
||||
}
|
||||
_, err := it.NextBatch(context.Background(), 10)
|
||||
if err == nil {
|
||||
t.Error("Next error 应透传到 NextBatch")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// newRangeHostSource 错误路径
|
||||
// =============================================================================
|
||||
|
||||
// TestNewRangeHostSource_TooManyDashes 超过一个 "-" 应报错(实际按首个切分:a-b-c 被 Split 成 3 段)
|
||||
func TestNewRangeHostSource_TooManyDashes(t *testing.T) {
|
||||
// "a-b-c" Split by "-" 得到 3 段,len != 2,应报错
|
||||
_, err := newRangeHostSource("10.0.0.1-10.0.0.5-extra")
|
||||
if err == nil {
|
||||
t.Error("三段格式应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewRangeHostSource_InvalidStartIP 起始 IP 无效
|
||||
func TestNewRangeHostSource_InvalidStartIP(t *testing.T) {
|
||||
_, err := newRangeHostSource("notanip-10.0.0.5")
|
||||
if err == nil {
|
||||
t.Error("无效起始 IP 应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewRangeHostSource_InvalidShortTailNonNumeric 短尾不是数字应报错
|
||||
func TestNewRangeHostSource_InvalidShortTailNonNumeric(t *testing.T) {
|
||||
// 尾部 "xyz" 不是数字
|
||||
_, err := newRangeHostSource("10.0.0.1-xyz")
|
||||
if err == nil {
|
||||
t.Error("非数字短尾应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewRangeHostSource_InvalidShortTailOver255 短尾超过 255 应报错
|
||||
func TestNewRangeHostSource_InvalidShortTailOver255(t *testing.T) {
|
||||
_, err := newRangeHostSource("10.0.0.1-300")
|
||||
if err == nil {
|
||||
t.Error("短尾 >255 应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewRangeHostSource_StartGTEnd 起始 > 结束应报错
|
||||
func TestNewRangeHostSource_StartGTEnd(t *testing.T) {
|
||||
_, err := newRangeHostSource("10.0.0.200-10.0.0.100")
|
||||
if err == nil {
|
||||
t.Error("start > end 应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewRangeHostSource_InvalidFullEndIP 完整结束 IP 无效(如 "10.0.0.999")
|
||||
func TestNewRangeHostSource_InvalidFullEndIP(t *testing.T) {
|
||||
// end IP 包含 "." 但无效
|
||||
_, err := newRangeHostSource("10.0.0.1-10.0.0.999")
|
||||
if err == nil {
|
||||
t.Error("无效结束 IP 应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewRangeHostSource_ShortTailStartGTEnd 短尾导致 start > end 应报错
|
||||
func TestNewRangeHostSource_ShortTailStartGTEnd(t *testing.T) {
|
||||
_, err := newRangeHostSource("10.0.0.200-100")
|
||||
if err == nil {
|
||||
t.Error("短尾结果 start > end 应报错")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// hostMatcher.addRange 错误路径
|
||||
// =============================================================================
|
||||
|
||||
// TestAddRange_InvalidRange addRange 传入无效范围应报错
|
||||
func TestAddRange_InvalidRange(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.addRange("notvalid-range"); err == nil {
|
||||
t.Error("无效 range 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddRange_ValidRange addRange 正常路径
|
||||
func TestAddRange_ValidRange(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
if err := m.addRange("10.0.0.10-10.0.0.20"); err != nil {
|
||||
t.Fatalf("addRange error = %v", err)
|
||||
}
|
||||
if !m.match("10.0.0.10") || !m.match("10.0.0.20") {
|
||||
t.Error("addRange 边界值应命中")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// hostMatcher.add 错误路径(shortcut 分支中 addCIDR 失败)
|
||||
// =============================================================================
|
||||
|
||||
// TestHostMatcherAdd_InvalidRange add 的 range 格式无效
|
||||
func TestHostMatcherAdd_InvalidRange(t *testing.T) {
|
||||
m := newHostMatcher()
|
||||
// 构造一个 looksLikeIPRange 通过但 newRangeHostSource 失败的字符串
|
||||
// "10.0.0.200-10.0.0.100" start>end 会报错
|
||||
if err := m.add("10.0.0.200-10.0.0.100"); err == nil {
|
||||
t.Error("无效 range (start>end) 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// newCIDRHostSource IPv6 路径
|
||||
// =============================================================================
|
||||
|
||||
// TestNewCIDRHostSource_IPv6Rejected IPv6 CIDR 应报错
|
||||
func TestNewCIDRHostSource_IPv6Rejected(t *testing.T) {
|
||||
_, err := newCIDRHostSource("2001:db8::/32")
|
||||
if err == nil {
|
||||
t.Error("IPv6 CIDR 应被拒绝")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// fileHostSource.Close 路径
|
||||
// =============================================================================
|
||||
|
||||
// TestFileHostSource_CloseWithCurrent fileHostSource.Close 时 current != nil 分支
|
||||
func TestFileHostSource_CloseWithCurrent(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := dir + "/hosts.txt"
|
||||
// 写入一个 CIDR,这样 fileHostSource 会持有 current source
|
||||
if err := os.WriteFile(path, []byte("10.0.0.0/30\n"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
src, err := newFileHostSource(path)
|
||||
if err != nil {
|
||||
t.Fatalf("newFileHostSource: %v", err)
|
||||
}
|
||||
// 触发 current 被设置
|
||||
_, _, _ = src.Next()
|
||||
// 此时 current 应非 nil,Close 应正常关闭它
|
||||
if err := src.Close(); err != nil {
|
||||
t.Errorf("Close with current error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFileHostSource_CloseNilFile file 已经为 nil 时 Close 直接返回 nil
|
||||
func TestFileHostSource_CloseNilFile(t *testing.T) {
|
||||
src := &fileHostSource{file: nil}
|
||||
if err := src.Close(); err != nil {
|
||||
t.Errorf("nil file Close error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// multiHostSource.Close 路径
|
||||
// =============================================================================
|
||||
|
||||
// TestMultiHostSource_CloseWithCurrent Close 时 current != nil 分支
|
||||
func TestMultiHostSource_CloseWithCurrent(t *testing.T) {
|
||||
inner := &closeTrackingSource{}
|
||||
ms := &multiHostSource{current: inner}
|
||||
if err := ms.Close(); err != nil {
|
||||
t.Errorf("Close error = %v", err)
|
||||
}
|
||||
if !inner.closed {
|
||||
t.Error("current 应被关闭")
|
||||
}
|
||||
if ms.current != nil {
|
||||
t.Error("Close 后 current 应为 nil")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ipToUint32 IPv6 路径
|
||||
// =============================================================================
|
||||
|
||||
// TestIpToUint32_IPv6ReturnsFalse IPv6 地址应返回 false
|
||||
func TestIpToUint32_IPv6ReturnsFalse(t *testing.T) {
|
||||
ip := net.ParseIP("2001:db8::1")
|
||||
_, ok := ipToUint32(ip)
|
||||
if ok {
|
||||
t.Error("IPv6 地址应返回 ok=false")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIpToUint32_NilReturnsFalse nil IP 应返回 false
|
||||
func TestIpToUint32_NilReturnsFalse(t *testing.T) {
|
||||
_, ok := ipToUint32(nil)
|
||||
if ok {
|
||||
t.Error("nil IP 应返回 ok=false")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 剩余未覆盖路径
|
||||
// =============================================================================
|
||||
|
||||
// TestFileHostSource_CurrentNextError fileHostSource.Next 中 current.Next() 报错应透传
|
||||
func TestFileHostSource_CurrentNextError(t *testing.T) {
|
||||
src := &fileHostSource{
|
||||
current: &errorSource{err: errors.New("inner error")},
|
||||
// scanner 为 nil——不会走到 scanner 分支
|
||||
scanner: bufio.NewScanner(strings.NewReader("")),
|
||||
}
|
||||
_, _, err := src.Next()
|
||||
if err == nil {
|
||||
t.Error("current.Next() 报错应透传")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiHostSource_InnerNextError multiHostSource.Next 中内部 source.Next() 报错应透传
|
||||
func TestMultiHostSource_InnerNextError(t *testing.T) {
|
||||
ms := &multiHostSource{
|
||||
sources: []hostSource{&errorSource{err: errors.New("inner error")}},
|
||||
}
|
||||
_, _, err := ms.Next()
|
||||
if err == nil {
|
||||
t.Error("内部 source.Next() 报错应透传到 multiHostSource.Next")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewHostSource_RangeError newHostSource range 分支中 newRangeHostSource 失败
|
||||
func TestNewHostSource_RangeError(t *testing.T) {
|
||||
// start > end,looksLikeIPRange 通过(前半部分是有效 IP),但 newRangeHostSource 返回错误
|
||||
_, err := newHostSource("10.0.0.200-10.0.0.100")
|
||||
if err == nil {
|
||||
t.Error("start>end range 应返回 error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewCIDRHostSource_IPv6DirectCall 直接调用 newCIDRHostSource 传入 IPv6 CIDR
|
||||
func TestNewCIDRHostSource_IPv6DirectCall(t *testing.T) {
|
||||
// IPv6 CIDR —— bits=128 != 32,触发 line 332-334
|
||||
_, err := newCIDRHostSource("::1/128")
|
||||
if err == nil {
|
||||
t.Error("IPv6 CIDR 应被 newCIDRHostSource 拒绝 (bits!=32)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user