fix(deps): update module github.com/onsi/gomega to v1.36.0

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
renovate[bot]
2024-11-25 14:43:41 +00:00
committed by GitHub
parent 70c255955a
commit c2dcfca4ca
17 changed files with 553 additions and 86 deletions

2
go.mod
View File

@ -54,7 +54,7 @@ require (
github.com/moby/term v0.5.0 github.com/moby/term v0.5.0
github.com/nxadm/tail v1.4.11 github.com/nxadm/tail v1.4.11
github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/ginkgo/v2 v2.22.0
github.com/onsi/gomega v1.35.1 github.com/onsi/gomega v1.36.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/image-spec v1.1.0
github.com/opencontainers/runc v1.2.2 github.com/opencontainers/runc v1.2.2

4
go.sum
View File

@ -392,8 +392,8 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.36.0 h1:Pb12RlruUtj4XUuPUqeEWc6j5DkVVVA49Uf6YLfC95Y=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.36.0/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=

View File

@ -1,3 +1,12 @@
## 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 ## 1.35.1
### Fixes ### Fixes

View File

@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
const GOMEGA_VERSION = "1.35.1" const GOMEGA_VERSION = "1.36.0"
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler. 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(). If you're using Ginkgo then you probably forgot to put your assertion in an It().

View File

@ -4,17 +4,31 @@ package matchers
import ( import (
"fmt" "fmt"
"reflect"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
) )
type BeEmptyMatcher struct { type BeEmptyMatcher struct {
} }
func (matcher *BeEmptyMatcher) Match(actual interface{}) (success bool, err error) { 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) length, ok := lengthOf(actual)
if !ok { 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 return length == 0, nil

View File

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

View File

@ -8,6 +8,7 @@ import (
"reflect" "reflect"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
) )
type ContainElementMatcher struct { type ContainElementMatcher struct {
@ -16,16 +17,18 @@ type ContainElementMatcher struct {
} }
func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, err error) { func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) { if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
return false, fmt.Errorf("ContainElement matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1)) return false, fmt.Errorf("ContainElement matcher expects an array/slice/map/iterator. Got:\n%s", format.Object(actual, 1))
} }
var actualT reflect.Type var actualT reflect.Type
var result reflect.Value var result reflect.Value
switch l := len(matcher.Result); { switch numResultArgs := len(matcher.Result); {
case l > 1: case numResultArgs > 1:
return false, errors.New("ContainElement matcher expects at most a single optional pointer to store its findings at") 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 { 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", 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)) format.Object(matcher.Result[0], 1))
@ -34,93 +37,209 @@ func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, e
resultReference := matcher.Result[0] resultReference := matcher.Result[0]
result = reflect.ValueOf(resultReference).Elem() // what ResultReference points to, to stash away our findings result = reflect.ValueOf(resultReference).Elem() // what ResultReference points to, to stash away our findings
switch result.Kind() { 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", return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.SliceOf(actualT.Elem()).String(), result.Type().String()) 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", return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.MapOf(actualT.Key(), actualT.Elem()).String(), result.Type().String()) 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", case reflect.Map: // result map
actualT.String(), result.Type().String()) // can we assign elements in actual to elements in what the result
} // arg points to?
case reflect.Map: // - ✔ actual is a map
if !isMap(actual) { // - ✔ actual is an iter.Seq2 (iter.Seq doesn't fit though)
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s", switch {
actualT.String(), result.Type().String()) case isMap(actual):
} if !actualT.AssignableTo(result.Type()) {
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", return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String()) actualT.String(), result.Type().String())
} }
default: default:
if !actualT.Elem().AssignableTo(result.Type()) { // can we assign a (single) element in actual to what the result arg
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s", // points to?
actualT.Elem().String(), result.Type().String()) 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) elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
if !elementIsMatcher { if !elementIsMatcher {
elemMatcher = &EqualMatcher{Expected: matcher.Element} elemMatcher = &EqualMatcher{Expected: matcher.Element}
} }
value := reflect.ValueOf(actual) value := reflect.ValueOf(actual)
var valueAt func(int) interface{}
var getFindings func() reflect.Value var getFindings func() reflect.Value // abstracts how the findings are collected and stored
var foundAt func(int) var lastError error
if isMap(actual) { if !miter.IsIter(actual) {
keys := value.MapKeys() var valueAt func(int) interface{}
valueAt = func(i int) interface{} { var foundAt func(int)
return value.MapIndex(keys[i]).Interface() // 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
if result.Kind() != reflect.Invalid { // keys in case of maps).
fm := reflect.MakeMap(actualT) if isMap(actual) {
getFindings = func() reflect.Value { keys := value.MapKeys()
return fm valueAt = func(i int) interface{} {
return value.MapIndex(keys[i]).Interface()
} }
foundAt = func(i int) { if result.Kind() != reflect.Invalid {
fm.SetMapIndex(keys[i], value.MapIndex(keys[i])) 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 { } else {
valueAt = func(i int) interface{} { // We're dealing with an iterator as a first-class construct, so things
return value.Index(i).Interface() // 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 { if result.Kind() != reflect.Invalid {
var f reflect.Value if result.Kind() == reflect.Map {
if result.Kind() == reflect.Slice { fm := reflect.MakeMap(result.Type())
f = reflect.MakeSlice(result.Type(), 0, 0) getFindings = func() reflect.Value { return fm }
found = func(k, v reflect.Value) { fm.SetMapIndex(k, v) }
} else { } else {
f = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0) var fsl reflect.Value
} if result.Kind() == reflect.Slice {
getFindings = func() reflect.Value { fsl = reflect.MakeSlice(result.Type(), 0, 0)
return f } else {
} fsl = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)
foundAt = func(i int) { }
f = reflect.Append(f, value.Index(i)) getFindings = func() reflect.Value { return fsl }
found = func(_, v reflect.Value) { fsl = reflect.Append(fsl, v) }
} }
} }
}
var lastError error success := false
for i := 0; i < value.Len(); i++ { actualkT, _ := miter.IterKVTypes(actual)
elem := valueAt(i) if actualkT == nil {
success, err := elemMatcher.Match(elem) miter.IterateV(actual, func(v reflect.Value) bool {
if err != nil { var err error
lastError = err success, err = elemMatcher.Match(v.Interface())
continue 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 success && result.Kind() == reflect.Invalid {
if result.Kind() == reflect.Invalid { return true, nil
return true, nil
}
foundAt(i)
} }
} }

View File

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

View File

@ -5,6 +5,7 @@ import (
"reflect" "reflect"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
) )
type HaveEachMatcher struct { type HaveEachMatcher struct {
@ -12,8 +13,8 @@ type HaveEachMatcher struct {
} }
func (matcher *HaveEachMatcher) Match(actual interface{}) (success bool, err error) { func (matcher *HaveEachMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) { if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
return false, fmt.Errorf("HaveEach matcher expects an array/slice/map. Got:\n%s", return false, fmt.Errorf("HaveEach matcher expects an array/slice/map/iter.Seq/iter.Seq2. Got:\n%s",
format.Object(actual, 1)) format.Object(actual, 1))
} }
@ -22,6 +23,38 @@ func (matcher *HaveEachMatcher) Match(actual interface{}) (success bool, err err
elemMatcher = &EqualMatcher{Expected: matcher.Element} 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) value := reflect.ValueOf(actual)
if value.Len() == 0 { if value.Len() == 0 {
return false, fmt.Errorf("HaveEach matcher expects a non-empty array/slice/map. Got:\n%s", 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++ { for i := 0; i < value.Len(); i++ {
success, err := elemMatcher.Match(valueAt(i)) success, err := elemMatcher.Match(valueAt(i))
if err != nil { if err != nil {

View File

@ -2,8 +2,10 @@ package matchers
import ( import (
"fmt" "fmt"
"reflect"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
) )
type mismatchFailure struct { type mismatchFailure struct {
@ -21,17 +23,58 @@ type HaveExactElementsMatcher struct {
func (matcher *HaveExactElementsMatcher) Match(actual interface{}) (success bool, err error) { func (matcher *HaveExactElementsMatcher) Match(actual interface{}) (success bool, err error) {
matcher.resetState() matcher.resetState()
if isMap(actual) { if isMap(actual) || miter.IsSeq2(actual) {
return false, fmt.Errorf("error") 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) matchers := matchers(matcher.Elements)
values := valuesOf(actual)
lenMatchers := len(matchers) lenMatchers := len(matchers)
lenValues := len(values)
success = true 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++ { for i := 0; i < lenMatchers || i < lenValues; i++ {
if i >= lenMatchers { if i >= lenMatchers {
matcher.extraIndex = i matcher.extraIndex = i

View File

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

View File

@ -7,6 +7,7 @@ import (
"reflect" "reflect"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/internal/miter"
) )
type HaveKeyWithValueMatcher struct { type HaveKeyWithValueMatcher struct {
@ -15,8 +16,8 @@ type HaveKeyWithValueMatcher struct {
} }
func (matcher *HaveKeyWithValueMatcher) Match(actual interface{}) (success bool, err error) { func (matcher *HaveKeyWithValueMatcher) Match(actual interface{}) (success bool, err error) {
if !isMap(actual) { if !isMap(actual) && !miter.IsSeq2(actual) {
return false, fmt.Errorf("HaveKeyWithValue matcher expects a map. Got:%s", format.Object(actual, 1)) return false, fmt.Errorf("HaveKeyWithValue matcher expects a map/iter.Seq2. Got:%s", format.Object(actual, 1))
} }
keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher) keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher)
@ -29,6 +30,27 @@ func (matcher *HaveKeyWithValueMatcher) Match(actual interface{}) (success bool,
valueMatcher = &EqualMatcher{Expected: matcher.Value} 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() keys := reflect.ValueOf(actual).MapKeys()
for i := 0; i < len(keys); i++ { for i := 0; i < len(keys); i++ {
success, err := keyMatcher.Match(keys[i].Interface()) 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) { func (matcher *HaveLenMatcher) Match(actual interface{}) (success bool, err error) {
length, ok := lengthOf(actual) length, ok := lengthOf(actual)
if !ok { 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 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" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"github.com/onsi/gomega/matchers/internal/miter"
) )
type omegaMatcher interface { type omegaMatcher interface {
@ -152,6 +154,17 @@ func lengthOf(a interface{}) (int, bool) {
switch reflect.TypeOf(a).Kind() { switch reflect.TypeOf(a).Kind() {
case reflect.Map, reflect.Array, reflect.String, reflect.Chan, reflect.Slice: case reflect.Map, reflect.Array, reflect.String, reflect.Chan, reflect.Slice:
return reflect.ValueOf(a).Len(), true 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: default:
return 0, false return 0, false
} }

3
vendor/modules.txt vendored
View File

@ -873,7 +873,7 @@ github.com/onsi/ginkgo/v2/internal/parallel_support
github.com/onsi/ginkgo/v2/internal/testingtproxy github.com/onsi/ginkgo/v2/internal/testingtproxy
github.com/onsi/ginkgo/v2/reporters github.com/onsi/ginkgo/v2/reporters
github.com/onsi/ginkgo/v2/types github.com/onsi/ginkgo/v2/types
# github.com/onsi/gomega v1.35.1 # github.com/onsi/gomega v1.36.0
## explicit; go 1.22 ## explicit; go 1.22
github.com/onsi/gomega github.com/onsi/gomega
github.com/onsi/gomega/format github.com/onsi/gomega/format
@ -884,6 +884,7 @@ github.com/onsi/gomega/gstruct/errors
github.com/onsi/gomega/internal github.com/onsi/gomega/internal
github.com/onsi/gomega/internal/gutil github.com/onsi/gomega/internal/gutil
github.com/onsi/gomega/matchers github.com/onsi/gomega/matchers
github.com/onsi/gomega/matchers/internal/miter
github.com/onsi/gomega/matchers/support/goraph/bipartitegraph github.com/onsi/gomega/matchers/support/goraph/bipartitegraph
github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/edge
github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/node