github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/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)Foo\n--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: myFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "\t[0-9.]+s"
   147  			},
   148  			wantError: "(?s).+1 package had failing tests:.+",
   149  		},
   150  		{
   151  			name: "test that does not compile fails",
   152  			filesToCreate: []gofiles.GoFileSpec{
   153  				{
   154  					RelPath: "foo.go",
   155  					Src: `package foo
   156  					import "fmt"
   157  					func Foo() {
   158  						fmt.Println("Foo")
   159  					}`,
   160  				},
   161  				{
   162  					RelPath: "foo_test.go",
   163  					Src: `package foo
   164  					import "testing"
   165  					import "github.com/palantir/godel/apps/gunit/blah/foo"
   166  					func TestFoo(t *testing.T) {
   167  						foo.Foo()
   168  						t.Errorf("myFail")
   169  					}`,
   170  				},
   171  			},
   172  			wantMatch: func(currCaseTmpDir string) string {
   173  				return `(?s)foo_test.go:[0-9]+:[0-9]+: cannot find package.+\nFAIL\t` + pkgName(t, currCaseTmpDir) + `\t\[setup failed\]`
   174  			},
   175  			wantError: "(?s).+1 package had failing tests:.+",
   176  		},
   177  		{
   178  			name: "running with a tag runs only tagged tests",
   179  			filesToCreate: []gofiles.GoFileSpec{
   180  				{
   181  					RelPath: "foo_test.go",
   182  					Src: `package foo
   183  					import "testing"
   184  					func TestFoo(t *testing.T) {
   185  						t.Errorf("fooFail")
   186  					}`,
   187  				},
   188  				{
   189  					RelPath: "integration/bar_test.go",
   190  					Src: `package bar
   191  					import "testing"
   192  					func TestBar(t *testing.T) {
   193  						t.Errorf("barFail")
   194  					}`,
   195  				},
   196  			},
   197  			config: unindent(`tags:
   198  					  integration:
   199  					    names:
   200  					      - "integration"
   201  					exclude:
   202  					  paths:
   203  					    - "vendor"
   204  					`),
   205  			args: []string{
   206  				"--tags", "integration",
   207  			},
   208  			wantMatch: func(currCaseTmpDir string) string {
   209  				return `(?s)--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + "/integration\t[0-9.]+s"
   210  			},
   211  			wantError: "(?s).+1 package had failing tests:.+",
   212  		},
   213  		{
   214  			name: "union of tags is run when multiple tags are specified",
   215  			filesToCreate: []gofiles.GoFileSpec{
   216  				{
   217  					RelPath: "foo_test.go",
   218  					Src: `package foo
   219  					import "testing"
   220  					func TestFoo(t *testing.T) {
   221  						t.Errorf("fooFail")
   222  					}`,
   223  				},
   224  				{
   225  					RelPath: "bar/bar_test.go",
   226  					Src: `package bar
   227  					import "testing"
   228  					func TestBar(t *testing.T) {
   229  						t.Errorf("barFail")
   230  					}`,
   231  				},
   232  				{
   233  					RelPath: "baz/baz_test.go",
   234  					Src: `package baz
   235  					import "testing"
   236  					func TestBaz(t *testing.T) {
   237  						t.Errorf("bazFail")
   238  					}`,
   239  				},
   240  			},
   241  			config: unindent(`tags:
   242  					  bar:
   243  					    paths:
   244  					      - "bar"
   245  					  baz:
   246  					    paths:
   247  					      - "baz"
   248  					exclude:
   249  					  paths:
   250  					    - "vendor"
   251  					`),
   252  			args: []string{
   253  				"--tags", "bar,baz",
   254  			},
   255  			wantMatch: func(currCaseTmpDir string) string {
   256  				return `(?s)--- FAIL: TestBar (.+)\n.+bar_test.go:[0-9]+: barFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/bar\t[0-9.]+s.+` +
   257  					`--- FAIL: TestBaz (.+)\n.+baz_test.go:[0-9]+: bazFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `/baz\t[0-9.]+s.+`
   258  			},
   259  			wantError: "(?s).+2 packages had failing tests:.+",
   260  		},
   261  		{
   262  			name: "only non-tagged tests are run if multiple tags are specified and tests are run without arguments",
   263  			filesToCreate: []gofiles.GoFileSpec{
   264  				{
   265  					RelPath: "foo_test.go",
   266  					Src: `package foo
   267  					import "testing"
   268  					func TestFoo(t *testing.T) {
   269  						t.Errorf("fooFail")
   270  					}`,
   271  				},
   272  				{
   273  					RelPath: "bar/bar_test.go",
   274  					Src: `package bar
   275  					import "testing"
   276  					func TestBar(t *testing.T) {
   277  						t.Errorf("barFail")
   278  					}`,
   279  				},
   280  				{
   281  					RelPath: "baz/baz_test.go",
   282  					Src: `package baz
   283  					import "testing"
   284  					func TestBaz(t *testing.T) {
   285  						t.Errorf("bazFail")
   286  					}`,
   287  				},
   288  			},
   289  			config: unindent(`tags:
   290  					  bar:
   291  					    paths:
   292  					      - "bar"
   293  					  baz:
   294  					    paths:
   295  					      - "baz"
   296  					exclude:
   297  					  paths:
   298  					    - "vendor"
   299  					`),
   300  			wantMatch: func(currCaseTmpDir string) string {
   301  				return `(?s)--- FAIL: TestFoo (.+)\n.+foo_test.go:[0-9]+: fooFail.+FAIL\t` + pkgName(t, currCaseTmpDir) + `\t[0-9.]+s.+`
   302  			},
   303  			wantError: "(?s).+1 package had failing tests:.+",
   304  		},
   305  		{
   306  			name: "fails if invalid tag is supplied",
   307  			filesToCreate: []gofiles.GoFileSpec{
   308  				{
   309  					RelPath: "foo_test.go",
   310  					Src: `package foo
   311  					import "testing"
   312  					func TestFoo(t *testing.T) {
   313  						t.Errorf("fooFail")
   314  					}`,
   315  				},
   316  			},
   317  			config: unindent(`exclude:
   318  					  paths:
   319  					    - "vendor"
   320  					`),
   321  			args: []string{
   322  				"--tags", "invalid,n!otvalid",
   323  			},
   324  			wantMatch: func(currCaseTmpDir string) string {
   325  				return `invalid tags: "invalid", "n!otvalid"`
   326  			},
   327  			wantError: `invalid tags: "invalid", "n!otvalid"`,
   328  		},
   329  	} {
   330  		currCaseTmpDir, err := ioutil.TempDir(tmpDir, "")
   331  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   332  
   333  		_, err = gofiles.Write(currCaseTmpDir, currCase.filesToCreate)
   334  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   335  
   336  		configFile := path.Join(currCaseTmpDir, "config.yml")
   337  		err = ioutil.WriteFile(configFile, []byte(currCase.config), 0644)
   338  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
   339  
   340  		outBuf := bytes.Buffer{}
   341  
   342  		err = os.Chdir(currCaseTmpDir)
   343  		require.NoError(t, err)
   344  
   345  		app := gunit.App(supplier)
   346  		app.Stdout = &outBuf
   347  		app.Stderr = &outBuf
   348  
   349  		args := []string{"gunit", "--config", configFile}
   350  		args = append(args, currCase.args...)
   351  		args = append(args, "test")
   352  
   353  		runTestCode := app.Run(args)
   354  		output := outBuf.String()
   355  
   356  		if runTestCode != 0 {
   357  			if currCase.wantError == "" {
   358  				t.Fatalf("Case %d: %s\nunexpected error:\n%v\nOutput: %v", i, currCase.name, err, output)
   359  			} else if !regexp.MustCompile(currCase.wantError).MatchString(output) {
   360  				t.Fatalf("Case %d: %s\nexpected error output to contain %v, but was %v", i, currCase.name, currCase.wantError, output)
   361  			}
   362  		} else if currCase.wantError != "" {
   363  			t.Fatalf("Case %d: %s\nexpected error %v, but was none.\nOutput: %v", i, currCase.name, currCase.wantError, output)
   364  		}
   365  
   366  		expectedExpr := currCase.wantMatch(currCaseTmpDir)
   367  		if !regexp.MustCompile(expectedExpr).MatchString(output) {
   368  			t.Errorf("Case %d: %s\nOutput did not match expected expression.\nExpected:\n%v\nActual:\n%v", i, currCase.name, expectedExpr, output)
   369  		}
   370  	}
   371  }
   372  
   373  func TestClean(t *testing.T) {
   374  	wd, err := os.Getwd()
   375  	require.NoError(t, err)
   376  
   377  	tmpDir, cleanup, err := dirs.TempDir(wd, "")
   378  	defer cleanup()
   379  	require.NoError(t, err)
   380  
   381  	err = ioutil.WriteFile(path.Join(tmpDir, "tmp_placeholder_test.go"), []byte("package main"), 0644)
   382  	require.NoError(t, err)
   383  
   384  	originalWd, err := os.Getwd()
   385  	require.NoError(t, err)
   386  	defer func() {
   387  		if err := os.Chdir(originalWd); err != nil {
   388  			fmt.Printf("%+v\n", errors.Wrapf(err, "failed to restore working directory to %s", originalWd))
   389  		}
   390  	}()
   391  
   392  	err = os.Chdir(tmpDir)
   393  	require.NoError(t, err)
   394  
   395  	app := gunit.App(supplier)
   396  	exitCode := app.Run([]string{"gunit", "clean"})
   397  	require.Equal(t, 0, exitCode)
   398  
   399  	_, err = os.Stat(path.Join(tmpDir, "tmp_placeholder_test.go"))
   400  	assert.True(t, os.IsNotExist(err))
   401  }
   402  
   403  func temporaryBuildRunnerSupplier(tmpDir string, libraries map[string]string) amalgomated.CmderSupplier {
   404  	wd, err := os.Getwd()
   405  	if err != nil {
   406  		panic(err)
   407  	}
   408  
   409  	// build executables for all commands once
   410  	for currCmd, currLibrary := range libraries {
   411  		executable := path.Join(tmpDir, currCmd)
   412  		cmd := exec.Command("go", "build", "-o", executable)
   413  		cmd.Dir = path.Join(wd, currLibrary)
   414  
   415  		bytes, err := cmd.CombinedOutput()
   416  		if err != nil {
   417  			panic(fmt.Errorf("go build failed\nPath: %v\nArgs: %v\nDir: %v\nOutput: %v", cmd.Path, cmd.Args, cmd.Dir, string(bytes)))
   418  		}
   419  	}
   420  
   421  	// return supplier that points to built executables
   422  	return func(cmd amalgomated.Cmd) (amalgomated.Cmder, error) {
   423  		return amalgomated.PathCmder(path.Join(tmpDir, cmd.Name())), nil
   424  	}
   425  }
   426  
   427  func unindent(input string) string {
   428  	return strings.Replace(input, "\n\t\t\t\t\t", "\n", -1)
   429  }
   430  
   431  func pkgName(t *testing.T, path string) string {
   432  	pkgPath, err := pkgpath.NewAbsPkgPath(path).GoPathSrcRel()
   433  	require.NoError(t, err)
   434  	return pkgPath
   435  }