reva-bump-2.42.2

This commit is contained in:
Viktor Scharf
2026-02-05 12:29:10 +01:00
committed by Ralf Haferkamp
parent f21207ed96
commit 2bf70a6f70
102 changed files with 20907 additions and 1540 deletions
+18
View File
@@ -1,3 +1,21 @@
## 2.28.0
Ginkgo's SemVer filter now supports filtering multiple components by SemVer version:
```go
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() {
// 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
})
```
can be filtered in or out with an invocation like:
```bash
ginkgo --sem-ver-filter="2.1.1, redis=8.2.0"
```
Huge thanks to @Icarus9913 for working on this!
## 2.27.5
### Fixes
+13 -6
View File
@@ -20,6 +20,7 @@ import (
"io"
"os"
"path/filepath"
"slices"
"strings"
"github.com/go-logr/logr"
@@ -268,7 +269,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
}
defer global.PopClone()
suiteLabels, suiteSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
var reporter reporters.Reporter
if suiteConfig.ParallelTotal == 1 {
@@ -311,7 +312,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
suitePath, err = filepath.Abs(suitePath)
exitIfErr(err)
passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
outputInterceptor.Shutdown()
flagSet.ValidateDeprecations(deprecationTracker)
@@ -330,9 +331,10 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
return passed
}
func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.AroundNodes) {
func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, ComponentSemVerConstraints, types.AroundNodes) {
suiteLabels := Labels{}
suiteSemVerConstraints := SemVerConstraints{}
suiteComponentSemVerConstraints := ComponentSemVerConstraints{}
aroundNodes := types.AroundNodes{}
configErrors := []error{}
for _, arg := range args {
@@ -345,6 +347,11 @@ func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.Aro
suiteLabels = append(suiteLabels, arg...)
case SemVerConstraints:
suiteSemVerConstraints = append(suiteSemVerConstraints, arg...)
case ComponentSemVerConstraints:
for component, constraints := range arg {
suiteComponentSemVerConstraints[component] = append(suiteComponentSemVerConstraints[component], constraints...)
suiteComponentSemVerConstraints[component] = slices.Compact(suiteComponentSemVerConstraints[component])
}
case types.AroundNodeDecorator:
aroundNodes = append(aroundNodes, arg)
default:
@@ -362,7 +369,7 @@ func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.Aro
os.Exit(1)
}
return suiteLabels, suiteSemVerConstraints, aroundNodes
return suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, aroundNodes
}
func getwd() (string, error) {
@@ -385,7 +392,7 @@ func PreviewSpecs(description string, args ...any) Report {
}
defer global.PopClone()
suiteLabels, suiteSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
priorDryRun, priorParallelTotal, priorParallelProcess := suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess
suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = true, 1, 1
defer func() {
@@ -403,7 +410,7 @@ func PreviewSpecs(description string, args ...any) Report {
suitePath, err = filepath.Abs(suitePath)
exitIfErr(err)
global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
return global.Suite.GetPreviewReport()
}
+21
View File
@@ -117,6 +117,27 @@ You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-fi
*/
type SemVerConstraints = internal.SemVerConstraints
/*
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.
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.
You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-filtering
You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference
*/
func ComponentSemVerConstraint(component string, semVerConstraints ...string) ComponentSemVerConstraints {
componentSemVerConstraints := ComponentSemVerConstraints{
component: semVerConstraints,
}
return componentSemVerConstraints
}
/*
ComponentSemVerConstraints are the type for spec ComponentSemVerConstraint decorators. Use ComponentSemVerConstraint(...) to construct ComponentSemVerConstraints.
You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-filtering
*/
type ComponentSemVerConstraints = internal.ComponentSemVerConstraints
/*
PollProgressAfter allows you to override the configured value for --poll-progress-after for a particular node.
+1 -1
View File
@@ -33,7 +33,7 @@ func BuildRunCommand() command.Command {
Usage: "ginkgo run <FLAGS> <PACKAGES> -- <PASS-THROUGHS>",
ShortDoc: "Run the tests in the passed in <PACKAGES> (or the package in the current directory if left blank)",
Documentation: "Any arguments after -- will be passed to the test.",
DocLink: "running-tests",
DocLink: "running-specs",
Command: func(args []string, additionalArgs []string) {
var errors []error
cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig)
+19 -2
View File
@@ -56,7 +56,7 @@ This function sets the `Skip` property on specs by applying Ginkgo's focus polic
*Note:* specs with pending nodes are Skipped when created by NewSpec.
*/
func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteConfig types.SuiteConfig) (Specs, bool) {
func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteComponentSemVerConstraints ComponentSemVerConstraints, suiteConfig types.SuiteConfig) (Specs, bool) {
focusString := strings.Join(suiteConfig.FocusStrings, "|")
skipString := strings.Join(suiteConfig.SkipStrings, "|")
@@ -87,7 +87,24 @@ func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suit
if suiteConfig.SemVerFilter != "" {
semVerFilter, _ := types.ParseSemVerFilter(suiteConfig.SemVerFilter)
skipChecks = append(skipChecks, func(spec Spec) bool {
return !semVerFilter(UnionOfSemVerConstraints(suiteSemVerConstraints, spec.Nodes.UnionOfSemVerConstraints()))
noRun := false
// non-component-specific constraints
constraints := UnionOfSemVerConstraints(suiteSemVerConstraints, spec.Nodes.UnionOfSemVerConstraints())
if len(constraints) != 0 && semVerFilter("", constraints) == false {
noRun = true
}
// component-specific constraints
componentConstraints := UnionOfComponentSemVerConstraints(suiteComponentSemVerConstraints, spec.Nodes.UnionOfComponentSemVerConstraints())
for component, constraints := range componentConstraints {
if semVerFilter(component, constraints) == false {
noRun = true
break
}
}
return noRun
})
}
+19 -16
View File
@@ -113,22 +113,24 @@ func newGroup(suite *Suite) *group {
// initialReportForSpec constructs a new SpecReport right before running the spec.
func (g *group) initialReportForSpec(spec Spec) types.SpecReport {
return types.SpecReport{
ContainerHierarchyTexts: spec.Nodes.WithType(types.NodeTypeContainer).Texts(),
ContainerHierarchyLocations: spec.Nodes.WithType(types.NodeTypeContainer).CodeLocations(),
ContainerHierarchyLabels: spec.Nodes.WithType(types.NodeTypeContainer).Labels(),
ContainerHierarchySemVerConstraints: spec.Nodes.WithType(types.NodeTypeContainer).SemVerConstraints(),
LeafNodeLocation: spec.FirstNodeWithType(types.NodeTypeIt).CodeLocation,
LeafNodeType: types.NodeTypeIt,
LeafNodeText: spec.FirstNodeWithType(types.NodeTypeIt).Text,
LeafNodeLabels: []string(spec.FirstNodeWithType(types.NodeTypeIt).Labels),
LeafNodeSemVerConstraints: []string(spec.FirstNodeWithType(types.NodeTypeIt).SemVerConstraints),
ParallelProcess: g.suite.config.ParallelProcess,
RunningInParallel: g.suite.isRunningInParallel(),
IsSerial: spec.Nodes.HasNodeMarkedSerial(),
IsInOrderedContainer: !spec.Nodes.FirstNodeMarkedOrdered().IsZero(),
MaxFlakeAttempts: spec.Nodes.GetMaxFlakeAttempts(),
MaxMustPassRepeatedly: spec.Nodes.GetMaxMustPassRepeatedly(),
SpecPriority: spec.Nodes.GetSpecPriority(),
ContainerHierarchyTexts: spec.Nodes.WithType(types.NodeTypeContainer).Texts(),
ContainerHierarchyLocations: spec.Nodes.WithType(types.NodeTypeContainer).CodeLocations(),
ContainerHierarchyLabels: spec.Nodes.WithType(types.NodeTypeContainer).Labels(),
ContainerHierarchySemVerConstraints: spec.Nodes.WithType(types.NodeTypeContainer).SemVerConstraints(),
ContainerHierarchyComponentSemVerConstraints: spec.Nodes.WithType(types.NodeTypeContainer).ComponentSemVerConstraints(),
LeafNodeLocation: spec.FirstNodeWithType(types.NodeTypeIt).CodeLocation,
LeafNodeType: types.NodeTypeIt,
LeafNodeText: spec.FirstNodeWithType(types.NodeTypeIt).Text,
LeafNodeLabels: []string(spec.FirstNodeWithType(types.NodeTypeIt).Labels),
LeafNodeSemVerConstraints: []string(spec.FirstNodeWithType(types.NodeTypeIt).SemVerConstraints),
LeafNodeComponentSemVerConstraints: map[string][]string(spec.FirstNodeWithType(types.NodeTypeIt).ComponentSemVerConstraints),
ParallelProcess: g.suite.config.ParallelProcess,
RunningInParallel: g.suite.isRunningInParallel(),
IsSerial: spec.Nodes.HasNodeMarkedSerial(),
IsInOrderedContainer: !spec.Nodes.FirstNodeMarkedOrdered().IsZero(),
MaxFlakeAttempts: spec.Nodes.GetMaxFlakeAttempts(),
MaxMustPassRepeatedly: spec.Nodes.GetMaxMustPassRepeatedly(),
SpecPriority: spec.Nodes.GetSpecPriority(),
}
}
@@ -152,6 +154,7 @@ func addNodeToReportForNode(report *types.ConstructionNodeReport, node *TreeNode
report.ContainerHierarchyLocations = append(report.ContainerHierarchyLocations, node.Node.CodeLocation)
report.ContainerHierarchyLabels = append(report.ContainerHierarchyLabels, node.Node.Labels)
report.ContainerHierarchySemVerConstraints = append(report.ContainerHierarchySemVerConstraints, node.Node.SemVerConstraints)
report.ContainerHierarchyComponentSemVerConstraints = append(report.ContainerHierarchyComponentSemVerConstraints, node.Node.ComponentSemVerConstraints)
if node.Node.MarkedSerial {
report.IsSerial = true
}
+100 -11
View File
@@ -57,6 +57,7 @@ type Node struct {
MustPassRepeatedly int
Labels Labels
SemVerConstraints SemVerConstraints
ComponentSemVerConstraints ComponentSemVerConstraints
PollProgressAfter time.Duration
PollProgressInterval time.Duration
NodeTimeout time.Duration
@@ -106,7 +107,24 @@ func (l Labels) MatchesLabelFilter(query string) bool {
type SemVerConstraints []string
func (svc SemVerConstraints) MatchesSemVerFilter(version string) bool {
return types.MustParseSemVerFilter(version)(svc)
return types.MustParseSemVerFilter(version)("", svc)
}
type ComponentSemVerConstraints map[string][]string
func (csvc ComponentSemVerConstraints) MatchesSemVerFilter(component, version string) bool {
for comp, constraints := range csvc {
if comp != component {
continue
}
input := version
if len(component) > 0 {
input = fmt.Sprintf("%s=%s", component, version)
}
return types.MustParseSemVerFilter(input)(component, constraints)
}
return false
}
func unionOf[S ~[]E, E comparable](slices ...S) S {
@@ -131,6 +149,16 @@ func UnionOfSemVerConstraints(semVerConstraints ...SemVerConstraints) SemVerCons
return unionOf(semVerConstraints...)
}
func UnionOfComponentSemVerConstraints(componentSemVerConstraintsSlice ...ComponentSemVerConstraints) ComponentSemVerConstraints {
unionComponentSemVerConstraints := ComponentSemVerConstraints{}
for _, componentSemVerConstraints := range componentSemVerConstraintsSlice {
for component, constraints := range componentSemVerConstraints {
unionComponentSemVerConstraints[component] = unionOf(unionComponentSemVerConstraints[component], constraints)
}
}
return unionComponentSemVerConstraints
}
func PartitionDecorations(args ...any) ([]any, []any) {
decorations := []any{}
remainingArgs := []any{}
@@ -174,6 +202,8 @@ func isDecoration(arg any) bool {
return true
case t == reflect.TypeOf(SemVerConstraints{}):
return true
case t == reflect.TypeOf(ComponentSemVerConstraints{}):
return true
case t == reflect.TypeOf(PollProgressInterval(0)):
return true
case t == reflect.TypeOf(PollProgressAfter(0)):
@@ -214,16 +244,17 @@ var specContextType = reflect.TypeOf(new(SpecContext)).Elem()
func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeType, text string, args ...any) (Node, []error) {
baseOffset := 2
node := Node{
ID: UniqueNodeID(),
NodeType: nodeType,
Text: text,
Labels: Labels{},
SemVerConstraints: SemVerConstraints{},
CodeLocation: types.NewCodeLocation(baseOffset),
NestingLevel: -1,
PollProgressAfter: -1,
PollProgressInterval: -1,
GracePeriod: -1,
ID: UniqueNodeID(),
NodeType: nodeType,
Text: text,
Labels: Labels{},
SemVerConstraints: SemVerConstraints{},
ComponentSemVerConstraints: ComponentSemVerConstraints{},
CodeLocation: types.NewCodeLocation(baseOffset),
NestingLevel: -1,
PollProgressAfter: -1,
PollProgressInterval: -1,
GracePeriod: -1,
}
errors := []error{}
@@ -360,6 +391,36 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
appendError(err)
}
}
case t == reflect.TypeOf(ComponentSemVerConstraints{}):
if !nodeType.Is(types.NodeTypesForContainerAndIt) {
appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "ComponentSemVerConstraint"))
}
for component, semVerConstraints := range arg.(ComponentSemVerConstraints) {
// while using ComponentSemVerConstraints, we should not allow empty component names.
// you should use SemVerConstraints for that.
hasErr := false
if len(component) == 0 {
appendError(types.GinkgoErrors.InvalidEmptyComponentForSemVerConstraint(node.CodeLocation))
hasErr = true
}
for _, semVerConstraint := range semVerConstraints {
_, err := types.ValidateAndCleanupSemVerConstraint(semVerConstraint, node.CodeLocation)
if err != nil {
appendError(err)
hasErr = true
}
}
if !hasErr {
// merge constraints if the component already exists
constraints := slices.Clone(semVerConstraints)
if existingConstraints, exists := node.ComponentSemVerConstraints[component]; exists {
constraints = UnionOfSemVerConstraints([]string(existingConstraints), constraints)
}
node.ComponentSemVerConstraints[component] = slices.Clone(constraints)
}
}
case t.Kind() == reflect.Func:
if nodeType.Is(types.NodeTypeContainer) {
if node.Body != nil {
@@ -899,6 +960,34 @@ func (n Nodes) UnionOfSemVerConstraints() []string {
return out
}
func (n Nodes) ComponentSemVerConstraints() []map[string][]string {
out := make([]map[string][]string, len(n))
for i := range n {
if n[i].ComponentSemVerConstraints == nil {
out[i] = map[string][]string{}
} else {
out[i] = map[string][]string(n[i].ComponentSemVerConstraints)
}
}
return out
}
func (n Nodes) UnionOfComponentSemVerConstraints() map[string][]string {
out := map[string][]string{}
seen := map[string]bool{}
for i := range n {
for component := range n[i].ComponentSemVerConstraints {
if !seen[component] {
seen[component] = true
out[component] = n[i].ComponentSemVerConstraints[component]
} else {
out[component] = UnionOfSemVerConstraints(out[component], n[i].ComponentSemVerConstraints[component])
}
}
}
return out
}
func (n Nodes) CodeLocations() []types.CodeLocation {
out := make([]types.CodeLocation, len(n))
for i := range n {
+30 -17
View File
@@ -83,7 +83,7 @@ func goJSONActionFromSpecState(state types.SpecState) GoJSONAction {
type gojsonReport struct {
o types.Report
// Extra calculated fields
goPkg string
goPkg string
elapsed float64
}
@@ -109,8 +109,8 @@ type gojsonSpecReport struct {
o types.SpecReport
// extra calculated fields
testName string
elapsed float64
action GoJSONAction
elapsed float64
action GoJSONAction
}
func newSpecReport(in types.SpecReport) *gojsonSpecReport {
@@ -141,18 +141,31 @@ func suitePathToPkg(dir string) (string, error) {
}
func createTestName(spec types.SpecReport) string {
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
if spec.FullText() != "" {
name = name + " " + spec.FullText()
}
labels := spec.Labels()
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
semVerConstraints := spec.SemVerConstraints()
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
name = strings.TrimSpace(name)
return name
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
if spec.FullText() != "" {
name = name + " " + spec.FullText()
}
labels := spec.Labels()
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
semVerConstraints := spec.SemVerConstraints()
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
componentSemVerConstraints := spec.ComponentSemVerConstraints()
if len(componentSemVerConstraints) > 0 {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
name = strings.TrimSpace(name)
return name
}
func formatComponentSemVerConstraintsToString(componentSemVerConstraints map[string][]string) string {
var tmpStr string
for component, semVerConstraints := range componentSemVerConstraints {
tmpStr = tmpStr + fmt.Sprintf("%s: %s, ", component, semVerConstraints)
}
tmpStr = strings.TrimSuffix(tmpStr, ", ")
return tmpStr
}
+11 -10
View File
@@ -108,13 +108,13 @@ func (suite *Suite) BuildTree() error {
return nil
}
func (suite *Suite) Run(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteAroundNodes types.AroundNodes, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) {
func (suite *Suite) Run(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteComponentSemVerConstraints ComponentSemVerConstraints, suiteAroundNodes types.AroundNodes, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) {
if suite.phase != PhaseBuildTree {
panic("cannot run before building the tree = call suite.BuildTree() first")
}
ApplyNestedFocusPolicyToTree(suite.tree)
specs := GenerateSpecsFromTreeRoot(suite.tree)
specs, hasProgrammaticFocus := ApplyFocusToSpecs(specs, description, suiteLabels, suiteSemVerConstraints, suiteConfig)
specs, hasProgrammaticFocus := ApplyFocusToSpecs(specs, description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteConfig)
specs = ComputeAroundNodes(specs)
suite.phase = PhaseRun
@@ -133,7 +133,7 @@ func (suite *Suite) Run(description string, suiteLabels Labels, suiteSemVerConst
cancelProgressHandler := progressSignalRegistrar(suite.handleProgressSignal)
success := suite.runSpecs(description, suiteLabels, suiteSemVerConstraints, suitePath, hasProgrammaticFocus, specs)
success := suite.runSpecs(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suitePath, hasProgrammaticFocus, specs)
cancelProgressHandler()
@@ -456,16 +456,17 @@ func (suite *Suite) processCurrentSpecReport() {
}
}
func (suite *Suite) runSpecs(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suitePath string, hasProgrammaticFocus bool, specs Specs) bool {
func (suite *Suite) runSpecs(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteComponentSemVerConstraints ComponentSemVerConstraints, suitePath string, hasProgrammaticFocus bool, specs Specs) bool {
numSpecsThatWillBeRun := specs.CountWithoutSkip()
suite.report = types.Report{
SuitePath: suitePath,
SuiteDescription: description,
SuiteLabels: suiteLabels,
SuiteSemVerConstraints: suiteSemVerConstraints,
SuiteConfig: suite.config,
SuiteHasProgrammaticFocus: hasProgrammaticFocus,
SuitePath: suitePath,
SuiteDescription: description,
SuiteLabels: suiteLabels,
SuiteSemVerConstraints: suiteSemVerConstraints,
SuiteComponentSemVerConstraints: suiteComponentSemVerConstraints,
SuiteConfig: suite.config,
SuiteHasProgrammaticFocus: hasProgrammaticFocus,
PreRunStats: types.PreRunStats{
TotalSpecs: len(specs),
SpecsThatWillRun: numSpecsThatWillBeRun,
+25 -2
View File
@@ -75,6 +75,9 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) {
if len(report.SuiteSemVerConstraints) > 0 {
r.emit(r.f("{{coral}}[%s]{{/}} ", strings.Join(report.SuiteSemVerConstraints, ", ")))
}
if len(report.SuiteComponentSemVerConstraints) > 0 {
r.emit(r.f("{{coral}}[Components: %s]{{/}} ", formatComponentSemVerConstraintsToString(report.SuiteComponentSemVerConstraints)))
}
r.emit(r.f("- %d/%d specs ", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs))
if report.SuiteConfig.ParallelTotal > 1 {
r.emit(r.f("- %d procs ", report.SuiteConfig.ParallelTotal))
@@ -97,6 +100,13 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) {
bannerWidth = len(semVerConstraints) + 2
}
}
if len(report.SuiteComponentSemVerConstraints) > 0 {
componentSemVerConstraints := formatComponentSemVerConstraintsToString(report.SuiteComponentSemVerConstraints)
r.emitBlock(r.f("{{coral}}[Components: %s]{{/}} ", componentSemVerConstraints))
if len(componentSemVerConstraints)+2 > bannerWidth {
bannerWidth = len(componentSemVerConstraints) + 2
}
}
r.emitBlock(strings.Repeat("=", bannerWidth))
out := r.f("Random Seed: {{bold}}%d{{/}}", report.SuiteConfig.RandomSeed)
@@ -725,8 +735,12 @@ func (r *DefaultReporter) cycleJoin(elements []string, joiner string) string {
}
func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightColor string, veryVerbose bool, usePreciseFailureLocation bool) string {
texts, locations, labels, semVerConstraints := []string{}, []types.CodeLocation{}, [][]string{}, [][]string{}
texts, locations, labels, semVerConstraints = append(texts, report.ContainerHierarchyTexts...), append(locations, report.ContainerHierarchyLocations...), append(labels, report.ContainerHierarchyLabels...), append(semVerConstraints, report.ContainerHierarchySemVerConstraints...)
texts, locations, labels, semVerConstraints, componentSemVerConstraints := []string{}, []types.CodeLocation{}, [][]string{}, [][]string{}, []map[string][]string{}
texts = append(texts, report.ContainerHierarchyTexts...)
locations = append(locations, report.ContainerHierarchyLocations...)
labels = append(labels, report.ContainerHierarchyLabels...)
semVerConstraints = append(semVerConstraints, report.ContainerHierarchySemVerConstraints...)
componentSemVerConstraints = append(componentSemVerConstraints, report.ContainerHierarchyComponentSemVerConstraints...)
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
texts = append(texts, r.f("[%s] %s", report.LeafNodeType, report.LeafNodeText))
@@ -735,6 +749,7 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
}
labels = append(labels, report.LeafNodeLabels)
semVerConstraints = append(semVerConstraints, report.LeafNodeSemVerConstraints)
componentSemVerConstraints = append(componentSemVerConstraints, report.LeafNodeComponentSemVerConstraints)
locations = append(locations, report.LeafNodeLocation)
failureLocation := report.Failure.FailureNodeLocation
@@ -749,6 +764,7 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
locations = append([]types.CodeLocation{failureLocation}, locations...)
labels = append([][]string{{}}, labels...)
semVerConstraints = append([][]string{{}}, semVerConstraints...)
componentSemVerConstraints = append([]map[string][]string{{}}, componentSemVerConstraints...)
highlightIndex = 0
case types.FailureNodeInContainer:
i := report.Failure.FailureNodeContainerIndex
@@ -779,6 +795,9 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
if len(semVerConstraints[i]) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", strings.Join(semVerConstraints[i], ", "))
}
if len(componentSemVerConstraints[i]) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", formatComponentSemVerConstraintsToString(componentSemVerConstraints[i]))
}
out += "\n"
out += r.fi(uint(i), "{{gray}}%s{{/}}\n", locations[i])
}
@@ -806,6 +825,10 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
if len(flattenedSemVerConstraints) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", strings.Join(flattenedSemVerConstraints, ", "))
}
flattenedComponentSemVerConstraints := report.ComponentSemVerConstraints()
if len(flattenedComponentSemVerConstraints) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", formatComponentSemVerConstraintsToString(flattenedComponentSemVerConstraints))
}
out += "\n"
if usePreciseFailureLocation {
out += r.f("{{gray}}%s{{/}}", failureLocation)
+20
View File
@@ -13,9 +13,11 @@ package reporters
import (
"encoding/xml"
"fmt"
"maps"
"os"
"path"
"regexp"
"slices"
"strings"
"github.com/onsi/ginkgo/v2/config"
@@ -39,6 +41,9 @@ type JunitReportConfig struct {
// Enable OmitSpecSemVerConstraints to prevent semantic version constraints from appearing in the spec name
OmitSpecSemVerConstraints bool
// Enable OmitSpecComponentSemVerConstraints to prevent component semantic version constraints from appearing in the spec name
OmitSpecComponentSemVerConstraints bool
// Enable OmitLeafNodeType to prevent the spec leaf node type from appearing in the spec name
OmitLeafNodeType bool
@@ -173,6 +178,7 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
{"SpecialSuiteFailureReason", strings.Join(report.SpecialSuiteFailureReasons, ",")},
{"SuiteLabels", fmt.Sprintf("[%s]", strings.Join(report.SuiteLabels, ","))},
{"SuiteSemVerConstraints", fmt.Sprintf("[%s]", strings.Join(report.SuiteSemVerConstraints, ","))},
{"SuiteComponentSemVerConstraints", fmt.Sprintf("[%s]", formatComponentSemVerConstraintsToString(report.SuiteComponentSemVerConstraints))},
{"RandomSeed", fmt.Sprintf("%d", report.SuiteConfig.RandomSeed)},
{"RandomizeAllSpecs", fmt.Sprintf("%t", report.SuiteConfig.RandomizeAllSpecs)},
{"LabelFilter", report.SuiteConfig.LabelFilter},
@@ -216,6 +222,10 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
if len(semVerConstraints) > 0 && !config.OmitSpecSemVerConstraints {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
componentSemVerConstraints := spec.ComponentSemVerConstraints()
if len(componentSemVerConstraints) > 0 && !config.OmitSpecComponentSemVerConstraints {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
name = strings.TrimSpace(name)
test := JUnitTestCase{
@@ -387,6 +397,16 @@ func systemOutForUnstructuredReporters(spec types.SpecReport) string {
return spec.CapturedStdOutErr
}
func formatComponentSemVerConstraintsToString(componentSemVerConstraints map[string][]string) string {
var tmpStr string
for _, key := range slices.Sorted(maps.Keys(componentSemVerConstraints)) {
tmpStr = tmpStr + fmt.Sprintf("%s: %s, ", key, componentSemVerConstraints[key])
}
tmpStr = strings.TrimSuffix(tmpStr, ", ")
return tmpStr
}
// Deprecated JUnitReporter (so folks can still compile their suites)
type JUnitReporter struct{}
+8
View File
@@ -39,12 +39,16 @@ func GenerateTeamcityReport(report types.Report, dst string) error {
name := report.SuiteDescription
labels := report.SuiteLabels
semVerConstraints := report.SuiteSemVerConstraints
componentSemVerConstraints := report.SuiteComponentSemVerConstraints
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
if len(componentSemVerConstraints) > 0 {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
fmt.Fprintf(f, "##teamcity[testSuiteStarted name='%s']\n", tcEscape(name))
for _, spec := range report.SpecReports {
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
@@ -59,6 +63,10 @@ func GenerateTeamcityReport(report types.Report, dst string) error {
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
componentSemVerConstraints := spec.ComponentSemVerConstraints()
if len(componentSemVerConstraints) > 0 {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
name = tcEscape(name)
fmt.Fprintf(f, "##teamcity[testStarted name='%s']\n", name)
+9
View File
@@ -450,6 +450,15 @@ func (g ginkgoErrors) InvalidEmptySemVerConstraint(cl CodeLocation) error {
}
}
func (g ginkgoErrors) InvalidEmptyComponentForSemVerConstraint(cl CodeLocation) error {
return GinkgoError{
Heading: "Invalid Empty Component for ComponentSemVerConstraint",
Message: "ComponentSemVerConstraint requires a non-empty component name",
CodeLocation: cl,
DocLink: "spec-semantic-version-filtering",
}
}
/* Table errors */
func (g ginkgoErrors) MultipleEntryBodyFunctionsForTable(cl CodeLocation) error {
return GinkgoError{
+77 -16
View File
@@ -2,11 +2,12 @@ package types
import (
"fmt"
"strings"
"github.com/Masterminds/semver/v3"
)
type SemVerFilter func([]string) bool
type SemVerFilter func(component string, constraints []string) bool
func MustParseSemVerFilter(input string) SemVerFilter {
filter, err := ParseSemVerFilter(input)
@@ -16,30 +17,90 @@ func MustParseSemVerFilter(input string) SemVerFilter {
return filter
}
func ParseSemVerFilter(filterVersion string) (SemVerFilter, error) {
if filterVersion == "" {
return func(_ []string) bool { return true }, nil
// ParseSemVerFilter parses non-component and component-specific semantic version filter string.
// The filter string can contain multiple non-component and component-specific versions separated by commas.
// Each component-specific version is in the format "component=version".
// If a version is specified without a component, it applies to non-component-specific constraints.
func ParseSemVerFilter(componentFilterVersions string) (SemVerFilter, error) {
if componentFilterVersions == "" {
return func(_ string, _ []string) bool { return true }, nil
}
targetVersion, err := semver.NewVersion(filterVersion)
if err != nil {
return nil, fmt.Errorf("invalid filter version: %w", err)
result := map[string]*semver.Version{}
parts := strings.Split(componentFilterVersions, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if len(part) == 0 {
continue
}
if strings.Contains(part, "=") {
// validate component-specific version string
invalidPart, invalidErr := false, fmt.Errorf("invalid component filter version: %s", part)
subParts := strings.Split(part, "=")
if len(subParts) != 2 {
invalidPart = true
}
component := strings.TrimSpace(subParts[0])
versionStr := strings.TrimSpace(subParts[1])
if len(component) == 0 || len(versionStr) == 0 {
invalidPart = true
}
if invalidPart {
return nil, invalidErr
}
// validate semver
v, err := semver.NewVersion(versionStr)
if err != nil {
return nil, fmt.Errorf("invalid component filter version: %s, error: %w", part, err)
}
result[component] = v
} else {
v, err := semver.NewVersion(part)
if err != nil {
return nil, fmt.Errorf("invalid filter version: %s, error: %w", part, err)
}
result[""] = v
}
}
return func(constraints []string) bool {
return func(component string, constraints []string) bool {
// unconstrained specs always run
if len(constraints) == 0 {
if len(component) == 0 && len(constraints) == 0 {
return true
}
for _, constraintStr := range constraints {
constraint, err := semver.NewConstraint(constraintStr)
if err != nil {
return false
}
// check non-component specific version constraints
if len(component) == 0 && len(constraints) != 0 {
v := result[""]
if v != nil {
for _, constraintStr := range constraints {
constraint, err := semver.NewConstraint(constraintStr)
if err != nil {
return false
}
if !constraint.Check(targetVersion) {
return false
if !constraint.Check(v) {
return false
}
}
}
}
// check component-specific version constraints
if len(component) != 0 && len(constraints) != 0 {
v := result[component]
if v != nil {
for _, constraintStr := range constraints {
constraint, err := semver.NewConstraint(constraintStr)
if err != nil {
return false
}
if !constraint.Check(v) {
return false
}
}
}
}
+108 -51
View File
@@ -38,6 +38,10 @@ type ConstructionNodeReport struct {
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchySemVerConstraints [][]string
// ContainerHierarchyComponentSemVerConstraints is a slice containing the component-specific semVerConstraints of
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchyComponentSemVerConstraints []map[string][]string
// IsSerial captures whether the any container has the Serial decorator
IsSerial bool
@@ -85,6 +89,9 @@ type Report struct {
//SuiteSemVerConstraints captures any semVerConstraints attached to the suite by the DSL's RunSpecs() function
SuiteSemVerConstraints []string
//SuiteComponentSemVerConstraints captures any component-specific semVerConstraints attached to the suite by the DSL's RunSpecs() function
SuiteComponentSemVerConstraints map[string][]string
//SuiteSucceeded captures the success or failure status of the test run
//If true, the test run is considered successful.
//If false, the test run is considered unsuccessful
@@ -188,14 +195,19 @@ type SpecReport struct {
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchySemVerConstraints [][]string
// ContainerHierarchyComponentSemVerConstraints is a slice containing the component-specific semVerConstraints of
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchyComponentSemVerConstraints []map[string][]string
// LeafNodeType, LeafNodeLocation, LeafNodeLabels, LeafNodeSemVerConstraints and LeafNodeText capture the NodeType, CodeLocation, and text
// of the Ginkgo node being tested (typically an NodeTypeIt node, though this can also be
// one of the NodeTypesForSuiteLevelNodes node types)
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeText string
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeComponentSemVerConstraints map[string][]string
LeafNodeText string
// Captures the Spec Priority
SpecPriority int
@@ -261,52 +273,54 @@ type SpecReport struct {
func (report SpecReport) MarshalJSON() ([]byte, error) {
//All this to avoid emitting an empty Failure struct in the JSON
out := struct {
ContainerHierarchyTexts []string
ContainerHierarchyLocations []CodeLocation
ContainerHierarchyLabels [][]string
ContainerHierarchySemVerConstraints [][]string
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeText string
State SpecState
StartTime time.Time
EndTime time.Time
RunTime time.Duration
ParallelProcess int
Failure *Failure `json:",omitempty"`
NumAttempts int
MaxFlakeAttempts int
MaxMustPassRepeatedly int
CapturedGinkgoWriterOutput string `json:",omitempty"`
CapturedStdOutErr string `json:",omitempty"`
ReportEntries ReportEntries `json:",omitempty"`
ProgressReports []ProgressReport `json:",omitempty"`
AdditionalFailures []AdditionalFailure `json:",omitempty"`
SpecEvents SpecEvents `json:",omitempty"`
ContainerHierarchyTexts []string
ContainerHierarchyLocations []CodeLocation
ContainerHierarchyLabels [][]string
ContainerHierarchySemVerConstraints [][]string
ContainerHierarchyComponentSemVerConstraints []map[string][]string
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeText string
State SpecState
StartTime time.Time
EndTime time.Time
RunTime time.Duration
ParallelProcess int
Failure *Failure `json:",omitempty"`
NumAttempts int
MaxFlakeAttempts int
MaxMustPassRepeatedly int
CapturedGinkgoWriterOutput string `json:",omitempty"`
CapturedStdOutErr string `json:",omitempty"`
ReportEntries ReportEntries `json:",omitempty"`
ProgressReports []ProgressReport `json:",omitempty"`
AdditionalFailures []AdditionalFailure `json:",omitempty"`
SpecEvents SpecEvents `json:",omitempty"`
}{
ContainerHierarchyTexts: report.ContainerHierarchyTexts,
ContainerHierarchyLocations: report.ContainerHierarchyLocations,
ContainerHierarchyLabels: report.ContainerHierarchyLabels,
ContainerHierarchySemVerConstraints: report.ContainerHierarchySemVerConstraints,
LeafNodeType: report.LeafNodeType,
LeafNodeLocation: report.LeafNodeLocation,
LeafNodeLabels: report.LeafNodeLabels,
LeafNodeSemVerConstraints: report.LeafNodeSemVerConstraints,
LeafNodeText: report.LeafNodeText,
State: report.State,
StartTime: report.StartTime,
EndTime: report.EndTime,
RunTime: report.RunTime,
ParallelProcess: report.ParallelProcess,
Failure: nil,
ReportEntries: nil,
NumAttempts: report.NumAttempts,
MaxFlakeAttempts: report.MaxFlakeAttempts,
MaxMustPassRepeatedly: report.MaxMustPassRepeatedly,
CapturedGinkgoWriterOutput: report.CapturedGinkgoWriterOutput,
CapturedStdOutErr: report.CapturedStdOutErr,
ContainerHierarchyTexts: report.ContainerHierarchyTexts,
ContainerHierarchyLocations: report.ContainerHierarchyLocations,
ContainerHierarchyLabels: report.ContainerHierarchyLabels,
ContainerHierarchySemVerConstraints: report.ContainerHierarchySemVerConstraints,
ContainerHierarchyComponentSemVerConstraints: report.ContainerHierarchyComponentSemVerConstraints,
LeafNodeType: report.LeafNodeType,
LeafNodeLocation: report.LeafNodeLocation,
LeafNodeLabels: report.LeafNodeLabels,
LeafNodeSemVerConstraints: report.LeafNodeSemVerConstraints,
LeafNodeText: report.LeafNodeText,
State: report.State,
StartTime: report.StartTime,
EndTime: report.EndTime,
RunTime: report.RunTime,
ParallelProcess: report.ParallelProcess,
Failure: nil,
ReportEntries: nil,
NumAttempts: report.NumAttempts,
MaxFlakeAttempts: report.MaxFlakeAttempts,
MaxMustPassRepeatedly: report.MaxMustPassRepeatedly,
CapturedGinkgoWriterOutput: report.CapturedGinkgoWriterOutput,
CapturedStdOutErr: report.CapturedStdOutErr,
}
if !report.Failure.IsZero() {
@@ -404,6 +418,34 @@ func (report SpecReport) SemVerConstraints() []string {
return out
}
// ComponentSemVerConstraints returns a deduped map of all the spec's component-specific SemVerConstraints.
func (report SpecReport) ComponentSemVerConstraints() map[string][]string {
out := map[string][]string{}
seen := map[string]bool{}
for _, compSemVerConstraints := range report.ContainerHierarchyComponentSemVerConstraints {
for component := range compSemVerConstraints {
if !seen[component] {
seen[component] = true
out[component] = compSemVerConstraints[component]
} else {
out[component] = append(out[component], compSemVerConstraints[component]...)
out[component] = slices.Compact(out[component])
}
}
}
for component := range report.LeafNodeComponentSemVerConstraints {
if !seen[component] {
seen[component] = true
out[component] = report.LeafNodeComponentSemVerConstraints[component]
} else {
out[component] = append(out[component], report.LeafNodeComponentSemVerConstraints[component]...)
out[component] = slices.Compact(out[component])
}
}
return out
}
// MatchesLabelFilter returns true if the spec satisfies the passed in label filter query
func (report SpecReport) MatchesLabelFilter(query string) (bool, error) {
filter, err := ParseLabelFilter(query)
@@ -419,7 +461,22 @@ func (report SpecReport) MatchesSemVerFilter(version string) (bool, error) {
if err != nil {
return false, err
}
return filter(report.SemVerConstraints()), nil
semVerConstraints := report.SemVerConstraints()
if len(semVerConstraints) != 0 && filter("", report.SemVerConstraints()) == false {
return false, nil
}
componentSemVerConstraints := report.ComponentSemVerConstraints()
if len(componentSemVerConstraints) != 0 {
for component, constraints := range componentSemVerConstraints {
if filter(component, constraints) == false {
return false, nil
}
}
}
return true, nil
}
// FileName() returns the name of the file containing the spec
+1 -1
View File
@@ -1,3 +1,3 @@
package types
const VERSION = "2.27.5"
const VERSION = "2.28.0"
+4
View File
@@ -1,3 +1,7 @@
## 1.39.1
Update all dependencies. This auto-updated the required version of Go to 1.24, consistent with the fact that Go 1.23 has been out of support for almost six months.
## 1.39.0
### Features
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types"
)
const GOMEGA_VERSION = "1.39.0"
const GOMEGA_VERSION = "1.39.1"
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
If you're using Ginkgo then you probably forgot to put your assertion in an It().