diff --git a/cmd/flags.go b/cmd/flags.go index a5d811bc..dc45a99d 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -132,3 +132,11 @@ func (s *stringSlice) Set(raw string) error { func (s *stringSlice) Type() string { return "list" } + +func truthyFlag(s string) bool { + switch strings.ToLower(s) { + case "true", "yes", "1": + return true + } + return false +} diff --git a/cmd/handler.go b/cmd/handler.go index 135eaded..440fd4d5 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -96,6 +96,7 @@ func writeJUnitFile(opts *options, execution *testjson.Execution) error { ProjectName: opts.junitProjectName, FormatTestSuiteName: opts.junitTestSuiteNameFormat.Value(), FormatTestCaseClassname: opts.junitTestCaseClassnameFormat.Value(), + HideEmptyPackages: opts.junitHideEmptyPackages, }) } diff --git a/cmd/main.go b/cmd/main.go index bfc1d017..8eacaa02 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -93,6 +93,9 @@ func setupFlags(name string) (*pflag.FlagSet, *options) { flags.StringVar(&opts.junitProjectName, "junitfile-project-name", lookEnvWithDefault("GOTESTSUM_JUNITFILE_PROJECT_NAME", ""), "name of the project used in the junit.xml file") + flags.BoolVar(&opts.junitHideEmptyPackages, "junitfile-hide-empty-pkg", + truthyFlag(lookEnvWithDefault("GOTESTSUM_JUNIT_HIDE_EMPTY_PKG", "")), + "omit packages with no tests from the junit.xml file") flags.IntVar(&opts.rerunFailsMaxAttempts, "rerun-fails", 0, "rerun failed tests until they all pass, or attempts exceeds maximum. Defaults to max 2 reruns when enabled.") @@ -160,6 +163,7 @@ type options struct { junitTestSuiteNameFormat *junitFieldFormatValue junitTestCaseClassnameFormat *junitFieldFormatValue junitProjectName string + junitHideEmptyPackages bool rerunFailsMaxAttempts int rerunFailsMaxInitialFailures int rerunFailsReportFile string diff --git a/cmd/testdata/gotestsum-help-text b/cmd/testdata/gotestsum-help-text index e220b941..83c98230 100644 --- a/cmd/testdata/gotestsum-help-text +++ b/cmd/testdata/gotestsum-help-text @@ -11,6 +11,7 @@ Flags: --hide-summary summary hide sections of the summary: skipped,failed,errors,output (default none) --jsonfile string write all TestEvents to file --junitfile string write a JUnit XML file + --junitfile-hide-empty-pkg omit packages with no tests from the junit.xml file --junitfile-project-name string name of the project used in the junit.xml file --junitfile-testcase-classname field-format format the testcase classname field as: full, relative, short (default full) --junitfile-testsuite-name field-format format the testsuite name field as: full, relative, short (default full) diff --git a/internal/junitxml/report.go b/internal/junitxml/report.go index cd23b1f4..f821e2e0 100644 --- a/internal/junitxml/report.go +++ b/internal/junitxml/report.go @@ -72,6 +72,7 @@ type Config struct { ProjectName string FormatTestSuiteName FormatFunc FormatTestCaseClassname FormatFunc + HideEmptyPackages bool // This is used for tests to have a consistent timestamp customTimestamp string customElapsed string @@ -104,6 +105,9 @@ func generate(exec *testjson.Execution, cfg Config) JUnitTestSuites { } for _, pkgname := range exec.Packages() { pkg := exec.Package(pkgname) + if cfg.HideEmptyPackages && pkg.IsEmpty() { + continue + } junitpkg := JUnitTestSuite{ Name: cfg.FormatTestSuiteName(pkgname), Tests: pkg.Total, diff --git a/internal/junitxml/report_test.go b/internal/junitxml/report_test.go index 14e8afb3..38d309ca 100644 --- a/internal/junitxml/report_test.go +++ b/internal/junitxml/report_test.go @@ -29,6 +29,21 @@ func TestWrite(t *testing.T) { golden.Assert(t, out.String(), "junitxml-report.golden") } +func TestWrite_HideEmptyPackages(t *testing.T) { + out := new(bytes.Buffer) + exec := createExecution(t) + + env.Patch(t, "GOVERSION", "go7.7.7") + err := Write(out, exec, Config{ + ProjectName: "test", + HideEmptyPackages: true, + customTimestamp: new(time.Time).Format(time.RFC3339), + customElapsed: "2.1", + }) + assert.NilError(t, err) + golden.Assert(t, out.String(), "junitxml-report-skip-empty.golden") +} + func createExecution(t *testing.T) *testjson.Execution { exec, err := testjson.ScanTestOutput(testjson.ScanConfig{ Stdout: readTestData(t, "out"), diff --git a/internal/junitxml/testdata/junitxml-report-skip-empty.golden b/internal/junitxml/testdata/junitxml-report-skip-empty.golden new file mode 100644 index 00000000..432accd4 --- /dev/null +++ b/internal/junitxml/testdata/junitxml-report-skip-empty.golden @@ -0,0 +1,119 @@ + + + + + + + + sometimes main can exit 2 FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + === RUN TestNestedParallelFailures/a === PAUSE TestNestedParallelFailures/a === CONT TestNestedParallelFailures/a fails_test.go:50: failed sub a --- FAIL: TestNestedParallelFailures/a (0.00s) + + + === RUN TestNestedParallelFailures/d === PAUSE TestNestedParallelFailures/d === CONT TestNestedParallelFailures/d fails_test.go:50: failed sub d --- FAIL: TestNestedParallelFailures/d (0.00s) + + + === RUN TestNestedParallelFailures/c === PAUSE TestNestedParallelFailures/c === CONT TestNestedParallelFailures/c fails_test.go:50: failed sub c --- FAIL: TestNestedParallelFailures/c (0.00s) + + + === RUN TestNestedParallelFailures/b === PAUSE TestNestedParallelFailures/b === CONT TestNestedParallelFailures/b fails_test.go:50: failed sub b --- FAIL: TestNestedParallelFailures/b (0.00s) + + + === RUN TestNestedParallelFailures --- FAIL: TestNestedParallelFailures (0.00s) + + + === RUN TestParallelTheFirst === PAUSE TestParallelTheFirst === CONT TestParallelTheFirst fails_test.go:29: failed the first --- FAIL: TestParallelTheFirst (0.01s) + + + === RUN TestParallelTheThird === PAUSE TestParallelTheThird === CONT TestParallelTheThird fails_test.go:41: failed the third --- FAIL: TestParallelTheThird (0.00s) + + + === RUN TestParallelTheSecond === PAUSE TestParallelTheSecond === CONT TestParallelTheSecond fails_test.go:35: failed the second --- FAIL: TestParallelTheSecond (0.01s) + + + + + + + + + + + + === RUN TestFailed fails_test.go:34: this failed --- FAIL: TestFailed (0.00s) + + + === RUN TestFailedWithStderr this is stderr fails_test.go:43: also failed --- FAIL: TestFailedWithStderr (0.00s) + + + === RUN TestNestedWithFailure/c fails_test.go:65: failed --- FAIL: TestNestedWithFailure/c (0.00s) + + + === RUN TestNestedWithFailure --- FAIL: TestNestedWithFailure (0.00s) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testjson/dotformat.go b/testjson/dotformat.go index bb0897ea..fd8a2936 100644 --- a/testjson/dotformat.go +++ b/testjson/dotformat.go @@ -103,7 +103,7 @@ func (d *dotFormatter) Format(event TestEvent, exec *Execution) error { sort.Slice(d.order, d.orderByLastUpdated) for _, pkg := range d.order { - if d.opts.HideEmptyPackages && exec.Package(pkg).Total == 0 { + if d.opts.HideEmptyPackages && exec.Package(pkg).IsEmpty() { continue } diff --git a/testjson/execution.go b/testjson/execution.go index dd6e5070..0204d07e 100644 --- a/testjson/execution.go +++ b/testjson/execution.go @@ -232,6 +232,11 @@ func (p *Package) TestMainFailed() bool { return p.action == ActionFail && len(p.Failed) == 0 } +// IsEmpty returns true if this package contains no tests. +func (p *Package) IsEmpty() bool { + return p.Total == 0 && !p.TestMainFailed() +} + const neverFinished time.Duration = -1 // end adds any tests that were missing an ActionFail TestEvent to the list of