github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/okgo/integration_test/integration_test.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package integration_test
    16  
    17  import (
    18  	"fmt"
    19  	"io/ioutil"
    20  	"os"
    21  	"os/exec"
    22  	"path"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/nmiyake/pkg/dirs"
    27  	"github.com/nmiyake/pkg/gofiles"
    28  	"github.com/palantir/amalgomate/amalgomated"
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  
    32  	"github.com/palantir/godel/apps/okgo/checkoutput"
    33  	"github.com/palantir/godel/apps/okgo/checks"
    34  	"github.com/palantir/godel/apps/okgo/cmd/cmdlib"
    35  	"github.com/palantir/godel/apps/okgo/config"
    36  	"github.com/palantir/godel/apps/okgo/params"
    37  	"github.com/palantir/godel/pkg/products"
    38  )
    39  
    40  func TestCheckers(t *testing.T) {
    41  	cli, err := products.Bin("okgo")
    42  	require.NoError(t, err)
    43  
    44  	for i, currCase := range []struct {
    45  		check amalgomated.Cmd
    46  		want  []string
    47  	}{
    48  		{
    49  			check: cmdlib.Instance().MustNewCmd("deadcode"),
    50  			want: []string{
    51  				"pkg1/bad.go:27:1: deadcode is unused",
    52  				"pkg1/bad.go:40:1: varcheck is unused",
    53  				"pkg2/bad2.go:27:1: deadcode is unused",
    54  				"pkg2/bad2.go:40:1: varcheck is unused",
    55  			},
    56  		},
    57  		{
    58  			check: cmdlib.Instance().MustNewCmd("errcheck"),
    59  			want: []string{
    60  				"pkg1/bad.go:11:8: helper()",
    61  				"pkg2/bad2.go:11:8: helper()",
    62  			},
    63  		},
    64  		{
    65  			check: cmdlib.Instance().MustNewCmd("golint"),
    66  			want: []string{
    67  				`pkg1/bad.go:49:1: comment on exported function Lint should be of the form "Lint ..."`,
    68  				`pkg2/bad2.go:49:1: comment on exported function Lint should be of the form "Lint ..."`,
    69  			},
    70  		},
    71  		{
    72  			check: cmdlib.Instance().MustNewCmd("govet"),
    73  			want: []string{
    74  				"pkg1/bad.go:23: self-assignment of foo to foo",
    75  				"pkg2/bad2.go:23: self-assignment of foo to foo",
    76  			},
    77  		},
    78  		{
    79  			check: cmdlib.Instance().MustNewCmd("importalias"),
    80  			want: []string{
    81  				`pkg1/bad.go:3:8: uses alias "myjson" to import package "encoding/json". No consensus alias exists for this import in the project ("ejson" and "myjson" are both used once each).`,
    82  				`pkg2/bad2.go:3:8: uses alias "ejson" to import package "encoding/json". No consensus alias exists for this import in the project ("ejson" and "myjson" are both used once each).`,
    83  			},
    84  		},
    85  		{
    86  			check: cmdlib.Instance().MustNewCmd("ineffassign"),
    87  			want: []string{
    88  				"pkg1/bad.go:34:2: ineffectual assignment to kvs",
    89  				"pkg1/bad.go:36:2: ineffectual assignment to kvs",
    90  				"pkg2/bad2.go:34:2: ineffectual assignment to kvs",
    91  				"pkg2/bad2.go:36:2: ineffectual assignment to kvs",
    92  			},
    93  		},
    94  		{
    95  			check: cmdlib.Instance().MustNewCmd("outparamcheck"),
    96  			want: []string{
    97  				`github.com/palantir/godel/apps/okgo/integration_test/testdata/standard/pkg1/bad.go:16:28: _ = myjson.Unmarshal(nil, "")  // 2nd argument of 'Unmarshal' requires '&'`,
    98  				`github.com/palantir/godel/apps/okgo/integration_test/testdata/standard/pkg2/bad2.go:16:27: _ = ejson.Unmarshal(nil, "")  // 2nd argument of 'Unmarshal' requires '&'`,
    99  			},
   100  		},
   101  		{
   102  			check: cmdlib.Instance().MustNewCmd("unconvert"),
   103  			want: []string{
   104  				"pkg1/bad.go:45:14: unnecessary conversion",
   105  				"pkg2/bad2.go:45:14: unnecessary conversion",
   106  			},
   107  		},
   108  		{
   109  			check: cmdlib.Instance().MustNewCmd("varcheck"),
   110  			want: []string{
   111  				"pkg1/bad.go:40:7: varcheck",
   112  				"pkg2/bad2.go:40:7: varcheck",
   113  			},
   114  		},
   115  	} {
   116  		checker, err := checks.GetChecker(currCase.check)
   117  		require.NoError(t, err)
   118  
   119  		runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+currCase.check.Name())
   120  		lineInfo, err := checker.Check(runner, "./testdata/standard", params.OKGo{})
   121  		require.NoError(t, err, "Case %d", i)
   122  
   123  		assert.Equal(t, currCase.want, toStringSlice(lineInfo), "Case %d", i)
   124  	}
   125  }
   126  
   127  func TestCompilesChecker(t *testing.T) {
   128  	cli, err := products.Bin("okgo")
   129  	require.NoError(t, err)
   130  
   131  	wd, err := os.Getwd()
   132  	require.NoError(t, err)
   133  
   134  	tmpDir, cleanup, err := dirs.TempDir(wd, "")
   135  	defer cleanup()
   136  	require.NoError(t, err)
   137  
   138  	for i, currCase := range []struct {
   139  		check         amalgomated.Cmd
   140  		filesToWrite  []gofiles.GoFileSpec
   141  		pathToCheck   func(projectDir string) string
   142  		want          func(files map[string]gofiles.GoFile) []string
   143  		customMatcher func(caseNum int, expected, actual []string)
   144  	}{
   145  		{
   146  			check: cmdlib.Instance().MustNewCmd("compiles"),
   147  			filesToWrite: []gofiles.GoFileSpec{
   148  				{
   149  					RelPath: "foo/foo.go",
   150  					Src: `package foo
   151  func Foo() int {
   152  	return "foo"
   153  }`,
   154  				},
   155  			},
   156  			pathToCheck: func(projectDir string) string {
   157  				return path.Join(projectDir, "foo")
   158  			},
   159  			want: func(files map[string]gofiles.GoFile) []string {
   160  				return []string{
   161  					`foo.go:3:9: cannot convert "foo" (untyped string constant) to int`,
   162  				}
   163  			},
   164  		},
   165  		{
   166  			check: cmdlib.Instance().MustNewCmd("compiles"),
   167  			filesToWrite: []gofiles.GoFileSpec{
   168  				{
   169  					RelPath: "foo/foo.go",
   170  					Src: `package foo
   171  import "bar"
   172  func Foo() {
   173  	bar.Bar()
   174  }`,
   175  				},
   176  			},
   177  			pathToCheck: func(projectDir string) string {
   178  				return path.Join(projectDir, "foo")
   179  			},
   180  			want: func(files map[string]gofiles.GoFile) []string {
   181  				return []string{
   182  					`foo.go:2:8: could not import bar \(cannot find package "bar" in any of:
   183  .+ \(vendor tree\)
   184  .+
   185  .+ \(from \$GOROOT\)
   186  .+ \(from \$GOPATH\)\)`,
   187  				}
   188  			},
   189  			customMatcher: func(caseNum int, want, got []string) {
   190  				ok := assert.Equal(t, len(want), len(got), "Case %d: number of output lines do not match", caseNum)
   191  				if ok {
   192  					for i := range want {
   193  						assert.Regexp(t, want[i], got[i], "Case %d, want case %d", caseNum, i)
   194  					}
   195  				}
   196  			},
   197  		},
   198  		{
   199  			check: cmdlib.Instance().MustNewCmd("compiles"),
   200  			filesToWrite: []gofiles.GoFileSpec{
   201  				{
   202  					RelPath: "foo/foo.go",
   203  					Src: `package foo
   204  func Foo() {
   205  	bar.Bar()
   206  	baz.Baz()
   207  }`,
   208  				},
   209  			},
   210  			pathToCheck: func(projectDir string) string {
   211  				return path.Join(projectDir, "foo")
   212  			},
   213  			want: func(files map[string]gofiles.GoFile) []string {
   214  				return []string{
   215  					`foo.go:3:2: undeclared name: bar`,
   216  					`foo.go:4:2: undeclared name: baz`,
   217  				}
   218  			},
   219  			customMatcher: func(caseNum int, want, got []string) {
   220  				ok := assert.Equal(t, len(want), len(got), "Case %d: number of output lines do not match", caseNum)
   221  				if ok {
   222  					for i := range want {
   223  						assert.Regexp(t, want[i], got[i], "Case %d, want case %d", caseNum, i)
   224  					}
   225  				}
   226  			},
   227  		},
   228  		{
   229  			check: cmdlib.Instance().MustNewCmd("extimport"),
   230  			filesToWrite: []gofiles.GoFileSpec{
   231  				{
   232  					RelPath: "foo/foo.go",
   233  					Src: `package foo
   234  import "{{index . "bar/bar.go"}}"
   235  func Foo() {
   236  	bar.Bar()
   237  }
   238  `,
   239  				},
   240  				{
   241  					RelPath: "bar/bar.go",
   242  					Src:     `package bar; func Bar() {}`,
   243  				},
   244  			},
   245  			pathToCheck: func(projectDir string) string {
   246  				return path.Join(projectDir, "foo")
   247  			},
   248  			want: func(files map[string]gofiles.GoFile) []string {
   249  				return []string{
   250  					fmt.Sprintf(`foo.go:2:8: imports external package %s`, files["bar/bar.go"].ImportPath),
   251  				}
   252  			},
   253  		},
   254  	} {
   255  		currCaseProjectDir, err := ioutil.TempDir(tmpDir, "")
   256  		require.NoError(t, err, "Case %d", i)
   257  
   258  		files, err := gofiles.Write(currCaseProjectDir, currCase.filesToWrite)
   259  		require.NoError(t, err, "Case %d", i)
   260  
   261  		checker, err := checks.GetChecker(currCase.check)
   262  		require.NoError(t, err, "Case %d", i)
   263  
   264  		runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+currCase.check.Name())
   265  		lineInfo, err := checker.Check(runner, currCase.pathToCheck(currCaseProjectDir), params.OKGo{})
   266  		require.NoError(t, err, "Case %d", i)
   267  
   268  		want := currCase.want(files)
   269  		got := toStringSlice(lineInfo)
   270  		if currCase.customMatcher == nil {
   271  			assert.Equal(t, want, got, "Case %d", i)
   272  		} else {
   273  			currCase.customMatcher(i, want, got)
   274  		}
   275  	}
   276  }
   277  
   278  func TestFilters(t *testing.T) {
   279  	cli, err := products.Bin("okgo")
   280  	require.NoError(t, err)
   281  
   282  	cmd := cmdlib.Instance().MustNewCmd("golint")
   283  	checker, err := checks.GetChecker(cmd)
   284  	require.NoError(t, err)
   285  	runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name())
   286  
   287  	for i, currCase := range []struct {
   288  		filters []checkoutput.Filterer
   289  		want    []string
   290  	}{
   291  		{
   292  			filters: nil,
   293  			want: []string{
   294  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   295  				"mock/mock.go:3:1: exported function Mock should have comment or be unexported",
   296  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   297  			},
   298  		},
   299  		{
   300  			filters: []checkoutput.Filterer{
   301  				checkoutput.RelativePathFilter("mock"),
   302  			},
   303  			want: []string{
   304  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   305  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   306  			},
   307  		},
   308  		{
   309  			filters: []checkoutput.Filterer{
   310  				checkoutput.NamePathFilter("mock"),
   311  			},
   312  			want: []string{
   313  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   314  			},
   315  		},
   316  		{
   317  			filters: []checkoutput.Filterer{
   318  				checkoutput.MessageRegexpFilter("should have comment or be unexported"),
   319  			},
   320  			want: []string{},
   321  		},
   322  	} {
   323  		lineInfo, err := checker.Check(runner, "./testdata/filter", params.OKGo{})
   324  		require.NoError(t, err, "Case %d", i)
   325  
   326  		filteredLines, err := checkoutput.ApplyFilters(lineInfo, currCase.filters)
   327  		require.NoError(t, err, "Case %d", i)
   328  
   329  		assert.Equal(t, currCase.want, toStringSlice(filteredLines), "Case %d", i)
   330  	}
   331  }
   332  
   333  func TestCheckerUsesConfig(t *testing.T) {
   334  	cli, err := products.Bin("okgo")
   335  	require.NoError(t, err)
   336  
   337  	tmpDir, cleanup, err := dirs.TempDir("", "")
   338  	defer cleanup()
   339  	require.NoError(t, err)
   340  
   341  	for i, currCase := range []struct {
   342  		config string
   343  		want   []string
   344  	}{
   345  		{
   346  			config: "",
   347  			want: []string{
   348  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   349  				"mock/mock.go:3:1: exported function Mock should have comment or be unexported",
   350  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   351  			},
   352  		},
   353  		{
   354  			config: `
   355  			exclude:
   356  			  paths:
   357  			    - "mock"
   358  			`,
   359  			want: []string{
   360  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   361  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   362  			},
   363  		},
   364  		{
   365  			config: `
   366  			exclude:
   367  			  names:
   368  			    - "m.ck"
   369  			`,
   370  			want: []string{
   371  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   372  			},
   373  		},
   374  		{
   375  			config: `
   376  			checks:
   377  			  golint:
   378  			    filters:
   379  			      - type: "message"
   380  			        value: "should have comment or be unexported"
   381  			`,
   382  			want: []string{},
   383  		},
   384  	} {
   385  		tmpFile, err := ioutil.TempFile(tmpDir, "")
   386  		require.NoError(t, err, "Case %d", i)
   387  		tmpFilePath := tmpFile.Name()
   388  		err = tmpFile.Close()
   389  		require.NoError(t, err, "Case %d", i)
   390  		err = ioutil.WriteFile(tmpFilePath, []byte(unindent(currCase.config)), 0644)
   391  		require.NoError(t, err, "Case %d", i)
   392  
   393  		cfg, err := config.Load(tmpFilePath, "")
   394  		require.NoError(t, err, "Case %d", i)
   395  
   396  		cmd := cmdlib.Instance().MustNewCmd("golint")
   397  		checker, err := checks.GetChecker(cmd)
   398  		require.NoError(t, err, "Case %d", i)
   399  
   400  		runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name())
   401  		lineInfo, err := checker.Check(runner, "./testdata/filter", cfg)
   402  		require.NoError(t, err, "Case %d", i)
   403  
   404  		assert.Equal(t, currCase.want, toStringSlice(lineInfo), "Case %d", i)
   405  	}
   406  }
   407  
   408  func TestCheckerUsesReleaseTagConfig(t *testing.T) {
   409  	cli, err := products.Bin("okgo")
   410  	require.NoError(t, err)
   411  
   412  	tmpDir, cleanup, err := dirs.TempDir("", "")
   413  	defer cleanup()
   414  	require.NoError(t, err)
   415  
   416  	for i, currCase := range []struct {
   417  		name   string
   418  		files  []gofiles.GoFileSpec
   419  		config string
   420  		want   []string
   421  	}{
   422  		{
   423  			name: "file with go1.7 build tag processed",
   424  			files: []gofiles.GoFileSpec{
   425  				{
   426  					RelPath: "foo.go",
   427  					Src: `// +build go1.7
   428  
   429  					package foo
   430  					import "os"
   431  					func Foo() {
   432  						os.Setenv("foo", "bar")
   433  					}
   434  					`,
   435  				},
   436  				{
   437  					RelPath: "bar.go",
   438  					Src: `package foo
   439  					import "os"
   440  					func Bar() {
   441  						os.Setenv("foo", "bar")
   442  					}
   443  					`,
   444  				},
   445  			},
   446  			config: "",
   447  			want: []string{
   448  				"Running errcheck...",
   449  				"bar.go:4:16: os.Setenv(\"foo\", \"bar\")",
   450  				"foo.go:6:16: os.Setenv(\"foo\", \"bar\")",
   451  				"",
   452  			},
   453  		},
   454  		{
   455  			name: "file with go1.7 build tag ignored if release-tag set to go1.6",
   456  			files: []gofiles.GoFileSpec{
   457  				{
   458  					RelPath: "foo.go",
   459  					Src: `// +build go1.7
   460  
   461  					package foo
   462  					import "os"
   463  					func Foo() {
   464  						os.Setenv("foo", "bar")
   465  					}
   466  					`,
   467  				},
   468  				{
   469  					RelPath: "bar.go",
   470  					Src: `package foo
   471  					import "os"
   472  					func Bar() {
   473  						os.Setenv("foo", "bar")
   474  					}
   475  					`,
   476  				},
   477  			},
   478  			config: `release-tag: go1.6`,
   479  			want: []string{
   480  				"Running errcheck...",
   481  				"bar.go:4:16: os.Setenv(\"foo\", \"bar\")",
   482  				"",
   483  			},
   484  		},
   485  		{
   486  			name: "ignoring a returned error is flagged",
   487  			files: []gofiles.GoFileSpec{
   488  				{
   489  					RelPath: "foo.go",
   490  					Src: `package foo
   491  					import "os"
   492  					func Foo() {
   493  						os.Open("/")
   494  						os.Pipe()
   495  					}
   496  					`,
   497  				},
   498  			},
   499  			config: ``,
   500  			want: []string{
   501  				"Running errcheck...",
   502  				"foo.go:4:14: os.Open(\"/\")",
   503  				"foo.go:5:14: os.Pipe()",
   504  				"",
   505  			},
   506  		},
   507  		{
   508  			name: "ignoring a returned error is not flagged if referenced from an exclude list",
   509  			files: []gofiles.GoFileSpec{
   510  				{
   511  					RelPath: "foo.go",
   512  					Src: `package foo
   513  					import "os"
   514  					func Foo() {
   515  						os.Open("/")
   516  						os.Pipe()
   517  					}
   518  					`,
   519  				},
   520  				{
   521  					RelPath: "exclude.txt",
   522  					Src: `os.Open
   523  					`,
   524  				},
   525  			},
   526  			config: `
   527  			checks:
   528  			  errcheck:
   529  			    args:
   530  			      - "-exclude"
   531  			      - "exclude.txt"
   532  			`,
   533  			want: []string{
   534  				"Running errcheck...",
   535  				"foo.go:5:14: os.Pipe()",
   536  				"",
   537  			},
   538  		},
   539  	} {
   540  		currCaseDir, err := ioutil.TempDir(tmpDir, "")
   541  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   542  
   543  		_, err = gofiles.Write(currCaseDir, currCase.files)
   544  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   545  
   546  		tmpFile, err := ioutil.TempFile(currCaseDir, "")
   547  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   548  		cfgFilePath := tmpFile.Name()
   549  		err = tmpFile.Close()
   550  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   551  		err = ioutil.WriteFile(cfgFilePath, []byte(unindent(currCase.config)), 0644)
   552  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   553  
   554  		cmd := exec.Command(cli, "--config", cfgFilePath, "errcheck", ".")
   555  		cmd.Dir = currCaseDir
   556  		output, err := cmd.CombinedOutput()
   557  		require.Error(t, err, fmt.Errorf("Expected command %v to fail. Output:\n%v", cmd.Args, string(output)))
   558  
   559  		assert.Equal(t, currCase.want, strings.Split(string(output), "\n"), "Case %d: %s", i, currCase.name)
   560  	}
   561  }
   562  
   563  func toStringSlice(input []checkoutput.Issue) []string {
   564  	output := make([]string, len(input))
   565  	for i, curr := range input {
   566  		output[i] = curr.String()
   567  	}
   568  	return output
   569  }
   570  
   571  func unindent(input string) string {
   572  	return strings.Replace(input, "\n\t\t\t", "\n", -1)
   573  }