// bash_classify_test.go -- 命令分类器的单元测试. // // 覆盖场景: // - 各类命令的正确分类 // - 管道分类和降级逻辑 // - 中性命令跳过 // - 未知命令归为 general // - 带路径的命令名提取 // - 完整 shell 命令字符串分类 // - IsSearchOrRead 辅助函数 package builtin import ( "testing" ) // TestClassifyCommand_Search 测试搜索类命令分类 func TestClassifyCommand_Search(t *testing.T) { searchCmds := []string{"grep", "rg", "find", "ag", "ack", "fd", "locate", "which", "whereis"} for _, cmd := range searchCmds { if got := ClassifyCommand(cmd); got != ClassSearch { t.Errorf("ClassifyCommand(%q) = %q, 期望 %q", cmd, got, ClassSearch) } } } // TestClassifyCommand_Read 测试读取类命令分类 func TestClassifyCommand_Read(t *testing.T) { readCmds := []string{"cat", "head", "tail", "less", "more", "wc", "jq", "yq", "sort", "uniq", "cut", "awk", "sed"} for _, cmd := range readCmds { if got := ClassifyCommand(cmd); got != ClassRead { t.Errorf("ClassifyCommand(%q) = %q, 期望 %q", cmd, got, ClassRead) } } } // TestClassifyCommand_List 测试列表类命令分类 func TestClassifyCommand_List(t *testing.T) { listCmds := []string{"ls", "tree", "du", "df", "file", "stat", "lsof", "ps", "top"} for _, cmd := range listCmds { if got := ClassifyCommand(cmd); got != ClassList { t.Errorf("ClassifyCommand(%q) = %q, 期望 %q", cmd, got, ClassList) } } } // TestClassifyCommand_Silent 测试静默操作类命令分类 func TestClassifyCommand_Silent(t *testing.T) { silentCmds := []string{"mv", "cp", "rm", "mkdir", "touch", "ln", "chmod", "chown"} for _, cmd := range silentCmds { if got := ClassifyCommand(cmd); got != ClassSilent { t.Errorf("ClassifyCommand(%q) = %q, 期望 %q", cmd, got, ClassSilent) } } } // TestClassifyCommand_General 测试未知命令归为 general func TestClassifyCommand_General(t *testing.T) { generalCmds := []string{"python", "node", "go", "docker", "kubectl", "unknowncmd"} for _, cmd := range generalCmds { if got := ClassifyCommand(cmd); got != ClassGeneral { t.Errorf("ClassifyCommand(%q) = %q, 期望 %q", cmd, got, ClassGeneral) } } } // TestClassifyCommand_WithPath 测试带路径的命令 func TestClassifyCommand_WithPath(t *testing.T) { tests := []struct { cmd string expected CommandClass }{ {"/usr/bin/grep", ClassSearch}, {"/bin/ls", ClassList}, {"/usr/local/bin/jq", ClassRead}, {"./custom_script", ClassGeneral}, } for _, tt := range tests { if got := ClassifyCommand(tt.cmd); got != tt.expected { t.Errorf("ClassifyCommand(%q) = %q, 期望 %q", tt.cmd, got, tt.expected) } } } // TestClassifyPipeline_SingleCommand 测试管道中只有一个命令 func TestClassifyPipeline_SingleCommand(t *testing.T) { if got := ClassifyPipeline([]string{"grep"}); got != ClassSearch { t.Errorf("单命令管道 grep: 期望 search,实际 %q", got) } } // TestClassifyPipeline_SearchPipe 测试纯搜索/读取管道不降级 func TestClassifyPipeline_SearchPipe(t *testing.T) { // grep | sort 应该是 search(search > read 优先级) if got := ClassifyPipeline([]string{"grep", "sort"}); got != ClassSearch { t.Errorf("grep | sort: 期望 search,实际 %q", got) } // cat | head 应该是 read if got := ClassifyPipeline([]string{"cat", "head"}); got != ClassRead { t.Errorf("cat | head: 期望 read,实际 %q", got) } } // TestClassifyPipeline_DegradeToGeneral 测试管道降级到 general func TestClassifyPipeline_DegradeToGeneral(t *testing.T) { // grep | python -> general(python 是 general 类) if got := ClassifyPipeline([]string{"grep", "python"}); got != ClassGeneral { t.Errorf("grep | python: 期望 general,实际 %q", got) } } // TestClassifyPipeline_NeutralSkip 测试中性命令被跳过 func TestClassifyPipeline_NeutralSkip(t *testing.T) { // echo | grep -> search(echo 是中性的,跳过) if got := ClassifyPipeline([]string{"echo", "grep"}); got != ClassSearch { t.Errorf("echo | grep: 期望 search,实际 %q", got) } // true | false -> general(全是中性命令) if got := ClassifyPipeline([]string{"true", "false"}); got != ClassGeneral { t.Errorf("true | false: 期望 general,实际 %q", got) } } // TestClassifyPipeline_Empty 测试空管道 func TestClassifyPipeline_Empty(t *testing.T) { if got := ClassifyPipeline(nil); got != ClassGeneral { t.Errorf("空管道: 期望 general,实际 %q", got) } if got := ClassifyPipeline([]string{}); got != ClassGeneral { t.Errorf("空管道[]: 期望 general,实际 %q", got) } } // TestClassifyPipeline_SilentCommands 测试静默命令管道 func TestClassifyPipeline_SilentCommands(t *testing.T) { if got := ClassifyPipeline([]string{"mkdir"}); got != ClassSilent { t.Errorf("mkdir: 期望 silent,实际 %q", got) } } // TestIsSearchOrRead 测试 IsSearchOrRead 辅助函数 func TestIsSearchOrRead(t *testing.T) { tests := []struct { cmd string wantSearch bool wantRead bool wantList bool }{ {"grep", true, false, false}, {"cat", false, true, false}, {"ls", false, false, true}, {"python", false, false, false}, {"", false, false, false}, {"/usr/bin/rg", true, false, false}, } for _, tt := range tests { s, r, l := IsSearchOrRead(tt.cmd) if s != tt.wantSearch || r != tt.wantRead || l != tt.wantList { t.Errorf("IsSearchOrRead(%q) = (%v, %v, %v), 期望 (%v, %v, %v)", tt.cmd, s, r, l, tt.wantSearch, tt.wantRead, tt.wantList) } } } // TestExtractCommandName 测试命令名提取 func TestExtractCommandName(t *testing.T) { tests := []struct { input string expected string }{ {"grep foo", "grep"}, {"VAR=val grep foo", "grep"}, {"sudo grep foo", "grep"}, {"env VAR=val grep foo", "grep"}, {"sudo env grep foo", "grep"}, {"/usr/bin/grep foo", "grep"}, {"", ""}, {" ls -la ", "ls"}, } for _, tt := range tests { if got := extractCommandName(tt.input); got != tt.expected { t.Errorf("extractCommandName(%q) = %q, 期望 %q", tt.input, got, tt.expected) } } } // TestClassifyShellCommand 测试完整 shell 命令分类 func TestClassifyShellCommand(t *testing.T) { tests := []struct { cmd string expected CommandClass }{ {"grep -r foo .", ClassSearch}, {"cat file.txt", ClassRead}, {"ls -la /tmp", ClassList}, {"mkdir -p /tmp/test", ClassSilent}, {"python script.py", ClassGeneral}, {"grep -r foo | sort | uniq", ClassSearch}, {"cat file.txt | python process.py", ClassGeneral}, {"echo hello | grep h", ClassSearch}, {"", ClassGeneral}, {"sudo grep foo bar", ClassSearch}, } for _, tt := range tests { if got := ClassifyShellCommand(tt.cmd); got != tt.expected { t.Errorf("ClassifyShellCommand(%q) = %q, 期望 %q", tt.cmd, got, tt.expected) } } }