Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions pkg/test/ginkgo/cmd_runsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ginkgo
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -55,6 +56,96 @@ const (
postUpgradeEvent = "PostUpgrade"
)

// Embed long_tests.json at compile time
//
//go:embed long_tests.json
var longTestsJSON []byte

// Embed long_never_fail_tests.json at compile time
//
//go:embed long_never_fail_tests.json
var longNeverFailTestsJSON []byte

// LongTestInfo represents duration information for a single test
type LongTestInfo struct {
Name string `json:"name"`
DurationSeconds float64 `json:"duration_seconds"`
GroupID string `json:"group_id"`
}

// LongTestGroup represents a group of tests with the same group_id
type LongTestGroup struct {
GroupID string `json:"group_id"`
Tests []LongTestInfo `json:"tests"`
}

// LongTestsData holds all long test groups loaded from long_tests.json
var longTestsData []LongTestGroup

// neverFailTestNames is a set of test names that never fail and should be skipped
var neverFailTestNames map[string]bool

func init() {
// Load long_tests.json
if err := json.Unmarshal(longTestsJSON, &longTestsData); err != nil {
logrus.WithError(err).Warn("Failed to load long_tests.json, test duration data will not be available")
} else {
totalTests := 0
for _, group := range longTestsData {
totalTests += len(group.Tests)
}
logrus.Infof("Loaded %d long test groups with %d total tests from long_tests.json", len(longTestsData), totalTests)
}

// Load long_never_fail_tests.json
var neverFailTestsData []LongTestGroup
if err := json.Unmarshal(longNeverFailTestsJSON, &neverFailTestsData); err != nil {
logrus.WithError(err).Warn("Failed to load long_never_fail_tests.json, never-fail test data will not be available")
} else {
neverFailTestNames = make(map[string]bool)
totalNeverFail := 0
for _, group := range neverFailTestsData {
for _, test := range group.Tests {
neverFailTestNames[test.Name] = true
totalNeverFail++
}
}
logrus.Infof("Loaded %d tests from long_never_fail_tests.json that will be skipped", totalNeverFail)
}
}

// GetTestDuration returns the expected duration in seconds for a test by name, or 0 if not found
func GetTestDuration(testName string) int {
for _, group := range longTestsData {
for _, test := range group.Tests {
if test.Name == testName {
return int(test.DurationSeconds)
}
}
}
return 0
}

// GetTestGroup returns the group ID for a test by name, or empty string if not found
func GetTestGroup(testName string) string {
for _, group := range longTestsData {
for _, test := range group.Tests {
if test.Name == testName {
return test.GroupID
}
}
}
return ""
}

// IsNeverFailTest returns true if the test is in the never-fail list and should be skipped
func IsNeverFailTest(testName string) bool {
if neverFailTestNames == nil {
return false
}
return neverFailTestNames[testName]
}

