github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/test/test.go (about)

     1  package test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/build"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/constabulary/gb"
    16  	"github.com/constabulary/gb/internal/debug"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // Test returns a Target representing the result of compiling the
    21  // package pkg, and its dependencies, and linking it with the
    22  // test runner.
    23  func Test(flags []string, pkgs ...*gb.Package) error {
    24  	test, err := TestPackages(flags, pkgs...)
    25  	if err != nil {
    26  		return err
    27  	}
    28  	return gb.Execute(test)
    29  }
    30  
    31  // TestPackages produces a graph of Actions that when executed build
    32  // and test the supplied packages.
    33  func TestPackages(flags []string, pkgs ...*gb.Package) (*gb.Action, error) {
    34  	if len(pkgs) < 1 {
    35  		return nil, errors.New("no test packages provided")
    36  	}
    37  	targets := make(map[string]*gb.Action) // maps package import paths to their test run action
    38  
    39  	names := func(pkgs []*gb.Package) []string {
    40  		var names []string
    41  		for _, pkg := range pkgs {
    42  			names = append(names, pkg.ImportPath)
    43  		}
    44  		return names
    45  	}
    46  
    47  	// create top level test action to root all test actions
    48  	t0 := time.Now()
    49  	test := gb.Action{
    50  		Name: fmt.Sprintf("test: %s", strings.Join(names(pkgs), ",")),
    51  		Run: func() error {
    52  			debug.Debugf("test duration: %v %v", time.Since(t0), pkgs[0].Statistics.String())
    53  			return nil
    54  		},
    55  	}
    56  
    57  	for _, pkg := range pkgs {
    58  		a, err := TestPackage(targets, pkg, flags)
    59  		if err != nil {
    60  			return nil, err
    61  		}
    62  		if a == nil {
    63  			// nothing to do ?? not even a test action ?
    64  			continue
    65  		}
    66  		test.Deps = append(test.Deps, a)
    67  	}
    68  	return &test, nil
    69  }
    70  
    71  // TestPackage returns an Action representing the steps required to build
    72  // and test this Package.
    73  func TestPackage(targets map[string]*gb.Action, pkg *gb.Package, flags []string) (*gb.Action, error) {
    74  	debug.Debugf("TestPackage: %s, flags: %s", pkg.ImportPath, flags)
    75  	var gofiles []string
    76  	gofiles = append(gofiles, pkg.GoFiles...)
    77  	gofiles = append(gofiles, pkg.TestGoFiles...)
    78  
    79  	var cgofiles []string
    80  	cgofiles = append(cgofiles, pkg.CgoFiles...)
    81  
    82  	var imports []string
    83  	imports = append(imports, pkg.Package.Imports...)
    84  	imports = append(imports, pkg.Package.TestImports...)
    85  
    86  	name := pkg.Name
    87  	if name == "main" {
    88  		// rename the main package to its package name for testing.
    89  		name = filepath.Base(filepath.FromSlash(pkg.ImportPath))
    90  	}
    91  
    92  	// internal tests
    93  	testpkg, err := pkg.NewPackage(&build.Package{
    94  		Name:       name,
    95  		ImportPath: pkg.ImportPath,
    96  		SrcRoot:    pkg.SrcRoot,
    97  
    98  		GoFiles:      gofiles,
    99  		CFiles:       pkg.CFiles,
   100  		CgoFiles:     cgofiles,
   101  		TestGoFiles:  pkg.TestGoFiles,  // passed directly to buildTestMain
   102  		XTestGoFiles: pkg.XTestGoFiles, // passed directly to buildTestMain
   103  
   104  		CgoCFLAGS:    pkg.CgoCFLAGS,
   105  		CgoCPPFLAGS:  pkg.CgoCPPFLAGS,
   106  		CgoCXXFLAGS:  pkg.CgoCXXFLAGS,
   107  		CgoLDFLAGS:   pkg.CgoLDFLAGS,
   108  		CgoPkgConfig: pkg.CgoPkgConfig,
   109  
   110  		Imports: imports,
   111  		Dir:     pkg.Dir,
   112  	})
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	testpkg.TestScope = true
   117  
   118  	// only build the internal test if there is Go source or
   119  	// internal test files.
   120  	var testobj *gb.Action
   121  	if len(testpkg.GoFiles)+len(testpkg.CgoFiles)+len(testpkg.TestGoFiles) > 0 {
   122  
   123  		// build internal testpkg dependencies
   124  		deps, err := gb.BuildDependencies(targets, testpkg)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  
   129  		testobj, err = gb.Compile(testpkg, deps...)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  
   135  	// external tests
   136  	if len(pkg.XTestGoFiles) > 0 {
   137  		xtestpkg, err := pkg.NewPackage(&build.Package{
   138  			Name:       name,
   139  			ImportPath: pkg.ImportPath + "_test",
   140  			GoFiles:    pkg.XTestGoFiles,
   141  			Imports:    pkg.XTestImports,
   142  			Dir:        pkg.Dir,
   143  		})
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  
   148  		// build external test dependencies
   149  		deps, err := gb.BuildDependencies(targets, xtestpkg)
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  		xtestpkg.TestScope = true
   154  
   155  		// if there is an internal test object, add it as a dependency.
   156  		if testobj != nil {
   157  			deps = append(deps, testobj)
   158  		}
   159  		testobj, err = gb.Compile(xtestpkg, deps...)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  	}
   164  
   165  	testmainpkg, err := buildTestMain(testpkg)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	testmain, err := gb.Compile(testmainpkg, testobj)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	return &gb.Action{
   175  		Name: fmt.Sprintf("run: %s", testmainpkg.Binfile()),
   176  		Deps: testmain.Deps,
   177  		Run: func() error {
   178  			// When used with the concurrent executor, building deps and
   179  			// linking the test binary can cause a lot of disk space to be
   180  			// pinned as linking will tend to occur more frequenty than retiring
   181  			// tests.
   182  			//
   183  			// To solve this, we merge the testmain compile step (which includes
   184  			// linking) and the test run and cleanup steps so they are executed
   185  			// as one atomic operation.
   186  			var output bytes.Buffer
   187  			err := testmain.Run() // compile and link
   188  			if err == nil {
   189  				// nope mode means we stop at the compile and link phase.
   190  				if !pkg.Nope {
   191  					cmd := exec.Command(testmainpkg.Binfile(), flags...)
   192  					cmd.Dir = pkg.Dir // tests run in the original source directory
   193  					cmd.Stdout = &output
   194  					cmd.Stderr = &output
   195  					debug.Debugf("%s", cmd.Args)
   196  					err = cmd.Run()                         // run test
   197  					err = errors.Wrapf(err, "%s", cmd.Args) // wrap error if failed
   198  				}
   199  
   200  				// test binaries can be very large, so always unlink the
   201  				// binary after the test has run to free up temporary space
   202  				// technically this is done by ctx.Destroy(), but freeing
   203  				// the space earlier is important for projects with many
   204  				// packages
   205  				os.Remove(testmainpkg.Binfile())
   206  			}
   207  
   208  			if err != nil {
   209  				fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
   210  			} else {
   211  				fmt.Println(pkg.ImportPath)
   212  			}
   213  			if err != nil || pkg.Verbose {
   214  				io.Copy(os.Stdout, &output)
   215  			}
   216  			return err
   217  		},
   218  	}, nil
   219  }
   220  
   221  func buildTestMain(pkg *gb.Package) (*gb.Package, error) {
   222  	if !pkg.TestScope {
   223  		return nil, errors.Errorf("package %q is not test scoped", pkg.Name)
   224  	}
   225  	dir := pkg.Workdir()
   226  	if err := mkdir(dir); err != nil {
   227  		return nil, err
   228  	}
   229  	tests, err := loadTestFuncs(pkg.Package)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	if len(pkg.Package.XTestGoFiles) > 0 {
   234  		// if there are external tests ensure that we import the
   235  		// test package into the final binary for side effects.
   236  		tests.ImportXtest = true
   237  	}
   238  	if err := writeTestmain(filepath.Join(dir, "_testmain.go"), tests); err != nil {
   239  		return nil, err
   240  	}
   241  	testmain, err := pkg.NewPackage(&build.Package{
   242  		Name:       pkg.Name,
   243  		ImportPath: path.Join(pkg.ImportPath, "testmain"),
   244  		SrcRoot:    pkg.SrcRoot,
   245  
   246  		GoFiles: []string{"_testmain.go"},
   247  
   248  		Imports: pkg.Package.Imports,
   249  		Dir:     dir,
   250  	})
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	if testmain.NotStale {
   255  		panic("testmain not marked stale")
   256  	}
   257  	testmain.TestScope = true
   258  	testmain.Main = true
   259  	return testmain, nil
   260  }
   261  
   262  func mkdir(path string) error {
   263  	return errors.Wrap(os.MkdirAll(path, 0755), "mkdir")
   264  }