github.com/jdhenke/godel@v0.0.0-20161213181855-abeb3861bf0d/apps/gunit/generated_src/internal/rsc.io/gt/main.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Gt is a wrapper for ``go test'' that caches test results. 6 // 7 // The usage of ``gt'' is nearly identical to that of ``go test.'' 8 // 9 // gt [-f] [-l] [arguments for "go test"] 10 // 11 // The difference between ``gt'' and ``go test'' is that when testing 12 // a list of packages, if a package and its dependencies have not changed 13 // since the last run, ``gt'' reuses the previous result. 14 // 15 // The -f flag causes gt to treat all test results as uncached, as does the 16 // use of any ``go test'' flag other than -short and -v. 17 // 18 // The -l flag causes gt to list the uncached tests it would run. 19 // 20 // Cached test results are saved in $CACHE/go-test-cache if $CACHE is set, 21 // or else $HOME/Library/Caches/go-test-cache on OS X 22 // and $HOME/.cache/go-test-cache on other systems. 23 // It is always safe to delete these directories if they become too large. 24 // 25 // Gt is an experiment in what it would mean and how well it would work 26 // to cache test results. If the experiment proves successful, the functionality 27 // may move into the standard go command. 28 // 29 // Examples 30 // 31 // Run (and cache) the strings test: 32 // 33 // $ gt strings 34 // ok strings 0.436s 35 // $ 36 // 37 // List tests in str... without cached results: 38 // 39 // $ gt -l str... 40 // strconv 41 // $ 42 // 43 // Run str... tests: 44 // 45 // $ gt str... 46 // ok strconv 1.548s 47 // ok strings 0.436s (cached) 48 // $ 49 // 50 // Force rerun of both: 51 // 52 // $ gt -f str... 53 // ok strconv 1.795s 54 // ok strings 0.629s 55 // $ 56 // 57 package amalgomated 58 59 import ( 60 "bufio" 61 "bytes" 62 "crypto/sha1" 63 "encoding/json" 64 "fmt" 65 "io" 66 "io/ioutil" 67 "log" 68 "os" 69 "os/exec" 70 "path/filepath" 71 "regexp" 72 "runtime" 73 "strings" 74 "time" 75 ) 76 77 func usage() { 78 fmt.Fprint(os.Stderr, "usage: gt [arguments for \"go test\"]\n") 79 os.Exit(2) 80 } 81 82 var ( 83 flagV bool 84 flagShort bool 85 flagRace bool 86 flagList bool 87 flagForce bool 88 flagTiming bool 89 failed bool 90 cacheDir string 91 start = time.Now() 92 ) 93 94 func AmalgomatedMain() { 95 log.SetFlags(0) 96 log.SetPrefix("gt: ") 97 98 opts, pkgs := parseFlags() 99 if len(pkgs) == 0 { 100 if flagList { 101 log.Fatal("cannot use -l without package list or with testing flags other than -v and -short") 102 } 103 cmd := exec.Command("go", append([]string{"test"}, os.Args[1:]...)...) 104 cmd.Stdin = os.Stdin 105 cmd.Stdout = os.Stdout 106 cmd.Stderr = os.Stderr 107 err := cmd.Run() 108 if err != nil { 109 log.Fatalf("go test: %v", err) 110 } 111 return 112 } 113 114 if flagTiming { 115 log.Printf("%.2fs go list", time.Since(start).Seconds()) 116 } 117 118 // Expand pkg list. 119 out, err := exec.Command("go", append([]string{"list"}, pkgs...)...).CombinedOutput() 120 if err != nil { 121 log.Fatalf("go list: %v", err) 122 } 123 pkgs = strings.Fields(string(out)) 124 125 if flagTiming { 126 log.Printf("%.2fs go list -json", time.Since(start).Seconds()) 127 } 128 129 // Build list of all dependencies. 130 readPkgs(pkgs) 131 132 first := true 133 next := pkgs 134 for { 135 var deps []string 136 for _, path := range next { 137 p := pkgInfo[path] 138 if p.Incomplete { 139 log.Fatalf("go list: errors loading packages") 140 } 141 for _, dep := range p.Deps { 142 if _, ok := pkgInfo[dep]; !ok { 143 pkgInfo[dep] = nil 144 deps = append(deps, dep) 145 } 146 } 147 if first { 148 for _, dep := range p.TestImports { 149 if _, ok := pkgInfo[dep]; !ok { 150 pkgInfo[dep] = nil 151 deps = append(deps, dep) 152 } 153 } 154 for _, dep := range p.XTestImports { 155 if _, ok := pkgInfo[dep]; !ok { 156 pkgInfo[dep] = nil 157 deps = append(deps, dep) 158 } 159 } 160 } 161 } 162 if len(deps) == 0 { 163 break 164 } 165 166 if flagTiming { 167 log.Printf("%.2fs go list -json", time.Since(start).Seconds()) 168 } 169 readPkgs(deps) 170 next = deps 171 first = false 172 } 173 174 if env := os.Getenv("CACHE"); env != "" { 175 cacheDir = fmt.Sprintf("%s/go-test-cache", env) 176 } else if runtime.GOOS == "darwin" { 177 cacheDir = fmt.Sprintf("%s/Library/Caches/go-test-cache", os.Getenv("HOME")) 178 } else { 179 cacheDir = fmt.Sprintf("%s/.cache/go-test-cache", os.Getenv("HOME")) 180 } 181 182 if flagTiming { 183 log.Printf("%.2fs compute hashes", time.Since(start).Seconds()) 184 } 185 186 computeStale(pkgs) 187 188 var toRun []string 189 for _, pkg := range pkgs { 190 if !haveTestResult(pkg) { 191 toRun = append(toRun, pkg) 192 } 193 } 194 195 if flagTiming { 196 log.Printf("%.2fs ready to run", time.Since(start).Seconds()) 197 } 198 199 if flagList { 200 for _, pkg := range toRun { 201 fmt.Printf("%s\n", pkg) 202 } 203 return 204 } 205 206 var cmd *exec.Cmd 207 pr, pw := io.Pipe() 208 r := bufio.NewReader(pr) 209 if len(toRun) > 0 { 210 if err := os.MkdirAll(cacheDir, 0700); err != nil { 211 log.Fatal(err) 212 } 213 214 args := []string{"test"} 215 args = append(args, opts...) 216 args = append(args, toRun...) 217 cmd = exec.Command("go", args...) 218 cmd.Stdout = pw 219 cmd.Stderr = pw 220 if err := cmd.Start(); err != nil { 221 log.Fatalf("go test: %v", err) 222 } 223 } 224 225 var cmdErr error 226 done := make(chan bool) 227 go func() { 228 if cmd != nil { 229 cmdErr = cmd.Wait() 230 } 231 pw.Close() 232 done <- true 233 }() 234 235 for _, pkg := range pkgs { 236 if len(toRun) > 0 && toRun[0] == pkg { 237 readTestResult(r, pkg) 238 toRun = toRun[1:] 239 } else { 240 showTestResult(pkg) 241 } 242 } 243 244 io.Copy(os.Stdout, r) 245 246 <-done 247 if cmdErr != nil && !failed { 248 log.Fatalf("go test: %v", cmdErr) 249 } 250 251 if flagTiming { 252 log.Printf("%.2fs done", time.Since(start).Seconds()) 253 } 254 255 if failed { 256 os.Exit(1) 257 } 258 } 259 260 var ( 261 pkgInfo = map[string]*Package{} 262 outOfSync bool 263 ) 264 265 type Package struct { 266 Dir string 267 ImportPath string 268 Standard bool 269 Goroot bool 270 Stale bool 271 GoFiles []string 272 CgoFiles []string 273 CFiles []string 274 CXXFiles []string 275 MFiles []string 276 HFiles []string 277 SFiles []string 278 SwigFiles []string 279 SwigCXXFiles []string 280 SysoFiles []string 281 Imports []string 282 Deps []string 283 Incomplete bool 284 TestGoFiles []string 285 TestImports []string 286 XTestGoFiles []string 287 XTestImports []string 288 289 testHash string 290 pkgHash string 291 } 292 293 func readPkgs(pkgs []string) { 294 out, err := exec.Command("go", append([]string{"list", "-json"}, pkgs...)...).CombinedOutput() 295 if err != nil { 296 log.Fatalf("go list: %v\n%s", err, out) 297 } 298 299 dec := json.NewDecoder(bytes.NewReader(out)) 300 for { 301 var p Package 302 if err := dec.Decode(&p); err != nil { 303 if err == io.EOF { 304 break 305 } 306 log.Fatalf("reading go list output: %v", err) 307 } 308 pkgInfo[p.ImportPath] = &p 309 } 310 } 311 312 func computeStale(pkgs []string) { 313 for _, pkg := range pkgs { 314 computeTestHash(pkgInfo[pkg]) 315 } 316 } 317 318 func computeTestHash(p *Package) { 319 if p.testHash != "" { 320 return 321 } 322 p.testHash = "cycle" 323 computePkgHash(p) 324 h := sha1.New() 325 fmt.Fprintf(h, "test\n") 326 if flagRace { 327 fmt.Fprintf(h, "-race\n") 328 } 329 if flagShort { 330 fmt.Fprintf(h, "-short\n") 331 } 332 if flagV { 333 fmt.Fprintf(h, "-v\n") 334 } 335 fmt.Fprintf(h, "pkg %s\n", p.pkgHash) 336 for _, imp := range p.TestImports { 337 p1 := pkgInfo[imp] 338 computePkgHash(p1) 339 fmt.Fprintf(h, "testimport %s\n", p1.pkgHash) 340 } 341 for _, imp := range p.XTestImports { 342 p1 := pkgInfo[imp] 343 computePkgHash(p1) 344 fmt.Fprintf(h, "xtestimport %s\n", p1.pkgHash) 345 } 346 hashFiles(h, p.Dir, p.TestGoFiles) 347 hashFiles(h, p.Dir, p.XTestGoFiles) 348 p.testHash = fmt.Sprintf("%x", h.Sum(nil)) 349 } 350 351 func computePkgHash(p *Package) { 352 if p.pkgHash != "" { 353 return 354 } 355 p.pkgHash = "cycle" 356 h := sha1.New() 357 fmt.Fprintf(h, "pkg\n") 358 for _, imp := range p.Deps { 359 p1 := pkgInfo[imp] 360 if p1 == nil { 361 log.Fatalf("lost package: %v for %v", imp, p.ImportPath) 362 } 363 computePkgHash(p1) 364 fmt.Fprintf(h, "import %s\n", p1.pkgHash) 365 } 366 hashFiles(h, p.Dir, p.GoFiles) 367 hashFiles(h, p.Dir, p.CgoFiles) 368 hashFiles(h, p.Dir, p.CFiles) 369 hashFiles(h, p.Dir, p.CXXFiles) 370 hashFiles(h, p.Dir, p.MFiles) 371 hashFiles(h, p.Dir, p.HFiles) 372 hashFiles(h, p.Dir, p.SFiles) 373 hashFiles(h, p.Dir, p.SwigFiles) 374 hashFiles(h, p.Dir, p.SwigCXXFiles) 375 hashFiles(h, p.Dir, p.SysoFiles) 376 377 p.pkgHash = fmt.Sprintf("%x", h.Sum(nil)) 378 } 379 380 func hashFiles(h io.Writer, dir string, files []string) { 381 for _, file := range files { 382 f, err := os.Open(filepath.Join(dir, file)) 383 if err != nil { 384 fmt.Fprintf(h, "%s error\n", file) 385 continue 386 } 387 fmt.Fprintf(h, "file %s\n", file) 388 n, _ := io.Copy(h, f) 389 fmt.Fprintf(h, "%d bytes\n", n) 390 f.Close() 391 } 392 } 393 394 func cacheFile(p *Package) string { 395 return filepath.Join(cacheDir, fmt.Sprintf("%s/%s.test", p.testHash[:3], p.testHash[3:])) 396 } 397 398 func haveTestResult(path string) bool { 399 if flagForce { 400 return false 401 } 402 p := pkgInfo[path] 403 if p.testHash == "cycle" { 404 return false 405 } 406 fi, err := os.Stat(cacheFile(pkgInfo[path])) 407 return err == nil && fi.Mode().IsRegular() 408 } 409 410 var fail = []byte("FAIL") 411 412 func showTestResult(path string) { 413 p := pkgInfo[path] 414 if p.testHash == "cycle" { 415 return 416 } 417 data, err := ioutil.ReadFile(cacheFile(pkgInfo[path])) 418 if err != nil { 419 fmt.Printf("%v\n", err) 420 fmt.Printf("FAIL\t%s\t(cached)\n", path) 421 return 422 } 423 os.Stdout.Write(data) 424 data = bytes.TrimSpace(data) 425 i := bytes.LastIndex(data, []byte{'\n'}) 426 line := data[i+1:] 427 if bytes.HasPrefix(line, fail) { 428 failed = true 429 } 430 } 431 432 var endRE = regexp.MustCompile(`\A(\?|ok|FAIL) ? ? ?\t([^ \t]+)\t([0-9.]+s|\[.*\])\n\z`) 433 434 func readTestResult(r *bufio.Reader, path string) { 435 var buf bytes.Buffer 436 for { 437 line, err := r.ReadString('\n') 438 os.Stdout.WriteString(line) 439 if err != nil { 440 log.Fatalf("reading test output for %s: %v", path, err) 441 } 442 if outOfSync { 443 continue 444 } 445 m := endRE.FindStringSubmatch(line) 446 if m == nil { 447 buf.WriteString(line) 448 continue 449 } 450 451 if m[1] == "FAIL" { 452 failed = true 453 } 454 455 fmt.Fprintf(&buf, "%s (cached)\n", strings.TrimSuffix(line, "\n")) 456 file := cacheFile(pkgInfo[path]) 457 if err := os.MkdirAll(filepath.Dir(file), 0700); err != nil { 458 log.Print(err) 459 } else if err := ioutil.WriteFile(file, buf.Bytes(), 0600); err != nil { 460 log.Print(err) 461 } 462 463 break 464 } 465 } 466 467 func parseFlags() (opts, pkgs []string) { 468 donePkgs := false 469 for i := 1; i < len(os.Args); i++ { 470 arg := os.Args[i] 471 if !strings.HasPrefix(arg, "-") { 472 if donePkgs { 473 // additional arguments after pkg list ended 474 return nil, nil 475 } 476 pkgs = append(pkgs, arg) 477 continue 478 } 479 donePkgs = len(pkgs) > 0 480 if strings.HasPrefix(arg, "--") && arg != "--" && !strings.HasPrefix(arg, "---") { 481 arg = arg[1:] 482 } 483 if arg == "-gt.timing" { 484 flagTiming = true 485 continue 486 } 487 if arg == "-v" { 488 flagV = true 489 opts = append(opts, arg) 490 donePkgs = len(pkgs) > 0 491 continue 492 } 493 if arg == "-race" { 494 flagRace = true 495 opts = append(opts, arg) 496 continue 497 } 498 if arg == "-short" { 499 flagShort = true 500 opts = append(opts, arg) 501 continue 502 } 503 if arg == "-f" { 504 flagForce = true 505 continue 506 } 507 if arg == "-l" { 508 flagList = true 509 continue 510 } 511 // unrecognized flag 512 return nil, nil 513 } 514 return opts, pkgs 515 }