// GinkgoRunSuiteOptions is used to run a suite of tests by invoking each test
// as a call to a child worker (the run-tests command).
type GinkgoRunSuiteOptions struct {
Expand Down Expand Up @@ -434,6 +525,54 @@ func (o *GinkgoRunSuiteOptions) Run(suite *TestSuite, clusterConfig *clusterdisc
return err
}

// Extract long-running tests first and organize them by group
// These will be prepended to their respective groups to run first
// Skip tests that never fail
longTestsByGroup := make(map[string][]*testCase)
var remainingTests []*testCase
var skippedNeverFailTests []*testCase

for _, test := range primaryTests {
// Skip tests that never fail
if IsNeverFailTest(test.name) {
skippedNeverFailTests = append(skippedNeverFailTests, test)
continue
}

group := GetTestGroup(test.name)
if group != "" {
// This is a known long-running test
longTestsByGroup[group] = append(longTestsByGroup[group], test)
} else {
// Not in long_tests.json, add to remaining
remainingTests = append(remainingTests, test)
}
}

if len(skippedNeverFailTests) > 0 {
logrus.Infof("Skipping %d tests that never fail", len(skippedNeverFailTests))
}

// Sort tests within each group by duration (longest first)
for group, tests := range longTestsByGroup {
sort.Slice(tests, func(i, j int) bool {
durationI := GetTestDuration(tests[i].name)
durationJ := GetTestDuration(tests[j].name)
return durationI > durationJ // Descending order (longest first)
})
longTestsByGroup[group] = tests
}

var longTestCount int
for group, tests := range longTestsByGroup {
longTestCount += len(tests)
logrus.Infof("Found %d long-running tests in group %s", len(tests), group)
}
logrus.Infof("Found %d total long-running tests, %d remaining tests", longTestCount, len(remainingTests))

// Now split the remaining tests (non-long tests) into groups
primaryTests = remainingTests

kubeTests, openshiftTests := splitTests(primaryTests, func(t *testCase) bool {
return strings.Contains(t.name, "[Suite:k8s]")
})
Expand Down Expand Up @@ -462,6 +601,64 @@ func (o *GinkgoRunSuiteOptions) Run(suite *TestSuite, clusterConfig *clusterdisc
return strings.Contains(t.name, "[sig-cli] oc adm must-gather")
})

// Prepend long-running tests to each group
// For groups that match our split categories, prepend their long tests
if longStorage := longTestsByGroup["sig-storage"]; len(longStorage) > 0 {
storageTests = append(longStorage, storageTests...)
}
if longNetworkK8s := longTestsByGroup["sig-network"]; len(longNetworkK8s) > 0 {
// Split sig-network long tests between k8s and openshift based on Suite marker
var longNetworkForK8s, longNetworkForOpenshift []*testCase
for _, test := range longNetworkK8s {
if strings.Contains(test.name, "[Suite:k8s]") {
longNetworkForK8s = append(longNetworkForK8s, test)
} else {
longNetworkForOpenshift = append(longNetworkForOpenshift, test)
}
}
networkK8sTests = append(longNetworkForK8s, networkK8sTests...)
networkTests = append(longNetworkForOpenshift, networkTests...)
}
if longHpa := longTestsByGroup["sig-apps"]; len(longHpa) > 0 {
// Split sig-apps long tests between HPA and openshift
var longForHpa, longForOpenshift []*testCase
for _, test := range longHpa {
if strings.Contains(test.name, "[Feature:HPA]") {
longForHpa = append(longForHpa, test)
} else {
longForOpenshift = append(longForOpenshift, test)
}
}
hpaTests = append(longForHpa, hpaTests...)
// Add non-HPA sig-apps tests to openshift
openshiftTests = append(longForOpenshift, openshiftTests...)
}
if longBuilds := longTestsByGroup["sig-builds"]; len(longBuilds) > 0 {
buildsTests = append(longBuilds, buildsTests...)
}

// For any other long test groups not explicitly split above, add them to openshiftTests
// These groups include: sig-node, sig-cli, sig-olmv1, sig-api-machinery, sig-network-edge, sig-auth, etc.
handledGroups := map[string]bool{
"sig-storage": true,
"sig-network": true,
"sig-apps": true,
"sig-builds": true,
}
var otherLongTests []*testCase
for group, tests := range longTestsByGroup {
if !handledGroups[group] {
otherLongTests = append(otherLongTests, tests...)
}
}
if len(otherLongTests) > 0 {
logrus.Infof("Adding %d long-running tests from other groups to openshift tests", len(otherLongTests))
openshiftTests = append(otherLongTests, openshiftTests...)
}

// Add long kube tests (any tests in kubeTests that are long but not in a specific group)
// Since we already extracted all long tests above, kubeTests should only contain short tests now

logrus.Infof("Found %d openshift tests", len(openshiftTests))
logrus.Infof("Found %d kube tests", len(kubeTests))
logrus.Infof("Found %d storage tests", len(storageTests))
Expand Down
Loading