Skip to content
Draft
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
1 change: 1 addition & 0 deletions pkg/test/ginkgo/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ type TestSuite struct {
TestTimeout time.Duration `json:"testTimeout,omitempty"`

// OTE
Parents []string `json:"parents,omitempty"`
Qualifiers []string `json:"qualifiers,omitempty"`
Extension *extensions.Extension `json:"-"`
}
Expand Down
181 changes: 181 additions & 0 deletions pkg/testsuites/parents_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package testsuites

import (
"testing"

"github.com/openshift/origin/pkg/test/ginkgo"
)

func TestMergeParentQualifiers(t *testing.T) {
t.Run("child qualifiers merge into parent", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "parent"},
{Name: "child", Parents: []string{"parent"}, Qualifiers: []string{"q1", "q2"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "parent", []string{"q1", "q2"})
})

t.Run("multiple children merge into same parent", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "parent"},
{Name: "child-a", Parents: []string{"parent"}, Qualifiers: []string{"qa"}},
{Name: "child-b", Parents: []string{"parent"}, Qualifiers: []string{"qb"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "parent", []string{"qa", "qb"})
})

t.Run("duplicate qualifiers are not added twice", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "parent", Qualifiers: []string{"shared"}},
{Name: "child", Parents: []string{"parent"}, Qualifiers: []string{"shared", "unique"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "parent", []string{"shared", "unique"})
})

t.Run("transitive parents propagate grandchild to grandparent", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "grandparent"},
{Name: "parent", Parents: []string{"grandparent"}, Qualifiers: []string{"qp"}},
{Name: "child", Parents: []string{"parent"}, Qualifiers: []string{"qc"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "parent", []string{"qp", "qc"})
assertQualifiers(t, suites, "grandparent", []string{"qp", "qc"})
})

t.Run("transitive parents work regardless of slice order", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "child", Parents: []string{"parent"}, Qualifiers: []string{"qc"}},
{Name: "grandparent"},
{Name: "parent", Parents: []string{"grandparent"}, Qualifiers: []string{"qp"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "parent", []string{"qp", "qc"})
assertQualifiers(t, suites, "grandparent", []string{"qp", "qc"})
})

t.Run("four levels deep propagates to root", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "root"},
{Name: "l1", Parents: []string{"root"}, Qualifiers: []string{"q1"}},
{Name: "l2", Parents: []string{"l1"}, Qualifiers: []string{"q2"}},
{Name: "l3", Parents: []string{"l2"}, Qualifiers: []string{"q3"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "l2", []string{"q2", "q3"})
assertQualifiers(t, suites, "l1", []string{"q1", "q2", "q3"})
assertQualifiers(t, suites, "root", []string{"q1", "q2", "q3"})
})

t.Run("child with multiple parents merges into all", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "parent-a"},
{Name: "parent-b"},
{Name: "child", Parents: []string{"parent-a", "parent-b"}, Qualifiers: []string{"qc"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "parent-a", []string{"qc"})
assertQualifiers(t, suites, "parent-b", []string{"qc"})
})

t.Run("child qualifiers are unchanged", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "parent"},
{Name: "child", Parents: []string{"parent"}, Qualifiers: []string{"qc"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "child", []string{"qc"})
})

t.Run("suite with no parents is unaffected", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "standalone", Qualifiers: []string{"qs"}},
{Name: "other", Qualifiers: []string{"qo"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "standalone", []string{"qs"})
assertQualifiers(t, suites, "other", []string{"qo"})
})

t.Run("parent with no children keeps its own qualifiers", func(t *testing.T) {
suites := []*ginkgo.TestSuite{
{Name: "parent", Qualifiers: []string{"qp"}},
}
mergeParentQualifiers(suites)
assertQualifiers(t, suites, "parent", []string{"qp"})
})
}

func TestConformanceInheritsFromChildren(t *testing.T) {
suites := InternalTestSuites()

var conformance, parallel, serial *ginkgo.TestSuite
for _, s := range suites {
switch s.Name {
case "openshift/conformance":
conformance = s
case "openshift/conformance/parallel":
parallel = s
case "openshift/conformance/serial":
serial = s
}
}

if conformance == nil || parallel == nil || serial == nil {
t.Fatal("expected to find conformance, parallel, and serial suites")
}

if len(parallel.Qualifiers) == 0 {
t.Fatal("parallel suite has no qualifiers")
}
if len(serial.Qualifiers) == 0 {
t.Fatal("serial suite has no qualifiers")
}

qualifierSet := make(map[string]bool, len(conformance.Qualifiers))
for _, q := range conformance.Qualifiers {
qualifierSet[q] = true
}

for _, q := range parallel.Qualifiers {
if !qualifierSet[q] {
t.Errorf("conformance suite missing qualifier from parallel: %s", q)
}
}
for _, q := range serial.Qualifiers {
if !qualifierSet[q] {
t.Errorf("conformance suite missing qualifier from serial: %s", q)
}
}
}

