github.com/dbraley/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  }