github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/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  .+ \(from \$GOROOT\)
   185  .+ \(from \$GOPATH\)\)`,
   186  					`foo.go:4:2: undeclared name: bar`,
   187  				}
   188  			},
   189  			customMatcher: func(caseNum int, want, got []string) {
   190  				assert.Equal(t, len(want), len(got), "Case %d: number of output lines do not match")
   191  
   192  				for i := range want {
   193  					assert.Regexp(t, want[i], got[i], "Case %d", i)
   194  				}
   195  			},
   196  		},
   197  		{
   198  			check: cmdlib.Instance().MustNewCmd("extimport"),
   199  			filesToWrite: []gofiles.GoFileSpec{
   200  				{
   201  					RelPath: "foo/foo.go",
   202  					Src: `package foo
   203  import "{{index . "bar/bar.go"}}"
   204  func Foo() {
   205  	bar.Bar()
   206  }
   207  `,
   208  				},
   209  				{
   210  					RelPath: "bar/bar.go",
   211  					Src:     `package bar; func Bar() {}`,
   212  				},
   213  			},
   214  			pathToCheck: func(projectDir string) string {
   215  				return path.Join(projectDir, "foo")
   216  			},
   217  			want: func(files map[string]gofiles.GoFile) []string {
   218  				return []string{
   219  					fmt.Sprintf(`foo.go:2:8: imports external package %s`, files["bar/bar.go"].ImportPath),
   220  				}
   221  			},
   222  		},
   223  	} {
   224  		currCaseProjectDir, err := ioutil.TempDir(tmpDir, "")
   225  		require.NoError(t, err, "Case %d", i)
   226  
   227  		files, err := gofiles.Write(currCaseProjectDir, currCase.filesToWrite)
   228  		require.NoError(t, err, "Case %d", i)
   229  
   230  		checker, err := checks.GetChecker(currCase.check)
   231  		require.NoError(t, err, "Case %d", i)
   232  
   233  		runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+currCase.check.Name())
   234  		lineInfo, err := checker.Check(runner, currCase.pathToCheck(currCaseProjectDir), params.OKGo{})
   235  		require.NoError(t, err, "Case %d", i)
   236  
   237  		want := currCase.want(files)
   238  		got := toStringSlice(lineInfo)
   239  		if currCase.customMatcher == nil {
   240  			assert.Equal(t, want, got, "Case %d", i)
   241  		} else {
   242  			currCase.customMatcher(i, want, got)
   243  		}
   244  	}
   245  }
   246  
   247  func TestFilters(t *testing.T) {
   248  	cli, err := products.Bin("okgo")
   249  	require.NoError(t, err)
   250  
   251  	cmd := cmdlib.Instance().MustNewCmd("golint")
   252  	checker, err := checks.GetChecker(cmd)
   253  	require.NoError(t, err)
   254  	runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name())
   255  
   256  	for i, currCase := range []struct {
   257  		filters []checkoutput.Filterer
   258  		want    []string
   259  	}{
   260  		{
   261  			filters: nil,
   262  			want: []string{
   263  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   264  				"mock/mock.go:3:1: exported function Mock should have comment or be unexported",
   265  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   266  			},
   267  		},
   268  		{
   269  			filters: []checkoutput.Filterer{
   270  				checkoutput.RelativePathFilter("mock"),
   271  			},
   272  			want: []string{
   273  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   274  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   275  			},
   276  		},
   277  		{
   278  			filters: []checkoutput.Filterer{
   279  				checkoutput.NamePathFilter("mock"),
   280  			},
   281  			want: []string{
   282  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   283  			},
   284  		},
   285  		{
   286  			filters: []checkoutput.Filterer{
   287  				checkoutput.MessageRegexpFilter("should have comment or be unexported"),
   288  			},
   289  			want: []string{},
   290  		},
   291  	} {
   292  		lineInfo, err := checker.Check(runner, "./testdata/filter", params.OKGo{})
   293  		require.NoError(t, err, "Case %d", i)
   294  
   295  		filteredLines, err := checkoutput.ApplyFilters(lineInfo, currCase.filters)
   296  		require.NoError(t, err, "Case %d", i)
   297  
   298  		assert.Equal(t, currCase.want, toStringSlice(filteredLines), "Case %d", i)
   299  	}
   300  }
   301  
   302  func TestCheckerUsesConfig(t *testing.T) {
   303  	cli, err := products.Bin("okgo")
   304  	require.NoError(t, err)
   305  
   306  	tmpDir, cleanup, err := dirs.TempDir("", "")
   307  	defer cleanup()
   308  	require.NoError(t, err)
   309  
   310  	for i, currCase := range []struct {
   311  		config string
   312  		want   []string
   313  	}{
   314  		{
   315  			config: "",
   316  			want: []string{
   317  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   318  				"mock/mock.go:3:1: exported function Mock should have comment or be unexported",
   319  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   320  			},
   321  		},
   322  		{
   323  			config: `
   324  			exclude:
   325  			  paths:
   326  			    - "mock"
   327  			`,
   328  			want: []string{
   329  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   330  				"nested/mock/nestedmock.go:3:1: exported function NestedMock should have comment or be unexported",
   331  			},
   332  		},
   333  		{
   334  			config: `
   335  			exclude:
   336  			  names:
   337  			    - "m.ck"
   338  			`,
   339  			want: []string{
   340  				"bad.go:3:1: exported function Bad should have comment or be unexported",
   341  			},
   342  		},
   343  		{
   344  			config: `
   345  			checks:
   346  			  golint:
   347  			    filters:
   348  			      - type: "message"
   349  			        value: "should have comment or be unexported"
   350  			`,
   351  			want: []string{},
   352  		},
   353  	} {
   354  		tmpFile, err := ioutil.TempFile(tmpDir, "")
   355  		require.NoError(t, err, "Case %d", i)
   356  		tmpFilePath := tmpFile.Name()
   357  		err = tmpFile.Close()
   358  		require.NoError(t, err, "Case %d", i)
   359  		err = ioutil.WriteFile(tmpFilePath, []byte(unindent(currCase.config)), 0644)
   360  		require.NoError(t, err, "Case %d", i)
   361  
   362  		cfg, err := config.Load(tmpFilePath, "")
   363  		require.NoError(t, err, "Case %d", i)
   364  
   365  		cmd := cmdlib.Instance().MustNewCmd("golint")
   366  		checker, err := checks.GetChecker(cmd)
   367  		require.NoError(t, err, "Case %d", i)
   368  
   369  		runner := amalgomated.PathCmder(cli, amalgomated.ProxyCmdPrefix+cmd.Name())
   370  		lineInfo, err := checker.Check(runner, "./testdata/filter", cfg)
   371  		require.NoError(t, err, "Case %d", i)
   372  
   373  		assert.Equal(t, currCase.want, toStringSlice(lineInfo), "Case %d", i)
   374  	}
   375  }
   376  
   377  func TestCheckerUsesReleaseTagConfig(t *testing.T) {
   378  	cli, err := products.Bin("okgo")
   379  	require.NoError(t, err)
   380  
   381  	tmpDir, cleanup, err := dirs.TempDir("", "")
   382  	defer cleanup()
   383  	require.NoError(t, err)
   384  
   385  	for i, currCase := range []struct {
   386  		name   string
   387  		files  []gofiles.GoFileSpec
   388  		config string
   389  		want   []string
   390  	}{
   391  		{
   392  			name: "file with go1.7 build tag processed",
   393  			files: []gofiles.GoFileSpec{
   394  				{
   395  					RelPath: "foo.go",
   396  					Src: `// +build go1.7
   397  
   398  					package foo
   399  					import "os"
   400  					func Foo() {
   401  						os.Setenv("foo", "bar")
   402  					}
   403  					`,
   404  				},
   405  				{
   406  					RelPath: "bar.go",
   407  					Src: `package foo
   408  					import "os"
   409  					func Bar() {
   410  						os.Setenv("foo", "bar")
   411  					}
   412  					`,
   413  				},
   414  			},
   415  			config: "",
   416  			want: []string{
   417  				"Running errcheck...",
   418  				"bar.go:4:16: os.Setenv(\"foo\", \"bar\")",
   419  				"foo.go:6:16: os.Setenv(\"foo\", \"bar\")",
   420  				"",
   421  			},
   422  		},
   423  		{
   424  			name: "file with go1.7 build tag ignored if release-tag set to go1.6",
   425  			files: []gofiles.GoFileSpec{
   426  				{
   427  					RelPath: "foo.go",
   428  					Src: `// +build go1.7
   429  
   430  					package foo
   431  					import "os"
   432  					func Foo() {
   433  						os.Setenv("foo", "bar")
   434  					}
   435  					`,
   436  				},
   437  				{
   438  					RelPath: "bar.go",
   439  					Src: `package foo
   440  					import "os"
   441  					func Bar() {
   442  						os.Setenv("foo", "bar")
   443  					}
   444  					`,
   445  				},
   446  			},
   447  			config: `release-tag: go1.6`,
   448  			want: []string{
   449  				"Running errcheck...",
   450  				"bar.go:4:16: os.Setenv(\"foo\", \"bar\")",
   451  				"",
   452  			},
   453  		},
   454  	} {
   455  		currCaseDir, err := ioutil.TempDir(tmpDir, "")
   456  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   457  
   458  		_, err = gofiles.Write(currCaseDir, currCase.files)
   459  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   460  
   461  		tmpFile, err := ioutil.TempFile(currCaseDir, "")
   462  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   463  		cfgFilePath := tmpFile.Name()
   464  		err = tmpFile.Close()
   465  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   466  		err = ioutil.WriteFile(cfgFilePath, []byte(unindent(currCase.config)), 0644)
   467  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   468  
   469  		cmd := exec.Command(cli, "--config", cfgFilePath, "errcheck", ".")
   470  		cmd.Dir = currCaseDir
   471  		output, err := cmd.CombinedOutput()
   472  		require.Error(t, err, fmt.Errorf("Expected command %v to fail. Output:\n%v", cmd.Args, string(output)))
   473  
   474  		assert.Equal(t, currCase.want, strings.Split(string(output), "\n"), "Case %d: %s", i, currCase.name)
   475  	}
   476  }
   477  
   478  func toStringSlice(input []checkoutput.Issue) []string {
   479  	output := make([]string, len(input))
   480  	for i, curr := range input {
   481  		output[i] = curr.String()
   482  	}
   483  	return output
   484  }
   485  
   486  func unindent(input string) string {
   487  	return strings.Replace(input, "\n\t\t\t", "\n", -1)
   488  }