github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/gunit/app_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 gunit_test
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"os/exec"
    23  	"path"
    24  	"regexp"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/nmiyake/pkg/dirs"
    29  	"github.com/nmiyake/pkg/gofiles"
    30  	"github.com/palantir/amalgomate/amalgomated"
    31  	"github.com/palantir/pkg/pkgpath"
    32  	"github.com/pkg/errors"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  
    36  	"github.com/palantir/godel/apps/gunit"
    37  )
    38  
    39  func TestMain(m *testing.M) {
    40  	code := testHelper(m)
    41  	os.Exit(code)
    42  }
    43  
    44  var supplier amalgomated.CmderSupplier
    45  
    46  func testHelper(m *testing.M) int {
    47  	tmpDir, cleanup, err := dirs.TempDir("", "")
    48  	defer cleanup()
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  
    53  	libraries := map[string]string{
    54  		"gocover":       "./vendor/github.com/nmiyake/gotest",
    55  		"gojunitreport": "./vendor/github.com/jstemmer/go-junit-report",
    56  		"gotest":        "./vendor/github.com/nmiyake/gotest",
    57  		"gt":            "./vendor/rsc.io/gt",
    58  	}
    59  
    60  	supplier = temporaryBuildRunnerSupplier(tmpDir, libraries)
    61  	return m.Run()
    62  }
    63  
    64  func TestRun(t *testing.T) {
    65  	wd, err := os.Getwd()
    66  	require.NoError(t, err)
    67  
    68  	tmpDir, cleanup, err := dirs.TempDir(wd, "")
    69  	defer cleanup()
    70  	require.NoError(t, err)
    71  
    72  	originalWd, err := os.Getwd()
    73  	require.NoError(t, err)
    74  	defer func() {
    75  		if err := os.Chdir(originalWd); err != nil {
    76  			fmt.Printf("Failed to set wd to %v: %v", originalWd, err)
    77  		}
    78  	}()
    79  
    80  	for i, currCase := range []struct {
    81  		name          string
    82  		filesToCreate []gofiles.GoFileSpec
    83  		config        string
    84  		args          []string
    85  		wantMatch     func(currCaseTmpDir string) string
    86  		wantError     string
    87  	}{
    88  		{
    89  			name: "passing tests",
    90  			filesToCreate: []gofiles.GoFileSpec{
    91  				{
    92  					RelPath: "foo.go",
    93  					Src: `package foo
    94  					import "fmt"
    95  					func Foo() {
    96  						fmt.Println("Foo")
    97  					}`,
    98  				},
    99  				{
   100  					RelPath: "foo_test.go",
   101  					Src: `package foo
   102  					import "testing"
   103  					func TestFoo(t *testing.T) {
   104  						Foo()
   105  					}`,
   106  				},
   107  				{
   108  					RelPath: "vendor/bar/bar.go",
   109  					Src: `package bar
   110  					import "fmt"
   111  					func Bar() {
   112  						fmt.Println("Bar")
   113  					}`,
   114  				},
   115  			},
   116  			config: unindent(`exclude:
   117  					  paths:
   118  					    - "vendor"
   119  					`),
   120  			wantMatch: func(currCaseTmpDir string) string {
   121  				return "ok  \t" + pkgName(t, currCaseTmpDir) + "\t[0-9.]+s"
   122  			},
   123  		},
   124  		{
   125  			name: "failing tests",
   126  			filesToCreate: []gofiles.GoFileSpec{
   127  				{
   128  					RelPath: "foo.go",
   129  					Src: `package foo
   130  					import "fmt"
   131  					func Foo() {
   132  						fmt.Println("Foo")
   133  					}`,
   134  				},
   135  				{
   136  					RelPath: "foo_test.go",
   137  					Src: `package foo
   138  					import "testing"
   139  					func TestFoo(t *testing.T) {
   140  						Foo()
   141  						t.Errorf("myFail")
   142  					}`,
   143  				},
   144  			},
   145  			wantMatch: func(currCaseTmpDir string) string {
   146  				return `(?s)` +
   147  					`Foo\n--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: myFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "\t[0-9.]+s"
   148  			},
   149  			wantError: "(?s).+1 package had failing tests:.+",
   150  		},
   151  		{
   152  			name: "test that does not compile fails",
   153  			filesToCreate: []gofiles.GoFileSpec{
   154  				{
   155  					RelPath: "foo.go",
   156  					Src: `package foo
   157  					import "fmt"
   158  					func Foo() {
   159  						fmt.Println("Foo")
   160  					}`,
   161  				},
   162  				{
   163  					RelPath: "foo_test.go",
   164  					Src: `package foo
   165  					import "testing"
   166  					import "github.com/palantir/godel/apps/gunit/blah/foo"
   167  					func TestFoo(t *testing.T) {
   168  						foo.Foo()
   169  						t.Errorf("myFail")
   170  					}`,
   171  				},
   172  			},
   173  			wantMatch: func(currCaseTmpDir string) string {
   174  				return `(?s)` +
   175  					`foo_test.go:[0-9]+:[0-9]+: cannot find package.+\nFAIL\t` + pkgName(t, currCaseTmpDir) + `\t\[setup failed\]`
   176  			},
   177  			wantError: "(?s).+1 package had failing tests:.+",
   178  		},
   179  		{
   180  			name: "running with a tag runs only tagged tests",
   181  			filesToCreate: []gofiles.GoFileSpec{
   182  				{
   183  					RelPath: "foo_test.go",
   184  					Src: `package foo
   185  					import "testing"
   186  					func TestFoo(t *testing.T) {
   187  						t.Errorf("fooFail")
   188  					}`,
   189  				},
   190  				{
   191  					RelPath: "integration/bar_test.go",
   192  					Src: `package bar
   193  					import "testing"
   194  					func TestBar(t *testing.T) {
   195  						t.Errorf("barFail")
   196  					}`,
   197  				},
   198  			},
   199  			config: unindent(`tags:
   200  					  integration:
   201  					    names:
   202  					      - "integration"
   203  					exclude:
   204  					  paths:
   205  					    - "vendor"
   206  					`),
   207  			args: []string{
   208  				"--tags", "integration",
   209  			},
   210  			wantMatch: func(currCaseTmpDir string) string {
   211  				return `(?s)` +
   212  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "/integration\t[0-9.]+s"
   213  			},
   214  			wantError: "(?s).+1 package had failing tests:.+",
   215  		},
   216  		{
   217  			name: "union of tags is run when multiple tags are specified",
   218  			filesToCreate: []gofiles.GoFileSpec{
   219  				{
   220  					RelPath: "foo_test.go",
   221  					Src: `package foo
   222  					import "testing"
   223  					func TestFoo(t *testing.T) {
   224  						t.Errorf("fooFail")
   225  					}`,
   226  				},
   227  				{
   228  					RelPath: "bar/bar_test.go",
   229  					Src: `package bar
   230  					import "testing"
   231  					func TestBar(t *testing.T) {
   232  						t.Errorf("barFail")
   233  					}`,
   234  				},
   235  				{
   236  					RelPath: "baz/baz_test.go",
   237  					Src: `package baz
   238  					import "testing"
   239  					func TestBaz(t *testing.T) {
   240  						t.Errorf("bazFail")
   241  					}`,
   242  				},
   243  			},
   244  			config: unindent(`tags:
   245  					  bar:
   246  					    paths:
   247  					      - "bar"
   248  					  baz:
   249  					    paths:
   250  					      - "baz"
   251  					exclude:
   252  					  paths:
   253  					    - "vendor"
   254  					`),
   255  			args: []string{
   256  				"--tags", "bar,baz",
   257  			},
   258  			wantMatch: func(currCaseTmpDir string) string {
   259  				return `(?s)` +
   260  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/bar\t[0-9.]+s.+` +
   261  					`--- FAIL: TestBaz (.+)\n.+baz_test.go:[0-9]+: bazFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/baz\t[0-9.]+s.+`
   262  			},
   263  			wantError: "(?s).+2 packages had failing tests:.+",
   264  		},
   265  		{
   266  			name: "only non-tagged tests are run if multiple tags are specified and tests are run with none argument",
   267  			filesToCreate: []gofiles.GoFileSpec{
   268  				{
   269  					RelPath: "foo_test.go",
   270  					Src: `package foo
   271  					import "testing"
   272  					func TestFoo(t *testing.T) {
   273  						t.Errorf("fooFail")
   274  					}`,
   275  				},
   276  				{
   277  					RelPath: "bar/bar_test.go",
   278  					Src: `package bar
   279  					import "testing"
   280  					func TestBar(t *testing.T) {
   281  						t.Errorf("barFail")
   282  					}`,
   283  				},
   284  				{
   285  					RelPath: "baz/baz_test.go",
   286  					Src: `package baz
   287  					import "testing"
   288  					func TestBaz(t *testing.T) {
   289  						t.Errorf("bazFail")
   290  					}`,
   291  				},
   292  			},
   293  			config: unindent(`tags:
   294  					  bar:
   295  					    paths:
   296  					      - "bar"
   297  					  baz:
   298  					    paths:
   299  					      - "baz"
   300  					exclude:
   301  					  paths:
   302  					    - "vendor"
   303  					`),
   304  			args: []string{
   305  				"--tags", "none",
   306  			},
   307  			wantMatch: func(currCaseTmpDir string) string {
   308  				return `(?s)` +
   309  					`--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: fooFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `\t[0-9.]+s.+`
   310  			},
   311  			wantError: "(?s).+1 package had failing tests:.+",
   312  		},
   313  		{
   314  			name: "all tests are run if multiple tags are specified and tests are run without arguments",
   315  			filesToCreate: []gofiles.GoFileSpec{
   316  				{
   317  					RelPath: "foo_test.go",
   318  					Src: `package foo
   319  					import "testing"
   320  					func TestFoo(t *testing.T) {
   321  						t.Errorf("fooFail")
   322  					}`,
   323  				},
   324  				{
   325  					RelPath: "bar/bar_test.go",
   326  					Src: `package bar
   327  					import "testing"
   328  					func TestBar(t *testing.T) {
   329  						t.Errorf("barFail")
   330  					}`,
   331  				},
   332  				{
   333  					RelPath: "baz/baz_test.go",
   334  					Src: `package baz
   335  					import "testing"
   336  					func TestBaz(t *testing.T) {
   337  						t.Errorf("bazFail")
   338  					}`,
   339  				},
   340  			},
   341  			config: unindent(`tags:
   342  					  bar:
   343  					    paths:
   344  					      - "bar"
   345  					  baz:
   346  					    paths:
   347  					      - "baz"
   348  					exclude:
   349  					  paths:
   350  					    - "vendor"
   351  					`),
   352  			wantMatch: func(currCaseTmpDir string) string {
   353  				return `(?s)` +
   354  					`--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: fooFail.+FAIL\s+` + pkgName(t, currCaseTmpDir) + `\s+[0-9.]+s.+` +
   355  					`--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\s+` + pkgName(t, currCaseTmpDir) + `/bar\s+[0-9.]+s.+` +
   356  					`--- FAIL: TestBaz (.+)\n.+baz_test.go:[0-9]+: bazFail.+FAIL\s+` + pkgName(t, currCaseTmpDir) + `/baz\s+[0-9.]+s.+`
   357  			},
   358  			wantError: "(?s).+3 packages had failing tests:.+",
   359  		},
   360  		{
   361  			name: "fails if invalid tag is supplied",
   362  			filesToCreate: []gofiles.GoFileSpec{
   363  				{
   364  					RelPath: "foo_test.go",
   365  					Src: `package foo
   366  					import "testing"
   367  					func TestFoo(t *testing.T) {
   368  						t.Errorf("fooFail")
   369  					}`,
   370  				},
   371  			},
   372  			config: unindent(`exclude:
   373  					  paths:
   374  					    - "vendor"
   375  					`),
   376  			args: []string{
   377  				"--tags", "invalid,n!otvalid",
   378  			},
   379  			wantMatch: func(currCaseTmpDir string) string {
   380  				return `Tags "invalid", "n!otvalid" not defined in configuration. No tags are defined.`
   381  			},
   382  			wantError: `Tags "invalid", "n!otvalid" not defined in configuration. No tags are defined.`,
   383  		},
   384  		{
   385  			name: "fails if invalid tag is supplied and tag exists",
   386  			filesToCreate: []gofiles.GoFileSpec{
   387  				{
   388  					RelPath: "foo_test.go",
   389  					Src: `package foo
   390  					import "testing"
   391  					func TestFoo(t *testing.T) {
   392  						t.Errorf("fooFail")
   393  					}`,
   394  				},
   395  			},
   396  			config: unindent(`tags:
   397  					  bar:
   398  					    paths:
   399  					      - "bar"
   400  					  exclude:
   401  					    paths:
   402  					      - "vendor"
   403  					  other:
   404  					    paths:
   405  					      - "other"
   406  					`),
   407  			args: []string{
   408  				"--tags", "invalid,n!otvalid",
   409  			},
   410  			wantMatch: func(currCaseTmpDir string) string {
   411  				return `Tags "invalid", "n!otvalid" not defined in configuration. Valid tags: "bar", "exclude", "other"`
   412  			},
   413  			wantError: `Tags "invalid", "n!otvalid" not defined in configuration. Valid tags: "bar", "exclude", "other"`,
   414  		},
   415  	} {
   416  		currCaseTmpDir, err := ioutil.TempDir(tmpDir, "")
   417  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   418  
   419  		_, err = gofiles.Write(currCaseTmpDir, currCase.filesToCreate)
   420  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   421  
   422  		configFile := path.Join(currCaseTmpDir, "config.yml")
   423  		err = ioutil.WriteFile(configFile, []byte(currCase.config), 0644)
   424  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   425  
   426  		outBuf := bytes.Buffer{}
   427  
   428  		err = os.Chdir(currCaseTmpDir)
   429  		require.NoError(t, err)
   430  
   431  		app := gunit.App(supplier)
   432  		app.Stdout = &outBuf
   433  		app.Stderr = &outBuf
   434  
   435  		args := []string{"gunit", "--config", configFile}
   436  		args = append(args, currCase.args...)
   437  		args = append(args, "test")
   438  
   439  		runTestCode := app.Run(args)
   440  		output := outBuf.String()
   441  
   442  		if runTestCode != 0 {
   443  			if currCase.wantError == "" {
   444  				t.Fatalf("Case %d: %s\nunexpected error:\n%v\nOutput: %v", i, currCase.name, err, output)
   445  			} else if !regexp.MustCompile(currCase.wantError).MatchString(output) {
   446  				t.Fatalf("Case %d: %s\nexpected error output to contain %v, but was %v", i, currCase.name, currCase.wantError, output)
   447  			}
   448  		} else if currCase.wantError != "" {
   449  			t.Fatalf("Case %d: %s\nexpected error %v, but was none.\nOutput: %v", i, currCase.name, currCase.wantError, output)
   450  		}
   451  
   452  		expectedExpr := currCase.wantMatch(currCaseTmpDir)
   453  		if !regexp.MustCompile(expectedExpr).MatchString(output) {
   454  			t.Errorf("Case %d: %s\nOutput did not match expected expression.\nExpected:\n%v\nActual:\n%v", i, currCase.name, expectedExpr, output)
   455  		}
   456  	}
   457  }
   458  
   459  func TestClean(t *testing.T) {
   460  	wd, err := os.Getwd()
   461  	require.NoError(t, err)
   462  
   463  	tmpDir, cleanup, err := dirs.TempDir(wd, "")
   464  	defer cleanup()
   465  	require.NoError(t, err)
   466  
   467  	err = ioutil.WriteFile(path.Join(tmpDir, "tmp_placeholder_test.go"), []byte("package main"), 0644)
   468  	require.NoError(t, err)
   469  
   470  	originalWd, err := os.Getwd()
   471  	require.NoError(t, err)
   472  	defer func() {
   473  		if err := os.Chdir(originalWd); err != nil {
   474  			fmt.Printf("%+v\n", errors.Wrapf(err, "failed to restore working directory to %s", originalWd))
   475  		}
   476  	}()
   477  
   478  	err = os.Chdir(tmpDir)
   479  	require.NoError(t, err)
   480  
   481  	app := gunit.App(supplier)
   482  	exitCode := app.Run([]string{"gunit", "clean"})
   483  	require.Equal(t, 0, exitCode)
   484  
   485  	_, err = os.Stat(path.Join(tmpDir, "tmp_placeholder_test.go"))
   486  	assert.True(t, os.IsNotExist(err))
   487  }
   488  
   489  func temporaryBuildRunnerSupplier(tmpDir string, libraries map[string]string) amalgomated.CmderSupplier {
   490  	wd, err := os.Getwd()
   491  	if err != nil {
   492  		panic(err)
   493  	}
   494  
   495  	// build executables for all commands once
   496  	for currCmd, currLibrary := range libraries {
   497  		executable := path.Join(tmpDir, currCmd)
   498  		cmd := exec.Command("go", "build", "-o", executable)
   499  		cmd.Dir = path.Join(wd, currLibrary)
   500  
   501  		bytes, err := cmd.CombinedOutput()
   502  		if err != nil {
   503  			panic(fmt.Errorf("go build failed\nPath: %v\nArgs: %v\nDir: %v\nOutput: %v", cmd.Path, cmd.Args, cmd.Dir, string(bytes)))
   504  		}
   505  	}
   506  
   507  	// return supplier that points to built executables
   508  	return func(cmd amalgomated.Cmd) (amalgomated.Cmder, error) {
   509  		return amalgomated.PathCmder(path.Join(tmpDir, cmd.Name())), nil
   510  	}
   511  }
   512  
   513  func unindent(input string) string {
   514  	return strings.Replace(input, "\n\t\t\t\t\t", "\n", -1)
   515  }
   516  
   517  func pkgName(t *testing.T, path string) string {
   518  	pkgPath, err := pkgpath.NewAbsPkgPath(path).GoPathSrcRel()
   519  	require.NoError(t, err)
   520  	return pkgPath
   521  }