Skip to content

Commit 71d2d89

Browse files
Icarus9913onsi
authored andcommitted
feat: support component semantic version filtering
Signed-off-by: Icarus Wu <[email protected]>
1 parent 8cbbcb4 commit 71d2d89

27 files changed

Lines changed: 920 additions & 196 deletions

core_dsl.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"io"
2121
"os"
2222
"path/filepath"
23+
"slices"
2324
"strings"
2425

2526
"github.com/go-logr/logr"
@@ -268,7 +269,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
268269
}
269270
defer global.PopClone()
270271

271-
suiteLabels, suiteSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
272+
suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
272273

273274
var reporter reporters.Reporter
274275
if suiteConfig.ParallelTotal == 1 {
@@ -311,7 +312,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
311312
suitePath, err = filepath.Abs(suitePath)
312313
exitIfErr(err)
313314

314-
passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
315+
passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
315316
outputInterceptor.Shutdown()
316317

317318
flagSet.ValidateDeprecations(deprecationTracker)
@@ -330,9 +331,10 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
330331
return passed
331332
}
332333

333-
func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.AroundNodes) {
334+
func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, ComponentSemVerConstraints, types.AroundNodes) {
334335
suiteLabels := Labels{}
335336
suiteSemVerConstraints := SemVerConstraints{}
337+
suiteComponentSemVerConstraints := ComponentSemVerConstraints{}
336338
aroundNodes := types.AroundNodes{}
337339
configErrors := []error{}
338340
for _, arg := range args {
@@ -345,6 +347,11 @@ func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.Aro
345347
suiteLabels = append(suiteLabels, arg...)
346348
case SemVerConstraints:
347349
suiteSemVerConstraints = append(suiteSemVerConstraints, arg...)
350+
case ComponentSemVerConstraints:
351+
for component, constraints := range arg {
352+
suiteComponentSemVerConstraints[component] = append(suiteComponentSemVerConstraints[component], constraints...)
353+
suiteComponentSemVerConstraints[component] = slices.Compact(suiteComponentSemVerConstraints[component])
354+
}
348355
case types.AroundNodeDecorator:
349356
aroundNodes = append(aroundNodes, arg)
350357
default:
@@ -362,7 +369,7 @@ func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.Aro
362369
os.Exit(1)
363370
}
364371

365-
return suiteLabels, suiteSemVerConstraints, aroundNodes
372+
return suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, aroundNodes
366373
}
367374

