Skip to content

Commit 7a1b397

Browse files
committed
Include additional OTE metadata in JUnit results
This allows us to add additional important information into the JUnit BigQuery table for use in Component Readness, or AI tools for analysis (e.g. correlating test failures with intervals).
1 parent e603cd9 commit 7a1b397

File tree

3 files changed

+215
-11
lines changed

3 files changed

+215
-11
lines changed

pkg/test/ginkgo/junit.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,32 @@ import (
1010
"time"
1111

1212
"github.com/openshift/origin/pkg/test"
13+
"github.com/openshift/origin/pkg/test/extensions"
1314
"github.com/openshift/origin/pkg/test/ginkgo/junitapi"
1415

1516
"github.com/openshift/origin/pkg/version"
1617
)
1718

19+
// populateOTEMetadata adds OTE metadata attributes to a JUnit test case if available
20+
func populateOTEMetadata(testCase *junitapi.JUnitTestCase, extensionResult *extensions.ExtensionTestResult) {
21+
if extensionResult == nil {
22+
return
23+
}
24+
25+
// Set lifecycle attribute
26+
testCase.Lifecycle = string(extensionResult.Lifecycle)
27+
28+
// Set start time attribute if available
29+
if extensionResult.StartTime != nil {
30+
testCase.StartTime = time.Time(*extensionResult.StartTime).UTC().Format(time.RFC3339)
31+
}
32+
33+
// Set end time attribute if available
34+
if extensionResult.EndTime != nil {
35+
testCase.EndTime = time.Time(*extensionResult.EndTime).UTC().Format(time.RFC3339)
36+
}
37+
}
38+
1839
func generateJUnitTestSuiteResults(
1940
name string,
2041
duration time.Duration,
@@ -36,49 +57,59 @@ func generateJUnitTestSuiteResults(
3657
case test.skipped:
3758
s.NumTests++
3859
s.NumSkipped++
39-
s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{
60+
testCase := &junitapi.JUnitTestCase{
4061
Name: test.name,
4162
SystemOut: string(test.testOutputBytes),
4263
Duration: test.duration.Seconds(),
4364
SkipMessage: &junitapi.SkipMessage{
4465
Message: lastLinesUntil(string(test.testOutputBytes), 100, "skip ["),
4566
},
46-
})
67+
}
68+
populateOTEMetadata(testCase, test.extensionTestResult)
69+
s.TestCases = append(s.TestCases, testCase)
4770
case test.failed:
4871
s.NumTests++
4972
s.NumFailed++
50-
s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{
73+
testCase := &junitapi.JUnitTestCase{
5174
Name: test.name,
5275
SystemOut: string(test.testOutputBytes),
5376
Duration: test.duration.Seconds(),
5477
FailureOutput: &junitapi.FailureOutput{
5578
Output: lastLinesUntil(string(test.testOutputBytes), 100, "fail ["),
5679
},
57-
})
80+
}
81+
populateOTEMetadata(testCase, test.extensionTestResult)
82+
s.TestCases = append(s.TestCases, testCase)
5883
case test.flake:
5984
s.NumTests++
6085
s.NumFailed++
61-
s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{
86+
failedTestCase := &junitapi.JUnitTestCase{
6287
Name: test.name,
6388
SystemOut: string(test.testOutputBytes),
6489
Duration: test.duration.Seconds(),
6590
FailureOutput: &junitapi.FailureOutput{
6691
Output: lastLinesUntil(string(test.testOutputBytes), 100, "flake:"),
6792
},
68-
})
93+
}
94+
populateOTEMetadata(failedTestCase, test.extensionTestResult)
95+
s.TestCases = append(s.TestCases, failedTestCase)
6996