func findSuite(suites []*ginkgo.TestSuite, name string) *ginkgo.TestSuite {
for _, s := range suites {
if s.Name == name {
return s
}
}
return nil
}

func assertQualifiers(t *testing.T, suites []*ginkgo.TestSuite, suiteName string, expected []string) {
t.Helper()
suite := findSuite(suites, suiteName)
if suite == nil {
t.Fatalf("suite %q not found", suiteName)
}
if len(suite.Qualifiers) != len(expected) {
t.Errorf("suite %q: expected %d qualifiers %v, got %d: %v",
suiteName, len(expected), expected, len(suite.Qualifiers), suite.Qualifiers)
return
}
for i, q := range expected {
if suite.Qualifiers[i] != q {
t.Errorf("suite %q qualifier[%d]: expected %q, got %q",
suiteName, i, q, suite.Qualifiers[i])
}
}
}
54 changes: 38 additions & 16 deletions pkg/testsuites/standard_suites.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func InternalTestSuites() []*ginkgo.TestSuite {
curr := staticSuites[i]
copied = append(copied, &curr)
}
mergeParentQualifiers(copied)
return copied
}

Expand Down Expand Up @@ -88,6 +89,7 @@ func AllTestSuites(ctx context.Context) ([]*ginkgo.TestSuite, error) {
Kind: ginkgo.KindExternal,
Count: s.Count,
Extension: e,
Parents: s.Parents,
Parallelism: s.Parallelism,
Qualifiers: s.Qualifiers,
TestTimeout: timeout,
Expand All @@ -96,19 +98,9 @@ func AllTestSuites(ctx context.Context) ([]*ginkgo.TestSuite, error) {
}
}

// Now handle setting qualifiers for parent suites once we've assembled the complete
// list of suites.
for _, e := range extensionInfos {
for _, s := range e.Suites {
for _, p := range s.Parents {
for _, parent := range suites {
if parent.Name == p {
parent.Qualifiers = append(parent.Qualifiers, s.Qualifiers...)
}
}
}
}
}
// Merge qualifiers from child suites into their declared parents. The fixed-point
// loop handles arbitrary depth (e.g. extension → parallel → conformance).
mergeParentQualifiers(suites)

return suites, nil
}
Expand All @@ -120,16 +112,14 @@ var staticSuites = []ginkgo.TestSuite{
Description: templates.LongDesc(`
Tests that ensure an OpenShift cluster and components are working properly.
`),
Qualifiers: []string{
withExcludedTestsFilter("name.contains('[Suite:openshift/conformance/')"),
},
Parallelism: 30,
},
{
Name: "openshift/conformance/parallel",
Description: templates.LongDesc(`
Only the portion of the openshift/conformance test suite that run in parallel.
`),
Parents: []string{"openshift/conformance"},
Qualifiers: []string{
withExcludedTestsFilter("name.contains('[Suite:openshift/conformance/parallel')"),
},
Expand All @@ -141,6 +131,7 @@ var staticSuites = []ginkgo.TestSuite{
Description: templates.LongDesc(`
Only the portion of the openshift/conformance test suite that run serially.
`),
Parents: []string{"openshift/conformance"},
Qualifiers: []string{
// Standard early and late tests are included in the serial suite
withExcludedTestsFilter(withStandardEarlyOrLateTests("name.contains('[Suite:openshift/conformance/serial')")),
Expand Down Expand Up @@ -511,6 +502,37 @@ var staticSuites = []ginkgo.TestSuite{
},
}

// mergeParentQualifiers appends each suite's qualifiers to its declared parent
// suites, deduplicating so the same qualifier isn't added twice. It repeats
// until no new qualifiers are added, so transitive parent chains (grandchildren)
// propagate fully regardless of slice order.
func mergeParentQualifiers(suites []*ginkgo.TestSuite) {
for {
changed := false
for _, child := range suites {
for _, parentName := range child.Parents {
for _, parent := range suites {
if parent.Name == parentName {
existing := make(map[string]bool, len(parent.Qualifiers))
for _, q := range parent.Qualifiers {
existing[q] = true
}
for _, q := range child.Qualifiers {
if !existing[q] {
parent.Qualifiers = append(parent.Qualifiers, q)
changed = true
Comment thread
stbenjam marked this conversation as resolved.
}
}
}
}
}
}
if !changed {
break
}
}
}

func withExcludedTestsFilter(baseExpr string) string {
filter := ""
for i, s := range extensions.ExcludedTests {
Expand Down