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  }