rebase: bump github.com/onsi/gomega from 1.22.1 to 1.23.0

Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.22.1 to 1.23.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.22.1...v1.23.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2022-11-01 15:23:27 +00:00 committed by mergify[bot]
parent 7b663279bf
commit 7ca2468d80
22 changed files with 570 additions and 410 deletions

4
go.mod
View File

@ -23,7 +23,7 @@ require (
github.com/kubernetes-csi/external-snapshotter/client/v6 v6.0.1 github.com/kubernetes-csi/external-snapshotter/client/v6 v6.0.1
github.com/libopenstorage/secrets v0.0.0-20210908194121-a1d19aa9713a github.com/libopenstorage/secrets v0.0.0-20210908194121-a1d19aa9713a
github.com/onsi/ginkgo/v2 v2.4.0 github.com/onsi/ginkgo/v2 v2.4.0
github.com/onsi/gomega v1.22.1 github.com/onsi/gomega v1.23.0
github.com/pkg/xattr v0.4.7 github.com/pkg/xattr v0.4.7
github.com/prometheus/client_golang v1.12.2 github.com/prometheus/client_golang v1.12.2
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
@ -81,7 +81,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.8 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.1.0 // indirect github.com/google/gofuzz v1.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect

6
go.sum
View File

@ -500,8 +500,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-metrics-stackdriver v0.2.0/go.mod h1:KLcPyp3dWJAFD+yHisGlJSZktIsTjb50eB72U2YZ9K0= github.com/google/go-metrics-stackdriver v0.2.0/go.mod h1:KLcPyp3dWJAFD+yHisGlJSZktIsTjb50eB72U2YZ9K0=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@ -953,8 +954,9 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI=
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
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=

View File

@ -13,21 +13,21 @@
// //
// The primary features of cmp are: // The primary features of cmp are:
// //
// • When the default behavior of equality does not suit the needs of the test, // - When the default behavior of equality does not suit the test's needs,
// custom equality functions can override the equality operation. // custom equality functions can override the equality operation.
// For example, an equality function may report floats as equal so long as they // For example, an equality function may report floats as equal so long as
// are within some tolerance of each other. // they are within some tolerance of each other.
// //
// • Types that have an Equal method may use that method to determine equality. // - Types with an Equal method may use that method to determine equality.
// This allows package authors to determine the equality operation for the types // This allows package authors to determine the equality operation
// that they define. // for the types that they define.
// //
// If no custom equality functions are used and no Equal method is defined, // - If no custom equality functions are used and no Equal method is defined,
// equality is determined by recursively comparing the primitive kinds on both // equality is determined by recursively comparing the primitive kinds on
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported // both values, much like reflect.DeepEqual. Unlike reflect.DeepEqual,
// fields are not compared by default; they result in panics unless suppressed // unexported fields are not compared by default; they result in panics
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly // unless suppressed by using an Ignore option (see cmpopts.IgnoreUnexported)
// compared using the Exporter option. // or explicitly compared using the Exporter option.
package cmp package cmp
import ( import (
@ -45,25 +45,25 @@ import (
// Equal reports whether x and y are equal by recursively applying the // Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values: // following rules in the given order to x and y and all of their sub-values:
// //
// Let S be the set of all Ignore, Transformer, and Comparer options that // - Let S be the set of all Ignore, Transformer, and Comparer options that
// remain after applying all path filters, value filters, and type filters. // remain after applying all path filters, value filters, and type filters.
// If at least one Ignore exists in S, then the comparison is ignored. // If at least one Ignore exists in S, then the comparison is ignored.
// If the number of Transformer and Comparer options in S is greater than one, // If the number of Transformer and Comparer options in S is non-zero,
// then Equal panics because it is ambiguous which option to use. // then Equal panics because it is ambiguous which option to use.
// If S contains a single Transformer, then use that to transform the current // If S contains a single Transformer, then use that to transform
// values and recursively call Equal on the output values. // the current values and recursively call Equal on the output values.
// If S contains a single Comparer, then use that to compare the current values. // If S contains a single Comparer, then use that to compare the current values.
// Otherwise, evaluation proceeds to the next rule. // Otherwise, evaluation proceeds to the next rule.
// //
// If the values have an Equal method of the form "(T) Equal(T) bool" or // - If the values have an Equal method of the form "(T) Equal(T) bool" or
// "(T) Equal(I) bool" where T is assignable to I, then use the result of // "(T) Equal(I) bool" where T is assignable to I, then use the result of
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and // x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
// evaluation proceeds to the next rule. // evaluation proceeds to the next rule.
// //
// Lastly, try to compare x and y based on their basic kinds. // - Lastly, try to compare x and y based on their basic kinds.
// Simple kinds like booleans, integers, floats, complex numbers, strings, and // Simple kinds like booleans, integers, floats, complex numbers, strings,
// channels are compared using the equivalent of the == operator in Go. // and channels are compared using the equivalent of the == operator in Go.
// Functions are only equal if they are both nil, otherwise they are unequal. // Functions are only equal if they are both nil, otherwise they are unequal.
// //
// Structs are equal if recursively calling Equal on all fields report equal. // Structs are equal if recursively calling Equal on all fields report equal.
// If a struct contains unexported fields, Equal panics unless an Ignore option // If a struct contains unexported fields, Equal panics unless an Ignore option
@ -144,7 +144,7 @@ func rootStep(x, y interface{}) PathStep {
// so that they have the same parent type. // so that they have the same parent type.
var t reflect.Type var t reflect.Type
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
t = reflect.TypeOf((*interface{})(nil)).Elem() t = anyType
if vx.IsValid() { if vx.IsValid() {
vvx := reflect.New(t).Elem() vvx := reflect.New(t).Elem()
vvx.Set(vx) vvx.Set(vx)
@ -639,7 +639,9 @@ type dynChecker struct{ curr, next int }
// Next increments the state and reports whether a check should be performed. // Next increments the state and reports whether a check should be performed.
// //
// Checks occur every Nth function call, where N is a triangular number: // Checks occur every Nth function call, where N is a triangular number:
//
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ... // 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
//
// See https://en.wikipedia.org/wiki/Triangular_number // See https://en.wikipedia.org/wiki/Triangular_number
// //
// This sequence ensures that the cost of checks drops significantly as // This sequence ensures that the cost of checks drops significantly as

View File

@ -127,9 +127,9 @@ var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
// This function returns an edit-script, which is a sequence of operations // This function returns an edit-script, which is a sequence of operations
// needed to convert one list into the other. The following invariants for // needed to convert one list into the other. The following invariants for
// the edit-script are maintained: // the edit-script are maintained:
// eq == (es.Dist()==0) // - eq == (es.Dist()==0)
// nx == es.LenX() // - nx == es.LenX()
// ny == es.LenY() // - ny == es.LenY()
// //
// This algorithm is not guaranteed to be an optimal solution (i.e., one that // This algorithm is not guaranteed to be an optimal solution (i.e., one that
// produces an edit-script with a minimal Levenshtein distance). This algorithm // produces an edit-script with a minimal Levenshtein distance). This algorithm
@ -169,12 +169,13 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// A diagonal edge is equivalent to a matching symbol between both X and Y. // A diagonal edge is equivalent to a matching symbol between both X and Y.
// Invariants: // Invariants:
// 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx // - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
// 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny // - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
// //
// In general: // In general:
// • fwdFrontier.X < revFrontier.X // - fwdFrontier.X < revFrontier.X
// • fwdFrontier.Y < revFrontier.Y // - fwdFrontier.Y < revFrontier.Y
//
// Unless, it is time for the algorithm to terminate. // Unless, it is time for the algorithm to terminate.
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
revPath := path{-1, point{nx, ny}, make(EditScript, 0)} revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
@ -195,19 +196,21 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// computing sub-optimal edit-scripts between two lists. // computing sub-optimal edit-scripts between two lists.
// //
// The algorithm is approximately as follows: // The algorithm is approximately as follows:
// • Searching for differences switches back-and-forth between // - Searching for differences switches back-and-forth between
// a search that starts at the beginning (the top-left corner), and // a search that starts at the beginning (the top-left corner), and
// a search that starts at the end (the bottom-right corner). The goal of // a search that starts at the end (the bottom-right corner).
// the search is connect with the search from the opposite corner. // The goal of the search is connect with the search
// • As we search, we build a path in a greedy manner, where the first // from the opposite corner.
// match seen is added to the path (this is sub-optimal, but provides a // - As we search, we build a path in a greedy manner,
// decent result in practice). When matches are found, we try the next pair // where the first match seen is added to the path (this is sub-optimal,
// of symbols in the lists and follow all matches as far as possible. // but provides a decent result in practice). When matches are found,
// • When searching for matches, we search along a diagonal going through // we try the next pair of symbols in the lists and follow all matches
// through the "frontier" point. If no matches are found, we advance the // as far as possible.
// frontier towards the opposite corner. // - When searching for matches, we search along a diagonal going through
// • This algorithm terminates when either the X coordinates or the // through the "frontier" point. If no matches are found,
// Y coordinates of the forward and reverse frontier points ever intersect. // we advance the frontier towards the opposite corner.
// - This algorithm terminates when either the X coordinates or the
// Y coordinates of the forward and reverse frontier points ever intersect.
// This algorithm is correct even if searching only in the forward direction // This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed // or in the reverse direction. We do both because it is commonly observed
@ -389,6 +392,7 @@ type point struct{ X, Y int }
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy } func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
// zigzag maps a consecutive sequence of integers to a zig-zag sequence. // zigzag maps a consecutive sequence of integers to a zig-zag sequence.
//
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...] // [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
func zigzag(x int) int { func zigzag(x int) int {
if x&1 != 0 { if x&1 != 0 {

View File

@ -1,48 +0,0 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package value
import (
"math"
"reflect"
)
// IsZero reports whether v is the zero value.
// This does not rely on Interface and so can be used on unexported fields.
func IsZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool() == false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return math.Float64bits(v.Float()) == 0
case reflect.Complex64, reflect.Complex128:
return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0
case reflect.String:
return v.String() == ""
case reflect.UnsafePointer:
return v.Pointer() == 0
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if !IsZero(v.Index(i)) {
return false
}
}
return true
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !IsZero(v.Field(i)) {
return false
}
}
return true
}
return false
}

View File

@ -33,6 +33,7 @@ type Option interface {
} }
// applicableOption represents the following types: // applicableOption represents the following types:
//
// Fundamental: ignore | validator | *comparer | *transformer // Fundamental: ignore | validator | *comparer | *transformer
// Grouping: Options // Grouping: Options
type applicableOption interface { type applicableOption interface {
@ -43,6 +44,7 @@ type applicableOption interface {
} }
// coreOption represents the following types: // coreOption represents the following types:
//
// Fundamental: ignore | validator | *comparer | *transformer // Fundamental: ignore | validator | *comparer | *transformer
// Filters: *pathFilter | *valuesFilter // Filters: *pathFilter | *valuesFilter
type coreOption interface { type coreOption interface {
@ -336,9 +338,9 @@ func (tr transformer) String() string {
// both implement T. // both implement T.
// //
// The equality function must be: // The equality function must be:
// Symmetric: equal(x, y) == equal(y, x) // - Symmetric: equal(x, y) == equal(y, x)
// Deterministic: equal(x, y) == equal(x, y) // - Deterministic: equal(x, y) == equal(x, y)
// Pure: equal(x, y) does not modify x or y // - Pure: equal(x, y) does not modify x or y
func Comparer(f interface{}) Option { func Comparer(f interface{}) Option {
v := reflect.ValueOf(f) v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Equal) || v.IsNil() { if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
@ -430,7 +432,7 @@ func AllowUnexported(types ...interface{}) Option {
} }
// Result represents the comparison result for a single node and // Result represents the comparison result for a single node and
// is provided by cmp when calling Result (see Reporter). // is provided by cmp when calling Report (see Reporter).
type Result struct { type Result struct {
_ [0]func() // Make Result incomparable _ [0]func() // Make Result incomparable
flags resultFlags flags resultFlags

View File

@ -41,13 +41,13 @@ type PathStep interface {
// The type of each valid value is guaranteed to be identical to Type. // The type of each valid value is guaranteed to be identical to Type.
// //
// In some cases, one or both may be invalid or have restrictions: // In some cases, one or both may be invalid or have restrictions:
// For StructField, both are not interface-able if the current field // - For StructField, both are not interface-able if the current field
// is unexported and the struct type is not explicitly permitted by // is unexported and the struct type is not explicitly permitted by
// an Exporter to traverse unexported fields. // an Exporter to traverse unexported fields.
// For SliceIndex, one may be invalid if an element is missing from // - For SliceIndex, one may be invalid if an element is missing from
// either the x or y slice. // either the x or y slice.
// For MapIndex, one may be invalid if an entry is missing from // - For MapIndex, one may be invalid if an entry is missing from
// either the x or y map. // either the x or y map.
// //
// The provided values must not be mutated. // The provided values must not be mutated.
Values() (vx, vy reflect.Value) Values() (vx, vy reflect.Value)
@ -94,6 +94,7 @@ func (pa Path) Index(i int) PathStep {
// The simplified path only contains struct field accesses. // The simplified path only contains struct field accesses.
// //
// For example: // For example:
//
// MyMap.MySlices.MyField // MyMap.MySlices.MyField
func (pa Path) String() string { func (pa Path) String() string {
var ss []string var ss []string
@ -108,6 +109,7 @@ func (pa Path) String() string {
// GoString returns the path to a specific node using Go syntax. // GoString returns the path to a specific node using Go syntax.
// //
// For example: // For example:
//
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField // (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
func (pa Path) GoString() string { func (pa Path) GoString() string {
var ssPre, ssPost []string var ssPre, ssPost []string
@ -159,7 +161,7 @@ func (ps pathStep) String() string {
if ps.typ == nil { if ps.typ == nil {
return "<nil>" return "<nil>"
} }
s := ps.typ.String() s := value.TypeString(ps.typ, false)
if s == "" || strings.ContainsAny(s, "{}\n") { if s == "" || strings.ContainsAny(s, "{}\n") {
return "root" // Type too simple or complex to print return "root" // Type too simple or complex to print
} }
@ -282,7 +284,7 @@ type typeAssertion struct {
func (ta TypeAssertion) Type() reflect.Type { return ta.typ } func (ta TypeAssertion) Type() reflect.Type { return ta.typ }
func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }
func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) }
// Transform is a transformation from the parent type to the current type. // Transform is a transformation from the parent type to the current type.
type Transform struct{ *transform } type Transform struct{ *transform }

View File

@ -7,8 +7,6 @@ package cmp
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/google/go-cmp/cmp/internal/value"
) )
// numContextRecords is the number of surrounding equal records to print. // numContextRecords is the number of surrounding equal records to print.
@ -117,7 +115,7 @@ func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out
// For leaf nodes, format the value based on the reflect.Values alone. // For leaf nodes, format the value based on the reflect.Values alone.
// As a special case, treat equal []byte as a leaf nodes. // As a special case, treat equal []byte as a leaf nodes.
isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == reflect.TypeOf(byte(0)) isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType
isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0 isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0
if v.MaxDepth == 0 || isEqualBytes { if v.MaxDepth == 0 || isEqualBytes {
switch opts.DiffMode { switch opts.DiffMode {
@ -248,11 +246,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, pt
var isZero bool var isZero bool
switch opts.DiffMode { switch opts.DiffMode {
case diffIdentical: case diffIdentical:
isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero()
case diffRemoved: case diffRemoved:
isZero = value.IsZero(r.Value.ValueX) isZero = r.Value.ValueX.IsZero()
case diffInserted: case diffInserted:
isZero = value.IsZero(r.Value.ValueY) isZero = r.Value.ValueY.IsZero()
} }
if isZero { if isZero {
continue continue

View File

@ -16,6 +16,13 @@ import (
"github.com/google/go-cmp/cmp/internal/value" "github.com/google/go-cmp/cmp/internal/value"
) )
var (
anyType = reflect.TypeOf((*interface{})(nil)).Elem()
stringType = reflect.TypeOf((*string)(nil)).Elem()
bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
byteType = reflect.TypeOf((*byte)(nil)).Elem()
)
type formatValueOptions struct { type formatValueOptions struct {
// AvoidStringer controls whether to avoid calling custom stringer // AvoidStringer controls whether to avoid calling custom stringer
// methods like error.Error or fmt.Stringer.String. // methods like error.Error or fmt.Stringer.String.
@ -184,7 +191,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
} }
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
vv := v.Field(i) vv := v.Field(i)
if value.IsZero(vv) { if vv.IsZero() {
continue // Elide fields with zero values continue // Elide fields with zero values
} }
if len(list) == maxLen { if len(list) == maxLen {
@ -205,7 +212,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
} }
// Check whether this is a []byte of text data. // Check whether this is a []byte of text data.
if t.Elem() == reflect.TypeOf(byte(0)) { if t.Elem() == byteType {
b := v.Bytes() b := v.Bytes()
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) } isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {

View File

@ -104,7 +104,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
case t.Kind() == reflect.String: case t.Kind() == reflect.String:
sx, sy = vx.String(), vy.String() sx, sy = vx.String(), vy.String()
isString = true isString = true
case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)): case t.Kind() == reflect.Slice && t.Elem() == byteType:
sx, sy = string(vx.Bytes()), string(vy.Bytes()) sx, sy = string(vx.Bytes()), string(vy.Bytes())
isString = true isString = true
case t.Kind() == reflect.Array: case t.Kind() == reflect.Array:
@ -147,7 +147,10 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
}) })
efficiencyLines := float64(esLines.Dist()) / float64(len(esLines)) efficiencyLines := float64(esLines.Dist()) / float64(len(esLines))
efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes)) efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes))
isPureLinedText = efficiencyLines < 4*efficiencyBytes quotedLength := len(strconv.Quote(sx + sy))
unquotedLength := len(sx) + len(sy)
escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength)
isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1
} }
} }
@ -171,12 +174,13 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
// differences in a string literal. This format is more readable, // differences in a string literal. This format is more readable,
// but has edge-cases where differences are visually indistinguishable. // but has edge-cases where differences are visually indistinguishable.
// This format is avoided under the following conditions: // This format is avoided under the following conditions:
// A line starts with `"""` // - A line starts with `"""`
// A line starts with "..." // - A line starts with "..."
// A line contains non-printable characters // - A line contains non-printable characters
// Adjacent different lines differ only by whitespace // - Adjacent different lines differ only by whitespace
// //
// For example: // For example:
//
// """ // """
// ... // 3 identical lines // ... // 3 identical lines
// foo // foo
@ -231,7 +235,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"} var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
switch t.Kind() { switch t.Kind() {
case reflect.String: case reflect.String:
if t != reflect.TypeOf(string("")) { if t != stringType {
out = opts.FormatType(t, out) out = opts.FormatType(t, out)
} }
case reflect.Slice: case reflect.Slice:
@ -326,12 +330,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
switch t.Kind() { switch t.Kind() {
case reflect.String: case reflect.String:
out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf(string("")) { if t != stringType {
out = opts.FormatType(t, out) out = opts.FormatType(t, out)
} }
case reflect.Slice: case reflect.Slice:
out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf([]byte(nil)) { if t != bytesType {
out = opts.FormatType(t, out) out = opts.FormatType(t, out)
} }
} }
@ -446,7 +450,6 @@ func (opts formatOptions) formatDiffSlice(
// {NumIdentical: 3}, // {NumIdentical: 3},
// {NumInserted: 1}, // {NumInserted: 1},
// ] // ]
//
func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) {
var prevMode byte var prevMode byte
lastStats := func(mode byte) *diffStats { lastStats := func(mode byte) *diffStats {
@ -503,7 +506,6 @@ func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats)
// {NumIdentical: 8, NumRemoved: 12, NumInserted: 3}, // {NumIdentical: 8, NumRemoved: 12, NumInserted: 3},
// {NumIdentical: 63}, // {NumIdentical: 63},
// ] // ]
//
func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats {
groups, groupsOrig := groups[:0], groups groups, groupsOrig := groups[:0], groups
for i, ds := range groupsOrig { for i, ds := range groupsOrig {
@ -548,7 +550,6 @@ func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStat
// {NumRemoved: 9}, // {NumRemoved: 9},
// {NumIdentical: 64}, // incremented by 10 // {NumIdentical: 64}, // incremented by 10
// ] // ]
//
func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats { func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats {
var ix, iy int // indexes into sequence x and y var ix, iy int // indexes into sequence x and y
for i, ds := range groups { for i, ds := range groups {

View File

@ -393,6 +393,7 @@ func (s diffStats) Append(ds diffStats) diffStats {
// String prints a humanly-readable summary of coalesced records. // String prints a humanly-readable summary of coalesced records.
// //
// Example: // Example:
//
// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields" // diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
func (s diffStats) String() string { func (s diffStats) String() string {
var ss []string var ss []string

View File

@ -3,3 +3,4 @@
. .
.idea .idea
gomega.iml gomega.iml
TODO.md

View File

@ -1,3 +1,23 @@
## 1.23.0
### Features
- Custom formatting on a per-type basis can be provided using `format.RegisterCustomFormatter()` -- see the docs [here](https://onsi.github.io/gomega/#adjusting-output)
- Substantial improvement have been made to `StopTrying()`:
- Users can now use `StopTrying().Wrap(err)` to wrap errors and `StopTrying().Attach(description, object)` to attach arbitrary objects to the `StopTrying()` error
- `StopTrying()` is now always interpreted as a failure. If you are an early adopter of `StopTrying()` you may need to change your code as the prior version would match against the returned value even if `StopTrying()` was returned. Going forward the `StopTrying()` api should remain stable.
- `StopTrying()` and `StopTrying().Now()` can both be used in matchers - not just polled functions.
- `TryAgainAfter(duration)` is used like `StopTrying()` but instructs `Eventually` and `Consistently` that the poll should be tried again after the specified duration. This allows you to dynamically adjust the polling duration.
- `ctx` can now be passed-in as the first argument to `Eventually` and `Consistently`.
## Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.3.0 to 2.3.1 (#597) [afed901]
- Bump nokogiri from 1.13.8 to 1.13.9 in /docs (#599) [7c691b3]
- Bump github.com/google/go-cmp from 0.5.8 to 0.5.9 (#587) [ff22665]
## 1.22.1 ## 1.22.1
## Fixes ## Fixes

View File

@ -65,6 +65,52 @@ type GomegaStringer interface {
GomegaString() string GomegaString() string
} }
/*
CustomFormatters can be registered with Gomega via RegisterCustomFormatter()
Any value to be rendered by Gomega is passed to each registered CustomFormatters.
The CustomFormatter signals that it will handle formatting the value by returning (formatted-string, true)
If the CustomFormatter does not want to handle the object it should return ("", false)
Strings returned by CustomFormatters are not truncated
*/
type CustomFormatter func(value interface{}) (string, bool)
type CustomFormatterKey uint
var customFormatterKey CustomFormatterKey = 1
type customFormatterKeyPair struct {
CustomFormatter
CustomFormatterKey
}
/*
RegisterCustomFormatter registers a CustomFormatter and returns a CustomFormatterKey
You can call UnregisterCustomFormatter with the returned key to unregister the associated CustomFormatter
*/
func RegisterCustomFormatter(customFormatter CustomFormatter) CustomFormatterKey {
key := customFormatterKey
customFormatterKey += 1
customFormatters = append(customFormatters, customFormatterKeyPair{customFormatter, key})
return key
}
/*
UnregisterCustomFormatter unregisters a previously registered CustomFormatter. You should pass in the key returned by RegisterCustomFormatter
*/
func UnregisterCustomFormatter(key CustomFormatterKey) {
formatters := []customFormatterKeyPair{}
for _, f := range customFormatters {
if f.CustomFormatterKey == key {
continue
}
formatters = append(formatters, f)
}
customFormatters = formatters
}
var customFormatters = []customFormatterKeyPair{}
/* /*
Generates a formatted matcher success/failure message of the form: Generates a formatted matcher success/failure message of the form:
@ -219,17 +265,24 @@ func Object(object interface{}, indentation uint) string {
IndentString takes a string and indents each line by the specified amount. IndentString takes a string and indents each line by the specified amount.
*/ */
func IndentString(s string, indentation uint) string { func IndentString(s string, indentation uint) string {
return indentString(s, indentation, true)
}
func indentString(s string, indentation uint, indentFirstLine bool) string {
result := &strings.Builder{}
components := strings.Split(s, "\n") components := strings.Split(s, "\n")
result := ""
indent := strings.Repeat(Indent, int(indentation)) indent := strings.Repeat(Indent, int(indentation))
for i, component := range components { for i, component := range components {
result += indent + component if i > 0 || indentFirstLine {
result.WriteString(indent)
}
result.WriteString(component)
if i < len(components)-1 { if i < len(components)-1 {
result += "\n" result.WriteString("\n")
} }
} }
return result return result.String()
} }
func formatType(v reflect.Value) string { func formatType(v reflect.Value) string {
@ -261,18 +314,27 @@ func formatValue(value reflect.Value, indentation uint) string {
if value.CanInterface() { if value.CanInterface() {
obj := value.Interface() obj := value.Interface()
// if a CustomFormatter handles this values, we'll go with that
for _, customFormatter := range customFormatters {
formatted, handled := customFormatter.CustomFormatter(obj)
// do not truncate a user-provided CustomFormatter()
if handled {
return indentString(formatted, indentation+1, false)
}
}
// GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation // GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation
if x, ok := obj.(GomegaStringer); ok { if x, ok := obj.(GomegaStringer); ok {
// do not truncate a user-defined GoMegaString() value // do not truncate a user-defined GomegaString() value
return x.GomegaString() return indentString(x.GomegaString(), indentation+1, false)
} }
if UseStringerRepresentation { if UseStringerRepresentation {
switch x := obj.(type) { switch x := obj.(type) {
case fmt.GoStringer: case fmt.GoStringer:
return truncateLongStrings(x.GoString()) return indentString(truncateLongStrings(x.GoString()), indentation+1, false)
case fmt.Stringer: case fmt.Stringer:
return truncateLongStrings(x.String()) return indentString(truncateLongStrings(x.String()), indentation+1, false)
} }
} }
} }

View File

@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
const GOMEGA_VERSION = "1.22.1" const GOMEGA_VERSION = "1.23.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().
@ -86,12 +86,12 @@ func internalGomega(g Gomega) *internal.Gomega {
// NewWithT takes a *testing.T and returns a `gomega.WithT` allowing you to use `Expect`, `Eventually`, and `Consistently` along with // NewWithT takes a *testing.T and returns a `gomega.WithT` allowing you to use `Expect`, `Eventually`, and `Consistently` along with
// Gomega's rich ecosystem of matchers in standard `testing` test suits. // Gomega's rich ecosystem of matchers in standard `testing` test suits.
// //
// func TestFarmHasCow(t *testing.T) { // func TestFarmHasCow(t *testing.T) {
// g := gomega.NewWithT(t) // g := gomega.NewWithT(t)
// //
// f := farm.New([]string{"Cow", "Horse"}) // f := farm.New([]string{"Cow", "Horse"})
// g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow") // g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
// } // }
func NewWithT(t types.GomegaTestingT) *WithT { func NewWithT(t types.GomegaTestingT) *WithT {
return internal.NewGomega(internalGomega(Default).DurationBundle).ConfigureWithT(t) return internal.NewGomega(internalGomega(Default).DurationBundle).ConfigureWithT(t)
} }
@ -171,7 +171,8 @@ func ensureDefaultGomegaIsConfigured() {
} }
// Ω wraps an actual value allowing assertions to be made on it: // Ω wraps an actual value allowing assertions to be made on it:
// Ω("foo").Should(Equal("foo")) //
// Ω("foo").Should(Equal("foo"))
// //
// If Ω is passed more than one argument it will pass the *first* argument to the matcher. // If Ω is passed more than one argument it will pass the *first* argument to the matcher.
// All subsequent arguments will be required to be nil/zero. // All subsequent arguments will be required to be nil/zero.
@ -180,10 +181,13 @@ func ensureDefaultGomegaIsConfigured() {
// a value and an error - a common patter in Go. // a value and an error - a common patter in Go.
// //
// For example, given a function with signature: // For example, given a function with signature:
// func MyAmazingThing() (int, error) //
// func MyAmazingThing() (int, error)
// //
// Then: // Then:
// Ω(MyAmazingThing()).Should(Equal(3)) //
// Ω(MyAmazingThing()).Should(Equal(3))
//
// Will succeed only if `MyAmazingThing()` returns `(3, nil)` // Will succeed only if `MyAmazingThing()` returns `(3, nil)`
// //
// Ω and Expect are identical // Ω and Expect are identical
@ -193,7 +197,8 @@ func Ω(actual interface{}, extra ...interface{}) Assertion {
} }
// Expect wraps an actual value allowing assertions to be made on it: // Expect wraps an actual value allowing assertions to be made on it:
// Expect("foo").To(Equal("foo")) //
// Expect("foo").To(Equal("foo"))
// //
// If Expect is passed more than one argument it will pass the *first* argument to the matcher. // If Expect is passed more than one argument it will pass the *first* argument to the matcher.
// All subsequent arguments will be required to be nil/zero. // All subsequent arguments will be required to be nil/zero.
@ -202,10 +207,13 @@ func Ω(actual interface{}, extra ...interface{}) Assertion {
// a value and an error - a common patter in Go. // a value and an error - a common patter in Go.
// //
// For example, given a function with signature: // For example, given a function with signature:
// func MyAmazingThing() (int, error) //
// func MyAmazingThing() (int, error)
// //
// Then: // Then:
// Expect(MyAmazingThing()).Should(Equal(3)) //
// Expect(MyAmazingThing()).Should(Equal(3))
//
// Will succeed only if `MyAmazingThing()` returns `(3, nil)` // Will succeed only if `MyAmazingThing()` returns `(3, nil)`
// //
// Expect and Ω are identical // Expect and Ω are identical
@ -215,7 +223,8 @@ func Expect(actual interface{}, extra ...interface{}) Assertion {
} }
// ExpectWithOffset wraps an actual value allowing assertions to be made on it: // ExpectWithOffset wraps an actual value allowing assertions to be made on it:
// ExpectWithOffset(1, "foo").To(Equal("foo")) //
// ExpectWithOffset(1, "foo").To(Equal("foo"))
// //
// Unlike `Expect` and `Ω`, `ExpectWithOffset` takes an additional integer argument // Unlike `Expect` and `Ω`, `ExpectWithOffset` takes an additional integer argument
// that is used to modify the call-stack offset when computing line numbers. It is // that is used to modify the call-stack offset when computing line numbers. It is
@ -241,15 +250,15 @@ Eventually works with any Gomega compatible matcher and supports making assertio
There are several examples of values that can change over time. These can be passed in to Eventually and will be passed to the matcher repeatedly until a match occurs. For example: There are several examples of values that can change over time. These can be passed in to Eventually and will be passed to the matcher repeatedly until a match occurs. For example:
c := make(chan bool) c := make(chan bool)
go DoStuff(c) go DoStuff(c)
Eventually(c, "50ms").Should(BeClosed()) Eventually(c, "50ms").Should(BeClosed())
will poll the channel repeatedly until it is closed. In this example `Eventually` will block until either the specified timeout of 50ms has elapsed or the channel is closed, whichever comes first. will poll the channel repeatedly until it is closed. In this example `Eventually` will block until either the specified timeout of 50ms has elapsed or the channel is closed, whichever comes first.
Several Gomega libraries allow you to use Eventually in this way. For example, the gomega/gexec package allows you to block until a *gexec.Session exits successfully via: Several Gomega libraries allow you to use Eventually in this way. For example, the gomega/gexec package allows you to block until a *gexec.Session exits successfully via:
Eventually(session).Should(gexec.Exit(0)) Eventually(session).Should(gexec.Exit(0))
And the gomega/gbytes package allows you to monitor a streaming *gbytes.Buffer until a given string is seen: And the gomega/gbytes package allows you to monitor a streaming *gbytes.Buffer until a given string is seen:
@ -270,34 +279,38 @@ Eventually can be passed functions that **return at least one value**. When con
For example: For example:
Eventually(func() int { Eventually(func() int {
return client.FetchCount() return client.FetchCount()
}).Should(BeNumerically(">=", 17)) }).Should(BeNumerically(">=", 17))
will repeatedly poll client.FetchCount until the BeNumerically matcher is satisfied. (Note that this example could have been written as Eventually(client.FetchCount).Should(BeNumerically(">=", 17))) will repeatedly poll client.FetchCount until the BeNumerically matcher is satisfied. (Note that this example could have been written as Eventually(client.FetchCount).Should(BeNumerically(">=", 17)))
If multiple values are returned by the function, Eventually will pass the first value to the matcher and require that all others are zero-valued. This allows you to pass Eventually a function that returns a value and an error - a common pattern in Go. If multiple values are returned by the function, Eventually will pass the first value to the matcher and require that all others are zero-valued. This allows you to pass Eventually a function that returns a value and an error - a common pattern in Go.
For example, consider a method that returns a value and an error: For example, consider a method that returns a value and an error:
func FetchFromDB() (string, error)
func FetchFromDB() (string, error)
Then Then
Eventually(FetchFromDB).Should(Equal("got it"))
Eventually(FetchFromDB).Should(Equal("got it"))
will pass only if and when the returned error is nil *and* the returned string satisfies the matcher. will pass only if and when the returned error is nil *and* the returned string satisfies the matcher.
Eventually can also accept functions that take arguments, however you must provide those arguments using .WithArguments(). For example, consider a function that takes a user-id and makes a network request to fetch a full name: Eventually can also accept functions that take arguments, however you must provide those arguments using .WithArguments(). For example, consider a function that takes a user-id and makes a network request to fetch a full name:
func FetchFullName(userId int) (string, error) func FetchFullName(userId int) (string, error)
You can poll this function like so: You can poll this function like so:
Eventually(FetchFullName).WithArguments(1138).Should(Equal("Wookie")) Eventually(FetchFullName).WithArguments(1138).Should(Equal("Wookie"))
It is important to note that the function passed into Eventually is invoked *synchronously* when polled. Eventually does not (in fact, it cannot) kill the function if it takes longer to return than Eventually's configured timeout. A common practice here is to use a context. Here's an example that combines Ginkgo's spec timeout support with Eventually: It is important to note that the function passed into Eventually is invoked *synchronously* when polled. Eventually does not (in fact, it cannot) kill the function if it takes longer to return than Eventually's configured timeout. A common practice here is to use a context. Here's an example that combines Ginkgo's spec timeout support with Eventually:
It("fetches the correct count", func(ctx SpecContext) { It("fetches the correct count", func(ctx SpecContext) {
Eventually(func() int { Eventually(ctx, func() int {
return client.FetchCount(ctx, "/users") return client.FetchCount(ctx, "/users")
}, ctx).Should(BeNumerically(">=", 17)) }).Should(BeNumerically(">=", 17))
}, SpecTimeout(time.Second)) }, SpecTimeout(time.Second))
you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in contexts play nicely with paseed-in arguments as long as the context appears first. You can rewrite the above example as: you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in contexts play nicely with paseed-in arguments as long as the context appears first. You can rewrite the above example as:
@ -326,13 +339,13 @@ will pass only if all the assertions in the polled function pass and the return
Eventually also supports a special case polling function that takes a single Gomega argument and returns no values. Eventually assumes such a function is making assertions and is designed to work with the Succeed matcher to validate that all assertions have passed. Eventually also supports a special case polling function that takes a single Gomega argument and returns no values. Eventually assumes such a function is making assertions and is designed to work with the Succeed matcher to validate that all assertions have passed.
For example: For example:
Eventually(func(g Gomega) { Eventually(func(g Gomega) {
model, err := client.Find(1138) model, err := client.Find(1138)
g.Expect(err).NotTo(HaveOccurred()) g.Expect(err).NotTo(HaveOccurred())
g.Expect(model.Reticulate()).To(Succeed()) g.Expect(model.Reticulate()).To(Succeed())
g.Expect(model.IsReticulated()).To(BeTrue()) g.Expect(model.IsReticulated()).To(BeTrue())
g.Expect(model.Save()).To(Succeed()) g.Expect(model.Save()).To(Succeed())
}).Should(Succeed()) }).Should(Succeed())
will rerun the function until all assertions pass. will rerun the function until all assertions pass.
@ -353,11 +366,11 @@ Finally, in addition to passing timeouts and a context to Eventually you can be
is equivalent to is equivalent to
Eventually(...).WithTimeout(time.Second).WithPolling(2*time.Second).WithContext(ctx).Should(...) Eventually(...).WithTimeout(time.Second).WithPolling(2*time.Second).WithContext(ctx).Should(...)
*/ */
func Eventually(actual interface{}, args ...interface{}) AsyncAssertion { func Eventually(args ...interface{}) AsyncAssertion {
ensureDefaultGomegaIsConfigured() ensureDefaultGomegaIsConfigured()
return Default.Eventually(actual, args...) return Default.Eventually(args...)
} }
// EventuallyWithOffset operates like Eventually but takes an additional // EventuallyWithOffset operates like Eventually but takes an additional
@ -369,9 +382,9 @@ func Eventually(actual interface{}, args ...interface{}) AsyncAssertion {
// `EventuallyWithOffset` specifying a timeout interval (and an optional polling interval) are // `EventuallyWithOffset` specifying a timeout interval (and an optional polling interval) are
// the same as `Eventually(...).WithOffset(...).WithTimeout` or // the same as `Eventually(...).WithOffset(...).WithTimeout` or
// `Eventually(...).WithOffset(...).WithTimeout(...).WithPolling`. // `Eventually(...).WithOffset(...).WithTimeout(...).WithPolling`.
func EventuallyWithOffset(offset int, actual interface{}, args ...interface{}) AsyncAssertion { func EventuallyWithOffset(offset int, args ...interface{}) AsyncAssertion {
ensureDefaultGomegaIsConfigured() ensureDefaultGomegaIsConfigured()
return Default.EventuallyWithOffset(offset, actual, args...) return Default.EventuallyWithOffset(offset, args...)
} }
/* /*
@ -385,13 +398,13 @@ Consistently accepts the same three categories of actual as Eventually, check th
Consistently is useful in cases where you want to assert that something *does not happen* for a period of time. For example, you may want to assert that a goroutine does *not* send data down a channel. In this case you could write: Consistently is useful in cases where you want to assert that something *does not happen* for a period of time. For example, you may want to assert that a goroutine does *not* send data down a channel. In this case you could write:
Consistently(channel, "200ms").ShouldNot(Receive()) Consistently(channel, "200ms").ShouldNot(Receive())
This will block for 200 milliseconds and repeatedly check the channel and ensure nothing has been received. This will block for 200 milliseconds and repeatedly check the channel and ensure nothing has been received.
*/ */
func Consistently(actual interface{}, args ...interface{}) AsyncAssertion { func Consistently(args ...interface{}) AsyncAssertion {
ensureDefaultGomegaIsConfigured() ensureDefaultGomegaIsConfigured()
return Default.Consistently(actual, args...) return Default.Consistently(args...)
} }
// ConsistentlyWithOffset operates like Consistently but takes an additional // ConsistentlyWithOffset operates like Consistently but takes an additional
@ -400,44 +413,54 @@ func Consistently(actual interface{}, args ...interface{}) AsyncAssertion {
// //
// `ConsistentlyWithOffset` is the same as `Consistently(...).WithOffset` and // `ConsistentlyWithOffset` is the same as `Consistently(...).WithOffset` and
// optional `WithTimeout` and `WithPolling`. // optional `WithTimeout` and `WithPolling`.
func ConsistentlyWithOffset(offset int, actual interface{}, args ...interface{}) AsyncAssertion { func ConsistentlyWithOffset(offset int, args ...interface{}) AsyncAssertion {
ensureDefaultGomegaIsConfigured() ensureDefaultGomegaIsConfigured()
return Default.ConsistentlyWithOffset(offset, actual, args...) return Default.ConsistentlyWithOffset(offset, args...)
} }
/* /*
StopTrying can be used to signal to Eventually and Consistently that the polled function will not change StopTrying can be used to signal to Eventually and Consistentlythat they should abort and stop trying. This always results in a failure of the assertion - and the failure message is the content of the StopTrying signal.
and that they should stop trying. In the case of Eventually, if a match does not occur in this, final, iteration then a failure will result. In the case of Consistently, as long as this last iteration satisfies the match, the assertion will be considered successful.
You can send the StopTrying signal by either returning a StopTrying("message") messages as an error from your passed-in function _or_ by calling StopTrying("message").Now() to trigger a panic and end execution. You can send the StopTrying signal by either returning StopTrying("message") as an error from your passed-in function _or_ by calling StopTrying("message").Now() to trigger a panic and end execution.
You can also wrap StopTrying around an error with `StopTrying("message").Wrap(err)` and can attach additional objects via `StopTrying("message").Attach("description", object). When rendered, the signal will include the wrapped error and any attached objects rendered using Gomega's default formatting.
Here are a couple of examples. This is how you might use StopTrying() as an error to signal that Eventually should stop: Here are a couple of examples. This is how you might use StopTrying() as an error to signal that Eventually should stop:
playerIndex, numPlayers := 0, 11 playerIndex, numPlayers := 0, 11
Eventually(func() (string, error) { Eventually(func() (string, error) {
name := client.FetchPlayer(playerIndex) if playerIndex == numPlayers {
playerIndex += 1 return "", StopTrying("no more players left")
if playerIndex == numPlayers { }
return name, StopTrying("No more players left") name := client.FetchPlayer(playerIndex)
} else { playerIndex += 1
return name, nil return name, nil
}
}).Should(Equal("Patrick Mahomes")) }).Should(Equal("Patrick Mahomes"))
note that the final `name` returned alongside `StopTrying()` will be processed.
And here's an example where `StopTrying().Now()` is called to halt execution immediately: And here's an example where `StopTrying().Now()` is called to halt execution immediately:
Eventually(func() []string { Eventually(func() []string {
names, err := client.FetchAllPlayers() names, err := client.FetchAllPlayers()
if err == client.IRRECOVERABLE_ERROR { if err == client.IRRECOVERABLE_ERROR {
StopTrying("Irrecoverable error occurred").Now() StopTrying("Irrecoverable error occurred").Wrap(err).Now()
} }
return names return names
}).Should(ContainElement("Patrick Mahomes")) }).Should(ContainElement("Patrick Mahomes"))
*/ */
var StopTrying = internal.StopTrying var StopTrying = internal.StopTrying
/*
TryAgainAfter(<duration>) allows you to adjust the polling interval for the _next_ iteration of `Eventually` or `Consistently`. Like `StopTrying` you can either return `TryAgainAfter` as an error or trigger it immedieately with `.Now()`
When `TryAgainAfter(<duration>` is triggered `Eventually` and `Consistently` will wait for that duration. If a timeout occurs before the next poll is triggered both `Eventually` and `Consistently` will always fail with the content of the TryAgainAfter message. As with StopTrying you can `.Wrap()` and error and `.Attach()` additional objects to `TryAgainAfter`.
*/
var TryAgainAfter = internal.TryAgainAfter
/*
PollingSignalError is the error returned by StopTrying() and TryAgainAfter()
*/
type PollingSignalError = internal.PollingSignalError
// SetDefaultEventuallyTimeout sets the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses. // SetDefaultEventuallyTimeout sets the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses.
func SetDefaultEventuallyTimeout(t time.Duration) { func SetDefaultEventuallyTimeout(t time.Duration) {
Default.SetDefaultEventuallyTimeout(t) Default.SetDefaultEventuallyTimeout(t)
@ -471,8 +494,8 @@ func SetDefaultConsistentlyPollingInterval(t time.Duration) {
// //
// Example: // Example:
// //
// Eventually(myChannel).Should(Receive(), "Something should have come down the pipe.") // Eventually(myChannel).Should(Receive(), "Something should have come down the pipe.")
// Consistently(myChannel).ShouldNot(Receive(), func() string { return "Nothing should have come down the pipe." }) // Consistently(myChannel).ShouldNot(Receive(), func() string { return "Nothing should have come down the pipe." })
type AsyncAssertion = types.AsyncAssertion type AsyncAssertion = types.AsyncAssertion
// GomegaAsyncAssertion is deprecated in favor of AsyncAssertion, which does not stutter. // GomegaAsyncAssertion is deprecated in favor of AsyncAssertion, which does not stutter.
@ -494,7 +517,7 @@ type GomegaAsyncAssertion = types.AsyncAssertion
// //
// Example: // Example:
// //
// Ω(farm.HasCow()).Should(BeTrue(), "Farm %v should have a cow", farm) // Ω(farm.HasCow()).Should(BeTrue(), "Farm %v should have a cow", farm)
type Assertion = types.Assertion type Assertion = types.Assertion
// GomegaAssertion is deprecated in favor of Assertion, which does not stutter. // GomegaAssertion is deprecated in favor of Assertion, which does not stutter.

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
@ -146,7 +147,12 @@ func vetActuals(actuals []interface{}, skipIndex int) (bool, string) {
if actual != nil { if actual != nil {
zeroValue := reflect.Zero(reflect.TypeOf(actual)).Interface() zeroValue := reflect.Zero(reflect.TypeOf(actual)).Interface()
if !reflect.DeepEqual(zeroValue, actual) { if !reflect.DeepEqual(zeroValue, actual) {
message := fmt.Sprintf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i, actual, actual) var message string
if err, ok := actual.(error); ok {
message = fmt.Sprintf("Unexpected error: %s\n%s", err, format.Object(err, 1))
} else {
message = fmt.Sprintf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i, actual, actual)
}
return false, message return false, message
} }
} }

View File

@ -2,58 +2,22 @@ package internal
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"runtime" "runtime"
"sync" "sync"
"time" "time"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
type StopTryingError interface { var errInterface = reflect.TypeOf((*error)(nil)).Elem()
error var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem()
Now() var contextType = reflect.TypeOf(new(context.Context)).Elem()
wasViaPanic() bool
}
func asStopTryingError(actual interface{}) (StopTryingError, bool) { type contextWithAttachProgressReporter interface {
if actual == nil { AttachProgressReporter(func() string) func()
return nil, false
}
if actualErr, ok := actual.(error); ok {
var target *stopTryingError
if errors.As(actualErr, &target) {
return target, true
} else {
return nil, false
}
}
return nil, false
}
type stopTryingError struct {
message string
viaPanic bool
}
func (s *stopTryingError) Error() string {
return s.message
}
func (s *stopTryingError) Now() {
s.viaPanic = true
panic(s)
}
func (s *stopTryingError) wasViaPanic() bool {
return s.viaPanic
}
var StopTrying = func(message string) StopTryingError {
return &stopTryingError{message: message}
} }
type AsyncAssertionType uint type AsyncAssertionType uint
@ -164,39 +128,40 @@ func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interfa
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n" return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
} }
func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error, StopTryingError) { func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error) {
var err error
var stopTrying StopTryingError
if len(values) == 0 { if len(values) == 0 {
return nil, fmt.Errorf("No values were returned by the function passed to Gomega"), stopTrying return nil, fmt.Errorf("No values were returned by the function passed to Gomega")
} }
actual := values[0].Interface() actual := values[0].Interface()
if stopTryingErr, ok := asStopTryingError(actual); ok { if _, ok := AsPollingSignalError(actual); ok {
stopTrying = stopTryingErr return actual, actual.(error)
} }
var err error
for i, extraValue := range values[1:] { for i, extraValue := range values[1:] {
extra := extraValue.Interface() extra := extraValue.Interface()
if extra == nil { if extra == nil {
continue continue
} }
if stopTryingErr, ok := asStopTryingError(extra); ok { if _, ok := AsPollingSignalError(extra); ok {
stopTrying = stopTryingErr return actual, extra.(error)
continue
} }
zero := reflect.Zero(reflect.TypeOf(extra)).Interface() extraType := reflect.TypeOf(extra)
zero := reflect.Zero(extraType).Interface()
if reflect.DeepEqual(extra, zero) { if reflect.DeepEqual(extra, zero) {
continue continue
} }
if i == len(values)-2 && extraType.Implements(errInterface) {
err = fmt.Errorf("function returned error: %w", extra.(error))
}
if err == nil { if err == nil {
err = fmt.Errorf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i+1, extra, extra) err = fmt.Errorf("Unexpected non-nil/non-zero return value at index %d:\n\t<%T>: %#v", i+1, extra, extra)
} }
} }
return actual, err, stopTrying
}
var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem() return actual, err
var contextType = reflect.TypeOf(new(context.Context)).Elem() }
func (assertion *AsyncAssertion) invalidFunctionError(t reflect.Type) error { func (assertion *AsyncAssertion) invalidFunctionError(t reflect.Type) error {
return fmt.Errorf(`The function passed to %s had an invalid signature of %s. Functions passed to %s must either: return fmt.Errorf(`The function passed to %s had an invalid signature of %s. Functions passed to %s must either:
@ -226,9 +191,9 @@ You can learn more at https://onsi.github.io/gomega/#eventually
`, assertion.asyncType, t, t.NumIn(), numProvided, have, assertion.asyncType) `, assertion.asyncType, t, t.NumIn(), numProvided, have, assertion.asyncType)
} }
func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error, StopTryingError), error) { func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error), error) {
if !assertion.actualIsFunc { if !assertion.actualIsFunc {
return func() (interface{}, error, StopTryingError) { return assertion.actual, nil, nil }, nil return func() (interface{}, error) { return assertion.actual, nil }, nil
} }
actualValue := reflect.ValueOf(assertion.actual) actualValue := reflect.ValueOf(assertion.actual)
actualType := reflect.TypeOf(assertion.actual) actualType := reflect.TypeOf(assertion.actual)
@ -236,23 +201,11 @@ func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error
if numIn == 0 && numOut == 0 { if numIn == 0 && numOut == 0 {
return nil, assertion.invalidFunctionError(actualType) return nil, assertion.invalidFunctionError(actualType)
} else if numIn == 0 {
return func() (actual interface{}, err error, stopTrying StopTryingError) {
defer func() {
if e := recover(); e != nil {
if stopTryingErr, ok := asStopTryingError(e); ok {
stopTrying = stopTryingErr
} else {
panic(e)
}
}
}()
actual, err, stopTrying = assertion.processReturnValues(actualValue.Call([]reflect.Value{}))
return
}, nil
} }
takesGomega, takesContext := actualType.In(0).Implements(gomegaType), actualType.In(0).Implements(contextType) takesGomega, takesContext := false, false
if numIn > 0 {
takesGomega, takesContext = actualType.In(0).Implements(gomegaType), actualType.In(0).Implements(contextType)
}
if takesGomega && numIn > 1 && actualType.In(1).Implements(contextType) { if takesGomega && numIn > 1 && actualType.In(1).Implements(contextType) {
takesContext = true takesContext = true
} }
@ -292,21 +245,22 @@ func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error
return nil, assertion.argumentMismatchError(actualType, len(inValues)) return nil, assertion.argumentMismatchError(actualType, len(inValues))
} }
return func() (actual interface{}, err error, stopTrying StopTryingError) { return func() (actual interface{}, err error) {
var values []reflect.Value var values []reflect.Value
assertionFailure = nil assertionFailure = nil
defer func() { defer func() {
if numOut == 0 { if numOut == 0 && takesGomega {
actual = assertionFailure actual = assertionFailure
} else { } else {
actual, err, stopTrying = assertion.processReturnValues(values) actual, err = assertion.processReturnValues(values)
if assertionFailure != nil { _, isAsyncError := AsPollingSignalError(err)
if assertionFailure != nil && !isAsyncError {
err = assertionFailure err = assertionFailure
} }
} }
if e := recover(); e != nil { if e := recover(); e != nil {
if stopTryingErr, ok := asStopTryingError(e); ok { if _, isAsyncError := AsPollingSignalError(e); isAsyncError {
stopTrying = stopTryingErr err = e.(error)
} else if assertionFailure == nil { } else if assertionFailure == nil {
panic(e) panic(e)
} }
@ -317,13 +271,6 @@ func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error
}, nil }, nil
} }
func (assertion *AsyncAssertion) matcherSaysStopTrying(matcher types.GomegaMatcher, value interface{}) StopTryingError {
if assertion.actualIsFunc || types.MatchMayChangeInTheFuture(matcher, value) {
return nil
}
return StopTrying("No future change is possible. Bailing out early")
}
func (assertion *AsyncAssertion) afterTimeout() <-chan time.Time { func (assertion *AsyncAssertion) afterTimeout() <-chan time.Time {
if assertion.timeoutInterval >= 0 { if assertion.timeoutInterval >= 0 {
return time.After(assertion.timeoutInterval) return time.After(assertion.timeoutInterval)
@ -351,8 +298,27 @@ func (assertion *AsyncAssertion) afterPolling() <-chan time.Time {
} }
} }
type contextWithAttachProgressReporter interface { func (assertion *AsyncAssertion) matcherSaysStopTrying(matcher types.GomegaMatcher, value interface{}) bool {
AttachProgressReporter(func() string) func() if assertion.actualIsFunc || types.MatchMayChangeInTheFuture(matcher, value) {
return false
}
return true
}
func (assertion *AsyncAssertion) pollMatcher(matcher types.GomegaMatcher, value interface{}) (matches bool, err error) {
defer func() {
if e := recover(); e != nil {
if _, isAsyncError := AsPollingSignalError(e); isAsyncError {
err = e.(error)
} else {
panic(e)
}
}
}()
matches, err = matcher.Match(value)
return
} }
func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool { func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
@ -362,6 +328,7 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
var matches bool var matches bool
var err error var err error
var oracleMatcherSaysStop bool
assertion.g.THelper() assertion.g.THelper()
@ -371,22 +338,27 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
return false return false
} }
value, err, stopTrying := pollActual() value, err := pollActual()
if err == nil { if err == nil {
if stopTrying == nil { oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, value)
stopTrying = assertion.matcherSaysStopTrying(matcher, value) matches, err = assertion.pollMatcher(matcher, value)
}
matches, err = matcher.Match(value)
} }
messageGenerator := func() string { messageGenerator := func() string {
// can be called out of band by Ginkgo if the user requests a progress report // can be called out of band by Ginkgo if the user requests a progress report
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
errMsg := ""
message := "" message := ""
if err != nil { if err != nil {
errMsg = "Error: " + err.Error() if pollingSignalErr, ok := AsPollingSignalError(err); ok && pollingSignalErr.IsStopTrying() {
message = err.Error()
for _, attachment := range pollingSignalErr.Attachments {
message += fmt.Sprintf("\n%s:\n", attachment.Description)
message += format.Object(attachment.Object, 1)
}
} else {
message = "Error: " + err.Error() + "\n" + format.Object(err, 1)
}
} else { } else {
if desiredMatch { if desiredMatch {
message = matcher.FailureMessage(value) message = matcher.FailureMessage(value)
@ -395,7 +367,7 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
} }
} }
description := assertion.buildDescription(optionalDescription...) description := assertion.buildDescription(optionalDescription...)
return fmt.Sprintf("%s%s%s", description, message, errMsg) return fmt.Sprintf("%s%s", description, message)
} }
fail := func(preamble string) { fail := func(preamble string) {
@ -412,84 +384,72 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
} }
} }
if assertion.asyncType == AsyncAssertionTypeEventually { for {
for { var nextPoll <-chan time.Time = nil
if err == nil && matches == desiredMatch { var isTryAgainAfterError = false
return true
}
if stopTrying != nil { if pollingSignalErr, ok := AsPollingSignalError(err); ok {
fail(stopTrying.Error() + " -") if pollingSignalErr.IsStopTrying() {
fail("Told to stop trying")
return false return false
} }
if pollingSignalErr.IsTryAgainAfter() {
select { nextPoll = time.After(pollingSignalErr.TryAgainDuration())
case <-assertion.afterPolling(): isTryAgainAfterError = true
v, e, st := pollActual()
if st != nil && st.wasViaPanic() {
// we were told to stop trying via panic - which means we dont' have reasonable new values
// we should simply use the old values and exit now
fail(st.Error() + " -")
return false
}
lock.Lock()
value, err, stopTrying = v, e, st
lock.Unlock()
if err == nil {
if stopTrying == nil {
stopTrying = assertion.matcherSaysStopTrying(matcher, value)
}
matches, e = matcher.Match(value)
lock.Lock()
err = e
lock.Unlock()
}
case <-contextDone:
fail("Context was cancelled")
return false
case <-timeout:
fail("Timed out")
return false
} }
} }
} else if assertion.asyncType == AsyncAssertionTypeConsistently {
for { if err == nil && matches == desiredMatch {
if !(err == nil && matches == desiredMatch) { if assertion.asyncType == AsyncAssertionTypeEventually {
return true
}
} else if !isTryAgainAfterError {
if assertion.asyncType == AsyncAssertionTypeConsistently {
fail("Failed") fail("Failed")
return false return false
} }
}
if stopTrying != nil { if oracleMatcherSaysStop {
if assertion.asyncType == AsyncAssertionTypeEventually {
fail("No future change is possible. Bailing out early")
return false
} else {
return true return true
} }
}
select { if nextPoll == nil {
case <-assertion.afterPolling(): nextPoll = assertion.afterPolling()
v, e, st := pollActual() }
if st != nil && st.wasViaPanic() {
// we were told to stop trying via panic - which means we made it this far and should return successfully select {
return true case <-nextPoll:
} v, e := pollActual()
lock.Lock()
value, err = v, e
lock.Unlock()
if err == nil {
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, value)
m, e := assertion.pollMatcher(matcher, value)
lock.Lock() lock.Lock()
value, err, stopTrying = v, e, st matches, err = m, e
lock.Unlock() lock.Unlock()
if err == nil { }
if stopTrying == nil { case <-contextDone:
stopTrying = assertion.matcherSaysStopTrying(matcher, value) fail("Context was cancelled")
} return false
matches, e = matcher.Match(value) case <-timeout:
lock.Lock() if assertion.asyncType == AsyncAssertionTypeEventually {
err = e fail("Timed out")
lock.Unlock()
}
case <-contextDone:
fail("Context was cancelled")
return false return false
case <-timeout: } else {
if isTryAgainAfterError {
fail("Timed out while waiting on TryAgainAfter")
return false
}
return true return true
} }
} }
} }
return false
} }

View File

@ -44,28 +44,28 @@ func durationFromEnv(key string, defaultDuration time.Duration) time.Duration {
return duration return duration
} }
func toDuration(input interface{}) time.Duration { func toDuration(input interface{}) (time.Duration, error) {
duration, ok := input.(time.Duration) duration, ok := input.(time.Duration)
if ok { if ok {
return duration return duration, nil
} }
value := reflect.ValueOf(input) value := reflect.ValueOf(input)
kind := reflect.TypeOf(input).Kind() kind := reflect.TypeOf(input).Kind()
if reflect.Int <= kind && kind <= reflect.Int64 { if reflect.Int <= kind && kind <= reflect.Int64 {
return time.Duration(value.Int()) * time.Second return time.Duration(value.Int()) * time.Second, nil
} else if reflect.Uint <= kind && kind <= reflect.Uint64 { } else if reflect.Uint <= kind && kind <= reflect.Uint64 {
return time.Duration(value.Uint()) * time.Second return time.Duration(value.Uint()) * time.Second, nil
} else if reflect.Float32 <= kind && kind <= reflect.Float64 { } else if reflect.Float32 <= kind && kind <= reflect.Float64 {
return time.Duration(value.Float() * float64(time.Second)) return time.Duration(value.Float() * float64(time.Second)), nil
} else if reflect.String == kind { } else if reflect.String == kind {
duration, err := time.ParseDuration(value.String()) duration, err := time.ParseDuration(value.String())
if err != nil { if err != nil {
panic(fmt.Sprintf("%#v is not a valid parsable duration string.", input)) return 0, fmt.Errorf("%#v is not a valid parsable duration string: %w", input, err)
} }
return duration return duration, nil
} }
panic(fmt.Sprintf("%v is not a valid interval. Must be time.Duration, parsable duration string or a number.", input)) return 0, fmt.Errorf("%#v is not a valid interval. Must be a time.Duration, a parsable duration string, or a number.", input)
} }

View File

@ -2,6 +2,7 @@ package internal
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
@ -52,16 +53,46 @@ func (g *Gomega) ExpectWithOffset(offset int, actual interface{}, extra ...inter
return NewAssertion(actual, g, offset, extra...) return NewAssertion(actual, g, offset, extra...)
} }
func (g *Gomega) Eventually(actual interface{}, intervals ...interface{}) types.AsyncAssertion { func (g *Gomega) Eventually(args ...interface{}) types.AsyncAssertion {
return g.EventuallyWithOffset(0, actual, intervals...) return g.makeAsyncAssertion(AsyncAssertionTypeEventually, 0, args...)
} }
func (g *Gomega) EventuallyWithOffset(offset int, actual interface{}, args ...interface{}) types.AsyncAssertion { func (g *Gomega) EventuallyWithOffset(offset int, args ...interface{}) types.AsyncAssertion {
return g.makeAsyncAssertion(AsyncAssertionTypeEventually, offset, args...)
}
func (g *Gomega) Consistently(args ...interface{}) types.AsyncAssertion {
return g.makeAsyncAssertion(AsyncAssertionTypeConsistently, 0, args...)
}
func (g *Gomega) ConsistentlyWithOffset(offset int, args ...interface{}) types.AsyncAssertion {
return g.makeAsyncAssertion(AsyncAssertionTypeConsistently, offset, args...)
}
func (g *Gomega) makeAsyncAssertion(asyncAssertionType AsyncAssertionType, offset int, args ...interface{}) types.AsyncAssertion {
baseOffset := 3
timeoutInterval := -time.Duration(1) timeoutInterval := -time.Duration(1)
pollingInterval := -time.Duration(1) pollingInterval := -time.Duration(1)
intervals := []interface{}{} intervals := []interface{}{}
var ctx context.Context var ctx context.Context
for _, arg := range args { if len(args) == 0 {
g.Fail(fmt.Sprintf("Call to %s is missing a value or function to poll", asyncAssertionType), offset+baseOffset)
return nil
}
actual := args[0]
startingIndex := 1
if _, isCtx := args[0].(context.Context); isCtx && len(args) > 1 {
// the first argument is a context, we should accept it as the context _only if_ it is **not** the only argumnent **and** the second argument is not a parseable duration
// this is due to an unfortunate ambiguity in early version of Gomega in which multi-type durations are allowed after the actual
if _, err := toDuration(args[1]); err != nil {
ctx = args[0].(context.Context)
actual = args[1]
startingIndex = 2
}
}
for _, arg := range args[startingIndex:] {
switch v := arg.(type) { switch v := arg.(type) {
case context.Context: case context.Context:
ctx = v ctx = v
@ -69,41 +100,21 @@ func (g *Gomega) EventuallyWithOffset(offset int, actual interface{}, args ...in
intervals = append(intervals, arg) intervals = append(intervals, arg)
} }
} }
var err error
if len(intervals) > 0 { if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0]) timeoutInterval, err = toDuration(intervals[0])
} if err != nil {
if len(intervals) > 1 { g.Fail(err.Error(), offset+baseOffset)
pollingInterval = toDuration(intervals[1])
}
return NewAsyncAssertion(AsyncAssertionTypeEventually, actual, g, timeoutInterval, pollingInterval, ctx, offset)
}
func (g *Gomega) Consistently(actual interface{}, intervals ...interface{}) types.AsyncAssertion {
return g.ConsistentlyWithOffset(0, actual, intervals...)
}
func (g *Gomega) ConsistentlyWithOffset(offset int, actual interface{}, args ...interface{}) types.AsyncAssertion {
timeoutInterval := -time.Duration(1)
pollingInterval := -time.Duration(1)
intervals := []interface{}{}
var ctx context.Context
for _, arg := range args {
switch v := arg.(type) {
case context.Context:
ctx = v
default:
intervals = append(intervals, arg)
} }
} }
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 { if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1]) pollingInterval, err = toDuration(intervals[1])
if err != nil {
g.Fail(err.Error(), offset+baseOffset)
}
} }
return NewAsyncAssertion(AsyncAssertionTypeConsistently, actual, g, timeoutInterval, pollingInterval, ctx, offset) return NewAsyncAssertion(asyncAssertionType, actual, g, timeoutInterval, pollingInterval, ctx, offset)
} }
func (g *Gomega) SetDefaultEventuallyTimeout(t time.Duration) { func (g *Gomega) SetDefaultEventuallyTimeout(t time.Duration) {

View File

@ -0,0 +1,106 @@
package internal
import (
"errors"
"fmt"
"time"
)
type PollingSignalErrorType int
const (
PollingSignalErrorTypeStopTrying PollingSignalErrorType = iota
PollingSignalErrorTypeTryAgainAfter
)
type PollingSignalError interface {
error
Wrap(err error) PollingSignalError
Attach(description string, obj any) PollingSignalError
Now()
}
var StopTrying = func(message string) PollingSignalError {
return &PollingSignalErrorImpl{
message: message,
pollingSignalErrorType: PollingSignalErrorTypeStopTrying,
}
}
var TryAgainAfter = func(duration time.Duration) PollingSignalError {
return &PollingSignalErrorImpl{
message: fmt.Sprintf("told to try again after %s", duration),
duration: duration,
pollingSignalErrorType: PollingSignalErrorTypeTryAgainAfter,
}
}
type PollingSignalErrorAttachment struct {
Description string
Object any
}
type PollingSignalErrorImpl struct {
message string
wrappedErr error
pollingSignalErrorType PollingSignalErrorType
duration time.Duration
Attachments []PollingSignalErrorAttachment
}
func (s *PollingSignalErrorImpl) Wrap(err error) PollingSignalError {
s.wrappedErr = err
return s
}
func (s *PollingSignalErrorImpl) Attach(description string, obj any) PollingSignalError {
s.Attachments = append(s.Attachments, PollingSignalErrorAttachment{description, obj})
return s
}
func (s *PollingSignalErrorImpl) Error() string {
if s.wrappedErr == nil {
return s.message
} else {
return s.message + ": " + s.wrappedErr.Error()
}
}
func (s *PollingSignalErrorImpl) Unwrap() error {
if s == nil {
return nil
}
return s.wrappedErr
}
func (s *PollingSignalErrorImpl) Now() {
panic(s)
}
func (s *PollingSignalErrorImpl) IsStopTrying() bool {
return s.pollingSignalErrorType == PollingSignalErrorTypeStopTrying
}
func (s *PollingSignalErrorImpl) IsTryAgainAfter() bool {
return s.pollingSignalErrorType == PollingSignalErrorTypeTryAgainAfter
}
func (s *PollingSignalErrorImpl) TryAgainDuration() time.Duration {
return s.duration
}
func AsPollingSignalError(actual interface{}) (*PollingSignalErrorImpl, bool) {
if actual == nil {
return nil, false
}
if actualErr, ok := actual.(error); ok {
var target *PollingSignalErrorImpl
if errors.As(actualErr, &target) {
return target, true
} else {
return nil, false
}
}
return nil, false
}

View File

@ -19,11 +19,11 @@ type Gomega interface {
Expect(actual interface{}, extra ...interface{}) Assertion Expect(actual interface{}, extra ...interface{}) Assertion
ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion
Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion Eventually(args ...interface{}) AsyncAssertion
EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion EventuallyWithOffset(offset int, args ...interface{}) AsyncAssertion
Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion Consistently(args ...interface{}) AsyncAssertion
ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion ConsistentlyWithOffset(offset int, args ...interface{}) AsyncAssertion
SetDefaultEventuallyTimeout(time.Duration) SetDefaultEventuallyTimeout(time.Duration)
SetDefaultEventuallyPollingInterval(time.Duration) SetDefaultEventuallyPollingInterval(time.Duration)

4
vendor/modules.txt vendored
View File

@ -247,7 +247,7 @@ github.com/google/gnostic/extensions
github.com/google/gnostic/jsonschema github.com/google/gnostic/jsonschema
github.com/google/gnostic/openapiv2 github.com/google/gnostic/openapiv2
github.com/google/gnostic/openapiv3 github.com/google/gnostic/openapiv3
# github.com/google/go-cmp v0.5.8 # github.com/google/go-cmp v0.5.9
## explicit; go 1.13 ## explicit; go 1.13
github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp
github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/diff
@ -455,7 +455,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.22.1 # github.com/onsi/gomega v1.23.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/onsi/gomega github.com/onsi/gomega
github.com/onsi/gomega/format github.com/onsi/gomega/format