From 228e3a805a0579a328589d6810bc1edd03f2299f Mon Sep 17 00:00:00 2001 From: Alexey Soshin Date: Fri, 21 Jul 2017 21:04:55 +0200 Subject: [PATCH] Handle -outputdir flag (#364) * Handle -coverprofile flag Fixes #346 * Add tool for easier contribution * Cast byte[], otherwise failure will be displayed as array of bytes, not very useful * Fix combine not finding files if coverProfile was set * Add test for recursive coverage * Fix test imports to point to correct subpackages * Refactor casts to be semantic functions * Add test for cover profile in parallel mode * Add failing test for outputdir case * Implement outputdir * Add test for moving files to output dir --- ginkgo/run_command.go | 75 ++++++++++++++++++++++++++++++++ ginkgo/testrunner/test_runner.go | 63 ++++++++++++++++++++------- integration/coverage_test.go | 53 +++++++++++++++++++++- 3 files changed, 174 insertions(+), 17 deletions(-) diff --git a/ginkgo/run_command.go b/ginkgo/run_command.go index d1483f602..2fb4ab253 100644 --- a/ginkgo/run_command.go +++ b/ginkgo/run_command.go @@ -11,6 +11,8 @@ import ( "github.com/onsi/ginkgo/ginkgo/interrupthandler" "github.com/onsi/ginkgo/ginkgo/testrunner" "github.com/onsi/ginkgo/types" + "io/ioutil" + "path/filepath" ) func BuildRunCommand() *Command { @@ -100,6 +102,18 @@ func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) { runResult, numSuites = r.suiteRunner.RunSuites(randomizedRunners, r.commandFlags.NumCompilers, r.commandFlags.KeepGoing, nil) } + if r.isInCoverageMode() { + if r.getOutputDir() != "" { + // If coverprofile is set, combine coverages + if r.getCoverprofile() != "" { + r.combineCoverprofiles(runners) + } else { + // Just move them + r.moveCoverprofiles(runners) + } + } + } + for _, runner := range runners { runner.CleanUp() } @@ -121,6 +135,67 @@ func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) { } } +// Moves all generated profiles to specified directory +func (r *SpecRunner) moveCoverprofiles(runners []*testrunner.TestRunner) { + for _, runner := range runners { + _, filename := filepath.Split(runner.CoverageFile) + err := os.Rename(runner.CoverageFile, filepath.Join(r.getOutputDir(), filename)) + + if err != nil { + fmt.Printf("Unable to move coverprofile %s, %v\n", runner.CoverageFile, err) + return + } + } +} + +// Combines all generated profiles in the specified directory +func (r *SpecRunner) combineCoverprofiles(runners []*testrunner.TestRunner) { + + path, _ := filepath.Abs(r.getOutputDir()) + + fmt.Println("path is " + path) + os.MkdirAll(path, os.ModePerm) + + combined, err := os.OpenFile(filepath.Join(path, r.getCoverprofile()), + os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) + + if err != nil { + fmt.Printf("Unable to create combined profile, %v\n", err) + return + } + + for _, runner := range runners { + contents, err := ioutil.ReadFile(runner.CoverageFile) + + if err != nil { + fmt.Printf("Unable to read coverage file %s to combine, %v\n", runner.CoverageFile, err) + return + } + + _, err = combined.Write(contents) + + if err != nil { + fmt.Printf("Unable to append to coverprofile, %v\n", err) + return + } + } + + fmt.Println("All profiles combined") +} + +func (r *SpecRunner) isInCoverageMode() bool { + opts := r.commandFlags.GoOpts + return *opts["cover"].(*bool) || *opts["coverpkg"].(*string) != "" || *opts["covermode"].(*string) != "" +} + +func (r *SpecRunner) getCoverprofile() string { + return *r.commandFlags.GoOpts["coverprofile"].(*string) +} + +func (r *SpecRunner) getOutputDir() string { + return *r.commandFlags.GoOpts["outputdir"].(*string) +} + func (r *SpecRunner) ComputeSuccinctMode(numSuites int) { if config.DefaultReporterConfig.Verbose { config.DefaultReporterConfig.Succinct = false diff --git a/ginkgo/testrunner/test_runner.go b/ginkgo/testrunner/test_runner.go index b8c744036..e12913668 100644 --- a/ginkgo/testrunner/test_runner.go +++ b/ginkgo/testrunner/test_runner.go @@ -32,6 +32,8 @@ type TestRunner struct { goOpts map[string]interface{} additionalArgs []string stderr *bytes.Buffer + + CoverageFile string } func New(suite testsuite.TestSuite, numCPU int, parallelStream bool, timeout time.Duration, goOpts map[string]interface{}, additionalArgs []string) *TestRunner { @@ -63,10 +65,10 @@ func (t *TestRunner) Compile() error { func (t *TestRunner) BuildArgs(path string) []string { args := []string{"test", "-c", "-i", "-o", path, t.Suite.Path} - if *t.goOpts["covermode"].(*string) != "" { - args = append(args, "-cover", fmt.Sprintf("-covermode=%s", *t.goOpts["covermode"].(*string))) + if t.getCoverMode() != "" { + args = append(args, "-cover", fmt.Sprintf("-covermode=%s", t.getCoverMode())) } else { - if *t.goOpts["cover"].(*bool) || *t.goOpts["coverpkg"].(*string) != "" { + if t.shouldCover() || t.getCoverPackage() != "" { args = append(args, "-cover", "-covermode=atomic") } } @@ -298,7 +300,7 @@ func (t *TestRunner) runAndStreamParallelGinkgoSuite() RunResult { os.Stdout.Sync() - if *t.goOpts["cover"].(*bool) || *t.goOpts["coverpkg"].(*string) != "" || *t.goOpts["covermode"].(*string) != "" { + if t.shouldCombineCoverprofiles() { t.combineCoverprofiles() } @@ -380,7 +382,7 @@ func (t *TestRunner) runParallelGinkgoSuite() RunResult { os.Stdout.Sync() } - if *t.goOpts["cover"].(*bool) || *t.goOpts["coverpkg"].(*string) != "" || *t.goOpts["covermode"].(*string) != "" { + if t.shouldCombineCoverprofiles() { t.combineCoverprofiles() } @@ -392,21 +394,24 @@ const CoverProfileSuffix = ".coverprofile" func (t *TestRunner) cmd(ginkgoArgs []string, stream io.Writer, node int) *exec.Cmd { args := []string{"--test.timeout=" + t.timeout.String()} - coverMode := *t.goOpts["covermode"].(*string) - coverPackage := *t.goOpts["coverpkg"].(*string) - cover := *t.goOpts["cover"].(*bool) - coverProfile := *t.goOpts["coverprofile"].(*string) + coverProfile := t.getCoverProfile() + + if t.shouldCombineCoverprofiles() { - if cover || coverPackage != "" || coverMode != "" { testCoverProfile := "--test.coverprofile=" + coverageFile := "" // Set default name for coverage results if coverProfile == "" { - testCoverProfile += t.Suite.PackageName + CoverProfileSuffix + coverageFile = t.Suite.PackageName + CoverProfileSuffix } else { - testCoverProfile += coverProfile + coverageFile = coverProfile } + testCoverProfile += coverageFile + + t.CoverageFile = filepath.Join(t.Suite.Path, coverageFile) + if t.numCPU > 1 { testCoverProfile = fmt.Sprintf("%s.%d", testCoverProfile, node) } @@ -430,6 +435,30 @@ func (t *TestRunner) cmd(ginkgoArgs []string, stream io.Writer, node int) *exec. return cmd } +func (t *TestRunner) shouldCover() bool { + return *t.goOpts["cover"].(*bool) +} + +func (t *TestRunner) shouldRequireSuite() bool { + return *t.goOpts["requireSuite"].(*bool) +} + +func (t *TestRunner) getCoverProfile() string { + return *t.goOpts["coverprofile"].(*string) +} + +func (t *TestRunner) getCoverPackage() string { + return *t.goOpts["coverpkg"].(*string) +} + +func (t *TestRunner) getCoverMode() string { + return *t.goOpts["covermode"].(*string) +} + +func (t *TestRunner) shouldCombineCoverprofiles() bool { + return t.shouldCover() || t.getCoverPackage() != "" || t.getCoverMode() != "" +} + func (t *TestRunner) run(cmd *exec.Cmd, completions chan RunResult) RunResult { var res RunResult @@ -452,7 +481,7 @@ func (t *TestRunner) run(cmd *exec.Cmd, completions chan RunResult) RunResult { res.HasProgrammaticFocus = (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) if strings.Contains(t.stderr.String(), "warning: no tests to run") { - if *t.goOpts["requireSuite"].(*bool) { + if t.shouldRequireSuite() { res.Passed = false } fmt.Fprintf(os.Stderr, `Found no test suites, did you forget to run "ginkgo bootstrap"?`) @@ -464,7 +493,7 @@ func (t *TestRunner) run(cmd *exec.Cmd, completions chan RunResult) RunResult { func (t *TestRunner) combineCoverprofiles() { profiles := []string{} - coverProfile := *t.goOpts["coverprofile"].(*string) + coverProfile := t.getCoverProfile() for cpu := 1; cpu <= t.numCPU; cpu++ { var coverFile string @@ -518,6 +547,8 @@ func (t *TestRunner) combineCoverprofiles() { finalFilename = fmt.Sprintf("%s%s", t.Suite.PackageName, CoverProfileSuffix) } - ioutil.WriteFile(filepath.Join(t.Suite.Path, finalFilename), - []byte(finalOutput), 0666) + coverageFilepath := filepath.Join(t.Suite.Path, finalFilename) + ioutil.WriteFile(coverageFilepath, []byte(finalOutput), 0666) + + t.CoverageFile = coverageFilepath } diff --git a/integration/coverage_test.go b/integration/coverage_test.go index 5cde59073..76385282c 100644 --- a/integration/coverage_test.go +++ b/integration/coverage_test.go @@ -4,10 +4,10 @@ import ( "os" "os/exec" + "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" - "fmt" ) var _ = Describe("Coverage Specs", func() { @@ -97,4 +97,55 @@ var _ = Describe("Coverage Specs", func() { // Cleanup os.RemoveAll(coverFile) }) + + It("Appends coverages if output dir and coverprofile were set", func() { + session := startGinkgo("./_fixtures/combined_coverage_fixture", "-outputdir=./", "-r", "-cover", "-coverprofile=coverage.txt") + + Eventually(session).Should(gexec.Exit(0)) + + _, err := os.Stat("./_fixtures/combined_coverage_fixture/coverage.txt") + + Ω(err).ShouldNot(HaveOccurred()) + + // Cleanup + os.RemoveAll("./_fixtures/combined_coverage_fixture/coverage.txt") + }) + + It("Creates directories in path if they don't exist", func() { + session := startGinkgo("./_fixtures/combined_coverage_fixture", "-outputdir=./all/profiles/here", "-r", "-cover", "-coverprofile=coverage.txt") + + defer os.RemoveAll("./_fixtures/combined_coverage_fixture/all") + defer os.RemoveAll("./_fixtures/combined_coverage_fixture/coverage.txt") + + Eventually(session).Should(gexec.Exit(0)) + + _, err := os.Stat("./_fixtures/combined_coverage_fixture/all/profiles/here/coverage.txt") + + Ω(err).ShouldNot(HaveOccurred()) + }) + + It("Moves coverages if only output dir was set", func() { + session := startGinkgo("./_fixtures/combined_coverage_fixture", "-outputdir=./", "-r", "-cover") + + Eventually(session).Should(gexec.Exit(0)) + + packages := []string{"first_package", "second_package"} + + for _, p := range packages { + coverFile := fmt.Sprintf("./_fixtures/combined_coverage_fixture/%s.coverprofile", p) + + // Cleanup + defer func (f string) { + os.RemoveAll(f) + } (coverFile) + + defer func (f string) { + os.RemoveAll(fmt.Sprintf("./_fixtures/combined_coverage_fixture/%s/coverage.txt", f)) + } (p) + + _, err := os.Stat(coverFile) + + Ω(err).ShouldNot(HaveOccurred()) + } + }) })