368375
func getwd() (string, error) {
@@ -385,7 +392,7 @@ func PreviewSpecs(description string, args ...any) Report {
385392
}
386393
defer global.PopClone()
387394

388-
suiteLabels, suiteSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
395+
suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
389396
priorDryRun, priorParallelTotal, priorParallelProcess := suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess
390397
suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = true, 1, 1
391398
defer func() {
@@ -403,7 +410,7 @@ func PreviewSpecs(description string, args ...any) Report {
403410
suitePath, err = filepath.Abs(suitePath)
404411
exitIfErr(err)
405412

406-
global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
413+
global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
407414

408415
return global.Suite.GetPreviewReport()
409416
}

decorator_dsl.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,27 @@ You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-fi
117117
*/
118118
type SemVerConstraints = internal.SemVerConstraints
119119

120+
/*
121+
ComponentSemVerConstraint decorates specs with ComponentSemVerConstraints. Multiple components semantic version constraints can be passed to ComponentSemVerConstraint and the component can't be empy, also the version strings must follow the semantic version constraint rules.
122+
ComponentSemVerConstraints can be applied to container and subject nodes, but not setup nodes. You can provide multiple ComponentSemVerConstraints to a given node and a spec's component semantic version constraints is the union of all component semantic version constraints in its node hierarchy.
123+
124+
You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-filtering
125+
You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference
126+
*/
127+
func ComponentSemVerConstraint(component string, semVerConstraints ...string) ComponentSemVerConstraints {
128+
componentSemVerConstraints := ComponentSemVerConstraints{
129+
component: semVerConstraints,
130+
}
131+
132+
return componentSemVerConstraints
133+
}
134+
135+
/*
136+
ComponentSemVerConstraints are the type for spec ComponentSemVerConstraint decorators. Use ComponentSemVerConstraint(...) to construct ComponentSemVerConstraints.
137+
You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-filtering
138+
*/
139+
type ComponentSemVerConstraints = internal.ComponentSemVerConstraints
140+
120141
/*
121142
PollProgressAfter allows you to override the configured value for --poll-progress-after for a particular node.
122143

docs/index.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2930,13 +2930,17 @@ Describe("Feature with version requirements", func() {
29302930
It("should work in a specific version range (1.0.0, 2.0.0)", SemVerConstraint("> 1.0.0", "< 2.0.0"), func() {
29312931
// This test will only run when version is between 1.0.0 (exclusive) and 2.0.0 (exclusive)
29322932
})
2933+
2934+
It("should work in a specific version range (1.0.0, 2.0.0) and third-party dependency redis in [8.0.0, ~)", SemVerConstraint(">= 3.2.0"), ComponentSemVerConstraint("redis", ">= 8.0.0") func() {
2935+
// This test will only run when version is between 1.0.0 (exclusive) and 2.0.0 (exclusive) and redis version is >= 8.0.0
2936+
})
29332937
})
29342938
```
29352939

29362940
You can then filter specs by providing a semantic version via the `--sem-ver-filter` flag:
29372941

29382942
```bash
2939-
ginkgo --sem-ver-filter="2.1.1"
2943+
ginkgo --sem-ver-filter="2.1.1, redis=8.2.0"
29402944
```
29412945

29422946
This will only run specs whose semantic version constraints are satisfied by the provided version.
@@ -2963,14 +2967,27 @@ Describe("Feature with version requirements", SemVerConstraint(">= 2.0.0, < 2.3.
29632967
It("should only run in a narrower range", SemVerConstraint(">= 2.1.0, <= 2.2.0"), func() {
29642968
// Effective constraint: >= 2.1.0, <= 2.2.0 (intersection with parent)
29652969
})
2970+
2971+
Context("should work in the base version range and third-party dependency", ComponentSemVerConstraint("redis", ">= 6.0.0"), func() {
2972+
It("should run when both constraints are satisfied", func() {
2973+
// Effective constraint: >= 2.0.0, < 2.3.0 and redis version >= 6.0.0
2974+
})
2975+
2976+
It("should run in a narrower range with third-party dependency", ComponentSemVerConstraint("redis", ">= 7.0.0", "< 8.0.0"), func() {
2977+
// Effective constraint: >= 2.0.0, < 2.3.0 and redis version >= 7.0.0, < 8.0.0
2978+
})
2979+
})
29662980
})
29672981
```
29682982

29692983
In this example, the second spec will only run when the version satisfies both the parent constraint (`>= 2.0.0, < 2.3.0`) and its own constraint (`>= 2.1.0, <= 2.2.0`), resulting in an effective constraint of `>= 2.1.0, <= 2.2.0`.
2984+
Additionally, the specs within the `Context` will only run when both the parent constraint and the third-party dependency constraint are satisfied.
2985+
2986+
Note that while using `ComponentSemVerConstraint` decorator, the component name can not be empty.
29702987

29712988
##### Unconstrained Specs
29722989

2973-
Specs that don't have a `SemVerConstraint` decorator are considered unconstrained and will always run when the `--sem-ver-filter` flag is provided. This allows you to have a mix of version-specific and version-agnostic tests in the same suite.
2990+
Specs that don't have `SemVerConstraint` or `ComponentSemVerConstraint` decorator are considered unconstrained and will always run when the `--sem-ver-filter` flag is provided. This allows you to have a mix of version-specific and version-agnostic tests in the same suite.
29742991

29752992
#### Location-Based Filtering
29762993

dsl/decorators/decorators_dsl.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type FlakeAttempts = ginkgo.FlakeAttempts
2222
type MustPassRepeatedly = ginkgo.MustPassRepeatedly
2323
type Labels = ginkgo.Labels
2424
type SemVerConstraints = ginkgo.SemVerConstraints
25+
type ComponentSemVerConstraints = ginkgo.ComponentSemVerConstraints
2526
type PollProgressAfter = ginkgo.PollProgressAfter
2627
type PollProgressInterval = ginkgo.PollProgressInterval
2728
type NodeTimeout = ginkgo.NodeTimeout
@@ -39,6 +40,7 @@ const SuppressProgressReporting = ginkgo.SuppressProgressReporting
3940

4041
var Label = ginkgo.Label
4142
var SemVerConstraint = ginkgo.SemVerConstraint
43+
var ComponentSemVerConstraint = ginkgo.ComponentSemVerConstraint
4244

4345
func AroundNode[F types.AroundNodeAllowedFuncs](f F) types.AroundNodeDecorator {
4446
return types.AroundNode(f, types.NewCodeLocation(1))

integration/_fixtures/semver_fixture/semver_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,21 @@ var _ = Describe("Semantic Version Filtering", func() {
1616

1717
It("should run with version in range [2.0.0, 5.0.0)", SemVerConstraint(">= 2.0.0"), SemVerConstraint("< 5.0.0"), func() {})
1818

19+
It("should run with ComponentA in range [2.0.0, ~)", ComponentSemVerConstraint("ComponentA", ">= 2.0.0"), func() {})
20+
21+
It("should run with ComponentA in range [2.0.0, 3.0.0)", ComponentSemVerConstraint("ComponentA", ">= 2.0.0, < 3.0.0"), func() {})
22+
23+
It("should run with ComponentA in range [2.0.0, 4.0.0)", ComponentSemVerConstraint("ComponentA", ">= 2.0.0", "< 4.0.0"), func() {})
24+
25+
It("should run with ComponentA in range [2.0.0, 5.0.0)", ComponentSemVerConstraint("ComponentA", ">= 2.0.0"), ComponentSemVerConstraint("ComponentA", "< 5.0.0"), func() {})
26+
27+
It("should run with a mixed of component-specific and non-component constraints", SemVerConstraint(">= 2.0.0"), ComponentSemVerConstraint("ComponentA", ">= 2.0.0"), func() {})
28+
1929
It("shouldn't run with version in a conflict range", SemVerConstraint("2.0.0 - 6.0.0"), SemVerConstraint("<= 1.0.0"), func() {})
30+
31+
It("shouldn't run with ComponentA in a conflict range", ComponentSemVerConstraint("ComponentA", "2.0.0 - 6.0.0"), ComponentSemVerConstraint("ComponentA", "<= 1.0.0"), func() {})
32+
33+
It("shouldn't run with a mixed of component-specific and non-component conflict constraints", SemVerConstraint(">= 2.0.0"), ComponentSemVerConstraint("ComponentA", "<= 1.0.0"), func() {})
2034
})
2135

2236
var _ = Describe("Hierarchy Semantic Version Filtering", func() {
@@ -35,6 +49,27 @@ var _ = Describe("Hierarchy Semantic Version Filtering", func() {
3549
// So, this test case would be skipped.
3650
})
3751
})
52+
53+
Context("with container component-specific constraints", ComponentSemVerConstraint("ComponentA", ">= 2.0.0", "< 3.0.0"), func() {
54+
It("should inherit container component-specific constraint", func() {})
55+
56+
It("should narrow down the component-specific constraint", ComponentSemVerConstraint("ComponentA", ">= 2.1.0, < 2.8.0"), func() {})
57+
58+
It("shouldn't expand the component-specific constraint", ComponentSemVerConstraint("ComponentA", "< 4.0.0"), func() {
59+
// If you pass '--sem-ver-filter=3.5.0', then the whole Context would be skipped since it doesn't match the top level ComponentSemVerConstraints.
60+
// But if you pass '--sem-ver-filter=2.5.0', this test case would keep running since it matches the combined constraint '>= 2.0.0, < 3.0.0, < 4.0.0'
61+
})
62+
63+
It("shouldn't combine with a component-specific conflict constraint", ComponentSemVerConstraint("ComponentA", "< 1.0.0"), func() {
64+
// The new combined constraint is '>= 2.0.0, < 3.0.0, <1.0.0', there's no such a version can match this constraint.
65+
// So, this test case would be skipped.
66+
})
67+
})
68+
69+
Context("with mixed container constraints", SemVerConstraint(">= 2.0.0", "< 3.0.0"),
70+
ComponentSemVerConstraint("ComponentA", ">= 2.0.0", "< 3.0.0"), func() {
71+
It("should inherit container constraints and a new component-specific constraint", ComponentSemVerConstraint("ComponentB", ">= 0.1.0"), func() {})
72+
})
3873
})
3974

4075
var _ = DescribeTable("Semantic Version Filtering in table-driven spec", func() {
@@ -43,4 +78,7 @@ var _ = DescribeTable("Semantic Version Filtering in table-driven spec", func()
4378
Entry("should run without constraints by table driven"),
4479
Entry("should run with version in range [2.0.0, ~) by table driven", SemVerConstraint(">= 2.0.0")),
4580
Entry("shouldn't run with version in a conflict range by table driven", SemVerConstraint(">= 2.0.0"), SemVerConstraint("~1.2.3")),
81+
Entry("should run with ComponentA in range [2.0.0, ~) by table driven", ComponentSemVerConstraint("ComponentA", ">= 2.0.0")),
82+
Entry("shouldn't run with ComponentA in a conflict range by table driven", ComponentSemVerConstraint("ComponentA", ">= 2.0.0"), ComponentSemVerConstraint("ComponentA", "~1.2.3")),
83+
Entry("should run with mixed constraints by table driven", SemVerConstraint(">= 2.0.0"), ComponentSemVerConstraint("ComponentA", ">= 2.0.0")),
4684
)

integration/_fixtures/semver_fixture/spechierarchy/spechierarchy_suite_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ var _ = Describe("Spec Hierarchy Semantic Version Filtering", func() {
1616
It("should inherit spec constraint", func() {})
1717

1818
It("should narrow down spec constraint", SemVerConstraint(">= 3.0.0, < 4.0.0"), func() {})
19+
20+
It("should integrate with component constraint", ComponentSemVerConstraint("compA", ">= 12.0.0"), func() {})
1921
})

integration/filter_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package integration_test
22

33
import (
4-
"path/filepath"
4+
"path/filepath"
55

66
. "github.com/onsi/ginkgo/v2"
77
. "github.com/onsi/gomega"
@@ -113,7 +113,7 @@ var _ = Describe("Filter", func() {
113113

114114
It("filters specs based on semantic version constraints", func() {
115115
session := startGinkgo(filepath.Join(fm.TmpDir, "semver"),
116-
"--sem-ver-filter=2.2.0",
116+
"--sem-ver-filter=2.2.0, ComponentA=2.5.0",
117117
"--json-report=report.json",
118118
)
119119
Eventually(session).Should(gexec.Exit(0))
@@ -124,35 +124,51 @@ var _ = Describe("Filter", func() {
124124
"should run with version in range [2.0.0, 3.0.0)",
125125
"should run with version in range [2.0.0, 4.0.0)",
126126
"should run with version in range [2.0.0, 5.0.0)",
127+
"should run with ComponentA in range [2.0.0, ~)",
128+
"should run with ComponentA in range [2.0.0, 3.0.0)",
129+
"should run with ComponentA in range [2.0.0, 4.0.0)",
130+
"should run with ComponentA in range [2.0.0, 5.0.0)",
131+
"should run with a mixed of component-specific and non-component constraints",
127132
"should inherit container constraint",
128133
"should narrow down the constraint",
129134
"shouldn't expand the constraint",
135+
"should inherit container component-specific constraint",
136+
"should narrow down the component-specific constraint",
137+
"shouldn't expand the component-specific constraint",
138+
"should inherit container constraints and a new component-specific constraint",
130139
"should run without constraints by table driven",
131140
"should run with version in range [2.0.0, ~) by table driven",
141+
"should run with ComponentA in range [2.0.0, ~) by table driven",
142+
"should run with mixed constraints by table driven",
132143
}
133144
skippedSpecs := []string{
134145
"shouldn't run with version in a conflict range",
146+
"shouldn't run with ComponentA in a conflict range",
147+
"shouldn't run with a mixed of component-specific and non-component conflict constraints",
135148
"shouldn't combine with a conflict constraint",
149+
"shouldn't combine with a component-specific conflict constraint",
136150
"shouldn't run with version in a conflict range by table driven",
151+
"shouldn't run with ComponentA in a conflict range by table driven",
137152
}
138153
Ω(specs).Should(HaveLen(len(passedSpecs) + len(skippedSpecs)))
139154
for _, passed := range passedSpecs {
140-
Ω(specs.Find(passed)).Should(HavePassed())
155+
Ω(specs.Find(passed)).Should(HavePassed(), passed)
141156
}
142157
for _, skipped := range skippedSpecs {
143-
Ω(specs.Find(skipped)).Should(HaveBeenSkipped())
158+
Ω(specs.Find(skipped)).Should(HaveBeenSkipped(), skipped)
144159
}
145160
})
146161

147162
It("filters specs with hierarchy based on semantic version constraints", func() {
148163
session := startGinkgo(filepath.Join(fm.TmpDir, "semver", "spechierarchy"),
149-
"--sem-ver-filter=2.2.0",
164+
"--sem-ver-filter=2.2.0, compA=12.0.0",
150165
"--json-report=report.json",
151166
)
152167
Eventually(session).Should(gexec.Exit(0))
153168
specs := Reports(fm.LoadJSONReports(filepath.Join("semver", "spechierarchy"), "report.json")[0].SpecReports)
154169
passedSpecs := []string{
155170
"should inherit spec constraint",
171+
"should integrate with component constraint",
156172
}
157173
skippedSpecs := []string{
158174
"should narrow down spec constraint",

internal/focus.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ This function sets the `Skip` property on specs by applying Ginkgo's focus polic
5656
5757
*Note:* specs with pending nodes are Skipped when created by NewSpec.
5858
*/
59-
func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteConfig types.SuiteConfig) (Specs, bool) {
59+
func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteComponentSemVerConstraints ComponentSemVerConstraints, suiteConfig types.SuiteConfig) (Specs, bool) {
6060
focusString := strings.Join(suiteConfig.FocusStrings, "|")
6161
skipString := strings.Join(suiteConfig.SkipStrings, "|")
6262

@@ -87,7 +87,24 @@ func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suit
8787
if suiteConfig.SemVerFilter != "" {
8888
semVerFilter, _ := types.ParseSemVerFilter(suiteConfig.SemVerFilter)
8989
skipChecks = append(skipChecks, func(spec Spec) bool {
90-
return !semVerFilter(UnionOfSemVerConstraints(suiteSemVerConstraints, spec.Nodes.UnionOfSemVerConstraints()))
90+
noRun := false
91+
92+
// non-component-specific constraints
93+
constraints := UnionOfSemVerConstraints(suiteSemVerConstraints, spec.Nodes.UnionOfSemVerConstraints())
94+
if len(constraints) != 0 && semVerFilter("", constraints) == false {
95+
noRun = true
96+
}
97+
98+
// component-specific constraints
99+
componentConstraints := UnionOfComponentSemVerConstraints(suiteComponentSemVerConstraints, spec.Nodes.UnionOfComponentSemVerConstraints())
100+
for component, constraints := range componentConstraints {
101+
if semVerFilter(component, constraints) == false {
102+
noRun = true
103+
break
104+
}
105+
}
106+
107+
return noRun
91108
})
92109
}
93110

0 commit comments

Comments
 (0)