// Run implements Check. func (c *Coverage) Run(change scm.Change, options *Options) error { profile, err := c.RunProfile(change, options) if err != nil { return err } if c.UseGlobalInference { out, err := ProcessProfile(profile, &c.Global) if out != "" { log.Printf("coverage for %s:\n%s\n", change.Repo().Root(), out) } if err != nil { return fmt.Errorf("coverage for %s: %s", change.Repo().Root(), err) } } else { for _, testPkg := range change.Indirect().TestPackages() { p := profile.Subset(pkgToDir(testPkg)) settings := c.SettingsForPkg(testPkg) if settings.MinCoverage == 0 { continue } out, err := ProcessProfile(p, settings) if out != "" { log.Printf("%s:\n%s\n", testPkg, out) } if err != nil { return fmt.Errorf("coverage for %s: %s", testPkg, err) } } } return nil }
// RunProfile runs a coverage run according to the settings and return results. func (c *Coverage) RunProfile(change scm.Change, options *Options) (profile CoverageProfile, err error) { // go test accepts packages, not files. var testPkgs []string if c.UseGlobalInference { testPkgs = change.All().TestPackages() } else { testPkgs = change.Indirect().TestPackages() } if len(testPkgs) == 0 { // Sir, there's no test. return nil, nil } tmpDir, err2 := ioutil.TempDir("", "pre-commit-go") if err2 != nil { return nil, err2 } defer func() { err2 := internal.RemoveAll(tmpDir) if err == nil { err = err2 } }() if c.UseGlobalInference { profile, err = c.RunGlobal(change, options, tmpDir) } else { profile, err = c.RunLocal(change, options, tmpDir) } if err != nil { return nil, err } if c.isGoverallsEnabled() { // Please send a pull request if the following doesn't work for you on your // favorite CI system. out, _, _, err2 := options.Capture(change.Repo(), "goveralls", "-coverprofile", filepath.Join(tmpDir, "profile.cov")) // Don't fail the build. if err2 != nil { fmt.Printf("%s", out) } } return profile, nil }
// RunLocal runs all tests and reports the merged coverage of each individual // covered package. func (c *Coverage) RunLocal(change scm.Change, options *Options, tmpDir string) (CoverageProfile, error) { testPkgs := change.Indirect().TestPackages() type result struct { file string err error } results := make(chan *result) for i, tp := range testPkgs { go func(index int, testPkg string) { settings := c.SettingsForPkg(testPkg) // Skip coverage if disabled for this directory. if settings.MinCoverage == 0 { results <- nil return } p := filepath.Join(tmpDir, fmt.Sprintf("test%d.cov", index)) args := []string{ "go", "test", "-v", "-covermode=count", "-coverprofile", p, "-timeout", fmt.Sprintf("%ds", options.MaxDuration), testPkg, } out, exitCode, duration, _ := options.Capture(change.Repo(), args...) if duration > time.Second { log.Printf("%s was slow: %s", args, round(duration, time.Millisecond)) } if exitCode != 0 { results <- &result{err: fmt.Errorf("%s %s failed:\n%s", strings.Join(args, " "), testPkg, processStackTrace(out))} return } results <- &result{file: p} }(i, tp) } // Sends to coveralls.io if applicable. Do not write to disk unless needed. var f readWriteSeekCloser var err error if c.isGoverallsEnabled() { if f, err = os.Create(filepath.Join(tmpDir, "profile.cov")); err != nil { return nil, err } } else { f = &buffer{} } // Aggregate all results. counts := map[string]int{} for i := 0; i < len(testPkgs); i++ { result := <-results if err != nil { continue } if result == nil { continue } if result.err != nil { err = result.err continue } if err2 := loadRawCoverage(result.file, counts); err == nil { // Wait for all tests to complete before returning. err = err2 } } if err != nil { f.Close() return nil, err } return loadMergeAndClose(f, counts, change) }
// RunGlobal runs the tests under coverage with global inference. // // This means that test can contribute coverage in any other package, even // outside their own package. func (c *Coverage) RunGlobal(change scm.Change, options *Options, tmpDir string) (CoverageProfile, error) { coverPkg := "" for i, p := range change.All().Packages() { if s := c.SettingsForPkg(p); s.MinCoverage != 0 { if i != 0 { coverPkg += "," } coverPkg += p } } // This part is similar to Test.Run() except that it passes a unique // -coverprofile file name, so that all the files can later be merged into a // single file. testPkgs := change.All().TestPackages() type result struct { file string err error } results := make(chan *result) for index, tp := range testPkgs { f := filepath.Join(tmpDir, fmt.Sprintf("test%d.cov", index)) go func(f string, testPkg string) { // Maybe fallback to 'pkg + "/..."' and post process to remove // uninteresting directories. The rationale is that it will eventually // blow up the OS specific command argument length. args := []string{ "go", "test", "-v", "-covermode=count", "-coverpkg", coverPkg, "-coverprofile", f, "-timeout", fmt.Sprintf("%ds", options.MaxDuration), testPkg, } out, exitCode, duration, err := options.Capture(change.Repo(), args...) if duration > time.Second { log.Printf("%s was slow: %s", args, round(duration, time.Millisecond)) } if exitCode != 0 { err = fmt.Errorf("%s %s failed:\n%s", strings.Join(args, " "), testPkg, processStackTrace(out)) } results <- &result{f, err} }(f, tp) } // Sends to coveralls.io if applicable. Do not write to disk unless needed. var f readWriteSeekCloser var err error if c.isGoverallsEnabled() { if f, err = os.Create(filepath.Join(tmpDir, "profile.cov")); err != nil { return nil, err } } else { f = &buffer{} } // Aggregate all results. counts := map[string]int{} for i := 0; i < len(testPkgs); i++ { result := <-results if err != nil { continue } if result.err != nil { err = result.err continue } if err2 := loadRawCoverage(result.file, counts); err == nil { // Wait for all tests to complete before returning. err = err2 } } if err != nil { f.Close() return nil, err } return loadMergeAndClose(f, counts, change) }