Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add lazy loading for feature cases #570

Merged
merged 1 commit into from
Apr 8, 2024
Merged
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
12 changes: 12 additions & 0 deletions testing/framework/conformance/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ limitations under the License.

package conformance

import (
. "github.com/onsi/ginkgo/v2"
)

// CaseSetFactory factory to create a case set
type CaseSetFactory interface {
// New construct a new case set
Expand All @@ -33,6 +37,14 @@ type CaseSet interface {
Optional() CaseSet
}

// FeatureCaseLabeler is an interface that provides methods to
// retrieve the labels associated with the feature case.
//
// Labels returns the labels associated with the feature case.
type FeatureCaseLabeler interface {
Labels() Labels
}

// CaseLinker describe a interface to link test case to parent node
type CaseLinker interface {
// LinkParentNode link test case to parent node
Expand Down
45 changes: 45 additions & 0 deletions testing/framework/conformance/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ limitations under the License.

package conformance

import (
. "github.com/onsi/ginkgo/v2"
)

// currently, only support 3 levels of hierarchy(module -> function -> feature).
// But it can be extended to support more levels of hierarchy easily.

Expand Down Expand Up @@ -47,6 +51,12 @@ func (m *moduleCase) AddFeatureCase(featureCases ...*featureCase) {
m.AddFunctionCase(virtualFunctionCase)
}

// GetFeatureCase get feature case by name
// to be used when binding to a test point and adding custom assertion
func (m *moduleCase) GetFeatureCase(name string) FeatureCaseLabeler {
return NewLazyFeatureCaseLabeler(name, m)
}

func (m *moduleCase) RegisterTestCase() {
m.node.RegisterTestCase()
}
Expand Down Expand Up @@ -83,6 +93,8 @@ func NewFeatureCase(featureName string, caseSets ...CaseSet) *featureCase {
return fCase
}

// featureCase represents a test case for a specific feature.
// It contains a node that represents the feature in the test hierarchy.
type featureCase struct {
node *Node
}
Expand All @@ -94,3 +106,36 @@ func (f *featureCase) AddTestCaseSet(caseSets ...CaseSet) *featureCase {
}
return f
}

// Labels returns the labels associated with the featureCase.
func (f *featureCase) Labels() Labels {
return f.node.Labels()
}

// NewLazyFeatureCaseLabeler creates a new FeatureCaseLabeler that lazily fetches the labels
// from the SubNodes of moduleCase when Labels method is called
// If no subFeature case with the given name exists, it will return empty labels
func NewLazyFeatureCaseLabeler(name string, module *moduleCase) FeatureCaseLabeler {
return &lazyFeatureCaseBuilder{
name: name,
module: module,
}
}

// lazyFeatureCaseBuilder is a struct that lazily fetches the labels
// from the SubNodes of moduleCase when Labels method is called.
// If no subFeature case with the given name exists, it will return empty labels.
type lazyFeatureCaseBuilder struct {
name string
module *moduleCase
}

// Labels returns the labels associated with the featureCase. If no subFeature case with the given name exists, it will return an empty slice of labels.
func (l *lazyFeatureCaseBuilder) Labels() Labels {
for _, f := range l.module.node.SubNodes {
if f.Name == l.name {
return f.Labels()
}
}
return []string{}
}
11 changes: 6 additions & 5 deletions testing/framework/conformance/testpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type testPoint struct {
node *Node

// additionalAssertions each feature can have a custom assertion
additionalAssertions map[*featureCase]interface{}
additionalAssertions map[FeatureCaseLabeler]interface{}
}

// Labels returns all the labels for the test point
Expand All @@ -56,7 +56,7 @@ func (t *testPoint) Labels(ctx context.Context) Labels {
func (t *testPoint) CheckExternalAssertion(args ...interface{}) {
contextLabel := CurrentSpecReport().Labels()
for feature, assertFunc := range t.additionalAssertions {
featureLabel := strings.Join(feature.node.Labels(), "#")
featureLabel := strings.Join(feature.Labels(), "#")
if !slices.Contains(contextLabel, featureLabel) {
continue
}
Expand All @@ -78,21 +78,22 @@ func (t *testPoint) CheckExternalAssertion(args ...interface{}) {
}

// Bind alias of AddAssertion
func (t *testPoint) Bind(feature *featureCase) CustomAssertion {
func (t *testPoint) Bind(feature FeatureCaseLabeler) CustomAssertion {
return AddAssertionFunc(func(f interface{}) *testPoint {
return t.AddAssertion(feature, f)
})
}

// AddAssertion add a custom assertion to the test point for a special feature
func (t *testPoint) AddAssertion(feature *featureCase, assertFunc interface{}) *testPoint {
// func (t *testPoint) AddAssertion(feature *featureCase, assertFunc interface{}) *testPoint {
func (t *testPoint) AddAssertion(feature FeatureCaseLabeler, assertFunc interface{}) *testPoint {
// check assertFunc is a function
val := reflect.ValueOf(assertFunc)
if val.Kind() != reflect.Func || val.Type().NumIn() == 0 {
panic("assertFunc must be a function with at least one argument")
}
if t.additionalAssertions == nil {
t.additionalAssertions = make(map[*featureCase]interface{})
t.additionalAssertions = make(map[FeatureCaseLabeler]interface{})
}

t.additionalAssertions[feature] = assertFunc
Expand Down