2018-05-31 23:53:01 +03:00
package test
import (
2018-11-07 21:55:26 +03:00
"bufio"
2022-01-04 22:36:27 +02:00
"fmt"
2022-04-06 10:44:20 +02:00
"go/build/constraint"
2018-11-07 21:55:26 +03:00
"os"
2018-05-31 23:53:01 +03:00
"os/exec"
2022-01-04 22:36:27 +02:00
"path"
2018-05-31 23:53:01 +03:00
"path/filepath"
2022-04-06 10:44:20 +02:00
"runtime"
2018-06-10 17:10:56 +03:00
"strings"
2018-05-31 23:53:01 +03:00
"testing"
2022-04-06 10:44:20 +02:00
hcversion "github.com/hashicorp/go-version"
2021-03-13 04:45:32 +01:00
"github.com/stretchr/testify/require"
2019-09-27 20:58:49 -04:00
2021-03-13 04:45:32 +01:00
"github.com/golangci/golangci-lint/pkg/exitcodes"
2019-09-27 20:58:49 -04:00
"github.com/golangci/golangci-lint/test/testshared"
2018-05-31 23:53:01 +03:00
)
2021-01-15 12:37:56 -08:00
func runGoErrchk ( c * exec . Cmd , defaultExpectedLinter string , files [ ] string , t * testing . T ) {
2018-05-31 23:53:01 +03:00
output , err := c . CombinedOutput ( )
2020-05-29 19:01:46 +05:30
// The returned error will be nil if the test file does not have any issues
// and thus the linter exits with exit code 0. So perform the additional
// assertions only if the error is non-nil.
if err != nil {
2021-03-13 04:45:32 +01:00
var exitErr * exec . ExitError
require . ErrorAs ( t , err , & exitErr )
2021-11-02 11:34:19 -07:00
require . Equal ( t , exitcodes . IssuesFound , exitErr . ExitCode ( ) , "Unexpected exit code: %s" , string ( output ) )
2020-05-29 19:01:46 +05:30
}
2019-03-05 21:44:52 +03:00
2019-03-05 21:20:43 +03:00
fullshort := make ( [ ] string , 0 , len ( files ) * 2 )
for _ , f := range files {
fullshort = append ( fullshort , f , filepath . Base ( f ) )
}
2018-05-31 23:53:01 +03:00
2021-01-15 12:37:56 -08:00
err = errorCheck ( string ( output ) , false , defaultExpectedLinter , fullshort ... )
2021-03-13 04:45:32 +01:00
require . NoError ( t , err )
2018-05-31 23:53:01 +03:00
}
2018-06-10 17:10:56 +03:00
func testSourcesFromDir ( t * testing . T , dir string ) {
t . Log ( filepath . Join ( dir , "*.go" ) )
2018-05-31 23:53:01 +03:00
2018-11-07 21:55:26 +03:00
findSources := func ( pathPatterns ... string ) [ ] string {
sources , err := filepath . Glob ( filepath . Join ( pathPatterns ... ) )
2021-03-13 04:45:32 +01:00
require . NoError ( t , err )
require . NotEmpty ( t , sources )
2018-11-07 21:55:26 +03:00
return sources
}
sources := findSources ( dir , "*.go" )
testshared . NewLintRunner ( t ) . Install ( )
2018-05-31 23:53:01 +03:00
for _ , s := range sources {
s := s
2020-05-09 15:15:34 +03:00
t . Run ( filepath . Base ( s ) , func ( subTest * testing . T ) {
subTest . Parallel ( )
testOneSource ( subTest , s )
2018-05-31 23:53:01 +03:00
} )
}
}
2018-06-10 17:10:56 +03:00
func TestSourcesFromTestdataWithIssuesDir ( t * testing . T ) {
testSourcesFromDir ( t , testdataDir )
}
func TestTypecheck ( t * testing . T ) {
testSourcesFromDir ( t , filepath . Join ( testdataDir , "notcompiles" ) )
}
2018-11-07 21:55:26 +03:00
func TestGoimportsLocal ( t * testing . T ) {
sourcePath := filepath . Join ( testdataDir , "goimports" , "goimports.go" )
args := [ ] string {
"--disable-all" , "--print-issued-lines=false" , "--print-linter-name=false" , "--out-format=line-number" ,
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2018-11-07 21:55:26 +03:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2018-11-07 21:55:26 +03:00
args = append ( args , rc . args ... )
2022-08-15 18:23:07 +02:00
cfg , err := os . ReadFile ( rc . configPath )
2021-03-13 04:45:32 +01:00
require . NoError ( t , err )
2018-11-07 21:55:26 +03:00
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( string ( cfg ) , args ... ) .
ExpectHasIssue ( "testdata/goimports/goimports.go:8: File is not `goimports`-ed" )
}
2020-07-28 18:55:02 +08:00
func TestGciLocal ( t * testing . T ) {
sourcePath := filepath . Join ( testdataDir , "gci" , "gci.go" )
args := [ ] string {
"--disable-all" , "--print-issued-lines=false" , "--print-linter-name=false" , "--out-format=line-number" ,
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2020-07-28 18:55:02 +08:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2020-07-28 18:55:02 +08:00
args = append ( args , rc . args ... )
2022-02-14 09:51:24 +01:00
cfg , err := os . ReadFile ( rc . configPath )
2021-03-13 04:45:32 +01:00
require . NoError ( t , err )
2020-07-28 18:55:02 +08:00
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( string ( cfg ) , args ... ) .
2022-05-31 01:51:38 +08:00
ExpectHasIssue ( "testdata/gci/gci.go:8: File is not `gci`-ed" )
2020-07-28 18:55:02 +08:00
}
2022-01-04 22:36:27 +02:00
func TestMultipleOutputs ( t * testing . T ) {
sourcePath := filepath . Join ( testdataDir , "gci" , "gci.go" )
args := [ ] string {
"--disable-all" , "--print-issued-lines=false" , "--print-linter-name=false" , "--out-format=line-number,json:stdout" ,
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2022-01-04 22:36:27 +02:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2022-01-04 22:36:27 +02:00
args = append ( args , rc . args ... )
2022-02-14 09:51:24 +01:00
cfg , err := os . ReadFile ( rc . configPath )
2022-01-04 22:36:27 +02:00
require . NoError ( t , err )
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( string ( cfg ) , args ... ) .
2022-05-31 01:51:38 +08:00
ExpectHasIssue ( "testdata/gci/gci.go:8: File is not `gci`-ed" ) .
2022-01-04 22:36:27 +02:00
ExpectOutputContains ( ` "Issues":[ ` )
}
func TestStderrOutput ( t * testing . T ) {
sourcePath := filepath . Join ( testdataDir , "gci" , "gci.go" )
args := [ ] string {
"--disable-all" , "--print-issued-lines=false" , "--print-linter-name=false" , "--out-format=line-number,json:stderr" ,
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2022-01-04 22:36:27 +02:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2022-01-04 22:36:27 +02:00
args = append ( args , rc . args ... )
2022-02-14 09:51:24 +01:00
cfg , err := os . ReadFile ( rc . configPath )
2022-01-04 22:36:27 +02:00
require . NoError ( t , err )
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( string ( cfg ) , args ... ) .
2022-05-31 01:51:38 +08:00
ExpectHasIssue ( "testdata/gci/gci.go:8: File is not `gci`-ed" ) .
2022-01-04 22:36:27 +02:00
ExpectOutputContains ( ` "Issues":[ ` )
}
func TestFileOutput ( t * testing . T ) {
resultPath := path . Join ( t . TempDir ( ) , "golangci_lint_test_result" )
sourcePath := filepath . Join ( testdataDir , "gci" , "gci.go" )
args := [ ] string {
"--disable-all" , "--print-issued-lines=false" , "--print-linter-name=false" ,
fmt . Sprintf ( "--out-format=json:%s,line-number" , resultPath ) ,
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2022-01-04 22:36:27 +02:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2022-01-04 22:36:27 +02:00
args = append ( args , rc . args ... )
2022-02-14 09:51:24 +01:00
cfg , err := os . ReadFile ( rc . configPath )
2022-01-04 22:36:27 +02:00
require . NoError ( t , err )
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( string ( cfg ) , args ... ) .
2022-05-31 01:51:38 +08:00
ExpectHasIssue ( "testdata/gci/gci.go:8: File is not `gci`-ed" ) .
2022-01-04 22:36:27 +02:00
ExpectOutputNotContains ( ` "Issues":[ ` )
b , err := os . ReadFile ( resultPath )
require . NoError ( t , err )
require . Contains ( t , string ( b ) , ` "Issues":[ ` )
}
2018-05-31 23:53:01 +03:00
func testOneSource ( t * testing . T , sourcePath string ) {
2018-06-10 17:10:56 +03:00
args := [ ] string {
2019-03-05 21:20:43 +03:00
"run" ,
2022-03-23 16:54:11 +01:00
"--go=1.17" , // TODO(ldez): we force to use an old version of Go for the CI and the tests.
2020-05-09 15:15:34 +03:00
"--allow-parallel-runners" ,
2018-06-10 17:10:56 +03:00
"--disable-all" ,
2018-05-31 23:53:01 +03:00
"--print-issued-lines=false" ,
"--out-format=line-number" ,
2020-06-09 05:21:58 +09:00
"--max-same-issues=100" ,
2018-06-10 17:10:56 +03:00
}
2018-11-07 21:55:26 +03:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
if rc == nil {
t . Skipf ( "Skipped: %s" , sourcePath )
}
2018-11-07 21:55:26 +03:00
2018-06-10 17:10:56 +03:00
for _ , addArg := range [ ] string { "" , "-Etypecheck" } {
caseArgs := append ( [ ] string { } , args ... )
2018-11-07 21:55:26 +03:00
caseArgs = append ( caseArgs , rc . args ... )
2018-06-10 17:10:56 +03:00
if addArg != "" {
caseArgs = append ( caseArgs , addArg )
}
2022-08-15 18:23:07 +02:00
if rc . configPath == "" {
2018-11-07 21:55:26 +03:00
caseArgs = append ( caseArgs , "--no-config" )
} else {
2022-08-15 18:23:07 +02:00
caseArgs = append ( caseArgs , "-c" , rc . configPath )
2018-11-07 21:55:26 +03:00
}
2018-06-10 17:10:56 +03:00
caseArgs = append ( caseArgs , sourcePath )
2019-03-05 21:20:43 +03:00
cmd := exec . Command ( binName , caseArgs ... )
2018-06-10 17:10:56 +03:00
t . Log ( caseArgs )
2021-01-15 12:37:56 -08:00
runGoErrchk ( cmd , rc . expectedLinter , [ ] string { sourcePath } , t )
2018-06-10 17:10:56 +03:00
}
}
2018-11-07 21:55:26 +03:00
type runContext struct {
2021-01-15 12:37:56 -08:00
args [ ] string
configPath string
expectedLinter string
2018-11-07 21:55:26 +03:00
}
2020-07-05 02:03:37 +07:00
func skipMultilineComment ( scanner * bufio . Scanner ) {
for line := scanner . Text ( ) ; ! strings . Contains ( line , "*/" ) && scanner . Scan ( ) ; {
line = scanner . Text ( )
}
}
2022-08-15 18:23:07 +02:00
//nolint:gocyclo
2018-11-07 21:55:26 +03:00
func extractRunContextFromComments ( t * testing . T , sourcePath string ) * runContext {
f , err := os . Open ( sourcePath )
2021-03-13 04:45:32 +01:00
require . NoError ( t , err )
2018-11-07 21:55:26 +03:00
defer f . Close ( )
2018-06-10 17:10:56 +03:00
2018-11-07 21:55:26 +03:00
rc := & runContext { }
2018-06-10 17:10:56 +03:00
2018-11-07 21:55:26 +03:00
scanner := bufio . NewScanner ( f )
for scanner . Scan ( ) {
line := scanner . Text ( )
2020-07-05 02:03:37 +07:00
if strings . HasPrefix ( line , "/*" ) {
skipMultilineComment ( scanner )
continue
}
2020-07-05 15:32:00 +07:00
if strings . TrimSpace ( line ) == "" {
continue
}
2018-11-07 21:55:26 +03:00
if ! strings . HasPrefix ( line , "//" ) {
2021-01-15 12:37:56 -08:00
break
2018-11-07 21:55:26 +03:00
}
2021-01-08 16:48:56 +01:00
2022-04-06 10:44:20 +02:00
if strings . HasPrefix ( line , "//go:build" ) || strings . HasPrefix ( line , "// +build" ) {
parse , err := constraint . Parse ( line )
require . NoError ( t , err )
if ! parse . Eval ( buildTagGoVersion ) {
return nil
}
continue
}
2022-07-15 15:32:10 +02:00
if ! strings . HasPrefix ( line , "//golangcitest:" ) {
require . Failf ( t , "invalid prefix of comment line %s" , line )
}
2022-08-03 20:53:06 +02:00
before , after , found := strings . Cut ( line , " " )
require . Truef ( t , found , "invalid prefix of comment line %s" , line )
after = strings . TrimSpace ( after )
2022-07-15 15:32:10 +02:00
switch before {
case "//golangcitest:args" :
2021-03-13 04:45:32 +01:00
require . Nil ( t , rc . args )
2022-07-15 15:32:10 +02:00
require . NotEmpty ( t , after )
rc . args = strings . Split ( after , " " )
2018-11-07 21:55:26 +03:00
continue
2022-07-15 15:32:10 +02:00
case "//golangcitest:config_path" :
require . NotEmpty ( t , after )
rc . configPath = after
2018-12-23 11:36:21 +03:00
continue
2022-07-15 15:32:10 +02:00
case "//golangcitest:expected_linter" :
require . NotEmpty ( t , after )
rc . expectedLinter = after
2021-01-15 12:37:56 -08:00
continue
2022-07-15 15:32:10 +02:00
default :
require . Failf ( t , "invalid prefix of comment line %s" , line )
}
2018-06-10 17:10:56 +03:00
}
2021-01-15 12:37:56 -08:00
// guess the expected linter if none is specified
if rc . expectedLinter == "" {
for _ , arg := range rc . args {
if strings . HasPrefix ( arg , "-E" ) && ! strings . Contains ( arg , "," ) {
2022-07-15 15:32:10 +02:00
require . Empty ( t , rc . expectedLinter , "could not infer expected linter for errors because multiple linters are enabled. Please use the `//golangcitest:expected_linter ` directive in your test to indicate the linter-under-test." ) //nolint:lll
2021-01-15 12:37:56 -08:00
rc . expectedLinter = arg [ 2 : ]
}
}
}
2018-11-07 21:55:26 +03:00
return rc
}
2022-04-06 10:44:20 +02:00
func buildTagGoVersion ( tag string ) bool {
vRuntime , err := hcversion . NewVersion ( strings . TrimPrefix ( runtime . Version ( ) , "go" ) )
if err != nil {
return false
}
vTag , err := hcversion . NewVersion ( strings . TrimPrefix ( tag , "go" ) )
if err != nil {
return false
}
return vRuntime . GreaterThanOrEqual ( vTag )
}
2018-11-07 21:55:26 +03:00
func TestExtractRunContextFromComments ( t * testing . T ) {
rc := extractRunContextFromComments ( t , filepath . Join ( testdataDir , "goimports" , "goimports.go" ) )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2021-03-13 04:45:32 +01:00
require . Equal ( t , [ ] string { "-Egoimports" } , rc . args )
2018-06-10 17:10:56 +03:00
}
2020-10-02 13:00:46 -07:00
func TestTparallel ( t * testing . T ) {
t . Run ( "should fail on missing top-level Parallel()" , func ( t * testing . T ) {
sourcePath := filepath . Join ( testdataDir , "tparallel" , "missing_toplevel_test.go" )
args := [ ] string {
2022-04-06 10:44:20 +02:00
"--disable-all" , "--enable" , "tparallel" ,
"--print-issued-lines=false" , "--print-linter-name=false" , "--out-format=line-number" ,
2020-10-02 13:00:46 -07:00
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2020-10-02 13:00:46 -07:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2020-10-02 13:00:46 -07:00
args = append ( args , rc . args ... )
2022-08-15 18:23:07 +02:00
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( "" , args ... ) .
2020-10-02 13:00:46 -07:00
ExpectHasIssue (
"testdata/tparallel/missing_toplevel_test.go:7:6: TestTopLevel should call t.Parallel on the top level as well as its subtests\n" ,
)
} )
t . Run ( "should fail on missing subtest Parallel()" , func ( t * testing . T ) {
sourcePath := filepath . Join ( testdataDir , "tparallel" , "missing_subtest_test.go" )
args := [ ] string {
2022-04-06 10:44:20 +02:00
"--disable-all" , "--enable" , "tparallel" ,
"--print-issued-lines=false" , "--print-linter-name=false" , "--out-format=line-number" ,
2020-10-02 13:00:46 -07:00
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2020-10-02 13:00:46 -07:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2020-10-02 13:00:46 -07:00
args = append ( args , rc . args ... )
2022-08-15 18:23:07 +02:00
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( "" , args ... ) .
2020-10-02 13:00:46 -07:00
ExpectHasIssue (
"testdata/tparallel/missing_subtest_test.go:7:6: TestSubtests's subtests should call t.Parallel\n" ,
)
} )
t . Run ( "should pass on parallel test with no subtests" , func ( t * testing . T ) {
sourcePath := filepath . Join ( testdataDir , "tparallel" , "happy_path_test.go" )
args := [ ] string {
2022-04-06 10:44:20 +02:00
"--disable-all" , "--enable" , "tparallel" ,
"--print-issued-lines=false" , "--print-linter-name=false" , "--out-format=line-number" ,
2020-10-02 13:00:46 -07:00
sourcePath ,
}
2022-04-06 10:44:20 +02:00
2020-10-02 13:00:46 -07:00
rc := extractRunContextFromComments ( t , sourcePath )
2022-04-06 10:44:20 +02:00
require . NotNil ( t , rc )
2020-10-02 13:00:46 -07:00
args = append ( args , rc . args ... )
2022-08-15 18:23:07 +02:00
testshared . NewLintRunner ( t ) . RunWithYamlConfig ( "" , args ... ) . ExpectNoIssues ( )
2020-10-02 13:00:46 -07:00
} )
2018-05-31 23:53:01 +03:00
}