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