Populate timestamp in test.xml (#4259)
**What type of PR is this?**
Feature
**What does this PR do? Why is it needed?**
The `<testsuite>` element in JUnit XML has a `timestamp` attribute, but
the `<testcase>` doesn't, while Go test's json has the timestamp for
every event. This PR find the smallest timestamp in the json events and
use that as the `timestamp` of `<testsuite>`.
**Test plan**
* Unit test
* Verify that the timestamps are different with `--runs_per_test=10`
diff --git a/go/tools/bzltestutil/testdata/timeout.xml b/go/tools/bzltestutil/testdata/timeout.xml
index ebb4947..0a3f6aa 100644
--- a/go/tools/bzltestutil/testdata/timeout.xml
+++ b/go/tools/bzltestutil/testdata/timeout.xml
@@ -1,5 +1,5 @@
<testsuites>
- <testsuite errors="2" failures="0" skipped="0" tests="5" time="8.555" name="pkg/testing">
+ <testsuite errors="2" failures="0" skipped="0" tests="5" time="8.555" name="pkg/testing" timestamp="2025-02-07T17:15:47.557Z">
<testcase classname="testing" name="TestReport" time="8.000">
<error message="Interrupted" type="">=== RUN TestReport
		TestReport (8s)
</error>
</testcase>
diff --git a/go/tools/bzltestutil/xml.go b/go/tools/bzltestutil/xml.go
index 73ef97c..327fcb2 100644
--- a/go/tools/bzltestutil/xml.go
+++ b/go/tools/bzltestutil/xml.go
@@ -39,6 +39,7 @@
Tests int `xml:"tests,attr"`
Time string `xml:"time,attr"`
Name string `xml:"name,attr"`
+ Timestamp string `xml:"timestamp,attr,omitempty"`
}
type xmlTestCase struct {
@@ -80,7 +81,10 @@
// json2xml converts test2json's output into an xml output readable by Bazel.
// http://windyroad.com.au/dl/Open%20Source/JUnit.xsd
func json2xml(r io.Reader, pkgName string) ([]byte, error) {
- var pkgDuration *float64
+ var (
+ pkgDuration *float64
+ pkgTimestamp *time.Time
+ )
testcases := make(map[string]*testCase)
testCaseByName := func(name string) *testCase {
if name == "" {
@@ -101,6 +105,9 @@
} else if err != nil {
return nil, fmt.Errorf("error decoding test2json output: %s", err)
}
+ if pkgTimestamp == nil || (e.Time != nil && e.Time.Unix() < pkgTimestamp.Unix()) {
+ pkgTimestamp = e.Time
+ }
switch s := e.Action; s {
case "run":
if c := testCaseByName(e.Test); c != nil {
@@ -128,7 +135,7 @@
if len(parts) != 2 || !strings.HasPrefix(parts[1], "(") || !strings.HasSuffix(parts[1], ")") {
inTimeoutSection = false
inRunningTestSection = false
- } else if duration, err := time.ParseDuration(parts[1][1:len(parts[1])-1]); err != nil {
+ } else if duration, err := time.ParseDuration(parts[1][1 : len(parts[1])-1]); err != nil {
inTimeoutSection = false
inRunningTestSection = false
} else if c := testCaseByName(parts[0]); c != nil {
@@ -165,10 +172,10 @@
}
}
- return xml.MarshalIndent(toXML(pkgName, pkgDuration, testcases), "", "\t")
+ return xml.MarshalIndent(toXML(pkgName, pkgDuration, pkgTimestamp, testcases), "", "\t")
}
-func toXML(pkgName string, pkgDuration *float64, testcases map[string]*testCase) *xmlTestSuites {
+func toXML(pkgName string, pkgDuration *float64, pkgTimestamp *time.Time, testcases map[string]*testCase) *xmlTestSuites {
cases := make([]string, 0, len(testcases))
for k := range testcases {
cases = append(cases, k)
@@ -180,6 +187,9 @@
if pkgDuration != nil {
suite.Time = fmt.Sprintf("%.3f", *pkgDuration)
}
+ if pkgTimestamp != nil {
+ suite.Timestamp = pkgTimestamp.Format("2006-01-02T15:04:05.000Z")
+ }
for _, name := range cases {
c := testcases[name]
suite.Tests++