github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/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 .+ 185 .+ \(from \$GOROOT\) 186 .+ \(from \$GOPATH\)\)`, 187 } 188 }, 189 customMatcher: func(caseNum int, want, got []string) { 190 ok := assert.Equal(t, len(want), len(got), "Case %d: number of output lines do not match", caseNum) 191 if ok { 192 for i := range want { 193 assert.Regexp(t, want[i], got[i], "Case %d, want case %d", caseNum, i) 194 } 195 } 196 }, 197 }, 198 { 199 check: cmdlib.Instance().MustNewCmd("compiles"), 200 filesToWrite: []gofiles.GoFileSpec{ 201 { 202 RelPath: "foo/foo.go", 203 Src: `package foo 204 func Foo() { 205 bar.Bar() 206 baz.Baz() 207 }`, 208 }, 209 }, 210 pathToCheck: func(projectDir string) string { 211 return path.Join(projectDir, "foo") 212 }, 213 want: func(files map[string]gofiles.GoFile) []string { 214 return []string{ 215 `foo.go:3:2: undeclared name: bar`, 216 `foo.go:4:2: undeclared name: baz`, 217 } 218 }, 219 customMatcher: func(caseNum int, want, got []string) { 220 ok := assert.Equal(t, len(want), len(got), "Case %d: number of output lines do not match", caseNum) 221 if ok { 222 for i := range want { 223 assert.Regexp(t, want[i], got[i], "Case %d, want case %d", caseNum, i) 224 } 225 } 226 }, 227 }, 228 { 229 check: cmdlib.Instance().MustNewCmd("extimport"), 230 filesToWrite: []gofiles.GoFileSpec{ 231 { 232 RelPath: "foo/foo.go", 233 Src: `package foo 234 import "{{index . "bar/bar.go"}}" 235 func Foo() { 236 bar.Bar() 237 } 238 `, 239 }, 240 { 241 RelPath: "bar/bar.go", 242 Src: `package bar; func Bar() {}`, 243 }, 244 }, 245 pathToCheck: func(projectDir string) string { 246 return path.Join(projectDir, "foo") 247 }, 248 want: func(files map[string]gofiles.GoFile) []string { 249 return []string{ 250 fmt.Sprintf(`foo.go:2:8: imports external package %s`, files["bar/bar.go"].ImportPath), 251 } 252 }, 253 }, 254 } { 255 currCaseProjectDir, err := ioutil.TempDir(tmpDir, "") 256 require.NoError(t, err, "Case %d", i) 257 258 files, err := gofiles.Write(currCaseProjectDir, currCase.filesToWrite) 259 require.NoError(t, err, "Case %d", i) 260 261 checker, err := checks.GetChecker(currCase.check) 262 require.NoError(t, err, "Case %d", i) 263 264 runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+currCase.check.Name()) 265 lineInfo, err := checker.Check(runner, currCase.pathToCheck(currCaseProjectDir), params.OKGo{}) 266 require.NoError(t, err, "Case %d", i) 267 268 want := currCase.want(files) 269 got := toStringSlice(lineInfo) 270 if currCase.customMatcher == nil { 271 assert.Equal(t, want, got, "Case %d", i) 272 } else { 273 currCase.customMatcher(i, want, got) 274 } 275 } 276 } 277 278 func TestFilters(t *testing.T) { 279 cli, err := products.Bin("okgo") 280 require.NoError(t, err) 281 282 cmd := cmdlib.Instance().MustNewCmd("golint") 283 checker, err := checks.GetChecker(cmd) 284 require.NoError(t, err) 285 runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name()) 286 287 for i, currCase := range []struct { 288 filters []checkoutput.Filterer 289 want []string 290 }{ 291 { 292 filters: nil, 293 want: []string{ 294 "bad.go:3:1: exported function Bad should have comment or be unexported", 295 "mock/mock.go:3:1: exported function Mock should have comment or be unexported", 296 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 297 }, 298 }, 299 { 300 filters: []checkoutput.Filterer{ 301 checkoutput.RelativePathFilter("mock"), 302 }, 303 want: []string{ 304 "bad.go:3:1: exported function Bad should have comment or be unexported", 305 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 306 }, 307 }, 308 { 309 filters: []checkoutput.Filterer{ 310 checkoutput.NamePathFilter("mock"), 311 }, 312 want: []string{ 313 "bad.go:3:1: exported function Bad should have comment or be unexported", 314 }, 315 }, 316 { 317 filters: []checkoutput.Filterer{ 318 checkoutput.MessageRegexpFilter("should have comment or be unexported"), 319 }, 320 want: []string{}, 321 }, 322 } { 323 lineInfo, err := checker.Check(runner, "./testdata/filter", params.OKGo{}) 324 require.NoError(t, err, "Case %d", i) 325 326 filteredLines, err := checkoutput.ApplyFilters(lineInfo, currCase.filters) 327 require.NoError(t, err, "Case %d", i) 328 329 assert.Equal(t, currCase.want, toStringSlice(filteredLines), "Case %d", i) 330 } 331 } 332 333 func TestCheckerUsesConfig(t *testing.T) { 334 cli, err := products.Bin("okgo") 335 require.NoError(t, err) 336 337 tmpDir, cleanup, err := dirs.TempDir("", "") 338 defer cleanup() 339 require.NoError(t, err) 340 341 for i, currCase := range []struct { 342 config string 343 want []string 344 }{ 345 { 346 config: "", 347 want: []string{ 348 "bad.go:3:1: exported function Bad should have comment or be unexported", 349 "mock/mock.go:3:1: exported function Mock should have comment or be unexported", 350 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 351 }, 352 }, 353 { 354 config: ` 355 exclude: 356 paths: 357 - "mock" 358 `, 359 want: []string{ 360 "bad.go:3:1: exported function Bad should have comment or be unexported", 361 "nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported", 362 }, 363 }, 364 { 365 config: ` 366 exclude: 367 names: 368 - "m.ck" 369 `, 370 want: []string{ 371 "bad.go:3:1: exported function Bad should have comment or be unexported", 372 }, 373 }, 374 { 375 config: ` 376 checks: 377 golint: 378 filters: 379 - type: "message" 380 value: "should have comment or be unexported" 381 `, 382 want: []string{}, 383 }, 384 } { 385 tmpFile, err := ioutil.TempFile(tmpDir, "") 386 require.NoError(t, err, "Case %d", i) 387 tmpFilePath := tmpFile.Name() 388 err = tmpFile.Close() 389 require.NoError(t, err, "Case %d", i) 390 err = ioutil.WriteFile(tmpFilePath, []byte(unindent(currCase.config)), 0644) 391 require.NoError(t, err, "Case %d", i) 392 393 cfg, err := config.Load(tmpFilePath, "") 394 require.NoError(t, err, "Case %d", i) 395 396 cmd := cmdlib.Instance().MustNewCmd("golint") 397 checker, err := checks.GetChecker(cmd) 398 require.NoError(t, err, "Case %d", i) 399 400 runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name()) 401 lineInfo, err := checker.Check(runner, "./testdata/filter", cfg) 402 require.NoError(t, err, "Case %d", i) 403 404 assert.Equal(t, currCase.want, toStringSlice(lineInfo), "Case %d", i) 405 } 406 } 407 408 func TestCheckerUsesReleaseTagConfig(t *testing.T) { 409 cli, err := products.Bin("okgo") 410 require.NoError(t, err) 411 412 tmpDir, cleanup, err := dirs.TempDir("", "") 413 defer cleanup() 414 require.NoError(t, err) 415 416 for i, currCase := range []struct { 417 name string 418 files []gofiles.GoFileSpec 419 config string 420 want []string 421 }{ 422 { 423 name: "file with go1.7 build tag processed", 424 files: []gofiles.GoFileSpec{ 425 { 426 RelPath: "foo.go", 427 Src: `// +build go1.7 428 429 package foo 430 import "os" 431 func Foo() { 432 os.Setenv("foo", "bar") 433 } 434 `, 435 }, 436 { 437 RelPath: "bar.go", 438 Src: `package foo 439 import "os" 440 func Bar() { 441 os.Setenv("foo", "bar") 442 } 443 `, 444 }, 445 }, 446 config: "", 447 want: []string{ 448 "Running errcheck...", 449 "bar.go:4:16: os.Setenv(\"foo\", \"bar\")", 450 "foo.go:6:16: os.Setenv(\"foo\", \"bar\")", 451 "", 452 }, 453 }, 454 { 455 name: "file with go1.7 build tag ignored if release-tag set to go1.6", 456 files: []gofiles.GoFileSpec{ 457 { 458 RelPath: "foo.go", 459 Src: `// +build go1.7 460 461 package foo 462 import "os" 463 func Foo() { 464 os.Setenv("foo", "bar") 465 } 466 `, 467 }, 468 { 469 RelPath: "bar.go", 470 Src: `package foo 471 import "os" 472 func Bar() { 473 os.Setenv("foo", "bar") 474 } 475 `, 476 }, 477 }, 478 config: `release-tag: go1.6`, 479 want: []string{ 480 "Running errcheck...", 481 "bar.go:4:16: os.Setenv(\"foo\", \"bar\")", 482 "", 483 }, 484 }, 485 { 486 name: "ignoring a returned error is flagged", 487 files: []gofiles.GoFileSpec{ 488 { 489 RelPath: "foo.go", 490 Src: `package foo 491 import "os" 492 func Foo() { 493 os.Open("/") 494 os.Pipe() 495 } 496 `, 497 }, 498 }, 499 config: ``, 500 want: []string{ 501 "Running errcheck...", 502 "foo.go:4:14: os.Open(\"/\")", 503 "foo.go:5:14: os.Pipe()", 504 "", 505 }, 506 }, 507 { 508 name: "ignoring a returned error is not flagged if referenced from an exclude list", 509 files: []gofiles.GoFileSpec{ 510 { 511 RelPath: "foo.go", 512 Src: `package foo 513 import "os" 514 func Foo() { 515 os.Open("/") 516 os.Pipe() 517 } 518 `, 519 }, 520 { 521 RelPath: "exclude.txt", 522 Src: `os.Open 523 `, 524 }, 525 }, 526 config: ` 527 checks: 528 errcheck: 529 args: 530 - "-exclude" 531 - "exclude.txt" 532 `, 533 want: []string{ 534 "Running errcheck...", 535 "foo.go:5:14: os.Pipe()", 536 "", 537 }, 538 }, 539 } { 540 currCaseDir, err := ioutil.TempDir(tmpDir, "") 541 require.NoError(t, err, "Case %d: %s", i, currCase.name) 542 543 _, err = gofiles.Write(currCaseDir, currCase.files) 544 require.NoError(t, err, "Case %d: %s", i, currCase.name) 545 546 tmpFile, err := ioutil.TempFile(currCaseDir, "") 547 require.NoError(t, err, "Case %d: %s", i, currCase.name) 548 cfgFilePath := tmpFile.Name() 549 err = tmpFile.Close() 550 require.NoError(t, err, "Case %d: %s", i, currCase.name) 551 err = ioutil.WriteFile(cfgFilePath, []byte(unindent(currCase.config)), 0644) 552 require.NoError(t, err, "Case %d: %s", i, currCase.name) 553 554 cmd := exec.Command(cli, "--config", cfgFilePath, "errcheck", ".") 555 cmd.Dir = currCaseDir 556 output, err := cmd.CombinedOutput() 557 require.Error(t, err, fmt.Errorf("Expected command %v to fail. Output:\n%v", cmd.Args, string(output))) 558 559 assert.Equal(t, currCase.want, strings.Split(string(output), "\n"), "Case %d: %s", i, currCase.name) 560 } 561 } 562 563 func toStringSlice(input []checkoutput.Issue) []string { 564 output := make([]string, len(input)) 565 for i, curr := range input { 566 output[i] = curr.String() 567 } 568 return output 569 } 570 571 func unindent(input string) string { 572 return strings.Replace(input, "\n\t\t\t", "\n", -1) 573 }