github.com/golangci/revgrep@v0.5.4-0.20240409234448-4d9d98340cb9/revgrep_test.go (about) 1 package revgrep 2 3 import ( 4 "bufio" 5 "bytes" 6 "io" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "reflect" 11 "regexp" 12 "sort" 13 "strings" 14 "testing" 15 ) 16 17 func setup(t *testing.T, stage, subdir string) (string, []byte) { 18 t.Helper() 19 20 wd, err := os.Getwd() 21 if err != nil { 22 t.Fatalf("could not get working dir: %s", err) 23 } 24 25 testDataDir := filepath.Join(wd, "testdata") 26 27 // Execute make 28 cmd := exec.Command("bash", "./make.sh", stage) 29 cmd.Dir = testDataDir 30 31 gitOutput, err := cmd.CombinedOutput() 32 if err != nil { 33 t.Logf("%s: git setup: %s", stage, string(gitOutput)) 34 t.Fatalf("could not run make.sh: %v", err) 35 } 36 37 gitDir := filepath.Join(testDataDir, "git") 38 t.Cleanup(func() { 39 _ = os.RemoveAll(gitDir) 40 }) 41 42 cmd = exec.Command("go", "vet", "./...") 43 cmd.Dir = gitDir 44 45 goVetOutput, err := cmd.CombinedOutput() 46 if cmd.ProcessState.ExitCode() != 1 { 47 t.Logf("%s: go vet: %s", stage, string(goVetOutput)) 48 t.Fatalf("could not run go vet: %v", err) 49 } 50 51 // chdir so the vcs exec commands read the correct testdata 52 err = os.Chdir(filepath.Join(gitDir, subdir)) 53 if err != nil { 54 t.Fatalf("could not chdir: %v", err) 55 } 56 57 if stage == "11-abs-path" { 58 goVetOutput = regexp.MustCompile(`(.+\.go)`). 59 ReplaceAll(goVetOutput, []byte(filepath.Join(gitDir, "$1"))) 60 } 61 62 // clean go vet output 63 goVetOutput = bytes.ReplaceAll(goVetOutput, []byte("."+string(filepath.Separator)), []byte("")) 64 65 t.Logf("%s: go vet clean: %s", stage, string(goVetOutput)) 66 67 return wd, goVetOutput 68 } 69 70 func teardown(t *testing.T, wd string) { 71 t.Helper() 72 73 err := os.Chdir(wd) 74 if err != nil { 75 t.Fatalf("could not chdir: %v", err) 76 } 77 } 78 79 // TestCheckerRegexp tests line matching and extraction of issue. 80 func TestCheckerRegexp(t *testing.T) { 81 tests := []struct { 82 regexp string 83 line string 84 want Issue 85 }{ 86 { 87 line: "file.go:1:issue", 88 want: Issue{File: "file.go", LineNo: 1, HunkPos: 2, Issue: "file.go:1:issue", Message: "issue"}, 89 }, 90 { 91 line: "file.go:1:5:issue", 92 want: Issue{File: "file.go", LineNo: 1, ColNo: 5, HunkPos: 2, Issue: "file.go:1:5:issue", Message: "issue"}, 93 }, 94 { 95 line: "file.go:1: issue", 96 want: Issue{File: "file.go", LineNo: 1, HunkPos: 2, Issue: "file.go:1: issue", Message: "issue"}, 97 }, 98 { 99 regexp: `.*?:(.*?\.go):([0-9]+):()(.*)`, 100 line: "prefix:file.go:1:issue", 101 want: Issue{File: "file.go", LineNo: 1, HunkPos: 2, Issue: "prefix:file.go:1:issue", Message: "issue"}, 102 }, 103 } 104 105 diff := []byte(`--- a/file.go 106 +++ b/file.go 107 @@ -1,1 +1,1 @@ 108 -func Line() {} 109 +func NewLine() {}`) 110 111 for _, test := range tests { 112 checker := Checker{ 113 Patch: bytes.NewReader(diff), 114 Regexp: test.regexp, 115 } 116 117 issues, err := checker.Check(bytes.NewReader([]byte(test.line)), io.Discard) 118 if err != nil { 119 t.Errorf("unexpected error: %v", err) 120 } 121 122 want := []Issue{test.want} 123 if !reflect.DeepEqual(issues, want) { 124 t.Errorf("unexpected issues for line: %q\nhave: %#v\nwant: %#v", test.line, issues, want) 125 } 126 } 127 } 128 129 // TestWholeFile tests Checker.WholeFiles will report any issues in files that have changes, even if 130 // they are outside the diff. 131 func TestWholeFiles(t *testing.T) { 132 tests := []struct { 133 name string 134 line string 135 matches bool 136 }{ 137 { 138 name: "inside diff", 139 line: "file.go:1:issue", 140 matches: true, 141 }, 142 { 143 name: "outside diff", 144 line: "file.go:10:5:issue", 145 matches: true, 146 }, 147 { 148 name: "different file", 149 line: "file2.go:1:issue", 150 }, 151 } 152 153 diff := []byte(`--- a/file.go 154 +++ b/file.go 155 @@ -1,1 +1,1 @@ 156 -func Line() {} 157 +func NewLine() {}`) 158 159 for _, test := range tests { 160 t.Run(test.name, func(t *testing.T) { 161 checker := Checker{ 162 Patch: bytes.NewReader(diff), 163 WholeFiles: true, 164 } 165 166 issues, err := checker.Check(bytes.NewReader([]byte(test.line)), io.Discard) 167 if err != nil { 168 t.Fatalf("unexpected error: %v", err) 169 } 170 if test.matches && len(issues) != 1 { 171 t.Fatalf("expected one issue to be returned, but got %#v", issues) 172 } 173 if !test.matches && len(issues) != 0 { 174 t.Fatalf("expected no issues to be returned, but got %#v", issues) 175 } 176 }) 177 } 178 } 179 180 // Tests the writer in the argument to the Changes function 181 // and generally tests the entire program functionality. 182 func TestChecker_Check_changesWriter(t *testing.T) { 183 tests := map[string]struct { 184 subdir string 185 exp []string // file:linenumber including trailing colon 186 revFrom string 187 revTo string 188 }{ 189 "2-untracked": {exp: []string{"main.go:3:"}}, 190 "3-untracked-subdir": {exp: []string{"main.go:3:", "subdir/main.go:3:"}}, 191 "3-untracked-subdir-cwd": {subdir: "subdir", exp: []string{"main.go:3:"}}, 192 "4-commit": {exp: []string{"main.go:3:", "subdir/main.go:3:"}}, 193 "5-unstaged-no-warning": {}, 194 "6-unstaged": {exp: []string{"main.go:6:"}}, 195 // From a commit, all changes should be shown 196 "7-commit": {exp: []string{"main.go:6:"}, revFrom: "HEAD~1"}, 197 // From a commit+unstaged, all changes should be shown 198 "8-unstaged": {exp: []string{"main.go:6:", "main.go:7:"}, revFrom: "HEAD~1"}, 199 // From a commit+unstaged+untracked, all changes should be shown 200 "9-untracked": {exp: []string{"main.go:6:", "main.go:7:", "main2.go:3:"}, revFrom: "HEAD~1"}, 201 // From a commit to last commit, all changes should be shown except recent unstaged, untracked 202 "10-committed": {exp: []string{"main.go:6:"}, revFrom: "HEAD~1", revTo: "HEAD~0"}, 203 // static analysis tools with absolute paths should be handled 204 "11-abs-path": {exp: []string{"main.go:6:"}, revFrom: "HEAD~1", revTo: "HEAD~0"}, 205 // Removing a single line shouldn't raise any issues. 206 "12-removed-lines": {}, 207 } 208 209 for stage, test := range tests { 210 t.Run(stage, func(t *testing.T) { 211 prevwd, goVetOutput := setup(t, stage, test.subdir) 212 213 var out bytes.Buffer 214 215 c := Checker{ 216 RevisionFrom: test.revFrom, 217 RevisionTo: test.revTo, 218 } 219 _, err := c.Check(bytes.NewBuffer(goVetOutput), &out) 220 if err != nil { 221 t.Errorf("%s: unexpected error: %v", stage, err) 222 } 223 224 var lines []string 225 226 scanner := bufio.NewScanner(&out) 227 for scanner.Scan() { 228 // Rewrite abs paths to for simpler matching 229 line := rewriteAbs(scanner.Text()) 230 lines = append(lines, strings.TrimPrefix(line, "./")) 231 } 232 233 sort.Slice(lines, func(i, j int) bool { 234 return lines[i] <= lines[j] 235 }) 236 237 var count int 238 for i, line := range lines { 239 count++ 240 if i > len(test.exp)-1 { 241 t.Errorf("%s: unexpected line: %q", stage, line) 242 } else if !strings.HasPrefix(line, filepath.FromSlash(test.exp[i])) { 243 t.Errorf("%s: line %q does not have prefix %q", stage, line, filepath.FromSlash(test.exp[i])) 244 } 245 } 246 247 if count != len(test.exp) { 248 t.Errorf("%s: got %d, expected %d", stage, count, len(test.exp)) 249 } 250 251 teardown(t, prevwd) 252 }) 253 } 254 } 255 256 func rewriteAbs(line string) string { 257 cwd, err := os.Getwd() 258 if err != nil { 259 panic(err) 260 } 261 return strings.TrimPrefix(line, cwd+string(filepath.Separator)) 262 } 263 264 func TestGitPatchNonGitDir(t *testing.T) { 265 // Change to non-git dir 266 err := os.Chdir("/") 267 if err != nil { 268 t.Fatalf("could not chdir: %v", err) 269 } 270 271 patch, newfiles, err := GitPatch("", "") 272 if err != nil { 273 t.Errorf("error expected nil, got: %v", err) 274 } 275 if patch != nil { 276 t.Errorf("patch expected nil, got: %v", patch) 277 } 278 if newfiles != nil { 279 t.Errorf("newFiles expected nil, got: %v", newfiles) 280 } 281 } 282 283 func TestLinesChanged(t *testing.T) { 284 diff := []byte(`--- a/file.go 285 +++ b/file.go 286 @@ -1,1 +1,1 @@ 287 // comment 288 -func Line() {} 289 +func NewLine() {} 290 @@ -20,1 +20,1 @@ 291 // comment 292 -func Line() {} 293 +func NewLine() {} 294 // comment 295 @@ -3,1 +30,1 @@ 296 -func Line() {} 297 +func NewLine() {} 298 // comment`) 299 300 checker := Checker{ 301 Patch: bytes.NewReader(diff), 302 } 303 304 have := checker.linesChanged() 305 306 want := map[string][]pos{ 307 "file.go": { 308 {lineNo: 2, hunkPos: 3}, 309 {lineNo: 21, hunkPos: 7}, 310 {lineNo: 30, hunkPos: 11}, 311 }, 312 } 313 314 if !reflect.DeepEqual(have, want) { 315 t.Errorf("unexpected pos:\nhave: %#v\nwant: %#v", have, want) 316 } 317 }