From 4e58e91af954cd204b83e9d7a917beac248da6d4 Mon Sep 17 00:00:00 2001 From: Zach Dylag Date: Tue, 7 May 2024 22:01:05 +0000 Subject: [PATCH] go junitxml: compute per package start times rather than using per execution This can be pretty meaningful if people want to build a timeline of when what test packages were executing. One example use case would be building a trace view ala https://docs.datadoghq.com/tests/setup/junit_xml/?tab=linux. --- internal/junitxml/report.go | 14 +++++++++++++- testjson/execution.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/internal/junitxml/report.go b/internal/junitxml/report.go index 9857f6ba..829f4ef1 100644 --- a/internal/junitxml/report.go +++ b/internal/junitxml/report.go @@ -104,11 +104,23 @@ func generate(exec *testjson.Execution, cfg Config) JUnitTestSuites { if cfg.customElapsed != "" { suites.Time = cfg.customElapsed } + + overallEarliestTimestamp := exec.EarliestTime() + if overallEarliestTimestamp.IsZero() { + overallEarliestTimestamp = exec.Started() + } + for _, pkgname := range exec.Packages() { pkg := exec.Package(pkgname) if cfg.HideEmptyPackages && pkg.IsEmpty() { continue } + + earliestTimestamp := pkg.EarliestTime() + if earliestTimestamp.IsZero() { + earliestTimestamp = overallEarliestTimestamp + } + junitpkg := JUnitTestSuite{ Name: cfg.FormatTestSuiteName(pkgname), Tests: pkg.Total, @@ -119,7 +131,7 @@ func generate(exec *testjson.Execution, cfg Config) JUnitTestSuites { Timestamp: cfg.customTimestamp, } if cfg.customTimestamp == "" { - junitpkg.Timestamp = exec.Started().Format(time.RFC3339) + junitpkg.Timestamp = earliestTimestamp.Format(time.RFC3339) } suites.Suites = append(suites.Suites, junitpkg) } diff --git a/testjson/execution.go b/testjson/execution.go index 25d4be08..e2cf7d20 100644 --- a/testjson/execution.go +++ b/testjson/execution.go @@ -131,6 +131,21 @@ func (p *Package) TestCases() []TestCase { return tc } +// EarliestTime returns the earliest start time found within a child test case. +func (p *Package) EarliestTime() time.Time { + found := false + earliest := time.Time{} + + for _, tc := range p.TestCases() { + if !found || tc.Time.Before(earliest) { + earliest = tc.Time + found = true + } + } + + return earliest +} + // LastFailedByName returns the most recent test with name in the list of Failed // tests. If no TestCase is found with that name, an empty TestCase is returned. // @@ -652,6 +667,26 @@ func (e *Execution) Started() time.Time { return e.started } +// EarliestTime returns the earliest runtime found in any of the packages within this execution. +func (e *Execution) EarliestTime() time.Time { + found := false + earliest := time.Time{} + + for _, p := range e.packages { + candidate := p.EarliestTime() + if candidate.IsZero() { + continue + } + + if !found || candidate.Before(earliest) { + earliest = candidate + found = true + } + } + + return earliest +} + // newExecution returns a new Execution and records the current time as the // time the test execution started. func newExecution() *Execution {