7097
// also add the successful junit result:
7198
s.NumTests++
72-
s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{
99+
successTestCase := &junitapi.JUnitTestCase{
73100
Name: test.name,
74101
Duration: test.duration.Seconds(),
75-
})
102+
}
103+
populateOTEMetadata(successTestCase, test.extensionTestResult)
104+
s.TestCases = append(s.TestCases, successTestCase)
76105
case test.success:
77106
s.NumTests++
78-
s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{
107+
testCase := &junitapi.JUnitTestCase{
79108
Name: test.name,
80109
Duration: test.duration.Seconds(),
81-
})
110+
}
111+
populateOTEMetadata(testCase, test.extensionTestResult)
112+
s.TestCases = append(s.TestCases, testCase)
82113
}
83114
}
84115
for _, result := range syntheticTestResults {

pkg/test/ginkgo/junit_test.go

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package ginkgo
22

3-
import "testing"
3+
import (
4+
"encoding/xml"
5+
"testing"
6+
"time"
7+
8+
"github.com/openshift-eng/openshift-tests-extension/pkg/dbtime"
9+
"github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests"
10+
"github.com/openshift/origin/pkg/test/extensions"
11+
"github.com/openshift/origin/pkg/test/ginkgo/junitapi"
12+
)
413

514
func Test_lastLines(t *testing.T) {
615
tests := []struct {
@@ -33,3 +42,162 @@ func Test_lastLines(t *testing.T) {
3342
})
3443
}
3544
}
45+
46+
func Test_populateOTEMetadata(t *testing.T) {
47+
startTime := time.Date(2023, 12, 25, 10, 0, 0, 0, time.UTC)
48+
endTime := time.Date(2023, 12, 25, 10, 5, 0, 0, time.UTC)
49+
50+
tests := []struct {
51+
name string
52+
extensionResult *extensions.ExtensionTestResult
53+
expectedLifecycle string
54+
expectedStartTime string
55+
expectedEndTime string
56+
}{
57+
{
58+
name: "nil extension result",
59+
extensionResult: nil,
60+
expectedLifecycle: "",
61+
expectedStartTime: "",
62+
expectedEndTime: "",
63+
},
64+
{
65+
name: "complete extension result",
66+
extensionResult: &extensions.ExtensionTestResult{
67+
ExtensionTestResult: &extensiontests.ExtensionTestResult{
68+
Name: "test-case",
69+
Lifecycle: extensiontests.LifecycleBlocking,
70+
StartTime: dbtime.Ptr(startTime),
71+
EndTime: dbtime.Ptr(endTime),
72+
},
73+
},
74+
expectedLifecycle: "blocking",
75+
expectedStartTime: "2023-12-25T10:00:00Z",
76+
expectedEndTime: "2023-12-25T10:05:00Z",
77+
},
78+
{
79+
name: "informing lifecycle",
80+
extensionResult: &extensions.ExtensionTestResult{
81+
ExtensionTestResult: &extensiontests.ExtensionTestResult{
82+
Name: "test-case",
83+
Lifecycle: extensiontests.LifecycleInforming,
84+
StartTime: dbtime.Ptr(startTime),
85+
EndTime: dbtime.Ptr(endTime),
86+
},
87+
},
88+
expectedLifecycle: "informing",
89+
expectedStartTime: "2023-12-25T10:00:00Z",
90+
expectedEndTime: "2023-12-25T10:05:00Z",
91+
},
92+
{
93+
name: "missing time fields",
94+
extensionResult: &extensions.ExtensionTestResult{
95+
ExtensionTestResult: &extensiontests.ExtensionTestResult{
96+
Name: "test-case",
97+
Lifecycle: extensiontests.LifecycleBlocking,
98+
StartTime: nil,
99+
EndTime: nil,
100+
},
101+
},
102+
expectedLifecycle: "blocking",
103+
expectedStartTime: "",
104+
expectedEndTime: "",
105+
},
106+
}
107+
108+
for _, tt := range tests {
109+
t.Run(tt.name, func(t *testing.T) {
110+
testCase := &junitapi.JUnitTestCase{
111+
Name: "test-case",
112+
Duration: 300.0, // 5 minutes
113+
}
114+
115+
populateOTEMetadata(testCase, tt.extensionResult)
116+
117+
if testCase.Lifecycle != tt.expectedLifecycle {
118+
t.Errorf("Lifecycle = %q, want %q", testCase.Lifecycle, tt.expectedLifecycle)
119+
}
120+
if testCase.StartTime != tt.expectedStartTime {
121+
t.Errorf("StartTime = %q, want %q", testCase.StartTime, tt.expectedStartTime)
122+
}
123+
if testCase.EndTime != tt.expectedEndTime {
124+
t.Errorf("EndTime = %q, want %q", testCase.EndTime, tt.expectedEndTime)
125+
}
126+
})
127+
}
128+
}
129+
130+
func Test_junitXMLWithOTEAttributes(t *testing.T) {
131+
startTime := time.Date(2023, 12, 25, 10, 0, 0, 0, time.UTC)
132+
endTime := time.Date(2023, 12, 25, 10, 5, 0, 0, time.UTC)
133+
134+
// Create a JUnit test case and populate it with OTE metadata
135+
extensionResult := &extensions.ExtensionTestResult{
136+
ExtensionTestResult: &extensiontests.ExtensionTestResult{
137+
Name: "example-test",
138+
Lifecycle: extensiontests.LifecycleBlocking,
139+
StartTime: dbtime.Ptr(startTime),
140+
EndTime: dbtime.Ptr(endTime),
141+
},
142+
}
143+
144+
junitTestCase := &junitapi.JUnitTestCase{
145+
Name: "example-test",
146+
Duration: 300.0, // 5 minutes
147+
}
148+
149+
// Populate the OTE metadata
150+
populateOTEMetadata(junitTestCase, extensionResult)
151+
152+
// Create a test suite containing our test case
153+
suite := &junitapi.JUnitTestSuite{
154+
Name: "test-suite",
155+
NumTests: 1,
156+
Duration: 300.0,
157+
TestCases: []*junitapi.JUnitTestCase{junitTestCase},
158+
}
159+
160+
// Verify OTE metadata is present
161+
if junitTestCase.Lifecycle != "blocking" {
162+
t.Errorf("Lifecycle = %q, want %q", junitTestCase.Lifecycle, "blocking")
163+
}
164+
if junitTestCase.StartTime != "2023-12-25T10:00:00Z" {
165+
t.Errorf("StartTime = %q, want %q", junitTestCase.StartTime, "2023-12-25T10:00:00Z")
166+
}
167+
if junitTestCase.EndTime != "2023-12-25T10:05:00Z" {
168+
t.Errorf("EndTime = %q, want %q", junitTestCase.EndTime, "2023-12-25T10:05:00Z")
169+
}
170+
171+
// Verify XML marshaling includes the new attributes
172+
xmlData, err := xml.MarshalIndent(suite, "", " ")
173+
if err != nil {
174+
t.Fatalf("Failed to marshal XML: %v", err)
175+
}
176+
177+
xmlString := string(xmlData)
178+
179+
// Check that our custom attributes are in the XML
180+
expectedAttributes := []string{
181+
`lifecycle="blocking"`,
182+
`start-time="2023-12-25T10:00:00Z"`,
183+
`end-time="2023-12-25T10:05:00Z"`,
184+
}
185+
186+
for _, attr := range expectedAttributes {
187+
if !contains(xmlString, attr) {
188+
t.Errorf("XML does not contain expected attribute: %s\nXML output:\n%s", attr, xmlString)
189+
}
190+
}
191+
}
192+
193+
// Helper function to check if a string contains a substring
194+
func contains(s, substr string) bool {
195+
return len(s) >= len(substr) && func() bool {
196+
for i := 0; i <= len(s)-len(substr); i++ {
197+
if s[i:i+len(substr)] == substr {
198+
return true
199+
}
200+
}
201+
return false
202+
}()
203+
}

pkg/test/ginkgo/junitapi/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ type JUnitTestCase struct {
6666
// Duration is the time taken in seconds to run the test
6767
Duration float64 `xml:"time,attr"`
6868

69+
// OTE metadata attributes
70+
StartTime string `xml:"start-time,attr,omitempty"`
71+
EndTime string `xml:"end-time,attr,omitempty"`
72+
Lifecycle string `xml:"lifecycle,attr,omitempty"`
73+
6974
// SkipMessage holds the reason why the test was skipped
7075
SkipMessage *SkipMessage `xml:"skipped"`
7176

0 commit comments

Comments
 (0)