diff --git a/pkg/test/ginkgo/junit.go b/pkg/test/ginkgo/junit.go index e726b6a5b93c..205a77f81030 100644 --- a/pkg/test/ginkgo/junit.go +++ b/pkg/test/ginkgo/junit.go @@ -10,11 +10,32 @@ import ( "time" "github.com/openshift/origin/pkg/test" + "github.com/openshift/origin/pkg/test/extensions" "github.com/openshift/origin/pkg/test/ginkgo/junitapi" "github.com/openshift/origin/pkg/version" ) +// populateOTEMetadata adds OTE metadata attributes to a JUnit test case if available +func populateOTEMetadata(testCase *junitapi.JUnitTestCase, extensionResult *extensions.ExtensionTestResult) { + if extensionResult == nil { + return + } + + // Set lifecycle attribute + testCase.Lifecycle = string(extensionResult.Lifecycle) + + // Set start time attribute if available + if extensionResult.StartTime != nil { + testCase.StartTime = time.Time(*extensionResult.StartTime).UTC().Format(time.RFC3339) + } + + // Set end time attribute if available + if extensionResult.EndTime != nil { + testCase.EndTime = time.Time(*extensionResult.EndTime).UTC().Format(time.RFC3339) + } +} + func generateJUnitTestSuiteResults( name string, duration time.Duration, @@ -36,49 +57,59 @@ func generateJUnitTestSuiteResults( case test.skipped: s.NumTests++ s.NumSkipped++ - s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{ + testCase := &junitapi.JUnitTestCase{ Name: test.name, SystemOut: string(test.testOutputBytes), Duration: test.duration.Seconds(), SkipMessage: &junitapi.SkipMessage{ Message: lastLinesUntil(string(test.testOutputBytes), 100, "skip ["), }, - }) + } + populateOTEMetadata(testCase, test.extensionTestResult) + s.TestCases = append(s.TestCases, testCase) case test.failed: s.NumTests++ s.NumFailed++ - s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{ + testCase := &junitapi.JUnitTestCase{ Name: test.name, SystemOut: string(test.testOutputBytes), Duration: test.duration.Seconds(), FailureOutput: &junitapi.FailureOutput{ Output: lastLinesUntil(string(test.testOutputBytes), 100, "fail ["), }, - }) + } + populateOTEMetadata(testCase, test.extensionTestResult) + s.TestCases = append(s.TestCases, testCase) case test.flake: s.NumTests++ s.NumFailed++ - s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{ + failedTestCase := &junitapi.JUnitTestCase{ Name: test.name, SystemOut: string(test.testOutputBytes), Duration: test.duration.Seconds(), FailureOutput: &junitapi.FailureOutput{ Output: lastLinesUntil(string(test.testOutputBytes), 100, "flake:"), }, - }) + } + populateOTEMetadata(failedTestCase, test.extensionTestResult) + s.TestCases = append(s.TestCases, failedTestCase) // also add the successful junit result: s.NumTests++ - s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{ + successTestCase := &junitapi.JUnitTestCase{ Name: test.name, Duration: test.duration.Seconds(), - }) + } + populateOTEMetadata(successTestCase, test.extensionTestResult) + s.TestCases = append(s.TestCases, successTestCase) case test.success: s.NumTests++ - s.TestCases = append(s.TestCases, &junitapi.JUnitTestCase{ + testCase := &junitapi.JUnitTestCase{ Name: test.name, Duration: test.duration.Seconds(), - }) + } + populateOTEMetadata(testCase, test.extensionTestResult) + s.TestCases = append(s.TestCases, testCase) } } for _, result := range syntheticTestResults { diff --git a/pkg/test/ginkgo/junit_test.go b/pkg/test/ginkgo/junit_test.go index 2db9c20d430b..7ef84427727d 100644 --- a/pkg/test/ginkgo/junit_test.go +++ b/pkg/test/ginkgo/junit_test.go @@ -1,6 +1,15 @@ package ginkgo -import "testing" +import ( + "encoding/xml" + "testing" + "time" + + "github.com/openshift-eng/openshift-tests-extension/pkg/dbtime" + "github.com/openshift-eng/openshift-tests-extension/pkg/extension/extensiontests" + "github.com/openshift/origin/pkg/test/extensions" + "github.com/openshift/origin/pkg/test/ginkgo/junitapi" +) func Test_lastLines(t *testing.T) { tests := []struct { @@ -33,3 +42,162 @@ func Test_lastLines(t *testing.T) { }) } } + +func Test_populateOTEMetadata(t *testing.T) { + startTime := time.Date(2023, 12, 25, 10, 0, 0, 0, time.UTC) + endTime := time.Date(2023, 12, 25, 10, 5, 0, 0, time.UTC) + + tests := []struct { + name string + extensionResult *extensions.ExtensionTestResult + expectedLifecycle string + expectedStartTime string + expectedEndTime string + }{ + { + name: "nil extension result", + extensionResult: nil, + expectedLifecycle: "", + expectedStartTime: "", + expectedEndTime: "", + }, + { + name: "complete extension result", + extensionResult: &extensions.ExtensionTestResult{ + ExtensionTestResult: &extensiontests.ExtensionTestResult{ + Name: "test-case", + Lifecycle: extensiontests.LifecycleBlocking, + StartTime: dbtime.Ptr(startTime), + EndTime: dbtime.Ptr(endTime), + }, + }, + expectedLifecycle: "blocking", + expectedStartTime: "2023-12-25T10:00:00Z", + expectedEndTime: "2023-12-25T10:05:00Z", + }, + { + name: "informing lifecycle", + extensionResult: &extensions.ExtensionTestResult{ + ExtensionTestResult: &extensiontests.ExtensionTestResult{ + Name: "test-case", + Lifecycle: extensiontests.LifecycleInforming, + StartTime: dbtime.Ptr(startTime), + EndTime: dbtime.Ptr(endTime), + }, + }, + expectedLifecycle: "informing", + expectedStartTime: "2023-12-25T10:00:00Z", + expectedEndTime: "2023-12-25T10:05:00Z", + }, + { + name: "missing time fields", + extensionResult: &extensions.ExtensionTestResult{ + ExtensionTestResult: &extensiontests.ExtensionTestResult{ + Name: "test-case", + Lifecycle: extensiontests.LifecycleBlocking, + StartTime: nil, + EndTime: nil, + }, + }, + expectedLifecycle: "blocking", + expectedStartTime: "", + expectedEndTime: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testCase := &junitapi.JUnitTestCase{ + Name: "test-case", + Duration: 300.0, // 5 minutes + } + + populateOTEMetadata(testCase, tt.extensionResult) + + if testCase.Lifecycle != tt.expectedLifecycle { + t.Errorf("Lifecycle = %q, want %q", testCase.Lifecycle, tt.expectedLifecycle) + } + if testCase.StartTime != tt.expectedStartTime { + t.Errorf("StartTime = %q, want %q", testCase.StartTime, tt.expectedStartTime) + } + if testCase.EndTime != tt.expectedEndTime { + t.Errorf("EndTime = %q, want %q", testCase.EndTime, tt.expectedEndTime) + } + }) + } +} + +func Test_junitXMLWithOTEAttributes(t *testing.T) { + startTime := time.Date(2023, 12, 25, 10, 0, 0, 0, time.UTC) + endTime := time.Date(2023, 12, 25, 10, 5, 0, 0, time.UTC) + + // Create a JUnit test case and populate it with OTE metadata + extensionResult := &extensions.ExtensionTestResult{ + ExtensionTestResult: &extensiontests.ExtensionTestResult{ + Name: "example-test", + Lifecycle: extensiontests.LifecycleBlocking, + StartTime: dbtime.Ptr(startTime), + EndTime: dbtime.Ptr(endTime), + }, + } + + junitTestCase := &junitapi.JUnitTestCase{ + Name: "example-test", + Duration: 300.0, // 5 minutes + } + + // Populate the OTE metadata + populateOTEMetadata(junitTestCase, extensionResult) + + // Create a test suite containing our test case + suite := &junitapi.JUnitTestSuite{ + Name: "test-suite", + NumTests: 1, + Duration: 300.0, + TestCases: []*junitapi.JUnitTestCase{junitTestCase}, + } + + // Verify OTE metadata is present + if junitTestCase.Lifecycle != "blocking" { + t.Errorf("Lifecycle = %q, want %q", junitTestCase.Lifecycle, "blocking") + } + if junitTestCase.StartTime != "2023-12-25T10:00:00Z" { + t.Errorf("StartTime = %q, want %q", junitTestCase.StartTime, "2023-12-25T10:00:00Z") + } + if junitTestCase.EndTime != "2023-12-25T10:05:00Z" { + t.Errorf("EndTime = %q, want %q", junitTestCase.EndTime, "2023-12-25T10:05:00Z") + } + + // Verify XML marshaling includes the new attributes + xmlData, err := xml.MarshalIndent(suite, "", " ") + if err != nil { + t.Fatalf("Failed to marshal XML: %v", err) + } + + xmlString := string(xmlData) + + // Check that our custom attributes are in the XML + expectedAttributes := []string{ + `lifecycle="blocking"`, + `start-time="2023-12-25T10:00:00Z"`, + `end-time="2023-12-25T10:05:00Z"`, + } + + for _, attr := range expectedAttributes { + if !contains(xmlString, attr) { + t.Errorf("XML does not contain expected attribute: %s\nXML output:\n%s", attr, xmlString) + } + } +} + +// Helper function to check if a string contains a substring +func contains(s, substr string) bool { + return len(s) >= len(substr) && func() bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false + }() +} diff --git a/pkg/test/ginkgo/junitapi/types.go b/pkg/test/ginkgo/junitapi/types.go index a74c71e03372..2313c2a0437d 100644 --- a/pkg/test/ginkgo/junitapi/types.go +++ b/pkg/test/ginkgo/junitapi/types.go @@ -66,6 +66,11 @@ type JUnitTestCase struct { // Duration is the time taken in seconds to run the test Duration float64 `xml:"time,attr"` + // OTE metadata attributes + StartTime string `xml:"start-time,attr,omitempty"` + EndTime string `xml:"end-time,attr,omitempty"` + Lifecycle string `xml:"lifecycle,attr,omitempty"` + // SkipMessage holds the reason why the test was skipped SkipMessage *SkipMessage `xml:"skipped"`