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 }