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 }