github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/apps/gunit/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  	"io/ioutil"
    19  	"os"
    20  	"os/exec"
    21  	"path"
    22  	"regexp"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/nmiyake/pkg/dirs"
    27  	"github.com/nmiyake/pkg/gofiles"
    28  	"github.com/palantir/pkg/pkgpath"
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  
    32  	"github.com/palantir/godel/pkg/products"
    33  )
    34  
    35  func TestRun(t *testing.T) {
    36  	cli, err := products.Bin("gunit")
    37  	require.NoError(t, err)
    38  
    39  	wd, err := os.Getwd()
    40  	require.NoError(t, err)
    41  
    42  	tmpDir, cleanup, err := dirs.TempDir(wd, "")
    43  	defer cleanup()
    44  	require.NoError(t, err)
    45  
    46  	originalWd, err := os.Getwd()
    47  	require.NoError(t, err)
    48  	defer func() {
    49  		if err := os.Chdir(originalWd); err != nil {
    50  			require.NoError(t, err)
    51  		}
    52  	}()
    53  
    54  	for i, currCase := range []struct {
    55  		name          string
    56  		filesToCreate []gofiles.GoFileSpec
    57  		config        string
    58  		args          []string
    59  		wantMatch     func(currCaseTmpDir string) string
    60  		wantError     string
    61  	}{
    62  		{
    63  			name: "passing tests",
    64  			filesToCreate: []gofiles.GoFileSpec{
    65  				{
    66  					RelPath: "foo.go",
    67  					Src: `package foo
    68  					import "fmt"
    69  					func Foo() {
    70  						fmt.Println("Foo")
    71  					}`,
    72  				},
    73  				{
    74  					RelPath: "foo_test.go",
    75  					Src: `package foo
    76  					import "testing"
    77  					func TestFoo(t *testing.T) {
    78  						Foo()
    79  					}`,
    80  				},
    81  				{
    82  					RelPath: "vendor/bar/bar.go",
    83  					Src: `package bar
    84  					import "fmt"
    85  					func Bar() {
    86  						fmt.Println("Bar")
    87  					}`,
    88  				},
    89  			},
    90  			config: unindent(`exclude:
    91  					  paths:
    92  					    - "vendor"
    93  					`),
    94  			wantMatch: func(currCaseTmpDir string) string {
    95  				return "ok  \t" + pkgName(t, currCaseTmpDir) + "\t[0-9.]+s"
    96  			},
    97  		},
    98  		{
    99  			name: "failing tests",
   100  			filesToCreate: []gofiles.GoFileSpec{
   101  				{
   102  					RelPath: "foo.go",
   103  					Src: `package foo
   104  					import "fmt"
   105  					func Foo() {
   106  						fmt.Println("Foo")
   107  					}`,
   108  				},
   109  				{
   110  					RelPath: "foo_test.go",
   111  					Src: `package foo
   112  					import "testing"
   113  					func TestFoo(t *testing.T) {
   114  						Foo()
   115  						t.Errorf("myFail")
   116  					}`,
   117  				},
   118  			},
   119  			wantMatch: func(currCaseTmpDir string) string {
   120  				return `(?s)` +
   121  					`Foo\n--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: myFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "\t[0-9.]+s"
   122  			},
   123  			wantError: "(?s).+1 package had failing tests:.+",
   124  		},
   125  		{
   126  			name: "fails if there are no packages to test (no buildable Go files)",
   127  			wantMatch: func(currCaseTmpDir string) string {
   128  				return "^no packages to test\n$"
   129  			},
   130  			wantError: "^no packages to test\n$",
   131  		},
   132  		{
   133  			name: "test that does not compile fails",
   134  			filesToCreate: []gofiles.GoFileSpec{
   135  				{
   136  					RelPath: "foo.go",
   137  					Src: `package foo
   138  					import "fmt"
   139  					func Foo() {
   140  						fmt.Println("Foo")
   141  					}`,
   142  				},
   143  				{
   144  					RelPath: "foo_test.go",
   145  					Src: `package foo
   146  					import "testing"
   147  					import "github.com/palantir/godel/apps/gunit/blah/foo"
   148  					func TestFoo(t *testing.T) {
   149  						foo.Foo()
   150  						t.Errorf("myFail")
   151  					}`,
   152  				},
   153  			},
   154  			wantMatch: func(currCaseTmpDir string) string {
   155  				return `(?s)` +
   156  					`foo_test.go:[0-9]+:[0-9]+: cannot find package.+\nFAIL\t` + pkgName(t, currCaseTmpDir) + `\t\[setup failed\]`
   157  			},
   158  			wantError: "(?s).+1 package had failing tests:.+",
   159  		},
   160  		{
   161  			name: "running with a tag runs only tagged tests",
   162  			filesToCreate: []gofiles.GoFileSpec{
   163  				{
   164  					RelPath: "foo_test.go",
   165  					Src: `package foo
   166  					import "testing"
   167  					func TestFoo(t *testing.T) {
   168  						t.Errorf("fooFail")
   169  					}`,
   170  				},
   171  				{
   172  					RelPath: "integration/bar_test.go",
   173  					Src: `package bar
   174  					import "testing"
   175  					func TestBar(t *testing.T) {
   176  						t.Errorf("barFail")
   177  					}`,
   178  				},
   179  			},
   180  			config: unindent(`tags:
   181  					  integration:
   182  					    names:
   183  					      - "integration"
   184  					exclude:
   185  					  paths:
   186  					    - "vendor"
   187  					`),
   188  			args: []string{
   189  				"--tags", "integration",
   190  			},
   191  			wantMatch: func(currCaseTmpDir string) string {
   192  				return `(?s)` +
   193  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "/integration\t[0-9.]+s"
   194  			},
   195  			wantError: "(?s).+1 package had failing tests:.+",
   196  		},
   197  		{
   198  			name: "tag will skip excluded tests",
   199  			filesToCreate: []gofiles.GoFileSpec{
   200  				{
   201  					RelPath: "foo_test.go",
   202  					Src: `package foo
   203  					import "testing"
   204  					func TestFoo(t *testing.T) {
   205  						t.Errorf("fooFail")
   206  					}`,
   207  				},
   208  				{
   209  					RelPath: "integration/bar_test.go",
   210  					Src: `package bar
   211  					import "testing"
   212  					func TestBar(t *testing.T) {
   213  						t.Errorf("barFail")
   214  					}`,
   215  				},
   216  				{
   217  					RelPath: "integration/baz/baz_test.go",
   218  					Src: `package baz
   219  					import "testing"
   220  					func TestBaz(t *testing.T) {
   221  						t.Errorf("bazFail")
   222  					}`,
   223  				},
   224  				{
   225  					RelPath: "integration/exclude/exclude_test.go",
   226  					Src: `package exclude
   227  					import "testing"
   228  					func TestExclude(t *testing.T) {
   229  						t.Errorf("exclude")
   230  					}`,
   231  				},
   232  			},
   233  			config: unindent(`tags:
   234  					  integration:
   235  					    names:
   236  					      - "integration"
   237  					    exclude:
   238  					      paths:
   239  					        - "integration/exclude"
   240  					exclude:
   241  					  paths:
   242  					    - "vendor"
   243  					`),
   244  			args: []string{
   245  				"--tags", "integration",
   246  			},
   247  			wantMatch: func(currCaseTmpDir string) string {
   248  				return `(?s)` +
   249  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/integration\s+[0-9.]+s.+` +
   250  					`--- FAIL: TestBaz (.+)\n.+baz_test.go:[0-9]+: bazFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/integration/baz\s+[0-9.]+s.+`
   251  			},
   252  			wantError: "(?s).+2 packages had failing tests:.+",
   253  		},
   254  		{
   255  			name: "tags are case-insensitive",
   256  			filesToCreate: []gofiles.GoFileSpec{
   257  				{
   258  					RelPath: "foo_test.go",
   259  					Src: `package foo
   260  					import "testing"
   261  					func TestFoo(t *testing.T) {
   262  						t.Errorf("fooFail")
   263  					}`,
   264  				},
   265  				{
   266  					RelPath: "integration/bar_test.go",
   267  					Src: `package bar
   268  					import "testing"
   269  					func TestBar(t *testing.T) {
   270  						t.Errorf("barFail")
   271  					}`,
   272  				},
   273  			},
   274  			config: unindent(`tags:
   275  					  integration:
   276  					    names:
   277  					      - "integration"
   278  					exclude:
   279  					  paths:
   280  					    - "vendor"
   281  					`),
   282  			args: []string{
   283  				"--tags", "INTEGRATION",
   284  			},
   285  			wantMatch: func(currCaseTmpDir string) string {
   286  				return `(?s)` +
   287  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "/integration\t[0-9.]+s"
   288  			},
   289  			wantError: "(?s).+1 package had failing tests:.+",
   290  		},
   291  		{
   292  			name: "tags can be specified multiple times",
   293  			filesToCreate: []gofiles.GoFileSpec{
   294  				{
   295  					RelPath: "foo_test.go",
   296  					Src: `package foo
   297  					import "testing"
   298  					func TestFoo(t *testing.T) {
   299  						t.Errorf("fooFail")
   300  					}`,
   301  				},
   302  				{
   303  					RelPath: "integration/bar_test.go",
   304  					Src: `package bar
   305  					import "testing"
   306  					func TestBar(t *testing.T) {
   307  						t.Errorf("barFail")
   308  					}`,
   309  				},
   310  			},
   311  			config: unindent(`tags:
   312  					  integration:
   313  					    names:
   314  					      - "integration"
   315  					exclude:
   316  					  paths:
   317  					    - "vendor"
   318  					`),
   319  			args: []string{
   320  				"--tags", "INTEGRATION,integration",
   321  			},
   322  			wantMatch: func(currCaseTmpDir string) string {
   323  				return `(?s)` +
   324  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "/integration\t[0-9.]+s"
   325  			},
   326  			wantError: "(?s).+1 package had failing tests:.+",
   327  		},
   328  		{
   329  			name: "fails if tags do not match any packages",
   330  			filesToCreate: []gofiles.GoFileSpec{
   331  				{
   332  					RelPath: "foo_test.go",
   333  					Src: `package foo
   334  					import "testing"
   335  					func TestFoo(t *testing.T) {
   336  						t.Errorf("fooFail")
   337  					}`,
   338  				},
   339  			},
   340  			config: unindent(`tags:
   341  					  integration:
   342  					    names:
   343  					      - "integration"
   344  					`),
   345  			args: []string{
   346  				"--tags", "integration",
   347  			},
   348  			wantMatch: func(currCaseTmpDir string) string {
   349  				return "^no packages to test\n$"
   350  			},
   351  			wantError: "^no packages to test\n$",
   352  		},
   353  		{
   354  			name: "union of tags is run when multiple tags are specified",
   355  			filesToCreate: []gofiles.GoFileSpec{
   356  				{
   357  					RelPath: "foo_test.go",
   358  					Src: `package foo
   359  					import "testing"
   360  					func TestFoo(t *testing.T) {
   361  						t.Errorf("fooFail")
   362  					}`,
   363  				},
   364  				{
   365  					RelPath: "bar/bar_test.go",
   366  					Src: `package bar
   367  					import "testing"
   368  					func TestBar(t *testing.T) {
   369  						t.Errorf("barFail")
   370  					}`,
   371  				},
   372  				{
   373  					RelPath: "baz/baz_test.go",
   374  					Src: `package baz
   375  					import "testing"
   376  					func TestBaz(t *testing.T) {
   377  						t.Errorf("bazFail")
   378  					}`,
   379  				},
   380  			},
   381  			config: unindent(`tags:
   382  					  bar:
   383  					    paths:
   384  					      - "bar"
   385  					  baz:
   386  					    paths:
   387  					      - "baz"
   388  					exclude:
   389  					  paths:
   390  					    - "vendor"
   391  					`),
   392  			args: []string{
   393  				"--tags", "bar,baz",
   394  			},
   395  			wantMatch: func(currCaseTmpDir string) string {
   396  				return `(?s)` +
   397  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/bar\t[0-9.]+s.+` +
   398  					`--- FAIL: TestBaz (.+)\n.+baz_test.go:[0-9]+: bazFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/baz\t[0-9.]+s.+`
   399  			},
   400  			wantError: "(?s).+2 packages had failing tests:.+",
   401  		},
   402  		{
   403  			name: "only non-tagged tests are run if multiple tags are specified and tests are run without arguments",
   404  			filesToCreate: []gofiles.GoFileSpec{
   405  				{
   406  					RelPath: "foo_test.go",
   407  					Src: `package foo
   408  					import "testing"
   409  					func TestFoo(t *testing.T) {
   410  						t.Errorf("fooFail")
   411  					}`,
   412  				},
   413  				{
   414  					RelPath: "bar/bar_test.go",
   415  					Src: `package bar
   416  					import "testing"
   417  					func TestBar(t *testing.T) {
   418  						t.Errorf("barFail")
   419  					}`,
   420  				},
   421  				{
   422  					RelPath: "baz/baz_test.go",
   423  					Src: `package baz
   424  					import "testing"
   425  					func TestBaz(t *testing.T) {
   426  						t.Errorf("bazFail")
   427  					}`,
   428  				},
   429  			},
   430  			config: unindent(`tags:
   431  					  bar:
   432  					    paths:
   433  					      - "bar"
   434  					  baz:
   435  					    paths:
   436  					      - "baz"
   437  					exclude:
   438  					  paths:
   439  					    - "vendor"
   440  					`),
   441  			wantMatch: func(currCaseTmpDir string) string {
   442  				return `(?s)` +
   443  					`--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: fooFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `\t[0-9.]+s.+`
   444  			},
   445  			wantError: "(?s).+1 package had failing tests:.+",
   446  		},
   447  		{
   448  			name: "all tests (tagged and non-tagged) are run if 'all' is specified as the tag",
   449  			filesToCreate: []gofiles.GoFileSpec{
   450  				{
   451  					RelPath: "foo_test.go",
   452  					Src: `package foo
   453  					import "testing"
   454  					func TestFoo(t *testing.T) {
   455  						t.Errorf("fooFail")
   456  					}`,
   457  				},
   458  				{
   459  					RelPath: "bar/bar_test.go",
   460  					Src: `package bar
   461  					import "testing"
   462  					func TestBar(t *testing.T) {
   463  						t.Errorf("barFail")
   464  					}`,
   465  				},
   466  				{
   467  					RelPath: "baz/baz_test.go",
   468  					Src: `package baz
   469  					import "testing"
   470  					func TestBaz(t *testing.T) {
   471  						t.Errorf("bazFail")
   472  					}`,
   473  				},
   474  			},
   475  			config: unindent(`tags:
   476  					  bar:
   477  					    paths:
   478  					      - "bar"
   479  					  baz:
   480  					    paths:
   481  					      - "baz"
   482  					exclude:
   483  					  paths:
   484  					    - "vendor"
   485  					`),
   486  			args: []string{
   487  				"--tags", "all",
   488  			},
   489  			wantMatch: func(currCaseTmpDir string) string {
   490  				return `(?s)` +
   491  					`--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: fooFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `\s+[0-9.]+s.+` +
   492  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/bar\s+[0-9.]+s.+` +
   493  					`--- FAIL: TestBaz (.+)\n.+baz_test.go:[0-9]+: bazFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/baz\s+[0-9.]+s.+`
   494  			},
   495  			wantError: "(?s).+3 packages had failing tests:.+",
   496  		},
   497  		{
   498  			name: "fails if invalid tag is supplied",
   499  			filesToCreate: []gofiles.GoFileSpec{
   500  				{
   501  					RelPath: "foo_test.go",
   502  					Src: `package foo
   503  					import "testing"
   504  					func TestFoo(t *testing.T) {
   505  						t.Errorf("fooFail")
   506  					}`,
   507  				},
   508  			},
   509  			config: unindent(`exclude:
   510  					  paths:
   511  					    - "vendor"
   512  					`),
   513  			args: []string{
   514  				"--tags", "invalid,n!otvalid",
   515  			},
   516  			wantMatch: func(currCaseTmpDir string) string {
   517  				return `invalid tags: "invalid", "n!otvalid"`
   518  			},
   519  			wantError: `invalid tags: "invalid", "n!otvalid"`,
   520  		},
   521  		{
   522  			name: "fails if 'all' is supplied as a non-exclusive tag",
   523  			filesToCreate: []gofiles.GoFileSpec{
   524  				{
   525  					RelPath: "foo_test.go",
   526  					Src: `package foo
   527  					import "testing"
   528  					func TestFoo(t *testing.T) {
   529  						t.Errorf("fooFail")
   530  					}`,
   531  				},
   532  				{
   533  					RelPath: "integration/bar_test.go",
   534  					Src: `package bar
   535  					import "testing"
   536  					func TestBar(t *testing.T) {
   537  						t.Errorf("barFail")
   538  					}`,
   539  				},
   540  			},
   541  			config: unindent(`tags:
   542  					  integration:
   543  					    names:
   544  					      - "integration"
   545  					exclude:
   546  					  paths:
   547  					    - "vendor"
   548  					`),
   549  			args: []string{
   550  				"--tags", "integration,all",
   551  			},
   552  			wantError: regexp.QuoteMeta(`if "all" tag is specified, it must be the only tag specified`),
   553  		},
   554  		{
   555  			name: "detects race conditions if race flag is supplied",
   556  			filesToCreate: []gofiles.GoFileSpec{
   557  				{
   558  					RelPath: "foo_test.go",
   559  					Src: `package foo
   560  					import (
   561  					  "fmt"
   562  					  "math/rand"
   563  					  "testing"
   564  					  "time"
   565  					)
   566  					func TestFoo(t *testing.T) {
   567  					    timeTest()
   568  					}
   569  					func timeTest() {
   570  					    start := time.Now()
   571  					     var t *time.Timer
   572  					     t = time.AfterFunc(randomDuration(), func() {
   573  						 fmt.Println(time.Now().Sub(start))
   574  						 t.Reset(randomDuration())
   575  					     })
   576  					     time.Sleep(1 * time.Second)
   577  					}
   578  					func randomDuration() time.Duration {
   579  					     return time.Duration(rand.Int63n(1e9))
   580  					}`,
   581  				},
   582  			},
   583  			args: []string{
   584  				"--race",
   585  			},
   586  			wantMatch: func(currCaseTmpDir string) string {
   587  				return `(?s).+WARNING: DATA RACE\n.+`
   588  			},
   589  			wantError: `(?s).+WARNING: DATA RACE\n.+`,
   590  		},
   591  	} {
   592  		currCaseTmpDir, err := ioutil.TempDir(tmpDir, "")
   593  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   594  
   595  		_, err = gofiles.Write(currCaseTmpDir, currCase.filesToCreate)
   596  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   597  
   598  		configFile := path.Join(currCaseTmpDir, "config.yml")
   599  		err = ioutil.WriteFile(configFile, []byte(currCase.config), 0644)
   600  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   601  
   602  		err = os.Chdir(currCaseTmpDir)
   603  		require.NoError(t, err)
   604  
   605  		args := []string{"--config", configFile}
   606  		args = append(args, currCase.args...)
   607  		args = append(args, "test")
   608  
   609  		cmd := exec.Command(cli, args...)
   610  		outputBytes, err := cmd.CombinedOutput()
   611  		output := string(outputBytes)
   612  
   613  		if err != nil {
   614  			if currCase.wantError == "" {
   615  				t.Fatalf("Case %d: %s\nunexpected error:\n%v\nOutput: %v", i, currCase.name, err, output)
   616  			} else if !regexp.MustCompile(currCase.wantError).MatchString(output) {
   617  				t.Fatalf("Case %d: %s\nexpected error output to contain %v, but was %v", i, currCase.name, currCase.wantError, output)
   618  			}
   619  		} else if currCase.wantError != "" {
   620  			t.Fatalf("Case %d: %s\nexpected error %v, but was none.\nOutput: %v", i, currCase.name, currCase.wantError, output)
   621  		}
   622  
   623  		if currCase.wantMatch != nil {
   624  			expectedExpr := currCase.wantMatch(currCaseTmpDir)
   625  			if !regexp.MustCompile(expectedExpr).MatchString(output) {
   626  				t.Errorf("Case %d: %s\nOutput did not match expected expression.\nExpected:\n%v\nActual:\n%v", i, currCase.name, expectedExpr, output)
   627  			}
   628  		}
   629  	}
   630  }
   631  
   632  func TestClean(t *testing.T) {
   633  	cli, err := products.Bin("gunit")
   634  	require.NoError(t, err)
   635  
   636  	wd, err := os.Getwd()
   637  	require.NoError(t, err)
   638  
   639  	tmpDir, cleanup, err := dirs.TempDir(wd, "")
   640  	defer cleanup()
   641  	require.NoError(t, err)
   642  
   643  	files, err := gofiles.Write(tmpDir, []gofiles.GoFileSpec{
   644  		{
   645  			RelPath: "tmp_placeholder_test.go",
   646  			Src:     "package main",
   647  		},
   648  	})
   649  	require.NoError(t, err)
   650  
   651  	originalWd, err := os.Getwd()
   652  	require.NoError(t, err)
   653  	defer func() {
   654  		if err := os.Chdir(originalWd); err != nil {
   655  			require.NoError(t, err)
   656  		}
   657  	}()
   658  
   659  	err = os.Chdir(tmpDir)
   660  	require.NoError(t, err)
   661  
   662  	cmd := exec.Command(cli, "clean")
   663  	output, err := cmd.CombinedOutput()
   664  	require.NoError(t, err, "Command %v failed with output:\n%s", cmd.Args, string(output))
   665  
   666  	_, err = os.Stat(files["tmp_placeholder_test.go"].Path)
   667  	assert.True(t, os.IsNotExist(err))
   668  }
   669  
   670  func unindent(input string) string {
   671  	return strings.Replace(input, "\n\t\t\t\t\t", "\n", -1)
   672  }
   673  
   674  func pkgName(t *testing.T, path string) string {
   675  	pkgPath, err := pkgpath.NewAbsPkgPath(path).GoPathSrcRel()
   676  	require.NoError(t, err)
   677  	return pkgPath
   678  }