github.com/thrasher-corp/golangci-lint@v1.17.3/test/linters_test.go (about)

     1  package test
     2  
     3  import (
     4  	"bufio"
     5  	"io/ioutil"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/golangci/golangci-lint/test/testshared"
    13  
    14  	assert "github.com/stretchr/testify/require"
    15  
    16  	yaml "gopkg.in/yaml.v2"
    17  )
    18  
    19  func runGoErrchk(c *exec.Cmd, files []string, t *testing.T) {
    20  	output, err := c.CombinedOutput()
    21  	assert.Error(t, err)
    22  	_, ok := err.(*exec.ExitError)
    23  	assert.True(t, ok)
    24  
    25  	// TODO: uncomment after deprecating go1.11
    26  	// assert.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode())
    27  
    28  	fullshort := make([]string, 0, len(files)*2)
    29  	for _, f := range files {
    30  		fullshort = append(fullshort, f, filepath.Base(f))
    31  	}
    32  
    33  	err = errorCheck(string(output), false, fullshort...)
    34  	assert.NoError(t, err)
    35  }
    36  
    37  func testSourcesFromDir(t *testing.T, dir string) {
    38  	t.Log(filepath.Join(dir, "*.go"))
    39  
    40  	findSources := func(pathPatterns ...string) []string {
    41  		sources, err := filepath.Glob(filepath.Join(pathPatterns...))
    42  		assert.NoError(t, err)
    43  		assert.NotEmpty(t, sources)
    44  		return sources
    45  	}
    46  	sources := findSources(dir, "*.go")
    47  
    48  	testshared.NewLintRunner(t).Install()
    49  
    50  	for _, s := range sources {
    51  		s := s
    52  		t.Run(filepath.Base(s), func(t *testing.T) {
    53  			t.Parallel()
    54  			testOneSource(t, s)
    55  		})
    56  	}
    57  }
    58  
    59  func TestSourcesFromTestdataWithIssuesDir(t *testing.T) {
    60  	testSourcesFromDir(t, testdataDir)
    61  }
    62  
    63  func TestTypecheck(t *testing.T) {
    64  	testSourcesFromDir(t, filepath.Join(testdataDir, "notcompiles"))
    65  }
    66  
    67  func TestGoimportsLocal(t *testing.T) {
    68  	sourcePath := filepath.Join(testdataDir, "goimports", "goimports.go")
    69  	args := []string{
    70  		"--disable-all", "--print-issued-lines=false", "--print-linter-name=false", "--out-format=line-number",
    71  		sourcePath,
    72  	}
    73  	rc := extractRunContextFromComments(t, sourcePath)
    74  	args = append(args, rc.args...)
    75  
    76  	cfg, err := yaml.Marshal(rc.config)
    77  	assert.NoError(t, err)
    78  
    79  	testshared.NewLintRunner(t).RunWithYamlConfig(string(cfg), args...).
    80  		ExpectHasIssue("testdata/goimports/goimports.go:8: File is not `goimports`-ed")
    81  }
    82  
    83  func saveConfig(t *testing.T, cfg map[string]interface{}) (cfgPath string, finishFunc func()) {
    84  	f, err := ioutil.TempFile("", "golangci_lint_test")
    85  	assert.NoError(t, err)
    86  
    87  	cfgPath = f.Name() + ".yml"
    88  	err = os.Rename(f.Name(), cfgPath)
    89  	assert.NoError(t, err)
    90  
    91  	err = yaml.NewEncoder(f).Encode(cfg)
    92  	assert.NoError(t, err)
    93  
    94  	return cfgPath, func() {
    95  		assert.NoError(t, f.Close())
    96  		if os.Getenv("GL_KEEP_TEMP_FILES") != "1" {
    97  			assert.NoError(t, os.Remove(cfgPath))
    98  		}
    99  	}
   100  }
   101  
   102  func testOneSource(t *testing.T, sourcePath string) {
   103  	args := []string{
   104  		"run",
   105  		"--disable-all",
   106  		"--print-issued-lines=false",
   107  		"--print-linter-name=false",
   108  		"--out-format=line-number",
   109  	}
   110  
   111  	rc := extractRunContextFromComments(t, sourcePath)
   112  	var cfgPath string
   113  
   114  	if rc.config != nil {
   115  		p, finish := saveConfig(t, rc.config)
   116  		defer finish()
   117  		cfgPath = p
   118  	} else if rc.configPath != "" {
   119  		cfgPath = rc.configPath
   120  	}
   121  
   122  	for _, addArg := range []string{"", "-Etypecheck"} {
   123  		caseArgs := append([]string{}, args...)
   124  		caseArgs = append(caseArgs, rc.args...)
   125  		if addArg != "" {
   126  			caseArgs = append(caseArgs, addArg)
   127  		}
   128  		if cfgPath == "" {
   129  			caseArgs = append(caseArgs, "--no-config")
   130  		} else {
   131  			caseArgs = append(caseArgs, "-c", cfgPath)
   132  		}
   133  
   134  		caseArgs = append(caseArgs, sourcePath)
   135  
   136  		cmd := exec.Command(binName, caseArgs...)
   137  		t.Log(caseArgs)
   138  		runGoErrchk(cmd, []string{sourcePath}, t)
   139  	}
   140  }
   141  
   142  type runContext struct {
   143  	args       []string
   144  	config     map[string]interface{}
   145  	configPath string
   146  }
   147  
   148  func buildConfigFromShortRepr(t *testing.T, repr string, config map[string]interface{}) {
   149  	kv := strings.Split(repr, "=")
   150  	assert.Len(t, kv, 2)
   151  
   152  	keyParts := strings.Split(kv[0], ".")
   153  	assert.True(t, len(keyParts) >= 2, len(keyParts))
   154  
   155  	lastObj := config
   156  	for _, k := range keyParts[:len(keyParts)-1] {
   157  		var v map[string]interface{}
   158  		if lastObj[k] == nil {
   159  			v = map[string]interface{}{}
   160  		} else {
   161  			v = lastObj[k].(map[string]interface{})
   162  		}
   163  
   164  		lastObj[k] = v
   165  		lastObj = v
   166  	}
   167  
   168  	lastObj[keyParts[len(keyParts)-1]] = kv[1]
   169  }
   170  
   171  func extractRunContextFromComments(t *testing.T, sourcePath string) *runContext {
   172  	f, err := os.Open(sourcePath)
   173  	assert.NoError(t, err)
   174  	defer f.Close()
   175  
   176  	rc := &runContext{}
   177  
   178  	scanner := bufio.NewScanner(f)
   179  	for scanner.Scan() {
   180  		line := scanner.Text()
   181  		if !strings.HasPrefix(line, "//") {
   182  			return rc
   183  		}
   184  
   185  		line = strings.TrimPrefix(line, "//")
   186  		if strings.HasPrefix(line, "args: ") {
   187  			assert.Nil(t, rc.args)
   188  			args := strings.TrimPrefix(line, "args: ")
   189  			assert.NotEmpty(t, args)
   190  			rc.args = strings.Split(args, " ")
   191  			continue
   192  		}
   193  
   194  		if strings.HasPrefix(line, "config: ") {
   195  			repr := strings.TrimPrefix(line, "config: ")
   196  			assert.NotEmpty(t, repr)
   197  			if rc.config == nil {
   198  				rc.config = map[string]interface{}{}
   199  			}
   200  			buildConfigFromShortRepr(t, repr, rc.config)
   201  			continue
   202  		}
   203  
   204  		if strings.HasPrefix(line, "config_path: ") {
   205  			configPath := strings.TrimPrefix(line, "config_path: ")
   206  			assert.NotEmpty(t, configPath)
   207  			rc.configPath = configPath
   208  			continue
   209  		}
   210  
   211  		assert.Fail(t, "invalid prefix of comment line %s", line)
   212  	}
   213  
   214  	return rc
   215  }
   216  
   217  func TestExtractRunContextFromComments(t *testing.T) {
   218  	rc := extractRunContextFromComments(t, filepath.Join(testdataDir, "goimports", "goimports.go"))
   219  	assert.Equal(t, []string{"-Egoimports"}, rc.args)
   220  }
   221  
   222  func TestGolintConsumesXTestFiles(t *testing.T) {
   223  	dir := getTestDataDir("withxtest")
   224  	const expIssue = "`if` block ends with a `return` statement, so drop this `else` and outdent its block"
   225  
   226  	r := testshared.NewLintRunner(t)
   227  	r.Run("--no-config", "--disable-all", "-Egolint", dir).ExpectHasIssue(expIssue)
   228  	r.Run("--no-config", "--disable-all", "-Egolint", filepath.Join(dir, "p_test.go")).ExpectHasIssue(expIssue)
   229  }