build(deps): bump the golang group across 1 directory with 7 updates

Bumps the golang group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) | `0.12.7` | `0.12.9` |
| [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) | `2.20.2` | `2.22.0` |
| [github.com/onsi/gomega](https://github.com/onsi/gomega) | `1.34.2` | `1.36.0` |
| [github.com/opencontainers/selinux](https://github.com/opencontainers/selinux) | `1.11.0` | `1.11.1` |
| [github.com/safchain/ethtool](https://github.com/safchain/ethtool) | `0.4.1` | `0.5.9` |
| [sigs.k8s.io/knftables](https://github.com/kubernetes-sigs/knftables) | `0.0.17` | `0.0.18` |



Updates `github.com/Microsoft/hcsshim` from 0.12.7 to 0.12.9
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.7...v0.12.9)

Updates `github.com/onsi/ginkgo/v2` from 2.20.2 to 2.22.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.20.2...v2.22.0)

Updates `github.com/onsi/gomega` from 1.34.2 to 1.36.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.34.2...v1.36.0)

Updates `github.com/opencontainers/selinux` from 1.11.0 to 1.11.1
- [Release notes](https://github.com/opencontainers/selinux/releases)
- [Commits](https://github.com/opencontainers/selinux/compare/v1.11.0...v1.11.1)

Updates `github.com/safchain/ethtool` from 0.4.1 to 0.5.9
- [Release notes](https://github.com/safchain/ethtool/releases)
- [Commits](https://github.com/safchain/ethtool/compare/v0.4.1...v0.5.9)

Updates `golang.org/x/sys` from 0.26.0 to 0.27.0
- [Commits](https://github.com/golang/sys/compare/v0.26.0...v0.27.0)

Updates `sigs.k8s.io/knftables` from 0.0.17 to 0.0.18
- [Changelog](https://github.com/kubernetes-sigs/knftables/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes-sigs/knftables/compare/v0.0.17...v0.0.18)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/opencontainers/selinux
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/safchain/ethtool
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: sigs.k8s.io/knftables
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2024-12-02 02:02:42 +00:00
committed by Casey Callendrello
parent 6de8a9853c
commit 3ffc42cdfd
167 changed files with 20766 additions and 1075 deletions

View File

@ -1,3 +1,28 @@
## 2.22.0
### Features
- Add label to serial nodes [0fcaa08]
This allows serial tests to be filtered using the `label-filter`
### Maintenance
Various doc fixes
## 2.21.0
### Features
- add support for GINKGO_TIME_FORMAT [a69eb39]
- add GINKGO_NO_COLOR to disable colors via environment variables [bcab9c8]
### Fixes
- increase threshold in timeline matcher [e548367]
- Fix the document by replacing `SpecsThatWillBeRun` with `SpecsThatWillRun`
[c2c4d3c]
### Maintenance
- bump various dependencies [7e65a00]
## 2.20.2
Require Go 1.22+

View File

@ -82,6 +82,10 @@ func New(colorMode ColorMode) Formatter {
return fmt.Sprintf("\x1b[38;5;%dm", colorCode)
}
if _, noColor := os.LookupEnv("GINKGO_NO_COLOR"); noColor {
colorMode = ColorModeNone
}
f := Formatter{
ColorMode: colorMode,
colors: map[string]string{

View File

@ -241,6 +241,9 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
}
case t == reflect.TypeOf(Serial):
node.MarkedSerial = bool(arg.(serialType))
if !labelsSeen["Serial"] {
node.Labels = append(node.Labels, "Serial")
}
if !nodeType.Is(types.NodeTypesForContainerAndIt) {
appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Serial"))
}

View File

@ -95,7 +95,7 @@ For example:
})
It("should return the expected message", func() {
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
Expect(string(body)).To(Equal(message))
})

View File

@ -328,7 +328,7 @@ var ParallelConfigFlags = GinkgoFlags{
// ReporterConfigFlags provides flags for the Ginkgo test process, and CLI
var ReporterConfigFlags = GinkgoFlags{
{KeyPath: "R.NoColor", Name: "no-color", SectionKey: "output", DeprecatedName: "noColor", DeprecatedDocLink: "changed-command-line-flags",
Usage: "If set, suppress color output in default reporter."},
Usage: "If set, suppress color output in default reporter. You can also set the environment variable GINKGO_NO_COLOR=TRUE"},
{KeyPath: "R.Verbose", Name: "v", SectionKey: "output",
Usage: "If set, emits more output including GinkgoWriter contents."},
{KeyPath: "R.VeryVerbose", Name: "vv", SectionKey: "output",

View File

@ -3,13 +3,21 @@ package types
import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"time"
)
const GINKGO_FOCUS_EXIT_CODE = 197
const GINKGO_TIME_FORMAT = "01/02/06 15:04:05.999"
var GINKGO_TIME_FORMAT = "01/02/06 15:04:05.999"
func init() {
if os.Getenv("GINKGO_TIME_FORMAT") != "" {
GINKGO_TIME_FORMAT = os.Getenv("GINKGO_TIME_FORMAT")
}
}
// Report captures information about a Ginkgo test run
type Report struct {

View File

@ -1,3 +1,3 @@
package types
const VERSION = "2.20.2"
const VERSION = "2.22.0"

View File

@ -1,3 +1,32 @@
## 1.36.0
### Features
- new: make collection-related matchers Go 1.23 iterator aware [4c964c6]
### Maintenance
- Replace min/max helpers with built-in min/max [ece6872]
- Fix some typos in docs [8e924d7]
## 1.35.1
### Fixes
- Export EnforceDefaultTimeoutsWhenUsingContexts and DisableDefaultTimeoutsWhenUsingContext [ca36da1]
## 1.35.0
### Features
- You can now call `EnforceDefaultTimeoutsWhenUsingContexts()` to have `Eventually` honor the default timeout when passed a context. (prior to this you had to expclility add a timeout) [e4c4265]
- You can call `StopTrying(message).Successfully()` to abort a `Consistently` early without failure [eeca931]
### Fixes
- Stop memoizing the result of `HaveField` to avoid unexpected errors when used with async assertions. [3bdbc4e]
### Maintenance
- Bump all dependencies [a05a416]
## 1.34.2
Require Go 1.22+

View File

@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types"
)
const GOMEGA_VERSION = "1.34.2"
const GOMEGA_VERSION = "1.36.0"
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().
@ -319,7 +319,19 @@ you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in
Eventually(client.FetchCount).WithContext(ctx).WithArguments("/users").Should(BeNumerically(">=", 17))
}, SpecTimeout(time.Second))
Either way the context passd to Eventually is also passed to the underlying function. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit.
Either way the context pasesd to Eventually is also passed to the underlying function. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit.
By default, when a context is passed to Eventually *without* an explicit timeout, Gomega will rely solely on the context's cancellation to determine when to stop polling. If you want to specify a timeout in addition to the context you can do so using the .WithTimeout() method. For example:
Eventually(client.FetchCount).WithContext(ctx).WithTimeout(10*time.Second).Should(BeNumerically(">=", 17))
now either the context cacnellation or the timeout will cause Eventually to stop polling.
If, instead, you would like to opt out of this behavior and have Gomega's default timeouts govern Eventuallys that take a context you can call:
EnforceDefaultTimeoutsWhenUsingContexts()
in the DSL (or on a Gomega instance). Now all calls to Eventually that take a context will fail if eitehr the context is cancelled or the default timeout elapses.
**Category 3: Making assertions _in_ the function passed into Eventually**
@ -491,6 +503,16 @@ func SetDefaultConsistentlyPollingInterval(t time.Duration) {
Default.SetDefaultConsistentlyPollingInterval(t)
}
// EnforceDefaultTimeoutsWhenUsingContexts forces `Eventually` to apply a default timeout even when a context is provided.
func EnforceDefaultTimeoutsWhenUsingContexts() {
Default.EnforceDefaultTimeoutsWhenUsingContexts()
}
// DisableDefaultTimeoutsWhenUsingContext disables the default timeout when a context is provided to `Eventually`.
func DisableDefaultTimeoutsWhenUsingContext() {
Default.DisableDefaultTimeoutsWhenUsingContext()
}
// AsyncAssertion is returned by Eventually and Consistently and polls the actual value passed into Eventually against
// the matcher passed to the Should and ShouldNot methods.
//

View File

@ -335,7 +335,7 @@ func (assertion *AsyncAssertion) afterTimeout() <-chan time.Time {
if assertion.asyncType == AsyncAssertionTypeConsistently {
return time.After(assertion.g.DurationBundle.ConsistentlyDuration)
} else {
if assertion.ctx == nil {
if assertion.ctx == nil || assertion.g.DurationBundle.EnforceDefaultTimeoutsWhenUsingContexts {
return time.After(assertion.g.DurationBundle.EventuallyTimeout)
} else {
return nil
@ -496,7 +496,15 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
for _, err := range []error{actualErr, matcherErr} {
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
if pollingSignalErr.IsStopTrying() {
fail("Told to stop trying")
if pollingSignalErr.IsSuccessful() {
if assertion.asyncType == AsyncAssertionTypeEventually {
fail("Told to stop trying (and ignoring call to Successfully(), as it is only relevant with Consistently)")
} else {
return true // early escape hatch for Consistently
}
} else {
fail("Told to stop trying")
}
return false
}
if pollingSignalErr.IsTryAgainAfter() {

View File

@ -8,10 +8,11 @@ import (
)
type DurationBundle struct {
EventuallyTimeout time.Duration
EventuallyPollingInterval time.Duration
ConsistentlyDuration time.Duration
ConsistentlyPollingInterval time.Duration
EventuallyTimeout time.Duration
EventuallyPollingInterval time.Duration
ConsistentlyDuration time.Duration
ConsistentlyPollingInterval time.Duration
EnforceDefaultTimeoutsWhenUsingContexts bool
}
const (
@ -20,15 +21,19 @@ const (
ConsistentlyDurationEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_DURATION"
ConsistentlyPollingIntervalEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_POLLING_INTERVAL"
EnforceDefaultTimeoutsWhenUsingContextsEnvVarName = "GOMEGA_ENFORCE_DEFAULT_TIMEOUTS_WHEN_USING_CONTEXTS"
)
func FetchDefaultDurationBundle() DurationBundle {
_, EnforceDefaultTimeoutsWhenUsingContexts := os.LookupEnv(EnforceDefaultTimeoutsWhenUsingContextsEnvVarName)
return DurationBundle{
EventuallyTimeout: durationFromEnv(EventuallyTimeoutEnvVarName, time.Second),
EventuallyPollingInterval: durationFromEnv(EventuallyPollingIntervalEnvVarName, 10*time.Millisecond),
ConsistentlyDuration: durationFromEnv(ConsistentlyDurationEnvVarName, 100*time.Millisecond),
ConsistentlyPollingInterval: durationFromEnv(ConsistentlyPollingIntervalEnvVarName, 10*time.Millisecond),
ConsistentlyDuration: durationFromEnv(ConsistentlyDurationEnvVarName, 100*time.Millisecond),
ConsistentlyPollingInterval: durationFromEnv(ConsistentlyPollingIntervalEnvVarName, 10*time.Millisecond),
EnforceDefaultTimeoutsWhenUsingContexts: EnforceDefaultTimeoutsWhenUsingContexts,
}
}

View File

@ -127,3 +127,11 @@ func (g *Gomega) SetDefaultConsistentlyDuration(t time.Duration) {
func (g *Gomega) SetDefaultConsistentlyPollingInterval(t time.Duration) {
g.DurationBundle.ConsistentlyPollingInterval = t
}
func (g *Gomega) EnforceDefaultTimeoutsWhenUsingContexts() {
g.DurationBundle.EnforceDefaultTimeoutsWhenUsingContexts = true
}
func (g *Gomega) DisableDefaultTimeoutsWhenUsingContext() {
g.DurationBundle.EnforceDefaultTimeoutsWhenUsingContexts = false
}

View File

@ -17,6 +17,7 @@ type PollingSignalError interface {
error
Wrap(err error) PollingSignalError
Attach(description string, obj any) PollingSignalError
Successfully() PollingSignalError
Now()
}
@ -45,6 +46,7 @@ type PollingSignalErrorImpl struct {
wrappedErr error
pollingSignalErrorType PollingSignalErrorType
duration time.Duration
successful bool
Attachments []PollingSignalErrorAttachment
}
@ -73,6 +75,11 @@ func (s *PollingSignalErrorImpl) Unwrap() error {
return s.wrappedErr
}
func (s *PollingSignalErrorImpl) Successfully() PollingSignalError {
s.successful = true
return s
}
func (s *PollingSignalErrorImpl) Now() {
panic(s)
}
@ -81,6 +88,10 @@ func (s *PollingSignalErrorImpl) IsStopTrying() bool {
return s.pollingSignalErrorType == PollingSignalErrorTypeStopTrying
}
func (s *PollingSignalErrorImpl) IsSuccessful() bool {
return s.successful
}
func (s *PollingSignalErrorImpl) IsTryAgainAfter() bool {
return s.pollingSignalErrorType == PollingSignalErrorTypeTryAgainAfter
}

View File

@ -4,17 +4,31 @@ package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
)
type BeEmptyMatcher struct {
}
func (matcher *BeEmptyMatcher) Match(actual interface{}) (success bool, err error) {
// short-circuit the iterator case, as we only need to see the first
// element, if any.
if miter.IsIter(actual) {
var length int
if miter.IsSeq2(actual) {
miter.IterateKV(actual, func(k, v reflect.Value) bool { length++; return false })
} else {
miter.IterateV(actual, func(v reflect.Value) bool { length++; return false })
}
return length == 0, nil
}
length, ok := lengthOf(actual)
if !ok {
return false, fmt.Errorf("BeEmpty matcher expects a string/array/map/channel/slice. Got:\n%s", format.Object(actual, 1))
return false, fmt.Errorf("BeEmpty matcher expects a string/array/map/channel/slice/iterator. Got:\n%s", format.Object(actual, 1))
}
return length == 0, nil

View File

@ -7,6 +7,7 @@ import (
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
"github.com/onsi/gomega/matchers/support/goraph/bipartitegraph"
)
@ -17,8 +18,8 @@ type ConsistOfMatcher struct {
}
func (matcher *ConsistOfMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) {
return false, fmt.Errorf("ConsistOf matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1))
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
return false, fmt.Errorf("ConsistOf matcher expects an array/slice/map/iter.Seq/iter.Seq2. Got:\n%s", format.Object(actual, 1))
}
matchers := matchers(matcher.Elements)
@ -60,10 +61,21 @@ func equalMatchersToElements(matchers []interface{}) (elements []interface{}) {
}
func flatten(elems []interface{}) []interface{} {
if len(elems) != 1 || !isArrayOrSlice(elems[0]) {
if len(elems) != 1 ||
!(isArrayOrSlice(elems[0]) ||
(miter.IsIter(elems[0]) && !miter.IsSeq2(elems[0]))) {
return elems
}
if miter.IsIter(elems[0]) {
flattened := []any{}
miter.IterateV(elems[0], func(v reflect.Value) bool {
flattened = append(flattened, v.Interface())
return true
})
return flattened
}
value := reflect.ValueOf(elems[0])
flattened := make([]interface{}, value.Len())
for i := 0; i < value.Len(); i++ {
@ -116,7 +128,19 @@ func presentable(elems []interface{}) interface{} {
func valuesOf(actual interface{}) []interface{} {
value := reflect.ValueOf(actual)
values := []interface{}{}
if isMap(actual) {
if miter.IsIter(actual) {
if miter.IsSeq2(actual) {
miter.IterateKV(actual, func(k, v reflect.Value) bool {
values = append(values, v.Interface())
return true
})
} else {
miter.IterateV(actual, func(v reflect.Value) bool {
values = append(values, v.Interface())
return true
})
}
} else if isMap(actual) {
keys := value.MapKeys()
for i := 0; i < value.Len(); i++ {
values = append(values, value.MapIndex(keys[i]).Interface())

View File

@ -8,6 +8,7 @@ import (
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
)
type ContainElementMatcher struct {
@ -16,16 +17,18 @@ type ContainElementMatcher struct {
}
func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) {
return false, fmt.Errorf("ContainElement matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1))
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
return false, fmt.Errorf("ContainElement matcher expects an array/slice/map/iterator. Got:\n%s", format.Object(actual, 1))
}
var actualT reflect.Type
var result reflect.Value
switch l := len(matcher.Result); {
case l > 1:
switch numResultArgs := len(matcher.Result); {
case numResultArgs > 1:
return false, errors.New("ContainElement matcher expects at most a single optional pointer to store its findings at")
case l == 1:
case numResultArgs == 1:
// Check the optional result arg to point to a single value/array/slice/map
// of a type compatible with the actual value.
if reflect.ValueOf(matcher.Result[0]).Kind() != reflect.Ptr {
return false, fmt.Errorf("ContainElement matcher expects a non-nil pointer to store its findings at. Got\n%s",
format.Object(matcher.Result[0], 1))
@ -34,93 +37,209 @@ func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, e
resultReference := matcher.Result[0]
result = reflect.ValueOf(resultReference).Elem() // what ResultReference points to, to stash away our findings
switch result.Kind() {
case reflect.Array:
case reflect.Array: // result arrays are not supported, as they cannot be dynamically sized.
if miter.IsIter(actual) {
_, actualvT := miter.IterKVTypes(actual)
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.SliceOf(actualvT), result.Type().String())
}
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.SliceOf(actualT.Elem()).String(), result.Type().String())
case reflect.Slice:
if !isArrayOrSlice(actual) {
case reflect.Slice: // result slice
// can we assign elements in actual to elements in what the result
// arg points to?
// - ✔ actual is an array or slice
// - ✔ actual is an iter.Seq producing "v" elements
// - ✔ actual is an iter.Seq2 producing "v" elements, ignoring
// the "k" elements.
switch {
case isArrayOrSlice(actual):
if !actualT.Elem().AssignableTo(result.Type().Elem()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
case miter.IsIter(actual):
_, actualvT := miter.IterKVTypes(actual)
if !actualvT.AssignableTo(result.Type().Elem()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualvT.String(), result.Type().String())
}
default: // incompatible result reference
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.MapOf(actualT.Key(), actualT.Elem()).String(), result.Type().String())
}
if !actualT.Elem().AssignableTo(result.Type().Elem()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
case reflect.Map:
if !isMap(actual) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
if !actualT.AssignableTo(result.Type()) {
case reflect.Map: // result map
// can we assign elements in actual to elements in what the result
// arg points to?
// - ✔ actual is a map
// - ✔ actual is an iter.Seq2 (iter.Seq doesn't fit though)
switch {
case isMap(actual):
if !actualT.AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
case miter.IsIter(actual):
actualkT, actualvT := miter.IterKVTypes(actual)
if actualkT == nil {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.SliceOf(actualvT).String(), result.Type().String())
}
if !reflect.MapOf(actualkT, actualvT).AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.MapOf(actualkT, actualvT), result.Type().String())
}
default: // incompatible result reference
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
default:
if !actualT.Elem().AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.Elem().String(), result.Type().String())
// can we assign a (single) element in actual to what the result arg
// points to?
switch {
case miter.IsIter(actual):
_, actualvT := miter.IterKVTypes(actual)
if !actualvT.AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualvT.String(), result.Type().String())
}
default:
if !actualT.Elem().AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.Elem().String(), result.Type().String())
}
}
}
}
// If the supplied matcher isn't an Omega matcher, default to the Equal
// matcher.
elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
if !elementIsMatcher {
elemMatcher = &EqualMatcher{Expected: matcher.Element}
}
value := reflect.ValueOf(actual)
var valueAt func(int) interface{}
var getFindings func() reflect.Value
var foundAt func(int)
var getFindings func() reflect.Value // abstracts how the findings are collected and stored
var lastError error
if isMap(actual) {
keys := value.MapKeys()
valueAt = func(i int) interface{} {
return value.MapIndex(keys[i]).Interface()
}
if result.Kind() != reflect.Invalid {
fm := reflect.MakeMap(actualT)
getFindings = func() reflect.Value {
return fm
if !miter.IsIter(actual) {
var valueAt func(int) interface{}
var foundAt func(int)
// We're dealing with an array/slice/map, so in all cases we can iterate
// over the elements in actual using indices (that can be considered
// keys in case of maps).
if isMap(actual) {
keys := value.MapKeys()
valueAt = func(i int) interface{} {
return value.MapIndex(keys[i]).Interface()
}
foundAt = func(i int) {
fm.SetMapIndex(keys[i], value.MapIndex(keys[i]))
if result.Kind() != reflect.Invalid {
fm := reflect.MakeMap(actualT)
getFindings = func() reflect.Value { return fm }
foundAt = func(i int) {
fm.SetMapIndex(keys[i], value.MapIndex(keys[i]))
}
}
} else {
valueAt = func(i int) interface{} {
return value.Index(i).Interface()
}
if result.Kind() != reflect.Invalid {
var fsl reflect.Value
if result.Kind() == reflect.Slice {
fsl = reflect.MakeSlice(result.Type(), 0, 0)
} else {
fsl = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)
}
getFindings = func() reflect.Value { return fsl }
foundAt = func(i int) {
fsl = reflect.Append(fsl, value.Index(i))
}
}
}
for i := 0; i < value.Len(); i++ {
elem := valueAt(i)
success, err := elemMatcher.Match(elem)
if err != nil {
lastError = err
continue
}
if success {
if result.Kind() == reflect.Invalid {
return true, nil
}
foundAt(i)
}
}
} else {
valueAt = func(i int) interface{} {
return value.Index(i).Interface()
}
// We're dealing with an iterator as a first-class construct, so things
// are slightly different: there is no index defined as in case of
// arrays/slices/maps, just "ooooorder"
var found func(k, v reflect.Value)
if result.Kind() != reflect.Invalid {
var f reflect.Value
if result.Kind() == reflect.Slice {
f = reflect.MakeSlice(result.Type(), 0, 0)
if result.Kind() == reflect.Map {
fm := reflect.MakeMap(result.Type())
getFindings = func() reflect.Value { return fm }
found = func(k, v reflect.Value) { fm.SetMapIndex(k, v) }
} else {
f = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)
}
getFindings = func() reflect.Value {
return f
}
foundAt = func(i int) {
f = reflect.Append(f, value.Index(i))
var fsl reflect.Value
if result.Kind() == reflect.Slice {
fsl = reflect.MakeSlice(result.Type(), 0, 0)
} else {
fsl = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)
}
getFindings = func() reflect.Value { return fsl }
found = func(_, v reflect.Value) { fsl = reflect.Append(fsl, v) }
}
}
}
var lastError error
for i := 0; i < value.Len(); i++ {
elem := valueAt(i)
success, err := elemMatcher.Match(elem)
if err != nil {
lastError = err
continue
success := false
actualkT, _ := miter.IterKVTypes(actual)
if actualkT == nil {
miter.IterateV(actual, func(v reflect.Value) bool {
var err error
success, err = elemMatcher.Match(v.Interface())
if err != nil {
lastError = err
return true // iterate on...
}
if success {
if result.Kind() == reflect.Invalid {
return false // a match and no result needed, so we're done
}
found(reflect.Value{}, v)
}
return true // iterate on...
})
} else {
miter.IterateKV(actual, func(k, v reflect.Value) bool {
var err error
success, err = elemMatcher.Match(v.Interface())
if err != nil {
lastError = err
return true // iterate on...
}
if success {
if result.Kind() == reflect.Invalid {
return false // a match and no result needed, so we're done
}
found(k, v)
}
return true // iterate on...
})
}
if success {
if result.Kind() == reflect.Invalid {
return true, nil
}
foundAt(i)
if success && result.Kind() == reflect.Invalid {
return true, nil
}
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
"github.com/onsi/gomega/matchers/support/goraph/bipartitegraph"
)
@ -13,8 +14,8 @@ type ContainElementsMatcher struct {
}
func (matcher *ContainElementsMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) {
return false, fmt.Errorf("ContainElements matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1))
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
return false, fmt.Errorf("ContainElements matcher expects an array/slice/map/iter.Seq/iter.Seq2. Got:\n%s", format.Object(actual, 1))
}
matchers := matchers(matcher.Elements)

View File

@ -5,6 +5,7 @@ import (
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
)
type HaveEachMatcher struct {
@ -12,8 +13,8 @@ type HaveEachMatcher struct {
}
func (matcher *HaveEachMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) {
return false, fmt.Errorf("HaveEach matcher expects an array/slice/map. Got:\n%s",
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
return false, fmt.Errorf("HaveEach matcher expects an array/slice/map/iter.Seq/iter.Seq2. Got:\n%s",
format.Object(actual, 1))
}
@ -22,6 +23,38 @@ func (matcher *HaveEachMatcher) Match(actual interface{}) (success bool, err err
elemMatcher = &EqualMatcher{Expected: matcher.Element}
}
if miter.IsIter(actual) {
// rejecting the non-elements case works different for iterators as we
// don't want to fetch all elements into a slice first.
count := 0
var success bool
var err error
if miter.IsSeq2(actual) {
miter.IterateKV(actual, func(k, v reflect.Value) bool {
count++
success, err = elemMatcher.Match(v.Interface())
if err != nil {
return false
}
return success
})
} else {
miter.IterateV(actual, func(v reflect.Value) bool {
count++
success, err = elemMatcher.Match(v.Interface())
if err != nil {
return false
}
return success
})
}
if count == 0 {
return false, fmt.Errorf("HaveEach matcher expects a non-empty iter.Seq/iter.Seq2. Got:\n%s",
format.Object(actual, 1))
}
return success, err
}
value := reflect.ValueOf(actual)
if value.Len() == 0 {
return false, fmt.Errorf("HaveEach matcher expects a non-empty array/slice/map. Got:\n%s",
@ -40,7 +73,8 @@ func (matcher *HaveEachMatcher) Match(actual interface{}) (success bool, err err
}
}
// if there are no elements, then HaveEach will match.
// if we never failed then we succeed; the empty/nil cases have already been
// rejected above.
for i := 0; i < value.Len(); i++ {
success, err := elemMatcher.Match(valueAt(i))
if err != nil {

View File

@ -2,8 +2,10 @@ package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
)
type mismatchFailure struct {
@ -21,17 +23,58 @@ type HaveExactElementsMatcher struct {
func (matcher *HaveExactElementsMatcher) Match(actual interface{}) (success bool, err error) {
matcher.resetState()
if isMap(actual) {
return false, fmt.Errorf("error")
if isMap(actual) || miter.IsSeq2(actual) {
return false, fmt.Errorf("HaveExactElements matcher doesn't work on map or iter.Seq2. Got:\n%s", format.Object(actual, 1))
}
matchers := matchers(matcher.Elements)
values := valuesOf(actual)
lenMatchers := len(matchers)
lenValues := len(values)
success = true
if miter.IsIter(actual) {
// In the worst case, we need to see everything before we can give our
// verdict. The only exception is fast fail.
i := 0
miter.IterateV(actual, func(v reflect.Value) bool {
if i >= lenMatchers {
// the iterator produces more values than we got matchers: this
// is not good.
matcher.extraIndex = i
success = false
return false
}
elemMatcher := matchers[i].(omegaMatcher)
match, err := elemMatcher.Match(v.Interface())
if err != nil {
matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{
index: i,
failure: err.Error(),
})
success = false
} else if !match {
matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{
index: i,
failure: elemMatcher.FailureMessage(v.Interface()),
})
success = false
}
i++
return true
})
if i < len(matchers) {
// the iterator produced less values than we got matchers: this is
// no good, no no no.
matcher.missingIndex = i
success = false
}
return success, nil
}
values := valuesOf(actual)
lenValues := len(values)
for i := 0; i < lenMatchers || i < lenValues; i++ {
if i >= lenMatchers {
matcher.extraIndex = i

View File

@ -17,7 +17,7 @@ func (e missingFieldError) Error() string {
return string(e)
}
func extractField(actual interface{}, field string, matchername string) (interface{}, error) {
func extractField(actual interface{}, field string, matchername string) (any, error) {
fields := strings.SplitN(field, ".", 2)
actualValue := reflect.ValueOf(actual)
@ -64,36 +64,46 @@ func extractField(actual interface{}, field string, matchername string) (interfa
type HaveFieldMatcher struct {
Field string
Expected interface{}
}
extractedField interface{}
expectedMatcher omegaMatcher
func (matcher *HaveFieldMatcher) expectedMatcher() omegaMatcher {
var isMatcher bool
expectedMatcher, isMatcher := matcher.Expected.(omegaMatcher)
if !isMatcher {
expectedMatcher = &EqualMatcher{Expected: matcher.Expected}
}
return expectedMatcher
}
func (matcher *HaveFieldMatcher) Match(actual interface{}) (success bool, err error) {
matcher.extractedField, err = extractField(actual, matcher.Field, "HaveField")
extractedField, err := extractField(actual, matcher.Field, "HaveField")
if err != nil {
return false, err
}
var isMatcher bool
matcher.expectedMatcher, isMatcher = matcher.Expected.(omegaMatcher)
if !isMatcher {
matcher.expectedMatcher = &EqualMatcher{Expected: matcher.Expected}
}
return matcher.expectedMatcher.Match(matcher.extractedField)
return matcher.expectedMatcher().Match(extractedField)
}
func (matcher *HaveFieldMatcher) FailureMessage(actual interface{}) (message string) {
extractedField, err := extractField(actual, matcher.Field, "HaveField")
if err != nil {
// this really shouldn't happen
return fmt.Sprintf("Failed to extract field '%s': %s", matcher.Field, err)
}
message = fmt.Sprintf("Value for field '%s' failed to satisfy matcher.\n", matcher.Field)
message += matcher.expectedMatcher.FailureMessage(matcher.extractedField)
message += matcher.expectedMatcher().FailureMessage(extractedField)
return message
}
func (matcher *HaveFieldMatcher) NegatedFailureMessage(actual interface{}) (message string) {
extractedField, err := extractField(actual, matcher.Field, "HaveField")
if err != nil {
// this really shouldn't happen
return fmt.Sprintf("Failed to extract field '%s': %s", matcher.Field, err)
}
message = fmt.Sprintf("Value for field '%s' satisfied matcher, but should not have.\n", matcher.Field)
message += matcher.expectedMatcher.NegatedFailureMessage(matcher.extractedField)
message += matcher.expectedMatcher().NegatedFailureMessage(extractedField)
return message
}

View File

@ -7,6 +7,7 @@ import (
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
)
type HaveKeyMatcher struct {
@ -14,8 +15,8 @@ type HaveKeyMatcher struct {
}
func (matcher *HaveKeyMatcher) Match(actual interface{}) (success bool, err error) {
if !isMap(actual) {
return false, fmt.Errorf("HaveKey matcher expects a map. Got:%s", format.Object(actual, 1))
if !isMap(actual) && !miter.IsSeq2(actual) {
return false, fmt.Errorf("HaveKey matcher expects a map/iter.Seq2. Got:%s", format.Object(actual, 1))
}
keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher)
@ -23,6 +24,20 @@ func (matcher *HaveKeyMatcher) Match(actual interface{}) (success bool, err erro
keyMatcher = &EqualMatcher{Expected: matcher.Key}
}
if miter.IsSeq2(actual) {
var success bool
var err error
miter.IterateKV(actual, func(k, v reflect.Value) bool {
success, err = keyMatcher.Match(k.Interface())
if err != nil {
err = fmt.Errorf("HaveKey's key matcher failed with:\n%s%s", format.Indent, err.Error())
return false
}
return !success
})
return success, err
}
keys := reflect.ValueOf(actual).MapKeys()
for i := 0; i < len(keys); i++ {
success, err := keyMatcher.Match(keys[i].Interface())

View File

@ -7,6 +7,7 @@ import (
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
)
type HaveKeyWithValueMatcher struct {
@ -15,8 +16,8 @@ type HaveKeyWithValueMatcher struct {
}
func (matcher *HaveKeyWithValueMatcher) Match(actual interface{}) (success bool, err error) {
if !isMap(actual) {
return false, fmt.Errorf("HaveKeyWithValue matcher expects a map. Got:%s", format.Object(actual, 1))
if !isMap(actual) && !miter.IsSeq2(actual) {
return false, fmt.Errorf("HaveKeyWithValue matcher expects a map/iter.Seq2. Got:%s", format.Object(actual, 1))
}
keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher)
@ -29,6 +30,27 @@ func (matcher *HaveKeyWithValueMatcher) Match(actual interface{}) (success bool,
valueMatcher = &EqualMatcher{Expected: matcher.Value}
}
if miter.IsSeq2(actual) {
var success bool
var err error
miter.IterateKV(actual, func(k, v reflect.Value) bool {
success, err = keyMatcher.Match(k.Interface())
if err != nil {
err = fmt.Errorf("HaveKey's key matcher failed with:\n%s%s", format.Indent, err.Error())
return false
}
if success {
success, err = valueMatcher.Match(v.Interface())
if err != nil {
err = fmt.Errorf("HaveKeyWithValue's value matcher failed with:\n%s%s", format.Indent, err.Error())
return false
}
}
return !success
})
return success, err
}
keys := reflect.ValueOf(actual).MapKeys()
for i := 0; i < len(keys); i++ {
success, err := keyMatcher.Match(keys[i].Interface())

View File

@ -13,7 +13,7 @@ type HaveLenMatcher struct {
func (matcher *HaveLenMatcher) Match(actual interface{}) (success bool, err error) {
length, ok := lengthOf(actual)
if !ok {
return false, fmt.Errorf("HaveLen matcher expects a string/array/map/channel/slice. Got:\n%s", format.Object(actual, 1))
return false, fmt.Errorf("HaveLen matcher expects a string/array/map/channel/slice/iterator. Got:\n%s", format.Object(actual, 1))
}
return length == matcher.Count, nil

View File

@ -0,0 +1,128 @@
//go:build go1.23
package miter
import (
"reflect"
)
// HasIterators always returns false for Go versions before 1.23.
func HasIterators() bool { return true }
// IsIter returns true if the specified value is a function type that can be
// range-d over, otherwise false.
//
// We don't use reflect's CanSeq and CanSeq2 directly, as these would return
// true also for other value types that are range-able, such as integers,
// slices, et cetera. Here, we aim only at range-able (iterator) functions.
func IsIter(it any) bool {
if it == nil { // on purpose we only test for untyped nil.
return false
}
// reject all non-iterator-func values, even if they're range-able.
t := reflect.TypeOf(it)
if t.Kind() != reflect.Func {
return false
}
return t.CanSeq() || t.CanSeq2()
}
// IterKVTypes returns the reflection types of an iterator's yield function's K
// and optional V arguments, otherwise nil K and V reflection types.
func IterKVTypes(it any) (k, v reflect.Type) {
if it == nil {
return
}
// reject all non-iterator-func values, even if they're range-able.
t := reflect.TypeOf(it)
if t.Kind() != reflect.Func {
return
}
// get the reflection types for V, and where applicable, K.
switch {
case t.CanSeq():
v = t. /*iterator fn*/ In(0). /*yield fn*/ In(0)
case t.CanSeq2():
yieldfn := t. /*iterator fn*/ In(0)
k = yieldfn.In(0)
v = yieldfn.In(1)
}
return
}
// IsSeq2 returns true if the passed iterator function is compatible with
// iter.Seq2, otherwise false.
//
// IsSeq2 hides the Go 1.23+ specific reflect.Type.CanSeq2 behind a facade which
// is empty for Go versions before 1.23.
func IsSeq2(it any) bool {
if it == nil {
return false
}
t := reflect.TypeOf(it)
return t.Kind() == reflect.Func && t.CanSeq2()
}
// isNilly returns true if v is either an untyped nil, or is a nil function (not
// necessarily an iterator function).
func isNilly(v any) bool {
if v == nil {
return true
}
rv := reflect.ValueOf(v)
return rv.Kind() == reflect.Func && rv.IsNil()
}
// IterateV loops over the elements produced by an iterator function, passing
// the elements to the specified yield function individually and stopping only
// when either the iterator function runs out of elements or the yield function
// tell us to stop it.
//
// IterateV works very much like reflect.Value.Seq but hides the Go 1.23+
// specific parts behind a facade which is empty for Go versions before 1.23, in
// order to simplify code maintenance for matchers when using older Go versions.
func IterateV(it any, yield func(v reflect.Value) bool) {
if isNilly(it) {
return
}
// reject all non-iterator-func values, even if they're range-able.
t := reflect.TypeOf(it)
if t.Kind() != reflect.Func || !t.CanSeq() {
return
}
// Call the specified iterator function, handing it our adaptor to call the
// specified generic reflection yield function.
reflectedYield := reflect.MakeFunc(
t. /*iterator fn*/ In(0),
func(args []reflect.Value) []reflect.Value {
return []reflect.Value{reflect.ValueOf(yield(args[0]))}
})
reflect.ValueOf(it).Call([]reflect.Value{reflectedYield})
}
// IterateKV loops over the key-value elements produced by an iterator function,
// passing the elements to the specified yield function individually and
// stopping only when either the iterator function runs out of elements or the
// yield function tell us to stop it.
//
// IterateKV works very much like reflect.Value.Seq2 but hides the Go 1.23+
// specific parts behind a facade which is empty for Go versions before 1.23, in
// order to simplify code maintenance for matchers when using older Go versions.
func IterateKV(it any, yield func(k, v reflect.Value) bool) {
if isNilly(it) {
return
}
// reject all non-iterator-func values, even if they're range-able.
t := reflect.TypeOf(it)
if t.Kind() != reflect.Func || !t.CanSeq2() {
return
}
// Call the specified iterator function, handing it our adaptor to call the
// specified generic reflection yield function.
reflectedYield := reflect.MakeFunc(
t. /*iterator fn*/ In(0),
func(args []reflect.Value) []reflect.Value {
return []reflect.Value{reflect.ValueOf(yield(args[0], args[1]))}
})
reflect.ValueOf(it).Call([]reflect.Value{reflectedYield})
}

View File

@ -0,0 +1,44 @@
//go:build !go1.23
/*
Gomega matchers
This package implements the Gomega matchers and does not typically need to be imported.
See the docs for Gomega for documentation on the matchers
http://onsi.github.io/gomega/
*/
package miter
import "reflect"
// HasIterators always returns false for Go versions before 1.23.
func HasIterators() bool { return false }
// IsIter always returns false for Go versions before 1.23 as there is no
// iterator (function) pattern defined yet; see also:
// https://tip.golang.org/blog/range-functions.
func IsIter(i any) bool { return false }
// IsSeq2 always returns false for Go versions before 1.23 as there is no
// iterator (function) pattern defined yet; see also:
// https://tip.golang.org/blog/range-functions.
func IsSeq2(it any) bool { return false }
// IterKVTypes always returns nil reflection types for Go versions before 1.23
// as there is no iterator (function) pattern defined yet; see also:
// https://tip.golang.org/blog/range-functions.
func IterKVTypes(i any) (k, v reflect.Type) {
return
}
// IterateV never loops over what has been passed to it as an iterator for Go
// versions before 1.23 as there is no iterator (function) pattern defined yet;
// see also: https://tip.golang.org/blog/range-functions.
func IterateV(it any, yield func(v reflect.Value) bool) {}
// IterateKV never loops over what has been passed to it as an iterator for Go
// versions before 1.23 as there is no iterator (function) pattern defined yet;
// see also: https://tip.golang.org/blog/range-functions.
func IterateKV(it any, yield func(k, v reflect.Value) bool) {}

View File

@ -15,6 +15,8 @@ import (
"encoding/json"
"fmt"
"reflect"
"github.com/onsi/gomega/matchers/internal/miter"
)
type omegaMatcher interface {
@ -152,6 +154,17 @@ func lengthOf(a interface{}) (int, bool) {
switch reflect.TypeOf(a).Kind() {
case reflect.Map, reflect.Array, reflect.String, reflect.Chan, reflect.Slice:
return reflect.ValueOf(a).Len(), true
case reflect.Func:
if !miter.IsIter(a) {
return 0, false
}
var l int
if miter.IsSeq2(a) {
miter.IterateKV(a, func(k, v reflect.Value) bool { l++; return true })
} else {
miter.IterateV(a, func(v reflect.Value) bool { l++; return true })
}
return l, true
default:
return 0, false
}

View File

@ -29,6 +29,8 @@ type Gomega interface {
SetDefaultEventuallyPollingInterval(time.Duration)
SetDefaultConsistentlyDuration(time.Duration)
SetDefaultConsistentlyPollingInterval(time.Duration)
EnforceDefaultTimeoutsWhenUsingContexts()
DisableDefaultTimeoutsWhenUsingContext()
}
// All Gomega matchers must implement the GomegaMatcher interface