github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/apps/okgo/integration_test/integration_test.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package integration_test 16 17 import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "os/exec" 22 "path" 23 "strings" 24 "testing" 25 26 "github.com/nmiyake/pkg/dirs" 27 "github.com/nmiyake/pkg/gofiles" 28 "github.com/palantir/amalgomate/amalgomated" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 32 "github.com/palantir/godel/apps/okgo/checkoutput" 33 "github.com/palantir/godel/apps/okgo/checks" 34 "github.com/palantir/godel/apps/okgo/cmd/cmdlib" 35 "github.com/palantir/godel/apps/okgo/config" 36 "github.com/palantir/godel/apps/okgo/params" 37 "github.com/palantir/godel/pkg/products" 38 ) 39 40 func TestCheckers(t *testing.T) { 41 cli, err := products.Bin("okgo") 42 require.NoError(t, err) 43 44 for i, currCase := range []struct { 45 check amalgomated.Cmd 46 want []string 47 }{ 48 { 49 check: cmdlib.Instance().MustNewCmd("deadcode"), 50 want: []string{ 51 "pkg1/bad.go:27:1: deadcode is unused", 52 "pkg1/bad.go:40:1: varcheck is unused", 53 "pkg2/bad2.go:27:1: deadcode is unused", 54 "pkg2/bad2.go:40:1: varcheck is unused", 55 }, 56 }, 57 { 58 check: cmdlib.Instance().MustNewCmd("errcheck"), 59 want: []string{ 60 "pkg1/bad.go:11:8: helper()", 61 "pkg2/bad2.go:11:8: helper()", 62 }, 63 }, 64 { 65 check: cmdlib.Instance().MustNewCmd("golint"), 66 want: []string{ 67 `pkg1/bad.go:49:1: comment on exported function Lint should be of the form "Lint ..."`, 68 `pkg2/bad2.go:49:1: comment on exported function Lint should be of the form "Lint ..."`, 69 }, 70 }, 71 { 72 check: cmdlib.Instance().MustNewCmd("govet"), 73 want: []string{ 74 "pkg1/bad.go:23: self-assignment of foo to foo", 75 "pkg2/bad2.go:23: self-assignment of foo to foo", 76 }, 77 }, 78 { 79 check: cmdlib.Instance().MustNewCmd("importalias"), 80 want: []string{ 81 `pkg1/bad.go:3:8: uses alias "myjson" to import package "encoding/json". No consensus alias exists for this import in the project ("ejson" and "myjson" are both used once each).`, 82 `pkg2/bad2.go:3:8: uses alias "ejson" to import package "encoding/json". No consensus alias exists for this import in the project ("ejson" and "myjson" are both used once each).`, 83 }, 84 }, 85 { 86 check: cmdlib.Instance().MustNewCmd("ineffassign"), 87 want: []string{ 88 "pkg1/bad.go:34:2: ineffectual assignment to kvs", 89 "pkg1/bad.go:36:2: ineffectual assignment to kvs", 90 "pkg2/bad2.go:34:2: ineffectual assignment to kvs", 91 "pkg2/bad2.go:36:2: ineffectual assignment to kvs", 92 }, 93 }, 94 { 95 check: cmdlib.Instance().MustNewCmd("outparamcheck"), 96 want: []string{ 97 `github.com/palantir/godel/apps/okgo/integration_test/testdata/standard/pkg1/bad.go:16:28: _ = myjson.Unmarshal(nil, "") // 2nd argument of 'Unmarshal' requires '&'`, 98 `github.com/palantir/godel/apps/okgo/integration_test/testdata/standard/pkg2/bad2.go:16:27: _ = ejson.Unmarshal(nil, "") // 2nd argument of 'Unmarshal' requires '&'`, 99 }, 100 }, 101 { 102 check: cmdlib.Instance().MustNewCmd("unconvert"), 103 want: []string{ 104 "pkg1/bad.go:45:14: unnecessary conversion", 105 "pkg2/bad2.go:45:14: unnecessary conversion", 106 }, 107 }, 108 { 109 check: cmdlib.Instance().MustNewCmd("varcheck"), 110 want: []string{ 111 "pkg1/bad.go:40:7: varcheck", 112 "pkg2/bad2.go:40:7: varcheck", 113 }, 114 }, 115 } { 116 checker, err := checks.GetChecker(currCase.check) 117 require.NoError(t, err) 118 119 runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+currCase.check.Name()) 120 lineInfo, err := checker.Check(runner, "./testdata/standard", params.OKGo{}) 121 require.NoError(t, err, "Case %d", i) 122 123 assert.Equal(t, currCase.want, toStringSlice(lineInfo), "Case %d", i) 124 } 125 } 126 127 func TestCompilesChecker(t *testing.T) { 128 cli, err := products.Bin("okgo") 129 require.NoError(t, err) 130 131 wd, err := os.Getwd() 132 require.NoError(t, err) 133 134 tmpDir, cleanup, err := dirs.TempDir(wd, "") 135 defer cleanup() 136 require.NoError(t, err) 137 138 for i, currCase := range []struct { 139 check amalgomated.Cmd 140 filesToWrite []gofiles.GoFileSpec 141 pathToCheck func(projectDir string) string 142 want func(files map[string]gofiles.GoFile) []string 143 customMatcher func(caseNum int, expected, actual []string) 144 }{ 145 { 146 check: cmdlib.Instance().MustNewCmd("compiles"), 147 filesToWrite: []gofiles.GoFileSpec{ 148 { 149 RelPath: "foo/foo.go", 150 Src: `package foo 151 func Foo() int { 152 return "foo" 153 }`, 154 }, 155 }, 156 pathToCheck: func(projectDir string) string { 157 return path.Join(projectDir, "foo") 158 }, 159 want: func(files map[string]gofiles.GoFile) []string { 160 return []string{ 161 `foo.go:3:9: cannot convert "foo" (untyped string constant) to int`, 162 } 163 }, 164 }, 165 { 166 check: cmdlib.Instance().MustNewCmd("compiles"), 167 filesToWrite: []gofiles.GoFileSpec{ 168 { 169 RelPath: "foo/foo.go", 170 Src: `package foo 171 import "bar" 172 func Foo() { 173 bar.Bar() 174 }`, 175 }, 176 }, 177 pathToCheck: func(projectDir string) string { 178 return path.Join(projectDir, "foo") 179 }, 180 want: func(files map[string]gofiles.GoFile) []string { 181 return []string{ 182 `foo.go:2:8: could not import bar \(cannot find package "bar" in any of: 183 .+ \(vendor tree\) 184 .+ \(from \$GOROOT\) 185 .+ \(from \$GOPATH\)\)`, 186 `foo.go:4:2: undeclared name: bar`, 187 } 188 }, 189 customMatcher: func(caseNum int, want, got []string) { 190 assert.Equal(t, len(want), len(got), "Case %d: number of output lines do not match") 191 192 for i := range want { 193 assert.Regexp(t, want[i], got[i], "Case %d", i) 194 } 195 }, 196 }, 197 { 198 check: cmdlib.Instance().MustNewCmd("extimport"), 199 filesToWrite: []gofiles.GoFileSpec{ 200 { 201 RelPath: "foo/foo.go", 202 Src: `package foo 203 import "{{index . "bar/bar.go"}}" 204 func Foo() { 205 bar.Bar() 206 } 207 `, 208 }, 209 { 210 RelPath: "bar/bar.go", 211 Src: `package bar; func Bar() {}`, 212 }, 213 }, 214 pathToCheck: func(projectDir string) string { 215 return path.Join(projectDir, "foo") 216 }, 217 want: func(files map[string]gofiles.GoFile) []string { 218 return []string{ 219 fmt.Sprintf(`foo.go:2:8: imports external package %s`, files["bar/bar.go"].ImportPath), 220 } 221 }, 222 }, 223 } { 224 currCaseProjectDir, err := ioutil.TempDir(tmpDir, "") 225 require.NoError(t, err, "Case %d", i) 226 227 files, err := gofiles.Write(currCaseProjectDir, currCase.filesToWrite) 228 require.NoError(t, err, "Case %d", i) 229 230 checker, err := checks.GetChecker(currCase.check) 231 require.NoError(t, err, "Case %d", i) 232 233 runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+currCase.check.Name()) 234 lineInfo, err := checker.Check(runner, currCase.pathToCheck(currCaseProjectDir), params.OKGo{}) 235 require.NoError(t, err, "Case %d", i) 236 237 want := currCase.want(files) 238 got := toStringSlice(lineInfo) 239 if currCase.customMatcher == nil { 240 assert.Equal(t, want, got, "Case %d", i) 241 } else { 242 currCase.customMatcher(i, want, got) 243 } 244 } 245 } 246 247 func TestFilters(t *testing.T) { 248 cli, err := products.Bin("okgo") 249 require.NoError(t, err) 250 251 cmd := cmdlib.Instance().MustNewCmd("golint") 252 checker, err := checks.GetChecker(cmd) 253 require.NoError(t, err) 254 runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name()) 255 256 for i, currCase := range []struct { 257 filters []checkoutput.Filterer 258 want []string 259 }{ 260 { 261 filters: nil, 262 want: []string{ 263 "bad.go:3:1: exported function Bad should have comment or be unexported", 264 "mock/mock.go:3:1: exported function Mock should have comment or be unexported", 265 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 266 }, 267 }, 268 { 269 filters: []checkoutput.Filterer{ 270 checkoutput.RelativePathFilter("mock"), 271 }, 272 want: []string{ 273 "bad.go:3:1: exported function Bad should have comment or be unexported", 274 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 275 }, 276 }, 277 { 278 filters: []checkoutput.Filterer{ 279 checkoutput.NamePathFilter("mock"), 280 }, 281 want: []string{ 282 "bad.go:3:1: exported function Bad should have comment or be unexported", 283 }, 284 }, 285 { 286 filters: []checkoutput.Filterer{ 287 checkoutput.MessageRegexpFilter("should have comment or be unexported"), 288 }, 289 want: []string{}, 290 }, 291 } { 292 lineInfo, err := checker.Check(runner, "./testdata/filter", params.OKGo{}) 293 require.NoError(t, err, "Case %d", i) 294 295 filteredLines, err := checkoutput.ApplyFilters(lineInfo, currCase.filters) 296 require.NoError(t, err, "Case %d", i) 297 298 assert.Equal(t, currCase.want, toStringSlice(filteredLines), "Case %d", i) 299 } 300 } 301 302 func TestCheckerUsesConfig(t *testing.T) { 303 cli, err := products.Bin("okgo") 304 require.NoError(t, err) 305 306 tmpDir, cleanup, err := dirs.TempDir("", "") 307 defer cleanup() 308 require.NoError(t, err) 309 310 for i, currCase := range []struct { 311 config string 312 want []string 313 }{ 314 { 315 config: "", 316 want: []string{ 317 "bad.go:3:1: exported function Bad should have comment or be unexported", 318 "mock/mock.go:3:1: exported function Mock should have comment or be unexported", 319 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 320 }, 321 }, 322 { 323 config: ` 324 exclude: 325 paths: 326 - "mock" 327 `, 328 want: []string{ 329 "bad.go:3:1: exported function Bad should have comment or be unexported", 330 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 331 }, 332 }, 333 { 334 config: ` 335 exclude: 336 names: 337 - "m.ck" 338 `, 339 want: []string{ 340 "bad.go:3:1: exported function Bad should have comment or be unexported", 341 }, 342 }, 343 { 344 config: ` 345 checks: 346 golint: 347 filters: 348 - type: "message" 349 value: "should have comment or be unexported" 350 `, 351 want: []string{}, 352 }, 353 } { 354 tmpFile, err := ioutil.TempFile(tmpDir, "") 355 require.NoError(t, err, "Case %d", i) 356 tmpFilePath := tmpFile.Name() 357 err = tmpFile.Close() 358 require.NoError(t, err, "Case %d", i) 359 err = ioutil.WriteFile(tmpFilePath, []byte(unindent(currCase.config)), 0644) 360 require.NoError(t, err, "Case %d", i) 361 362 cfg, err := config.Load(tmpFilePath, "") 363 require.NoError(t, err, "Case %d", i) 364 365 cmd := cmdlib.Instance().MustNewCmd("golint") 366 checker, err := checks.GetChecker(cmd) 367 require.NoError(t, err, "Case %d", i) 368 369 runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name()) 370 lineInfo, err := checker.Check(runner, "./testdata/filter", cfg) 371 require.NoError(t, err, "Case %d", i) 372 373 assert.Equal(t, currCase.want, toStringSlice(lineInfo), "Case %d", i) 374 } 375 } 376 377 func TestCheckerUsesReleaseTagConfig(t *testing.T) { 378 cli, err := products.Bin("okgo") 379 require.NoError(t, err) 380 381 tmpDir, cleanup, err := dirs.TempDir("", "") 382 defer cleanup() 383 require.NoError(t, err) 384 385 for i, currCase := range []struct { 386 name string 387 files []gofiles.GoFileSpec 388 config string 389 want []string 390 }{ 391 { 392 name: "file with go1.7 build tag processed", 393 files: []gofiles.GoFileSpec{ 394 { 395 RelPath: "foo.go", 396 Src: `// +build go1.7 397 398 package foo 399 import "os" 400 func Foo() { 401 os.Setenv("foo", "bar") 402 } 403 `, 404 }, 405 { 406 RelPath: "bar.go", 407 Src: `package foo 408 import "os" 409 func Bar() { 410 os.Setenv("foo", "bar") 411 } 412 `, 413 }, 414 }, 415 config: "", 416 want: []string{ 417 "Running errcheck...", 418 "bar.go:4:16: os.Setenv(\"foo\", \"bar\")", 419 "foo.go:6:16: os.Setenv(\"foo\", \"bar\")", 420 "", 421 }, 422 }, 423 { 424 name: "file with go1.7 build tag ignored if release-tag set to go1.6", 425 files: []gofiles.GoFileSpec{ 426 { 427 RelPath: "foo.go", 428 Src: `// +build go1.7 429 430 package foo 431 import "os" 432 func Foo() { 433 os.Setenv("foo", "bar") 434 } 435 `, 436 }, 437 { 438 RelPath: "bar.go", 439 Src: `package foo 440 import "os" 441 func Bar() { 442 os.Setenv("foo", "bar") 443 } 444 `, 445 }, 446 }, 447 config: `release-tag: go1.6`, 448 want: []string{ 449 "Running errcheck...", 450 "bar.go:4:16: os.Setenv(\"foo\", \"bar\")", 451 "", 452 }, 453 }, 454 } { 455 currCaseDir, err := ioutil.TempDir(tmpDir, "") 456 require.NoError(t, err, "Case %d: %s", i, currCase.name) 457 458 _, err = gofiles.Write(currCaseDir, currCase.files) 459 require.NoError(t, err, "Case %d: %s", i, currCase.name) 460 461 tmpFile, err := ioutil.TempFile(currCaseDir, "") 462 require.NoError(t, err, "Case %d: %s", i, currCase.name) 463 cfgFilePath := tmpFile.Name() 464 err = tmpFile.Close() 465 require.NoError(t, err, "Case %d: %s", i, currCase.name) 466 err = ioutil.WriteFile(cfgFilePath, []byte(unindent(currCase.config)), 0644) 467 require.NoError(t, err, "Case %d: %s", i, currCase.name) 468 469 cmd := exec.Command(cli, "--config", cfgFilePath, "errcheck", ".") 470 cmd.Dir = currCaseDir 471 output, err := cmd.CombinedOutput() 472 require.Error(t, err, fmt.Errorf("Expected command %v to fail. Output:\n%v", cmd.Args, string(output))) 473 474 assert.Equal(t, currCase.want, strings.Split(string(output), "\n"), "Case %d: %s", i, currCase.name) 475 } 476 } 477 478 func toStringSlice(input []checkoutput.Issue) []string { 479 output := make([]string, len(input)) 480 for i, curr := range input { 481 output[i] = curr.String() 482 } 483 return output 484 } 485 486 func unindent(input string) string { 487 return strings.Replace(input, "\n\t\t\t", "\n", -1) 488 }