github.com/golangCi/golangCi-lint@v1.10.1/test/run_test.go (about) 1 package test 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "sort" 11 "strings" 12 "sync" 13 "syscall" 14 "testing" 15 16 "github.com/golangci/golangci-lint/pkg/exitcodes" 17 "github.com/golangci/golangci-lint/pkg/lint/lintersdb" 18 19 "github.com/stretchr/testify/assert" 20 ) 21 22 var root = filepath.Join("..", "...") 23 var installOnce sync.Once 24 25 const noIssuesOut = "" 26 27 func installBinary(t assert.TestingT) { 28 installOnce.Do(func() { 29 cmd := exec.Command("go", "install", filepath.Join("..", "cmd", binName)) 30 assert.NoError(t, cmd.Run(), "Can't go install %s", binName) 31 }) 32 } 33 34 func checkNoIssuesRun(t *testing.T, out string, exitCode int) { 35 assert.Equal(t, exitcodes.Success, exitCode) 36 assert.Equal(t, noIssuesOut, out) 37 } 38 39 func TestNoCongratsMessage(t *testing.T) { 40 out, exitCode := runGolangciLint(t, "../...") 41 assert.Equal(t, exitcodes.Success, exitCode) 42 assert.Equal(t, "", out) 43 } 44 45 func TestCongratsMessageIfNoIssues(t *testing.T) { 46 out, exitCode := runGolangciLint(t, root) 47 checkNoIssuesRun(t, out, exitCode) 48 } 49 50 func TestAutogeneratedNoIssues(t *testing.T) { 51 out, exitCode := runGolangciLint(t, filepath.Join(testdataDir, "autogenerated")) 52 checkNoIssuesRun(t, out, exitCode) 53 } 54 55 func TestSymlinkLoop(t *testing.T) { 56 out, exitCode := runGolangciLint(t, filepath.Join(testdataDir, "symlink_loop", "...")) 57 checkNoIssuesRun(t, out, exitCode) 58 } 59 60 func TestRunOnAbsPath(t *testing.T) { 61 absPath, err := filepath.Abs(filepath.Join(testdataDir, "..")) 62 assert.NoError(t, err) 63 64 out, exitCode := runGolangciLint(t, "--no-config", "--fast", absPath) 65 checkNoIssuesRun(t, out, exitCode) 66 67 out, exitCode = runGolangciLint(t, "--no-config", absPath) 68 checkNoIssuesRun(t, out, exitCode) 69 } 70 71 func TestDeadline(t *testing.T) { 72 out, exitCode := runGolangciLint(t, "--deadline=1ms", root) 73 assert.Equal(t, exitcodes.Timeout, exitCode) 74 assert.Contains(t, out, "deadline exceeded: try increase it by passing --deadline option") 75 } 76 77 func runGolangciLint(t *testing.T, args ...string) (string, int) { 78 installBinary(t) 79 80 runArgs := append([]string{"run"}, args...) 81 log.Printf("golangci-lint %s", strings.Join(runArgs, " ")) 82 cmd := exec.Command("golangci-lint", runArgs...) 83 out, err := cmd.CombinedOutput() 84 if err != nil { 85 if exitError, ok := err.(*exec.ExitError); ok { 86 t.Logf("stderr: %s", exitError.Stderr) 87 ws := exitError.Sys().(syscall.WaitStatus) 88 return string(out), ws.ExitStatus() 89 } 90 91 t.Fatalf("can't get error code from %s", err) 92 return "", -1 93 } 94 95 // success, exitCode should be 0 if go is ok 96 ws := cmd.ProcessState.Sys().(syscall.WaitStatus) 97 return string(out), ws.ExitStatus() 98 } 99 100 func runGolangciLintWithYamlConfig(t *testing.T, cfg string, args ...string) string { 101 out, ec := runGolangciLintWithYamlConfigWithCode(t, cfg, args...) 102 assert.Equal(t, exitcodes.Success, ec) 103 104 return out 105 } 106 107 func runGolangciLintWithYamlConfigWithCode(t *testing.T, cfg string, args ...string) (string, int) { 108 f, err := ioutil.TempFile("", "golangci_lint_test") 109 assert.NoError(t, err) 110 f.Close() 111 112 cfgPath := f.Name() + ".yml" 113 err = os.Rename(f.Name(), cfgPath) 114 assert.NoError(t, err) 115 116 defer os.Remove(cfgPath) 117 118 cfg = strings.TrimSpace(cfg) 119 cfg = strings.Replace(cfg, "\t", " ", -1) 120 121 err = ioutil.WriteFile(cfgPath, []byte(cfg), os.ModePerm) 122 assert.NoError(t, err) 123 124 pargs := append([]string{"-c", cfgPath}, args...) 125 return runGolangciLint(t, pargs...) 126 } 127 128 func TestTestsAreLintedByDefault(t *testing.T) { 129 out, exitCode := runGolangciLint(t, "./testdata/withtests") 130 assert.Equal(t, exitcodes.Success, exitCode, out) 131 } 132 133 func TestCgoOk(t *testing.T) { 134 out, exitCode := runGolangciLint(t, "--enable-all", filepath.Join(testdataDir, "cgo")) 135 checkNoIssuesRun(t, out, exitCode) 136 } 137 138 func TestUnsafeOk(t *testing.T) { 139 out, exitCode := runGolangciLint(t, "--enable-all", filepath.Join(testdataDir, "unsafe")) 140 checkNoIssuesRun(t, out, exitCode) 141 } 142 143 func TestDeadcodeNoFalsePositivesInMainPkg(t *testing.T) { 144 out, exitCode := runGolangciLint(t, "--no-config", "--disable-all", "-Edeadcode", 145 filepath.Join(testdataDir, "deadcode_main_pkg")) 146 checkNoIssuesRun(t, out, exitCode) 147 } 148 149 func TestConfigFileIsDetected(t *testing.T) { 150 checkGotConfig := func(out string, exitCode int) { 151 assert.Equal(t, exitcodes.Success, exitCode, out) 152 assert.Equal(t, "test\n", out) // test config contains InternalTest: true, it triggers such output 153 } 154 155 checkGotConfig(runGolangciLint(t, "testdata/withconfig/pkg")) 156 checkGotConfig(runGolangciLint(t, "testdata/withconfig/...")) 157 158 out, exitCode := runGolangciLint(t) // doesn't detect when no args 159 checkNoIssuesRun(t, out, exitCode) 160 } 161 162 func inSlice(s []string, v string) bool { 163 for _, sv := range s { 164 if sv == v { 165 return true 166 } 167 } 168 169 return false 170 } 171 172 func getEnabledByDefaultFastLintersExcept(except ...string) []string { 173 ebdl := lintersdb.GetAllEnabledByDefaultLinters() 174 ret := []string{} 175 for _, linter := range ebdl { 176 if linter.DoesFullImport { 177 continue 178 } 179 180 if !inSlice(except, linter.Linter.Name()) { 181 ret = append(ret, linter.Linter.Name()) 182 } 183 } 184 185 return ret 186 } 187 188 func getAllFastLintersWith(with ...string) []string { 189 linters := lintersdb.GetAllSupportedLinterConfigs() 190 ret := append([]string{}, with...) 191 for _, linter := range linters { 192 if linter.DoesFullImport { 193 continue 194 } 195 ret = append(ret, linter.Linter.Name()) 196 } 197 198 return ret 199 } 200 201 func getEnabledByDefaultLinters() []string { 202 ebdl := lintersdb.GetAllEnabledByDefaultLinters() 203 ret := []string{} 204 for _, linter := range ebdl { 205 ret = append(ret, linter.Linter.Name()) 206 } 207 208 return ret 209 } 210 211 func getEnabledByDefaultFastLintersWith(with ...string) []string { 212 ebdl := lintersdb.GetAllEnabledByDefaultLinters() 213 ret := append([]string{}, with...) 214 for _, linter := range ebdl { 215 if linter.DoesFullImport { 216 continue 217 } 218 219 ret = append(ret, linter.Linter.Name()) 220 } 221 222 return ret 223 } 224 225 func mergeMegacheck(linters []string) []string { 226 if inSlice(linters, "staticcheck") && 227 inSlice(linters, "gosimple") && 228 inSlice(linters, "unused") { 229 ret := []string{"megacheck"} 230 for _, linter := range linters { 231 if !inSlice([]string{"staticcheck", "gosimple", "unused"}, linter) { 232 ret = append(ret, linter) 233 } 234 } 235 236 return ret 237 } 238 239 return linters 240 } 241 242 func TestEnableAllFastAndEnableCanCoexist(t *testing.T) { 243 out, exitCode := runGolangciLint(t, "--fast", "--enable-all", "--enable=typecheck") 244 checkNoIssuesRun(t, out, exitCode) 245 246 _, exitCode = runGolangciLint(t, "--enable-all", "--enable=typecheck") 247 assert.Equal(t, exitcodes.Failure, exitCode) 248 249 } 250 251 func TestEnabledLinters(t *testing.T) { 252 type tc struct { 253 name string 254 cfg string 255 el []string 256 args string 257 noImplicitFast bool 258 } 259 260 cases := []tc{ 261 { 262 name: "disable govet in config", 263 cfg: ` 264 linters: 265 disable: 266 - govet 267 `, 268 el: getEnabledByDefaultFastLintersExcept("govet"), 269 }, 270 { 271 name: "enable golint in config", 272 cfg: ` 273 linters: 274 enable: 275 - golint 276 `, 277 el: getEnabledByDefaultFastLintersWith("golint"), 278 }, 279 { 280 name: "disable govet in cmd", 281 args: "-Dgovet", 282 el: getEnabledByDefaultFastLintersExcept("govet"), 283 }, 284 { 285 name: "enable gofmt in cmd and enable golint in config", 286 args: "-Egofmt", 287 cfg: ` 288 linters: 289 enable: 290 - golint 291 `, 292 el: getEnabledByDefaultFastLintersWith("golint", "gofmt"), 293 }, 294 { 295 name: "fast option in config", 296 cfg: ` 297 linters: 298 fast: true 299 `, 300 el: getEnabledByDefaultFastLintersWith(), 301 noImplicitFast: true, 302 }, 303 { 304 name: "explicitly unset fast option in config", 305 cfg: ` 306 linters: 307 fast: false 308 `, 309 el: getEnabledByDefaultLinters(), 310 noImplicitFast: true, 311 }, 312 { 313 name: "set fast option in command-line", 314 args: "--fast", 315 el: getEnabledByDefaultFastLintersWith(), 316 noImplicitFast: true, 317 }, 318 { 319 name: "fast option in command-line has higher priority to enable", 320 cfg: ` 321 linters: 322 fast: false 323 `, 324 args: "--fast", 325 el: getEnabledByDefaultFastLintersWith(), 326 noImplicitFast: true, 327 }, 328 { 329 name: "fast option in command-line has higher priority to disable", 330 cfg: ` 331 linters: 332 fast: true 333 `, 334 args: "--fast=false", 335 el: getEnabledByDefaultLinters(), 336 noImplicitFast: true, 337 }, 338 { 339 name: "fast option combined with enable and enable-all", 340 args: "--enable-all --fast --enable=typecheck", 341 el: getAllFastLintersWith("typecheck"), 342 noImplicitFast: true, 343 }, 344 } 345 346 for _, c := range cases { 347 t.Run(c.name, func(t *testing.T) { 348 runArgs := []string{"-v"} 349 if !c.noImplicitFast { 350 runArgs = append(runArgs, "--fast") 351 } 352 if c.args != "" { 353 runArgs = append(runArgs, strings.Split(c.args, " ")...) 354 } 355 out := runGolangciLintWithYamlConfig(t, c.cfg, runArgs...) 356 el := mergeMegacheck(c.el) 357 sort.StringSlice(el).Sort() 358 expectedLine := fmt.Sprintf("Active %d linters: [%s]", len(el), strings.Join(el, " ")) 359 assert.Contains(t, out, expectedLine) 360 }) 361 } 362 } 363 364 func TestGovetInFastMode(t *testing.T) { 365 cfg := ` 366 linters-settings: 367 use-installed-packages: true 368 ` 369 out := runGolangciLintWithYamlConfig(t, cfg, "--fast", "-Egovet", root) 370 assert.Equal(t, noIssuesOut, out) 371 } 372 373 func TestEnabledPresetsAreNotDuplicated(t *testing.T) { 374 out, ec := runGolangciLint(t, "--no-config", "-v", "-p", "style,bugs") 375 assert.Equal(t, exitcodes.Success, ec) 376 assert.Contains(t, out, "Active presets: [bugs style]") 377 } 378 379 func TestDisallowedOptionsInConfig(t *testing.T) { 380 type tc struct { 381 cfg string 382 option string 383 } 384 385 cases := []tc{ 386 { 387 cfg: ` 388 ruN: 389 Args: 390 - 1 391 `, 392 }, 393 { 394 cfg: ` 395 run: 396 CPUProfilePath: path 397 `, 398 option: "--cpu-profile-path=path", 399 }, 400 { 401 cfg: ` 402 run: 403 MemProfilePath: path 404 `, 405 option: "--mem-profile-path=path", 406 }, 407 { 408 cfg: ` 409 run: 410 Verbose: true 411 `, 412 option: "-v", 413 }, 414 } 415 416 for _, c := range cases { 417 // Run with disallowed option set only in config 418 _, ec := runGolangciLintWithYamlConfigWithCode(t, c.cfg) 419 assert.Equal(t, exitcodes.Failure, ec) 420 421 if c.option == "" { 422 continue 423 } 424 425 args := []string{c.option, "--fast"} 426 427 // Run with disallowed option set only in command-line 428 _, ec = runGolangciLint(t, args...) 429 assert.Equal(t, exitcodes.Success, ec) 430 431 // Run with disallowed option set both in command-line and in config 432 _, ec = runGolangciLintWithYamlConfigWithCode(t, c.cfg, args...) 433 assert.Equal(t, exitcodes.Failure, ec) 434 } 435 }