github.com/aca02djr/gb@v0.4.1/test/test.go (about)

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