Initial QSfera import
This commit is contained in:
+62
@@ -0,0 +1,62 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
type AndMatcher struct {
|
||||
Matchers []types.GomegaMatcher
|
||||
|
||||
// state
|
||||
firstFailedMatcher types.GomegaMatcher
|
||||
}
|
||||
|
||||
func (m *AndMatcher) Match(actual any) (success bool, err error) {
|
||||
m.firstFailedMatcher = nil
|
||||
for _, matcher := range m.Matchers {
|
||||
success, err := matcher.Match(actual)
|
||||
if !success || err != nil {
|
||||
m.firstFailedMatcher = matcher
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *AndMatcher) FailureMessage(actual any) (message string) {
|
||||
return m.firstFailedMatcher.FailureMessage(actual)
|
||||
}
|
||||
|
||||
func (m *AndMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
// not the most beautiful list of matchers, but not bad either...
|
||||
return format.Message(actual, fmt.Sprintf("To not satisfy all of these matchers: %s", m.Matchers))
|
||||
}
|
||||
|
||||
func (m *AndMatcher) MatchMayChangeInTheFuture(actual any) bool {
|
||||
/*
|
||||
Example with 3 matchers: A, B, C
|
||||
|
||||
Match evaluates them: T, F, <?> => F
|
||||
So match is currently F, what should MatchMayChangeInTheFuture() return?
|
||||
Seems like it only depends on B, since currently B MUST change to allow the result to become T
|
||||
|
||||
Match eval: T, T, T => T
|
||||
So match is currently T, what should MatchMayChangeInTheFuture() return?
|
||||
Seems to depend on ANY of them being able to change to F.
|
||||
*/
|
||||
|
||||
if m.firstFailedMatcher == nil {
|
||||
// so all matchers succeeded.. Any one of them changing would change the result.
|
||||
for _, matcher := range m.Matchers {
|
||||
if types.MatchMayChangeInTheFuture(matcher, actual) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false // none of were going to change
|
||||
}
|
||||
// one of the matchers failed.. it must be able to change in order to affect the result
|
||||
return types.MatchMayChangeInTheFuture(m.firstFailedMatcher, actual)
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type AssignableToTypeOfMatcher struct {
|
||||
Expected any
|
||||
}
|
||||
|
||||
func (matcher *AssignableToTypeOfMatcher) Match(actual any) (success bool, err error) {
|
||||
if actual == nil && matcher.Expected == nil {
|
||||
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
|
||||
} else if matcher.Expected == nil {
|
||||
return false, fmt.Errorf("Refusing to compare type to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
|
||||
} else if actual == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
actualType := reflect.TypeOf(actual)
|
||||
expectedType := reflect.TypeOf(matcher.Expected)
|
||||
|
||||
return actualType.AssignableTo(expectedType), nil
|
||||
}
|
||||
|
||||
func (matcher *AssignableToTypeOfMatcher) FailureMessage(actual any) string {
|
||||
return format.Message(actual, fmt.Sprintf("to be assignable to the type: %T", matcher.Expected))
|
||||
}
|
||||
|
||||
func (matcher *AssignableToTypeOfMatcher) NegatedFailureMessage(actual any) string {
|
||||
return format.Message(actual, fmt.Sprintf("not to be assignable to the type: %T", matcher.Expected))
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type attributesSlice []xml.Attr
|
||||
|
||||
func (attrs attributesSlice) Len() int { return len(attrs) }
|
||||
func (attrs attributesSlice) Less(i, j int) bool {
|
||||
return strings.Compare(attrs[i].Name.Local, attrs[j].Name.Local) == -1
|
||||
}
|
||||
func (attrs attributesSlice) Swap(i, j int) { attrs[i], attrs[j] = attrs[j], attrs[i] }
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// untested sections: 5
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type notADirectoryError struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (t notADirectoryError) Error() string {
|
||||
fileInfo := os.FileInfo(t)
|
||||
switch {
|
||||
case fileInfo.Mode().IsRegular():
|
||||
return "file is a regular file"
|
||||
default:
|
||||
return fmt.Sprintf("file mode is: %s", fileInfo.Mode().String())
|
||||
}
|
||||
}
|
||||
|
||||
type BeADirectoryMatcher struct {
|
||||
expected any
|
||||
err error
|
||||
}
|
||||
|
||||
func (matcher *BeADirectoryMatcher) Match(actual any) (success bool, err error) {
|
||||
actualFilename, ok := actual.(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("BeADirectoryMatcher matcher expects a file path")
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(actualFilename)
|
||||
if err != nil {
|
||||
matcher.err = err
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !fileInfo.Mode().IsDir() {
|
||||
matcher.err = notADirectoryError{fileInfo}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (matcher *BeADirectoryMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("to be a directory: %s", matcher.err))
|
||||
}
|
||||
|
||||
func (matcher *BeADirectoryMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not be a directory")
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// untested sections: 5
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type notARegularFileError struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (t notARegularFileError) Error() string {
|
||||
fileInfo := os.FileInfo(t)
|
||||
switch {
|
||||
case fileInfo.IsDir():
|
||||
return "file is a directory"
|
||||
default:
|
||||
return fmt.Sprintf("file mode is: %s", fileInfo.Mode().String())
|
||||
}
|
||||
}
|
||||
|
||||
type BeARegularFileMatcher struct {
|
||||
expected any
|
||||
err error
|
||||
}
|
||||
|
||||
func (matcher *BeARegularFileMatcher) Match(actual any) (success bool, err error) {
|
||||
actualFilename, ok := actual.(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("BeARegularFileMatcher matcher expects a file path")
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(actualFilename)
|
||||
if err != nil {
|
||||
matcher.err = err
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !fileInfo.Mode().IsRegular() {
|
||||
matcher.err = notARegularFileError{fileInfo}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (matcher *BeARegularFileMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("to be a regular file: %s", matcher.err))
|
||||
}
|
||||
|
||||
func (matcher *BeARegularFileMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not be a regular file")
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// untested sections: 3
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeAnExistingFileMatcher struct {
|
||||
expected any
|
||||
}
|
||||
|
||||
func (matcher *BeAnExistingFileMatcher) Match(actual any) (success bool, err error) {
|
||||
actualFilename, ok := actual.(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("BeAnExistingFileMatcher matcher expects a file path")
|
||||
}
|
||||
|
||||
if _, err = os.Stat(actualFilename); err != nil {
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return false, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (matcher *BeAnExistingFileMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to exist")
|
||||
}
|
||||
|
||||
func (matcher *BeAnExistingFileMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to exist")
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeClosedMatcher struct {
|
||||
}
|
||||
|
||||
func (matcher *BeClosedMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isChan(actual) {
|
||||
return false, fmt.Errorf("BeClosed matcher expects a channel. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
channelType := reflect.TypeOf(actual)
|
||||
channelValue := reflect.ValueOf(actual)
|
||||
|
||||
if channelType.ChanDir() == reflect.SendDir {
|
||||
return false, fmt.Errorf("BeClosed matcher cannot determine if a send-only channel is closed or open. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
winnerIndex, _, open := reflect.Select([]reflect.SelectCase{
|
||||
{Dir: reflect.SelectRecv, Chan: channelValue},
|
||||
{Dir: reflect.SelectDefault},
|
||||
})
|
||||
|
||||
var closed bool
|
||||
if winnerIndex == 0 {
|
||||
closed = !open
|
||||
} else if winnerIndex == 1 {
|
||||
closed = false
|
||||
}
|
||||
|
||||
return closed, nil
|
||||
}
|
||||
|
||||
func (matcher *BeClosedMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be closed")
|
||||
}
|
||||
|
||||
func (matcher *BeClosedMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be open")
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeComparableToMatcher struct {
|
||||
Expected any
|
||||
Options cmp.Options
|
||||
}
|
||||
|
||||
func (matcher *BeComparableToMatcher) Match(actual any) (success bool, matchErr error) {
|
||||
if actual == nil && matcher.Expected == nil {
|
||||
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
|
||||
}
|
||||
// Shortcut for byte slices.
|
||||
// Comparing long byte slices with reflect.DeepEqual is very slow,
|
||||
// so use bytes.Equal if actual and expected are both byte slices.
|
||||
if actualByteSlice, ok := actual.([]byte); ok {
|
||||
if expectedByteSlice, ok := matcher.Expected.([]byte); ok {
|
||||
return bytes.Equal(actualByteSlice, expectedByteSlice), nil
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
success = false
|
||||
if err, ok := r.(error); ok {
|
||||
matchErr = err
|
||||
} else if errMsg, ok := r.(string); ok {
|
||||
matchErr = errors.New(errMsg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return cmp.Equal(actual, matcher.Expected, matcher.Options...), nil
|
||||
}
|
||||
|
||||
func (matcher *BeComparableToMatcher) FailureMessage(actual any) (message string) {
|
||||
return fmt.Sprint("Expected object to be comparable, diff: ", cmp.Diff(actual, matcher.Expected, matcher.Options...))
|
||||
}
|
||||
|
||||
func (matcher *BeComparableToMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to be comparable to", matcher.Expected)
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// untested sections: 1
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeElementOfMatcher struct {
|
||||
Elements []any
|
||||
}
|
||||
|
||||
func (matcher *BeElementOfMatcher) Match(actual any) (success bool, err error) {
|
||||
if reflect.TypeOf(actual) == nil {
|
||||
return false, fmt.Errorf("BeElement matcher expects actual to be typed")
|
||||
}
|
||||
|
||||
var lastError error
|
||||
for _, m := range flatten(matcher.Elements) {
|
||||
matcher := &EqualMatcher{Expected: m}
|
||||
success, err := matcher.Match(actual)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
if success {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, lastError
|
||||
}
|
||||
|
||||
func (matcher *BeElementOfMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be an element of", presentable(matcher.Elements))
|
||||
}
|
||||
|
||||
func (matcher *BeElementOfMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to be an element of", presentable(matcher.Elements))
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
)
|
||||
|
||||
type BeEmptyMatcher struct {
|
||||
}
|
||||
|
||||
func (matcher *BeEmptyMatcher) Match(actual any) (success bool, err error) {
|
||||
// short-circuit the iterator case, as we only need to see the first
|
||||
// element, if any.
|
||||
if miter.IsIter(actual) {
|
||||
var length int
|
||||
if miter.IsSeq2(actual) {
|
||||
miter.IterateKV(actual, func(k, v reflect.Value) bool { length++; return false })
|
||||
} else {
|
||||
miter.IterateV(actual, func(v reflect.Value) bool { length++; return false })
|
||||
}
|
||||
return length == 0, nil
|
||||
}
|
||||
|
||||
length, ok := lengthOf(actual)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("BeEmpty matcher expects a string/array/map/channel/slice/iterator. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
return length == 0, nil
|
||||
}
|
||||
|
||||
func (matcher *BeEmptyMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be empty")
|
||||
}
|
||||
|
||||
func (matcher *BeEmptyMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to be empty")
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeEquivalentToMatcher struct {
|
||||
Expected any
|
||||
}
|
||||
|
||||
func (matcher *BeEquivalentToMatcher) Match(actual any) (success bool, err error) {
|
||||
if actual == nil && matcher.Expected == nil {
|
||||
return false, fmt.Errorf("Both actual and expected must not be nil.")
|
||||
}
|
||||
|
||||
convertedActual := actual
|
||||
|
||||
if actual != nil && matcher.Expected != nil && reflect.TypeOf(actual).ConvertibleTo(reflect.TypeOf(matcher.Expected)) {
|
||||
convertedActual = reflect.ValueOf(actual).Convert(reflect.TypeOf(matcher.Expected)).Interface()
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(convertedActual, matcher.Expected), nil
|
||||
}
|
||||
|
||||
func (matcher *BeEquivalentToMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be equivalent to", matcher.Expected)
|
||||
}
|
||||
|
||||
func (matcher *BeEquivalentToMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to be equivalent to", matcher.Expected)
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeFalseMatcher struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (matcher *BeFalseMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isBool(actual) {
|
||||
return false, fmt.Errorf("Expected a boolean. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
return actual == false, nil
|
||||
}
|
||||
|
||||
func (matcher *BeFalseMatcher) FailureMessage(actual any) (message string) {
|
||||
if matcher.Reason == "" {
|
||||
return format.Message(actual, "to be false")
|
||||
} else {
|
||||
return matcher.Reason
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *BeFalseMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
if matcher.Reason == "" {
|
||||
return format.Message(actual, "not to be false")
|
||||
} else {
|
||||
return fmt.Sprintf(`Expected not false but got false\nNegation of "%s" failed`, matcher.Reason)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeIdenticalToMatcher struct {
|
||||
Expected any
|
||||
}
|
||||
|
||||
func (matcher *BeIdenticalToMatcher) Match(actual any) (success bool, matchErr error) {
|
||||
if actual == nil && matcher.Expected == nil {
|
||||
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
success = false
|
||||
matchErr = nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return actual == matcher.Expected, nil
|
||||
}
|
||||
|
||||
func (matcher *BeIdenticalToMatcher) FailureMessage(actual any) string {
|
||||
return format.Message(actual, "to be identical to", matcher.Expected)
|
||||
}
|
||||
|
||||
func (matcher *BeIdenticalToMatcher) NegatedFailureMessage(actual any) string {
|
||||
return format.Message(actual, "not to be identical to", matcher.Expected)
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeKeyOfMatcher struct {
|
||||
Map any
|
||||
}
|
||||
|
||||
func (matcher *BeKeyOfMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isMap(matcher.Map) {
|
||||
return false, fmt.Errorf("BeKeyOf matcher needs expected to be a map type")
|
||||
}
|
||||
|
||||
if reflect.TypeOf(actual) == nil {
|
||||
return false, fmt.Errorf("BeKeyOf matcher expects actual to be typed")
|
||||
}
|
||||
|
||||
var lastError error
|
||||
for _, key := range reflect.ValueOf(matcher.Map).MapKeys() {
|
||||
matcher := &EqualMatcher{Expected: key.Interface()}
|
||||
success, err := matcher.Match(actual)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
if success {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, lastError
|
||||
}
|
||||
|
||||
func (matcher *BeKeyOfMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be a key of", presentable(valuesOf(matcher.Map)))
|
||||
}
|
||||
|
||||
func (matcher *BeKeyOfMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to be a key of", presentable(valuesOf(matcher.Map)))
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import "github.com/onsi/gomega/format"
|
||||
|
||||
type BeNilMatcher struct {
|
||||
}
|
||||
|
||||
func (matcher *BeNilMatcher) Match(actual any) (success bool, err error) {
|
||||
return isNil(actual), nil
|
||||
}
|
||||
|
||||
func (matcher *BeNilMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be nil")
|
||||
}
|
||||
|
||||
func (matcher *BeNilMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to be nil")
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// untested sections: 4
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeNumericallyMatcher struct {
|
||||
Comparator string
|
||||
CompareTo []any
|
||||
}
|
||||
|
||||
func (matcher *BeNumericallyMatcher) FailureMessage(actual any) (message string) {
|
||||
return matcher.FormatFailureMessage(actual, false)
|
||||
}
|
||||
|
||||
func (matcher *BeNumericallyMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return matcher.FormatFailureMessage(actual, true)
|
||||
}
|
||||
|
||||
func (matcher *BeNumericallyMatcher) FormatFailureMessage(actual any, negated bool) (message string) {
|
||||
if len(matcher.CompareTo) == 1 {
|
||||
message = fmt.Sprintf("to be %s", matcher.Comparator)
|
||||
} else {
|
||||
message = fmt.Sprintf("to be within %v of %s", matcher.CompareTo[1], matcher.Comparator)
|
||||
}
|
||||
if negated {
|
||||
message = "not " + message
|
||||
}
|
||||
return format.Message(actual, message, matcher.CompareTo[0])
|
||||
}
|
||||
|
||||
func (matcher *BeNumericallyMatcher) Match(actual any) (success bool, err error) {
|
||||
if len(matcher.CompareTo) == 0 || len(matcher.CompareTo) > 2 {
|
||||
return false, fmt.Errorf("BeNumerically requires 1 or 2 CompareTo arguments. Got:\n%s", format.Object(matcher.CompareTo, 1))
|
||||
}
|
||||
if !isNumber(actual) {
|
||||
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
if !isNumber(matcher.CompareTo[0]) {
|
||||
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
|
||||
}
|
||||
if len(matcher.CompareTo) == 2 && !isNumber(matcher.CompareTo[1]) {
|
||||
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[1], 1))
|
||||
}
|
||||
|
||||
switch matcher.Comparator {
|
||||
case "==", "~", ">", ">=", "<", "<=":
|
||||
default:
|
||||
return false, fmt.Errorf("Unknown comparator: %s", matcher.Comparator)
|
||||
}
|
||||
|
||||
if isFloat(actual) || isFloat(matcher.CompareTo[0]) {
|
||||
var secondOperand float64 = 1e-8
|
||||
if len(matcher.CompareTo) == 2 {
|
||||
secondOperand = toFloat(matcher.CompareTo[1])
|
||||
}
|
||||
success = matcher.matchFloats(toFloat(actual), toFloat(matcher.CompareTo[0]), secondOperand)
|
||||
} else if isInteger(actual) {
|
||||
var secondOperand int64 = 0
|
||||
if len(matcher.CompareTo) == 2 {
|
||||
secondOperand = toInteger(matcher.CompareTo[1])
|
||||
}
|
||||
success = matcher.matchIntegers(toInteger(actual), toInteger(matcher.CompareTo[0]), secondOperand)
|
||||
} else if isUnsignedInteger(actual) {
|
||||
var secondOperand uint64 = 0
|
||||
if len(matcher.CompareTo) == 2 {
|
||||
secondOperand = toUnsignedInteger(matcher.CompareTo[1])
|
||||
}
|
||||
success = matcher.matchUnsignedIntegers(toUnsignedInteger(actual), toUnsignedInteger(matcher.CompareTo[0]), secondOperand)
|
||||
} else {
|
||||
return false, fmt.Errorf("Failed to compare:\n%s\n%s:\n%s", format.Object(actual, 1), matcher.Comparator, format.Object(matcher.CompareTo[0], 1))
|
||||
}
|
||||
|
||||
return success, nil
|
||||
}
|
||||
|
||||
func (matcher *BeNumericallyMatcher) matchIntegers(actual, compareTo, threshold int64) (success bool) {
|
||||
switch matcher.Comparator {
|
||||
case "==", "~":
|
||||
diff := actual - compareTo
|
||||
return -threshold <= diff && diff <= threshold
|
||||
case ">":
|
||||
return (actual > compareTo)
|
||||
case ">=":
|
||||
return (actual >= compareTo)
|
||||
case "<":
|
||||
return (actual < compareTo)
|
||||
case "<=":
|
||||
return (actual <= compareTo)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (matcher *BeNumericallyMatcher) matchUnsignedIntegers(actual, compareTo, threshold uint64) (success bool) {
|
||||
switch matcher.Comparator {
|
||||
case "==", "~":
|
||||
if actual < compareTo {
|
||||
actual, compareTo = compareTo, actual
|
||||
}
|
||||
return actual-compareTo <= threshold
|
||||
case ">":
|
||||
return (actual > compareTo)
|
||||
case ">=":
|
||||
return (actual >= compareTo)
|
||||
case "<":
|
||||
return (actual < compareTo)
|
||||
case "<=":
|
||||
return (actual <= compareTo)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (matcher *BeNumericallyMatcher) matchFloats(actual, compareTo, threshold float64) (success bool) {
|
||||
switch matcher.Comparator {
|
||||
case "~":
|
||||
return math.Abs(actual-compareTo) <= threshold
|
||||
case "==":
|
||||
return (actual == compareTo)
|
||||
case ">":
|
||||
return (actual > compareTo)
|
||||
case ">=":
|
||||
return (actual >= compareTo)
|
||||
case "<":
|
||||
return (actual < compareTo)
|
||||
case "<=":
|
||||
return (actual <= compareTo)
|
||||
}
|
||||
return false
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// untested sections: 3
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeSentMatcher struct {
|
||||
Arg any
|
||||
channelClosed bool
|
||||
}
|
||||
|
||||
func (matcher *BeSentMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isChan(actual) {
|
||||
return false, fmt.Errorf("BeSent expects a channel. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
channelType := reflect.TypeOf(actual)
|
||||
channelValue := reflect.ValueOf(actual)
|
||||
|
||||
if channelType.ChanDir() == reflect.RecvDir {
|
||||
return false, fmt.Errorf("BeSent matcher cannot be passed a receive-only channel. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
argType := reflect.TypeOf(matcher.Arg)
|
||||
assignable := argType.AssignableTo(channelType.Elem())
|
||||
|
||||
if !assignable {
|
||||
return false, fmt.Errorf("Cannot pass:\n%s to the channel:\n%s\nThe types don't match.", format.Object(matcher.Arg, 1), format.Object(actual, 1))
|
||||
}
|
||||
|
||||
argValue := reflect.ValueOf(matcher.Arg)
|
||||
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
success = false
|
||||
err = fmt.Errorf("Cannot send to a closed channel")
|
||||
matcher.channelClosed = true
|
||||
}
|
||||
}()
|
||||
|
||||
winnerIndex, _, _ := reflect.Select([]reflect.SelectCase{
|
||||
{Dir: reflect.SelectSend, Chan: channelValue, Send: argValue},
|
||||
{Dir: reflect.SelectDefault},
|
||||
})
|
||||
|
||||
var didSend bool
|
||||
if winnerIndex == 0 {
|
||||
didSend = true
|
||||
}
|
||||
|
||||
return didSend, nil
|
||||
}
|
||||
|
||||
func (matcher *BeSentMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to send:", matcher.Arg)
|
||||
}
|
||||
|
||||
func (matcher *BeSentMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to send:", matcher.Arg)
|
||||
}
|
||||
|
||||
func (matcher *BeSentMatcher) MatchMayChangeInTheFuture(actual any) bool {
|
||||
if !isChan(actual) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !matcher.channelClosed
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// untested sections: 3
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeTemporallyMatcher struct {
|
||||
Comparator string
|
||||
CompareTo time.Time
|
||||
Threshold []time.Duration
|
||||
}
|
||||
|
||||
func (matcher *BeTemporallyMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("to be %s", matcher.Comparator), matcher.CompareTo)
|
||||
}
|
||||
|
||||
func (matcher *BeTemporallyMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("not to be %s", matcher.Comparator), matcher.CompareTo)
|
||||
}
|
||||
|
||||
func (matcher *BeTemporallyMatcher) Match(actual any) (bool, error) {
|
||||
// predicate to test for time.Time type
|
||||
isTime := func(t any) bool {
|
||||
_, ok := t.(time.Time)
|
||||
return ok
|
||||
}
|
||||
|
||||
if !isTime(actual) {
|
||||
return false, fmt.Errorf("Expected a time.Time. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
switch matcher.Comparator {
|
||||
case "==", "~", ">", ">=", "<", "<=":
|
||||
default:
|
||||
return false, fmt.Errorf("Unknown comparator: %s", matcher.Comparator)
|
||||
}
|
||||
|
||||
var threshold = time.Millisecond
|
||||
if len(matcher.Threshold) == 1 {
|
||||
threshold = matcher.Threshold[0]
|
||||
}
|
||||
|
||||
return matcher.matchTimes(actual.(time.Time), matcher.CompareTo, threshold), nil
|
||||
}
|
||||
|
||||
func (matcher *BeTemporallyMatcher) matchTimes(actual, compareTo time.Time, threshold time.Duration) (success bool) {
|
||||
switch matcher.Comparator {
|
||||
case "==":
|
||||
return actual.Equal(compareTo)
|
||||
case "~":
|
||||
diff := actual.Sub(compareTo)
|
||||
return -threshold <= diff && diff <= threshold
|
||||
case ">":
|
||||
return actual.After(compareTo)
|
||||
case ">=":
|
||||
return !actual.Before(compareTo)
|
||||
case "<":
|
||||
return actual.Before(compareTo)
|
||||
case "<=":
|
||||
return !actual.After(compareTo)
|
||||
}
|
||||
return false
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeTrueMatcher struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (matcher *BeTrueMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isBool(actual) {
|
||||
return false, fmt.Errorf("Expected a boolean. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
return actual.(bool), nil
|
||||
}
|
||||
|
||||
func (matcher *BeTrueMatcher) FailureMessage(actual any) (message string) {
|
||||
if matcher.Reason == "" {
|
||||
return format.Message(actual, "to be true")
|
||||
} else {
|
||||
return matcher.Reason
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *BeTrueMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
if matcher.Reason == "" {
|
||||
return format.Message(actual, "not to be true")
|
||||
} else {
|
||||
return fmt.Sprintf(`Expected not true but got true\nNegation of "%s" failed`, matcher.Reason)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type BeZeroMatcher struct {
|
||||
}
|
||||
|
||||
func (matcher *BeZeroMatcher) Match(actual any) (success bool, err error) {
|
||||
if actual == nil {
|
||||
return true, nil
|
||||
}
|
||||
zeroValue := reflect.Zero(reflect.TypeOf(actual)).Interface()
|
||||
|
||||
return reflect.DeepEqual(zeroValue, actual), nil
|
||||
|
||||
}
|
||||
|
||||
func (matcher *BeZeroMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to be zero-valued")
|
||||
}
|
||||
|
||||
func (matcher *BeZeroMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to be zero-valued")
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
// untested sections: 3
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
"github.com/onsi/gomega/matchers/support/goraph/bipartitegraph"
|
||||
)
|
||||
|
||||
type ConsistOfMatcher struct {
|
||||
Elements []any
|
||||
missingElements []any
|
||||
extraElements []any
|
||||
}
|
||||
|
||||
func (matcher *ConsistOfMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
|
||||
return false, fmt.Errorf("ConsistOf matcher expects an array/slice/map/iter.Seq/iter.Seq2. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
matchers := matchers(matcher.Elements)
|
||||
values := valuesOf(actual)
|
||||
|
||||
bipartiteGraph, err := bipartitegraph.NewBipartiteGraph(values, matchers, neighbours)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
edges := bipartiteGraph.LargestMatching()
|
||||
if len(edges) == len(values) && len(edges) == len(matchers) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var missingMatchers []any
|
||||
matcher.extraElements, missingMatchers = bipartiteGraph.FreeLeftRight(edges)
|
||||
matcher.missingElements = equalMatchersToElements(missingMatchers)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func neighbours(value, matcher any) (bool, error) {
|
||||
match, err := matcher.(omegaMatcher).Match(value)
|
||||
return match && err == nil, nil
|
||||
}
|
||||
|
||||
func equalMatchersToElements(matchers []any) (elements []any) {
|
||||
for _, matcher := range matchers {
|
||||
if equalMatcher, ok := matcher.(*EqualMatcher); ok {
|
||||
elements = append(elements, equalMatcher.Expected)
|
||||
} else if _, ok := matcher.(*BeNilMatcher); ok {
|
||||
elements = append(elements, nil)
|
||||
} else {
|
||||
elements = append(elements, matcher)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func flatten(elems []any) []any {
|
||||
if len(elems) != 1 ||
|
||||
!(isArrayOrSlice(elems[0]) ||
|
||||
(miter.IsIter(elems[0]) && !miter.IsSeq2(elems[0]))) {
|
||||
return elems
|
||||
}
|
||||
|
||||
if miter.IsIter(elems[0]) {
|
||||
flattened := []any{}
|
||||
miter.IterateV(elems[0], func(v reflect.Value) bool {
|
||||
flattened = append(flattened, v.Interface())
|
||||
return true
|
||||
})
|
||||
return flattened
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(elems[0])
|
||||
flattened := make([]any, value.Len())
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
flattened[i] = value.Index(i).Interface()
|
||||
}
|
||||
return flattened
|
||||
}
|
||||
|
||||
func matchers(expectedElems []any) (matchers []any) {
|
||||
for _, e := range flatten(expectedElems) {
|
||||
if e == nil {
|
||||
matchers = append(matchers, &BeNilMatcher{})
|
||||
} else if matcher, isMatcher := e.(omegaMatcher); isMatcher {
|
||||
matchers = append(matchers, matcher)
|
||||
} else {
|
||||
matchers = append(matchers, &EqualMatcher{Expected: e})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func presentable(elems []any) any {
|
||||
elems = flatten(elems)
|
||||
|
||||
if len(elems) == 0 {
|
||||
return []any{}
|
||||
}
|
||||
|
||||
sv := reflect.ValueOf(elems)
|
||||
firstEl := sv.Index(0)
|
||||
if firstEl.IsNil() {
|
||||
return elems
|
||||
}
|
||||
tt := firstEl.Elem().Type()
|
||||
for i := 1; i < sv.Len(); i++ {
|
||||
el := sv.Index(i)
|
||||
if el.IsNil() || (sv.Index(i).Elem().Type() != tt) {
|
||||
return elems
|
||||
}
|
||||
}
|
||||
|
||||
ss := reflect.MakeSlice(reflect.SliceOf(tt), sv.Len(), sv.Len())
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
ss.Index(i).Set(sv.Index(i).Elem())
|
||||
}
|
||||
|
||||
return ss.Interface()
|
||||
}
|
||||
|
||||
func valuesOf(actual any) []any {
|
||||
value := reflect.ValueOf(actual)
|
||||
values := []any{}
|
||||
if miter.IsIter(actual) {
|
||||
if miter.IsSeq2(actual) {
|
||||
miter.IterateKV(actual, func(k, v reflect.Value) bool {
|
||||
values = append(values, v.Interface())
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
miter.IterateV(actual, func(v reflect.Value) bool {
|
||||
values = append(values, v.Interface())
|
||||
return true
|
||||
})
|
||||
}
|
||||
} else if isMap(actual) {
|
||||
keys := value.MapKeys()
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
values = append(values, value.MapIndex(keys[i]).Interface())
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
values = append(values, value.Index(i).Interface())
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func (matcher *ConsistOfMatcher) FailureMessage(actual any) (message string) {
|
||||
message = format.Message(actual, "to consist of", presentable(matcher.Elements))
|
||||
message = appendMissingElements(message, matcher.missingElements)
|
||||
if len(matcher.extraElements) > 0 {
|
||||
message = fmt.Sprintf("%s\nthe extra elements were\n%s", message,
|
||||
format.Object(presentable(matcher.extraElements), 1))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func appendMissingElements(message string, missingElements []any) string {
|
||||
if len(missingElements) == 0 {
|
||||
return message
|
||||
}
|
||||
return fmt.Sprintf("%s\nthe missing elements were\n%s", message,
|
||||
format.Object(presentable(missingElements), 1))
|
||||
}
|
||||
|
||||
func (matcher *ConsistOfMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to consist of", presentable(matcher.Elements))
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
)
|
||||
|
||||
type ContainElementMatcher struct {
|
||||
Element any
|
||||
Result []any
|
||||
}
|
||||
|
||||
func (matcher *ContainElementMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
|
||||
return false, fmt.Errorf("ContainElement matcher expects an array/slice/map/iterator. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
var actualT reflect.Type
|
||||
var result reflect.Value
|
||||
switch numResultArgs := len(matcher.Result); {
|
||||
case numResultArgs > 1:
|
||||
return false, errors.New("ContainElement matcher expects at most a single optional pointer to store its findings at")
|
||||
case numResultArgs == 1:
|
||||
// Check the optional result arg to point to a single value/array/slice/map
|
||||
// of a type compatible with the actual value.
|
||||
if reflect.ValueOf(matcher.Result[0]).Kind() != reflect.Ptr {
|
||||
return false, fmt.Errorf("ContainElement matcher expects a non-nil pointer to store its findings at. Got\n%s",
|
||||
format.Object(matcher.Result[0], 1))
|
||||
}
|
||||
actualT = reflect.TypeOf(actual)
|
||||
resultReference := matcher.Result[0]
|
||||
result = reflect.ValueOf(resultReference).Elem() // what ResultReference points to, to stash away our findings
|
||||
switch result.Kind() {
|
||||
case reflect.Array: // result arrays are not supported, as they cannot be dynamically sized.
|
||||
if miter.IsIter(actual) {
|
||||
_, actualvT := miter.IterKVTypes(actual)
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
reflect.SliceOf(actualvT), result.Type().String())
|
||||
}
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
reflect.SliceOf(actualT.Elem()).String(), result.Type().String())
|
||||
|
||||
case reflect.Slice: // result slice
|
||||
// can we assign elements in actual to elements in what the result
|
||||
// arg points to?
|
||||
// - ✔ actual is an array or slice
|
||||
// - ✔ actual is an iter.Seq producing "v" elements
|
||||
// - ✔ actual is an iter.Seq2 producing "v" elements, ignoring
|
||||
// the "k" elements.
|
||||
switch {
|
||||
case isArrayOrSlice(actual):
|
||||
if !actualT.Elem().AssignableTo(result.Type().Elem()) {
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
actualT.String(), result.Type().String())
|
||||
}
|
||||
|
||||
case miter.IsIter(actual):
|
||||
_, actualvT := miter.IterKVTypes(actual)
|
||||
if !actualvT.AssignableTo(result.Type().Elem()) {
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
actualvT.String(), result.Type().String())
|
||||
}
|
||||
|
||||
default: // incompatible result reference
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
reflect.MapOf(actualT.Key(), actualT.Elem()).String(), result.Type().String())
|
||||
}
|
||||
|
||||
case reflect.Map: // result map
|
||||
// can we assign elements in actual to elements in what the result
|
||||
// arg points to?
|
||||
// - ✔ actual is a map
|
||||
// - ✔ actual is an iter.Seq2 (iter.Seq doesn't fit though)
|
||||
switch {
|
||||
case isMap(actual):
|
||||
if !actualT.AssignableTo(result.Type()) {
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
actualT.String(), result.Type().String())
|
||||
}
|
||||
|
||||
case miter.IsIter(actual):
|
||||
actualkT, actualvT := miter.IterKVTypes(actual)
|
||||
if actualkT == nil {
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
reflect.SliceOf(actualvT).String(), result.Type().String())
|
||||
}
|
||||
if !reflect.MapOf(actualkT, actualvT).AssignableTo(result.Type()) {
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
reflect.MapOf(actualkT, actualvT), result.Type().String())
|
||||
}
|
||||
|
||||
default: // incompatible result reference
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
actualT.String(), result.Type().String())
|
||||
}
|
||||
|
||||
default:
|
||||
// can we assign a (single) element in actual to what the result arg
|
||||
// points to?
|
||||
switch {
|
||||
case miter.IsIter(actual):
|
||||
_, actualvT := miter.IterKVTypes(actual)
|
||||
if !actualvT.AssignableTo(result.Type()) {
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
actualvT.String(), result.Type().String())
|
||||
}
|
||||
default:
|
||||
if !actualT.Elem().AssignableTo(result.Type()) {
|
||||
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
|
||||
actualT.Elem().String(), result.Type().String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the supplied matcher isn't an Omega matcher, default to the Equal
|
||||
// matcher.
|
||||
elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
|
||||
if !elementIsMatcher {
|
||||
elemMatcher = &EqualMatcher{Expected: matcher.Element}
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(actual)
|
||||
|
||||
var getFindings func() reflect.Value // abstracts how the findings are collected and stored
|
||||
var lastError error
|
||||
|
||||
if !miter.IsIter(actual) {
|
||||
var valueAt func(int) any
|
||||
var foundAt func(int)
|
||||
// We're dealing with an array/slice/map, so in all cases we can iterate
|
||||
// over the elements in actual using indices (that can be considered
|
||||
// keys in case of maps).
|
||||
if isMap(actual) {
|
||||
keys := value.MapKeys()
|
||||
valueAt = func(i int) any {
|
||||
return value.MapIndex(keys[i]).Interface()
|
||||
}
|
||||
if result.Kind() != reflect.Invalid {
|
||||
fm := reflect.MakeMap(actualT)
|
||||
getFindings = func() reflect.Value { return fm }
|
||||
foundAt = func(i int) {
|
||||
fm.SetMapIndex(keys[i], value.MapIndex(keys[i]))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
valueAt = func(i int) any {
|
||||
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 {
|
||||
// We're dealing with an iterator as a first-class construct, so things
|
||||
// are slightly different: there is no index defined as in case of
|
||||
// arrays/slices/maps, just "ooooorder"
|
||||
var found func(k, v reflect.Value)
|
||||
if result.Kind() != reflect.Invalid {
|
||||
if result.Kind() == reflect.Map {
|
||||
fm := reflect.MakeMap(result.Type())
|
||||
getFindings = func() reflect.Value { return fm }
|
||||
found = func(k, v reflect.Value) { fm.SetMapIndex(k, v) }
|
||||
} else {
|
||||
var fsl reflect.Value
|
||||
if result.Kind() == reflect.Slice {
|
||||
fsl = reflect.MakeSlice(result.Type(), 0, 0)
|
||||
} else {
|
||||
fsl = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)
|
||||
}
|
||||
getFindings = func() reflect.Value { return fsl }
|
||||
found = func(_, v reflect.Value) { fsl = reflect.Append(fsl, v) }
|
||||
}
|
||||
}
|
||||
|
||||
success := false
|
||||
actualkT, _ := miter.IterKVTypes(actual)
|
||||
if actualkT == nil {
|
||||
miter.IterateV(actual, func(v reflect.Value) bool {
|
||||
var err error
|
||||
success, err = elemMatcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
lastError = err
|
||||
return true // iterate on...
|
||||
}
|
||||
if success {
|
||||
if result.Kind() == reflect.Invalid {
|
||||
return false // a match and no result needed, so we're done
|
||||
}
|
||||
found(reflect.Value{}, v)
|
||||
}
|
||||
return true // iterate on...
|
||||
})
|
||||
} else {
|
||||
miter.IterateKV(actual, func(k, v reflect.Value) bool {
|
||||
var err error
|
||||
success, err = elemMatcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
lastError = err
|
||||
return true // iterate on...
|
||||
}
|
||||
if success {
|
||||
if result.Kind() == reflect.Invalid {
|
||||
return false // a match and no result needed, so we're done
|
||||
}
|
||||
found(k, v)
|
||||
}
|
||||
return true // iterate on...
|
||||
})
|
||||
}
|
||||
if success && result.Kind() == reflect.Invalid {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// when the expectation isn't interested in the findings except for success
|
||||
// or non-success, then we're done here and return the last matcher error
|
||||
// seen, if any, as well as non-success.
|
||||
if result.Kind() == reflect.Invalid {
|
||||
return false, lastError
|
||||
}
|
||||
|
||||
// pick up any findings the test is interested in as it specified a non-nil
|
||||
// result reference. However, the expectation always is that there are at
|
||||
// least one or multiple findings. So, if a result is expected, but we had
|
||||
// no findings, then this is an error.
|
||||
findings := getFindings()
|
||||
if findings.Len() == 0 {
|
||||
return false, lastError
|
||||
}
|
||||
|
||||
// there's just a single finding and the result is neither a slice nor a map
|
||||
// (so it's a scalar): pick the one and only finding and return it in the
|
||||
// place the reference points to.
|
||||
if findings.Len() == 1 && !isArrayOrSlice(result.Interface()) && !isMap(result.Interface()) {
|
||||
if isMap(actual) {
|
||||
miter := findings.MapRange()
|
||||
miter.Next()
|
||||
result.Set(miter.Value())
|
||||
} else {
|
||||
result.Set(findings.Index(0))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// at least one or even multiple findings and a the result references a
|
||||
// slice or a map, so all we need to do is to store our findings where the
|
||||
// reference points to.
|
||||
if !findings.Type().AssignableTo(result.Type()) {
|
||||
return false, fmt.Errorf("ContainElement cannot return multiple findings. Need *%s, got *%s",
|
||||
findings.Type().String(), result.Type().String())
|
||||
}
|
||||
result.Set(findings)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (matcher *ContainElementMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to contain element matching", matcher.Element)
|
||||
}
|
||||
|
||||
func (matcher *ContainElementMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to contain element matching", matcher.Element)
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
"github.com/onsi/gomega/matchers/support/goraph/bipartitegraph"
|
||||
)
|
||||
|
||||
type ContainElementsMatcher struct {
|
||||
Elements []any
|
||||
missingElements []any
|
||||
}
|
||||
|
||||
func (matcher *ContainElementsMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
|
||||
return false, fmt.Errorf("ContainElements matcher expects an array/slice/map/iter.Seq/iter.Seq2. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
matchers := matchers(matcher.Elements)
|
||||
bipartiteGraph, err := bipartitegraph.NewBipartiteGraph(valuesOf(actual), matchers, neighbours)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
edges := bipartiteGraph.LargestMatching()
|
||||
if len(edges) == len(matchers) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
_, missingMatchers := bipartiteGraph.FreeLeftRight(edges)
|
||||
matcher.missingElements = equalMatchersToElements(missingMatchers)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (matcher *ContainElementsMatcher) FailureMessage(actual any) (message string) {
|
||||
message = format.Message(actual, "to contain elements", presentable(matcher.Elements))
|
||||
return appendMissingElements(message, matcher.missingElements)
|
||||
}
|
||||
|
||||
func (matcher *ContainElementsMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to contain elements", presentable(matcher.Elements))
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type ContainSubstringMatcher struct {
|
||||
Substr string
|
||||
Args []any
|
||||
}
|
||||
|
||||
func (matcher *ContainSubstringMatcher) Match(actual any) (success bool, err error) {
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("ContainSubstring matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
return strings.Contains(actualString, matcher.stringToMatch()), nil
|
||||
}
|
||||
|
||||
func (matcher *ContainSubstringMatcher) stringToMatch() string {
|
||||
stringToMatch := matcher.Substr
|
||||
if len(matcher.Args) > 0 {
|
||||
stringToMatch = fmt.Sprintf(matcher.Substr, matcher.Args...)
|
||||
}
|
||||
return stringToMatch
|
||||
}
|
||||
|
||||
func (matcher *ContainSubstringMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to contain substring", matcher.stringToMatch())
|
||||
}
|
||||
|
||||
func (matcher *ContainSubstringMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to contain substring", matcher.stringToMatch())
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type EqualMatcher struct {
|
||||
Expected any
|
||||
}
|
||||
|
||||
func (matcher *EqualMatcher) Match(actual any) (success bool, err error) {
|
||||
if actual == nil && matcher.Expected == nil {
|
||||
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
|
||||
}
|
||||
// Shortcut for byte slices.
|
||||
// Comparing long byte slices with reflect.DeepEqual is very slow,
|
||||
// so use bytes.Equal if actual and expected are both byte slices.
|
||||
if actualByteSlice, ok := actual.([]byte); ok {
|
||||
if expectedByteSlice, ok := matcher.Expected.([]byte); ok {
|
||||
return bytes.Equal(actualByteSlice, expectedByteSlice), nil
|
||||
}
|
||||
}
|
||||
return reflect.DeepEqual(actual, matcher.Expected), nil
|
||||
}
|
||||
|
||||
func (matcher *EqualMatcher) FailureMessage(actual any) (message string) {
|
||||
actualString, actualOK := actual.(string)
|
||||
expectedString, expectedOK := matcher.Expected.(string)
|
||||
if actualOK && expectedOK {
|
||||
return format.MessageWithDiff(actualString, "to equal", expectedString)
|
||||
}
|
||||
|
||||
return format.Message(actual, "to equal", matcher.Expected)
|
||||
}
|
||||
|
||||
func (matcher *EqualMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to equal", matcher.Expected)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type HaveCapMatcher struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
func (matcher *HaveCapMatcher) Match(actual any) (success bool, err error) {
|
||||
length, ok := capOf(actual)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("HaveCap matcher expects a array/channel/slice. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
return length == matcher.Count, nil
|
||||
}
|
||||
|
||||
func (matcher *HaveCapMatcher) FailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\nto have capacity %d", format.Object(actual, 1), matcher.Count)
|
||||
}
|
||||
|
||||
func (matcher *HaveCapMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\nnot to have capacity %d", format.Object(actual, 1), matcher.Count)
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
)
|
||||
|
||||
type HaveEachMatcher struct {
|
||||
Element any
|
||||
}
|
||||
|
||||
func (matcher *HaveEachMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {
|
||||
return false, fmt.Errorf("HaveEach matcher expects an array/slice/map/iter.Seq/iter.Seq2. Got:\n%s",
|
||||
format.Object(actual, 1))
|
||||
}
|
||||
|
||||
elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
|
||||
if !elementIsMatcher {
|
||||
elemMatcher = &EqualMatcher{Expected: matcher.Element}
|
||||
}
|
||||
|
||||
if miter.IsIter(actual) {
|
||||
// rejecting the non-elements case works different for iterators as we
|
||||
// don't want to fetch all elements into a slice first.
|
||||
count := 0
|
||||
var success bool
|
||||
var err error
|
||||
if miter.IsSeq2(actual) {
|
||||
miter.IterateKV(actual, func(k, v reflect.Value) bool {
|
||||
count++
|
||||
success, err = elemMatcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return success
|
||||
})
|
||||
} else {
|
||||
miter.IterateV(actual, func(v reflect.Value) bool {
|
||||
count++
|
||||
success, err = elemMatcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return success
|
||||
})
|
||||
}
|
||||
if count == 0 {
|
||||
return false, fmt.Errorf("HaveEach matcher expects a non-empty iter.Seq/iter.Seq2. Got:\n%s",
|
||||
format.Object(actual, 1))
|
||||
}
|
||||
return success, err
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(actual)
|
||||
if value.Len() == 0 {
|
||||
return false, fmt.Errorf("HaveEach matcher expects a non-empty array/slice/map. Got:\n%s",
|
||||
format.Object(actual, 1))
|
||||
}
|
||||
|
||||
var valueAt func(int) any
|
||||
if isMap(actual) {
|
||||
keys := value.MapKeys()
|
||||
valueAt = func(i int) any {
|
||||
return value.MapIndex(keys[i]).Interface()
|
||||
}
|
||||
} else {
|
||||
valueAt = func(i int) any {
|
||||
return value.Index(i).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
// if we never failed then we succeed; the empty/nil cases have already been
|
||||
// rejected above.
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
success, err := elemMatcher.Match(valueAt(i))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !success {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// FailureMessage returns a suitable failure message.
|
||||
func (matcher *HaveEachMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to contain element matching", matcher.Element)
|
||||
}
|
||||
|
||||
// NegatedFailureMessage returns a suitable negated failure message.
|
||||
func (matcher *HaveEachMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to contain element matching", matcher.Element)
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
)
|
||||
|
||||
type mismatchFailure struct {
|
||||
failure string
|
||||
index int
|
||||
}
|
||||
|
||||
type HaveExactElementsMatcher struct {
|
||||
Elements []any
|
||||
mismatchFailures []mismatchFailure
|
||||
missingIndex int
|
||||
extraIndex int
|
||||
}
|
||||
|
||||
func (matcher *HaveExactElementsMatcher) Match(actual any) (success bool, err error) {
|
||||
matcher.resetState()
|
||||
|
||||
if isMap(actual) || miter.IsSeq2(actual) {
|
||||
return false, fmt.Errorf("HaveExactElements matcher doesn't work on map or iter.Seq2. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
matchers := matchers(matcher.Elements)
|
||||
lenMatchers := len(matchers)
|
||||
|
||||
success = true
|
||||
|
||||
if miter.IsIter(actual) {
|
||||
// In the worst case, we need to see everything before we can give our
|
||||
// verdict. The only exception is fast fail.
|
||||
i := 0
|
||||
miter.IterateV(actual, func(v reflect.Value) bool {
|
||||
if i >= lenMatchers {
|
||||
// the iterator produces more values than we got matchers: this
|
||||
// is not good.
|
||||
matcher.extraIndex = i
|
||||
success = false
|
||||
return false
|
||||
}
|
||||
|
||||
elemMatcher := matchers[i].(omegaMatcher)
|
||||
match, err := elemMatcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{
|
||||
index: i,
|
||||
failure: err.Error(),
|
||||
})
|
||||
success = false
|
||||
} else if !match {
|
||||
matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{
|
||||
index: i,
|
||||
failure: elemMatcher.FailureMessage(v.Interface()),
|
||||
})
|
||||
success = false
|
||||
}
|
||||
i++
|
||||
return true
|
||||
})
|
||||
if i < len(matchers) {
|
||||
// the iterator produced less values than we got matchers: this is
|
||||
// no good, no no no.
|
||||
matcher.missingIndex = i
|
||||
success = false
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
values := valuesOf(actual)
|
||||
lenValues := len(values)
|
||||
|
||||
for i := 0; i < lenMatchers || i < lenValues; i++ {
|
||||
if i >= lenMatchers {
|
||||
matcher.extraIndex = i
|
||||
success = false
|
||||
continue
|
||||
}
|
||||
|
||||
if i >= lenValues {
|
||||
matcher.missingIndex = i
|
||||
success = false
|
||||
return
|
||||
}
|
||||
|
||||
elemMatcher := matchers[i].(omegaMatcher)
|
||||
match, err := elemMatcher.Match(values[i])
|
||||
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(values[i]),
|
||||
})
|
||||
success = false
|
||||
}
|
||||
}
|
||||
|
||||
return success, nil
|
||||
}
|
||||
|
||||
func (matcher *HaveExactElementsMatcher) FailureMessage(actual any) (message string) {
|
||||
message = format.Message(actual, "to have exact elements with", presentable(matcher.Elements))
|
||||
if matcher.missingIndex > 0 {
|
||||
message = fmt.Sprintf("%s\nthe missing elements start from index %d", message, matcher.missingIndex)
|
||||
}
|
||||
if matcher.extraIndex > 0 {
|
||||
message = fmt.Sprintf("%s\nthe extra elements start from index %d", message, matcher.extraIndex)
|
||||
}
|
||||
if len(matcher.mismatchFailures) != 0 {
|
||||
message = fmt.Sprintf("%s\nthe mismatch indexes were:", message)
|
||||
}
|
||||
for _, mismatch := range matcher.mismatchFailures {
|
||||
message = fmt.Sprintf("%s\n%d: %s", message, mismatch.index, mismatch.failure)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (matcher *HaveExactElementsMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to contain elements", presentable(matcher.Elements))
|
||||
}
|
||||
|
||||
func (matcher *HaveExactElementsMatcher) resetState() {
|
||||
matcher.mismatchFailures = nil
|
||||
matcher.missingIndex = 0
|
||||
matcher.extraIndex = 0
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type HaveExistingFieldMatcher struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
func (matcher *HaveExistingFieldMatcher) Match(actual any) (success bool, err error) {
|
||||
// we don't care about the field's actual value, just about any error in
|
||||
// trying to find the field (or method).
|
||||
_, err = extractField(actual, matcher.Field, "HaveExistingField")
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
var mferr missingFieldError
|
||||
if errors.As(err, &mferr) {
|
||||
// missing field errors aren't errors in this context, but instead
|
||||
// unsuccessful matches.
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (matcher *HaveExistingFieldMatcher) FailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\nto have field '%s'", format.Object(actual, 1), matcher.Field)
|
||||
}
|
||||
|
||||
func (matcher *HaveExistingFieldMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\nnot to have field '%s'", format.Object(actual, 1), matcher.Field)
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
// missingFieldError represents a missing field extraction error that
|
||||
// HaveExistingFieldMatcher can ignore, as opposed to other, sever field
|
||||
// extraction errors, such as nil pointers, et cetera.
|
||||
type missingFieldError string
|
||||
|
||||
func (e missingFieldError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func extractField(actual any, field string, matchername string) (any, error) {
|
||||
fields := strings.SplitN(field, ".", 2)
|
||||
actualValue := reflect.ValueOf(actual)
|
||||
|
||||
if actualValue.Kind() == reflect.Ptr {
|
||||
actualValue = actualValue.Elem()
|
||||
}
|
||||
if actualValue == (reflect.Value{}) {
|
||||
return nil, fmt.Errorf("%s encountered nil while dereferencing a pointer of type %T.", matchername, actual)
|
||||
}
|
||||
|
||||
if actualValue.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("%s encountered:\n%s\nWhich is not a struct.", matchername, format.Object(actual, 1))
|
||||
}
|
||||
|
||||
var extractedValue reflect.Value
|
||||
|
||||
if strings.HasSuffix(fields[0], "()") {
|
||||
extractedValue = actualValue.MethodByName(strings.TrimSuffix(fields[0], "()"))
|
||||
if extractedValue == (reflect.Value{}) && actualValue.CanAddr() {
|
||||
extractedValue = actualValue.Addr().MethodByName(strings.TrimSuffix(fields[0], "()"))
|
||||
}
|
||||
if extractedValue == (reflect.Value{}) {
|
||||
ptr := reflect.New(actualValue.Type())
|
||||
ptr.Elem().Set(actualValue)
|
||||
extractedValue = ptr.MethodByName(strings.TrimSuffix(fields[0], "()"))
|
||||
if extractedValue == (reflect.Value{}) {
|
||||
return nil, missingFieldError(fmt.Sprintf("%s could not find method named '%s' in struct of type %T.", matchername, fields[0], actual))
|
||||
}
|
||||
}
|
||||
t := extractedValue.Type()
|
||||
if t.NumIn() != 0 || t.NumOut() != 1 {
|
||||
return nil, fmt.Errorf("%s found an invalid method named '%s' in struct of type %T.\nMethods must take no arguments and return exactly one value.", matchername, fields[0], actual)
|
||||
}
|
||||
extractedValue = extractedValue.Call([]reflect.Value{})[0]
|
||||
} else {
|
||||
extractedValue = actualValue.FieldByName(fields[0])
|
||||
if extractedValue == (reflect.Value{}) {
|
||||
return nil, missingFieldError(fmt.Sprintf("%s could not find field named '%s' in struct:\n%s", matchername, fields[0], format.Object(actual, 1)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(fields) == 1 {
|
||||
return extractedValue.Interface(), nil
|
||||
} else {
|
||||
return extractField(extractedValue.Interface(), fields[1], matchername)
|
||||
}
|
||||
}
|
||||
|
||||
type HaveFieldMatcher struct {
|
||||
Field string
|
||||
Expected any
|
||||
}
|
||||
|
||||
func (matcher *HaveFieldMatcher) expectedMatcher() omegaMatcher {
|
||||
var isMatcher bool
|
||||
expectedMatcher, isMatcher := matcher.Expected.(omegaMatcher)
|
||||
if !isMatcher {
|
||||
expectedMatcher = &EqualMatcher{Expected: matcher.Expected}
|
||||
}
|
||||
return expectedMatcher
|
||||
}
|
||||
|
||||
func (matcher *HaveFieldMatcher) Match(actual any) (success bool, err error) {
|
||||
extractedField, err := extractField(actual, matcher.Field, "HaveField")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return matcher.expectedMatcher().Match(extractedField)
|
||||
}
|
||||
|
||||
func (matcher *HaveFieldMatcher) FailureMessage(actual any) (message string) {
|
||||
extractedField, err := extractField(actual, matcher.Field, "HaveField")
|
||||
if err != nil {
|
||||
// this really shouldn't happen
|
||||
return fmt.Sprintf("Failed to extract field '%s': %s", matcher.Field, err)
|
||||
}
|
||||
message = fmt.Sprintf("Value for field '%s' failed to satisfy matcher.\n", matcher.Field)
|
||||
message += matcher.expectedMatcher().FailureMessage(extractedField)
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func (matcher *HaveFieldMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
extractedField, err := extractField(actual, matcher.Field, "HaveField")
|
||||
if err != nil {
|
||||
// this really shouldn't happen
|
||||
return fmt.Sprintf("Failed to extract field '%s': %s", matcher.Field, err)
|
||||
}
|
||||
message = fmt.Sprintf("Value for field '%s' satisfied matcher, but should not have.\n", matcher.Field)
|
||||
message += matcher.expectedMatcher().NegatedFailureMessage(extractedField)
|
||||
|
||||
return message
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/internal/gutil"
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
type HaveHTTPBodyMatcher struct {
|
||||
Expected any
|
||||
cachedResponse any
|
||||
cachedBody []byte
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPBodyMatcher) Match(actual any) (bool, error) {
|
||||
body, err := matcher.body(actual)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch e := matcher.Expected.(type) {
|
||||
case string:
|
||||
return (&EqualMatcher{Expected: e}).Match(string(body))
|
||||
case []byte:
|
||||
return (&EqualMatcher{Expected: e}).Match(body)
|
||||
case types.GomegaMatcher:
|
||||
return e.Match(body)
|
||||
default:
|
||||
return false, fmt.Errorf("HaveHTTPBody matcher expects string, []byte, or GomegaMatcher. Got:\n%s", format.Object(matcher.Expected, 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPBodyMatcher) FailureMessage(actual any) (message string) {
|
||||
body, err := matcher.body(actual)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to read body: %s", err)
|
||||
}
|
||||
|
||||
switch e := matcher.Expected.(type) {
|
||||
case string:
|
||||
return (&EqualMatcher{Expected: e}).FailureMessage(string(body))
|
||||
case []byte:
|
||||
return (&EqualMatcher{Expected: e}).FailureMessage(body)
|
||||
case types.GomegaMatcher:
|
||||
return e.FailureMessage(body)
|
||||
default:
|
||||
return fmt.Sprintf("HaveHTTPBody matcher expects string, []byte, or GomegaMatcher. Got:\n%s", format.Object(matcher.Expected, 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPBodyMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
body, err := matcher.body(actual)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to read body: %s", err)
|
||||
}
|
||||
|
||||
switch e := matcher.Expected.(type) {
|
||||
case string:
|
||||
return (&EqualMatcher{Expected: e}).NegatedFailureMessage(string(body))
|
||||
case []byte:
|
||||
return (&EqualMatcher{Expected: e}).NegatedFailureMessage(body)
|
||||
case types.GomegaMatcher:
|
||||
return e.NegatedFailureMessage(body)
|
||||
default:
|
||||
return fmt.Sprintf("HaveHTTPBody matcher expects string, []byte, or GomegaMatcher. Got:\n%s", format.Object(matcher.Expected, 1))
|
||||
}
|
||||
}
|
||||
|
||||
// body returns the body. It is cached because once we read it in Match()
|
||||
// the Reader is closed and it is not readable again in FailureMessage()
|
||||
// or NegatedFailureMessage()
|
||||
func (matcher *HaveHTTPBodyMatcher) body(actual any) ([]byte, error) {
|
||||
if matcher.cachedResponse == actual && matcher.cachedBody != nil {
|
||||
return matcher.cachedBody, nil
|
||||
}
|
||||
|
||||
body := func(a *http.Response) ([]byte, error) {
|
||||
if a.Body != nil {
|
||||
defer a.Body.Close()
|
||||
var err error
|
||||
matcher.cachedBody, err = gutil.ReadAll(a.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
}
|
||||
return matcher.cachedBody, nil
|
||||
}
|
||||
|
||||
switch a := actual.(type) {
|
||||
case *http.Response:
|
||||
matcher.cachedResponse = a
|
||||
return body(a)
|
||||
case *httptest.ResponseRecorder:
|
||||
matcher.cachedResponse = a
|
||||
return body(a.Result())
|
||||
default:
|
||||
return nil, fmt.Errorf("HaveHTTPBody matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
}
|
||||
Generated
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
type HaveHTTPHeaderWithValueMatcher struct {
|
||||
Header string
|
||||
Value any
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPHeaderWithValueMatcher) Match(actual any) (success bool, err error) {
|
||||
headerValue, err := matcher.extractHeader(actual)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
headerMatcher, err := matcher.getSubMatcher()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return headerMatcher.Match(headerValue)
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPHeaderWithValueMatcher) FailureMessage(actual any) string {
|
||||
headerValue, err := matcher.extractHeader(actual)
|
||||
if err != nil {
|
||||
panic(err) // protected by Match()
|
||||
}
|
||||
|
||||
headerMatcher, err := matcher.getSubMatcher()
|
||||
if err != nil {
|
||||
panic(err) // protected by Match()
|
||||
}
|
||||
|
||||
diff := format.IndentString(headerMatcher.FailureMessage(headerValue), 1)
|
||||
return fmt.Sprintf("HTTP header %q:\n%s", matcher.Header, diff)
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPHeaderWithValueMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
headerValue, err := matcher.extractHeader(actual)
|
||||
if err != nil {
|
||||
panic(err) // protected by Match()
|
||||
}
|
||||
|
||||
headerMatcher, err := matcher.getSubMatcher()
|
||||
if err != nil {
|
||||
panic(err) // protected by Match()
|
||||
}
|
||||
|
||||
diff := format.IndentString(headerMatcher.NegatedFailureMessage(headerValue), 1)
|
||||
return fmt.Sprintf("HTTP header %q:\n%s", matcher.Header, diff)
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPHeaderWithValueMatcher) getSubMatcher() (types.GomegaMatcher, error) {
|
||||
switch m := matcher.Value.(type) {
|
||||
case string:
|
||||
return &EqualMatcher{Expected: matcher.Value}, nil
|
||||
case types.GomegaMatcher:
|
||||
return m, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("HaveHTTPHeaderWithValue matcher must be passed a string or a GomegaMatcher. Got:\n%s", format.Object(matcher.Value, 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPHeaderWithValueMatcher) extractHeader(actual any) (string, error) {
|
||||
switch r := actual.(type) {
|
||||
case *http.Response:
|
||||
return r.Header.Get(matcher.Header), nil
|
||||
case *httptest.ResponseRecorder:
|
||||
return r.Result().Header.Get(matcher.Header), nil
|
||||
default:
|
||||
return "", fmt.Errorf("HaveHTTPHeaderWithValue matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/internal/gutil"
|
||||
)
|
||||
|
||||
type HaveHTTPStatusMatcher struct {
|
||||
Expected []any
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPStatusMatcher) Match(actual any) (success bool, err error) {
|
||||
var resp *http.Response
|
||||
switch a := actual.(type) {
|
||||
case *http.Response:
|
||||
resp = a
|
||||
case *httptest.ResponseRecorder:
|
||||
resp = a.Result()
|
||||
default:
|
||||
return false, fmt.Errorf("HaveHTTPStatus matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
if len(matcher.Expected) == 0 {
|
||||
return false, fmt.Errorf("HaveHTTPStatus matcher must be passed an int or a string. Got nothing")
|
||||
}
|
||||
|
||||
for _, expected := range matcher.Expected {
|
||||
switch e := expected.(type) {
|
||||
case int:
|
||||
if resp.StatusCode == e {
|
||||
return true, nil
|
||||
}
|
||||
case string:
|
||||
if resp.Status == e {
|
||||
return true, nil
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("HaveHTTPStatus matcher must be passed int or string types. Got:\n%s", format.Object(expected, 1))
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPStatusMatcher) FailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\n%s\n%s", formatHttpResponse(actual), "to have HTTP status", matcher.expectedString())
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPStatusMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\n%s\n%s", formatHttpResponse(actual), "not to have HTTP status", matcher.expectedString())
|
||||
}
|
||||
|
||||
func (matcher *HaveHTTPStatusMatcher) expectedString() string {
|
||||
var lines []string
|
||||
for _, expected := range matcher.Expected {
|
||||
lines = append(lines, format.Object(expected, 1))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func formatHttpResponse(input any) string {
|
||||
var resp *http.Response
|
||||
switch r := input.(type) {
|
||||
case *http.Response:
|
||||
resp = r
|
||||
case *httptest.ResponseRecorder:
|
||||
resp = r.Result()
|
||||
default:
|
||||
return "cannot format invalid HTTP response"
|
||||
}
|
||||
|
||||
body := "<nil>"
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
data, err := gutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
data = []byte("<error reading body>")
|
||||
}
|
||||
body = format.Object(string(data), 0)
|
||||
}
|
||||
|
||||
var s strings.Builder
|
||||
s.WriteString(fmt.Sprintf("%s<%s>: {\n", format.Indent, reflect.TypeOf(input)))
|
||||
s.WriteString(fmt.Sprintf("%s%sStatus: %s\n", format.Indent, format.Indent, format.Object(resp.Status, 0)))
|
||||
s.WriteString(fmt.Sprintf("%s%sStatusCode: %s\n", format.Indent, format.Indent, format.Object(resp.StatusCode, 0)))
|
||||
s.WriteString(fmt.Sprintf("%s%sBody: %s\n", format.Indent, format.Indent, body))
|
||||
s.WriteString(fmt.Sprintf("%s}", format.Indent))
|
||||
|
||||
return s.String()
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// untested sections: 6
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
)
|
||||
|
||||
type HaveKeyMatcher struct {
|
||||
Key any
|
||||
}
|
||||
|
||||
func (matcher *HaveKeyMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isMap(actual) && !miter.IsSeq2(actual) {
|
||||
return false, fmt.Errorf("HaveKey matcher expects a map/iter.Seq2. Got:%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher)
|
||||
if !keyIsMatcher {
|
||||
keyMatcher = &EqualMatcher{Expected: matcher.Key}
|
||||
}
|
||||
|
||||
if miter.IsSeq2(actual) {
|
||||
var success bool
|
||||
var err error
|
||||
miter.IterateKV(actual, func(k, v reflect.Value) bool {
|
||||
success, err = keyMatcher.Match(k.Interface())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("HaveKey's key matcher failed with:\n%s%s", format.Indent, err.Error())
|
||||
return false
|
||||
}
|
||||
return !success
|
||||
})
|
||||
return success, err
|
||||
}
|
||||
|
||||
keys := reflect.ValueOf(actual).MapKeys()
|
||||
for i := range keys {
|
||||
success, err := keyMatcher.Match(keys[i].Interface())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("HaveKey's key matcher failed with:\n%s%s", format.Indent, err.Error())
|
||||
}
|
||||
if success {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (matcher *HaveKeyMatcher) FailureMessage(actual any) (message string) {
|
||||
switch matcher.Key.(type) {
|
||||
case omegaMatcher:
|
||||
return format.Message(actual, "to have key matching", matcher.Key)
|
||||
default:
|
||||
return format.Message(actual, "to have key", matcher.Key)
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *HaveKeyMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
switch matcher.Key.(type) {
|
||||
case omegaMatcher:
|
||||
return format.Message(actual, "not to have key matching", matcher.Key)
|
||||
default:
|
||||
return format.Message(actual, "not to have key", matcher.Key)
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// untested sections:10
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
)
|
||||
|
||||
type HaveKeyWithValueMatcher struct {
|
||||
Key any
|
||||
Value any
|
||||
}
|
||||
|
||||
func (matcher *HaveKeyWithValueMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isMap(actual) && !miter.IsSeq2(actual) {
|
||||
return false, fmt.Errorf("HaveKeyWithValue matcher expects a map/iter.Seq2. Got:%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher)
|
||||
if !keyIsMatcher {
|
||||
keyMatcher = &EqualMatcher{Expected: matcher.Key}
|
||||
}
|
||||
|
||||
valueMatcher, valueIsMatcher := matcher.Value.(omegaMatcher)
|
||||
if !valueIsMatcher {
|
||||
valueMatcher = &EqualMatcher{Expected: matcher.Value}
|
||||
}
|
||||
|
||||
if miter.IsSeq2(actual) {
|
||||
var success bool
|
||||
var err error
|
||||
miter.IterateKV(actual, func(k, v reflect.Value) bool {
|
||||
success, err = keyMatcher.Match(k.Interface())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("HaveKey's key matcher failed with:\n%s%s", format.Indent, err.Error())
|
||||
return false
|
||||
}
|
||||
if success {
|
||||
success, err = valueMatcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("HaveKeyWithValue's value matcher failed with:\n%s%s", format.Indent, err.Error())
|
||||
return false
|
||||
}
|
||||
}
|
||||
return !success
|
||||
})
|
||||
return success, err
|
||||
}
|
||||
|
||||
keys := reflect.ValueOf(actual).MapKeys()
|
||||
for i := range keys {
|
||||
success, err := keyMatcher.Match(keys[i].Interface())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("HaveKeyWithValue's key matcher failed with:\n%s%s", format.Indent, err.Error())
|
||||
}
|
||||
if success {
|
||||
actualValue := reflect.ValueOf(actual).MapIndex(keys[i])
|
||||
success, err := valueMatcher.Match(actualValue.Interface())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("HaveKeyWithValue's value matcher failed with:\n%s%s", format.Indent, err.Error())
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (matcher *HaveKeyWithValueMatcher) FailureMessage(actual any) (message string) {
|
||||
str := "to have {key: value}"
|
||||
if _, ok := matcher.Key.(omegaMatcher); ok {
|
||||
str += " matching"
|
||||
} else if _, ok := matcher.Value.(omegaMatcher); ok {
|
||||
str += " matching"
|
||||
}
|
||||
|
||||
expect := make(map[any]any, 1)
|
||||
expect[matcher.Key] = matcher.Value
|
||||
return format.Message(actual, str, expect)
|
||||
}
|
||||
|
||||
func (matcher *HaveKeyWithValueMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
kStr := "not to have key"
|
||||
if _, ok := matcher.Key.(omegaMatcher); ok {
|
||||
kStr = "not to have key matching"
|
||||
}
|
||||
|
||||
vStr := "or that key's value not be"
|
||||
if _, ok := matcher.Value.(omegaMatcher); ok {
|
||||
vStr = "or to have that key's value not matching"
|
||||
}
|
||||
|
||||
return format.Message(actual, kStr, matcher.Key, vStr, matcher.Value)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type HaveLenMatcher struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
func (matcher *HaveLenMatcher) Match(actual any) (success bool, err error) {
|
||||
length, ok := lengthOf(actual)
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
|
||||
func (matcher *HaveLenMatcher) FailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\nto have length %d", format.Object(actual, 1), matcher.Count)
|
||||
}
|
||||
|
||||
func (matcher *HaveLenMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected\n%s\nnot to have length %d", format.Object(actual, 1), matcher.Count)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// untested sections: 2
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type HaveOccurredMatcher struct {
|
||||
}
|
||||
|
||||
func (matcher *HaveOccurredMatcher) Match(actual any) (success bool, err error) {
|
||||
// is purely nil?
|
||||
if actual == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// must be an 'error' type
|
||||
if !isError(actual) {
|
||||
return false, fmt.Errorf("Expected an error-type. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
// must be non-nil (or a pointer to a non-nil)
|
||||
return !isNil(actual), nil
|
||||
}
|
||||
|
||||
func (matcher *HaveOccurredMatcher) FailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Expected an error to have occurred. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
func (matcher *HaveOccurredMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return fmt.Sprintf("Unexpected error:\n%s\n%s", format.Object(actual, 1), "occurred")
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type HavePrefixMatcher struct {
|
||||
Prefix string
|
||||
Args []any
|
||||
}
|
||||
|
||||
func (matcher *HavePrefixMatcher) Match(actual any) (success bool, err error) {
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("HavePrefix matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
prefix := matcher.prefix()
|
||||
return len(actualString) >= len(prefix) && actualString[0:len(prefix)] == prefix, nil
|
||||
}
|
||||
|
||||
func (matcher *HavePrefixMatcher) prefix() string {
|
||||
if len(matcher.Args) > 0 {
|
||||
return fmt.Sprintf(matcher.Prefix, matcher.Args...)
|
||||
}
|
||||
return matcher.Prefix
|
||||
}
|
||||
|
||||
func (matcher *HavePrefixMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to have prefix", matcher.prefix())
|
||||
}
|
||||
|
||||
func (matcher *HavePrefixMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to have prefix", matcher.prefix())
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type HaveSuffixMatcher struct {
|
||||
Suffix string
|
||||
Args []any
|
||||
}
|
||||
|
||||
func (matcher *HaveSuffixMatcher) Match(actual any) (success bool, err error) {
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("HaveSuffix matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
suffix := matcher.suffix()
|
||||
return len(actualString) >= len(suffix) && actualString[len(actualString)-len(suffix):] == suffix, nil
|
||||
}
|
||||
|
||||
func (matcher *HaveSuffixMatcher) suffix() string {
|
||||
if len(matcher.Args) > 0 {
|
||||
return fmt.Sprintf(matcher.Suffix, matcher.Args...)
|
||||
}
|
||||
return matcher.Suffix
|
||||
}
|
||||
|
||||
func (matcher *HaveSuffixMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to have suffix", matcher.suffix())
|
||||
}
|
||||
|
||||
func (matcher *HaveSuffixMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to have suffix", matcher.suffix())
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
const maxIndirections = 31
|
||||
|
||||
type HaveValueMatcher struct {
|
||||
Matcher types.GomegaMatcher // the matcher to apply to the "resolved" actual value.
|
||||
resolvedActual any // the ("resolved") value.
|
||||
}
|
||||
|
||||
func (m *HaveValueMatcher) Match(actual any) (bool, error) {
|
||||
val := reflect.ValueOf(actual)
|
||||
for allowedIndirs := maxIndirections; allowedIndirs > 0; allowedIndirs-- {
|
||||
// return an error if value isn't valid. Please note that we cannot
|
||||
// check for nil here, as we might not deal with a pointer or interface
|
||||
// at this point.
|
||||
if !val.IsValid() {
|
||||
return false, errors.New(format.Message(
|
||||
actual, "not to be <nil>"))
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
// resolve pointers and interfaces to their values, then rinse and
|
||||
// repeat.
|
||||
if val.IsNil() {
|
||||
return false, errors.New(format.Message(
|
||||
actual, "not to be <nil>"))
|
||||
}
|
||||
val = val.Elem()
|
||||
continue
|
||||
default:
|
||||
// forward the final value to the specified matcher.
|
||||
m.resolvedActual = val.Interface()
|
||||
return m.Matcher.Match(m.resolvedActual)
|
||||
}
|
||||
}
|
||||
// too many indirections: extreme star gazing, indeed...?
|
||||
return false, errors.New(format.Message(actual, "too many indirections"))
|
||||
}
|
||||
|
||||
func (m *HaveValueMatcher) FailureMessage(_ any) (message string) {
|
||||
return m.Matcher.FailureMessage(m.resolvedActual)
|
||||
}
|
||||
|
||||
func (m *HaveValueMatcher) NegatedFailureMessage(_ any) (message string) {
|
||||
return m.Matcher.NegatedFailureMessage(m.resolvedActual)
|
||||
}
|
||||
Generated
Vendored
+128
@@ -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})
|
||||
}
|
||||
Generated
Vendored
+44
@@ -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) {}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type MatchErrorMatcher struct {
|
||||
Expected any
|
||||
FuncErrDescription []any
|
||||
isFunc bool
|
||||
}
|
||||
|
||||
func (matcher *MatchErrorMatcher) Match(actual any) (success bool, err error) {
|
||||
matcher.isFunc = false
|
||||
|
||||
if isNil(actual) {
|
||||
return false, fmt.Errorf("Expected an error, got nil")
|
||||
}
|
||||
|
||||
if !isError(actual) {
|
||||
return false, fmt.Errorf("Expected an error. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
actualErr := actual.(error)
|
||||
expected := matcher.Expected
|
||||
|
||||
if isError(expected) {
|
||||
// first try the built-in errors.Is
|
||||
if errors.Is(actualErr, expected.(error)) {
|
||||
return true, nil
|
||||
}
|
||||
// if not, try DeepEqual along the error chain
|
||||
for unwrapped := actualErr; unwrapped != nil; unwrapped = errors.Unwrap(unwrapped) {
|
||||
if reflect.DeepEqual(unwrapped, expected) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if isString(expected) {
|
||||
return actualErr.Error() == expected, nil
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(expected)
|
||||
t := v.Type()
|
||||
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
||||
if t.Kind() == reflect.Func && t.NumIn() == 1 && t.In(0).Implements(errorInterface) && t.NumOut() == 1 && t.Out(0).Kind() == reflect.Bool {
|
||||
if len(matcher.FuncErrDescription) == 0 {
|
||||
return false, fmt.Errorf("MatchError requires an additional description when passed a function")
|
||||
}
|
||||
matcher.isFunc = true
|
||||
return v.Call([]reflect.Value{reflect.ValueOf(actualErr)})[0].Bool(), nil
|
||||
}
|
||||
|
||||
var subMatcher omegaMatcher
|
||||
var hasSubMatcher bool
|
||||
if expected != nil {
|
||||
subMatcher, hasSubMatcher = (expected).(omegaMatcher)
|
||||
if hasSubMatcher {
|
||||
return subMatcher.Match(actualErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return false, fmt.Errorf(
|
||||
"MatchError must be passed an error, a string, or a Matcher that can match on strings. Got:\n%s",
|
||||
format.Object(expected, 1))
|
||||
}
|
||||
|
||||
func (matcher *MatchErrorMatcher) FailureMessage(actual any) (message string) {
|
||||
if matcher.isFunc {
|
||||
return format.Message(actual, fmt.Sprintf("to match error function %s", matcher.FuncErrDescription[0]))
|
||||
}
|
||||
return format.Message(actual, "to match error", matcher.Expected)
|
||||
}
|
||||
|
||||
func (matcher *MatchErrorMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
if matcher.isFunc {
|
||||
return format.Message(actual, fmt.Sprintf("not to match error function %s", matcher.FuncErrDescription[0]))
|
||||
}
|
||||
return format.Message(actual, "not to match error", matcher.Expected)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type MatchErrorStrictlyMatcher struct {
|
||||
Expected error
|
||||
}
|
||||
|
||||
func (matcher *MatchErrorStrictlyMatcher) Match(actual any) (success bool, err error) {
|
||||
|
||||
if isNil(matcher.Expected) {
|
||||
return false, fmt.Errorf("Expected error is nil, use \"ToNot(HaveOccurred())\" to explicitly check for nil errors")
|
||||
}
|
||||
|
||||
if isNil(actual) {
|
||||
return false, fmt.Errorf("Expected an error, got nil")
|
||||
}
|
||||
|
||||
if !isError(actual) {
|
||||
return false, fmt.Errorf("Expected an error. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
actualErr := actual.(error)
|
||||
|
||||
return errors.Is(actualErr, matcher.Expected), nil
|
||||
}
|
||||
|
||||
func (matcher *MatchErrorStrictlyMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to match error", matcher.Expected)
|
||||
}
|
||||
|
||||
func (matcher *MatchErrorStrictlyMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to match error", matcher.Expected)
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type MatchJSONMatcher struct {
|
||||
JSONToMatch any
|
||||
firstFailurePath []any
|
||||
}
|
||||
|
||||
func (matcher *MatchJSONMatcher) Match(actual any) (success bool, err error) {
|
||||
actualString, expectedString, err := matcher.prettyPrint(actual)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var aval any
|
||||
var eval any
|
||||
|
||||
// this is guarded by prettyPrint
|
||||
json.Unmarshal([]byte(actualString), &aval)
|
||||
json.Unmarshal([]byte(expectedString), &eval)
|
||||
var equal bool
|
||||
equal, matcher.firstFailurePath = deepEqual(aval, eval)
|
||||
return equal, nil
|
||||
}
|
||||
|
||||
func (matcher *MatchJSONMatcher) FailureMessage(actual any) (message string) {
|
||||
actualString, expectedString, _ := matcher.prettyPrint(actual)
|
||||
return formattedMessage(format.Message(actualString, "to match JSON of", expectedString), matcher.firstFailurePath)
|
||||
}
|
||||
|
||||
func (matcher *MatchJSONMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
actualString, expectedString, _ := matcher.prettyPrint(actual)
|
||||
return formattedMessage(format.Message(actualString, "not to match JSON of", expectedString), matcher.firstFailurePath)
|
||||
}
|
||||
|
||||
func (matcher *MatchJSONMatcher) prettyPrint(actual any) (actualFormatted, expectedFormatted string, err error) {
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
expectedString, ok := toString(matcher.JSONToMatch)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.JSONToMatch, 1))
|
||||
}
|
||||
|
||||
abuf := new(bytes.Buffer)
|
||||
ebuf := new(bytes.Buffer)
|
||||
|
||||
if err := json.Indent(abuf, []byte(actualString), "", " "); err != nil {
|
||||
return "", "", fmt.Errorf("Actual '%s' should be valid JSON, but it is not.\nUnderlying error:%s", actualString, err)
|
||||
}
|
||||
|
||||
if err := json.Indent(ebuf, []byte(expectedString), "", " "); err != nil {
|
||||
return "", "", fmt.Errorf("Expected '%s' should be valid JSON, but it is not.\nUnderlying error:%s", expectedString, err)
|
||||
}
|
||||
|
||||
return abuf.String(), ebuf.String(), nil
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type MatchRegexpMatcher struct {
|
||||
Regexp string
|
||||
Args []any
|
||||
}
|
||||
|
||||
func (matcher *MatchRegexpMatcher) Match(actual any) (success bool, err error) {
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("RegExp matcher requires a string or stringer.\nGot:%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
match, err := regexp.Match(matcher.regexp(), []byte(actualString))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("RegExp match failed to compile with error:\n\t%s", err.Error())
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
func (matcher *MatchRegexpMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to match regular expression", matcher.regexp())
|
||||
}
|
||||
|
||||
func (matcher *MatchRegexpMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "not to match regular expression", matcher.regexp())
|
||||
}
|
||||
|
||||
func (matcher *MatchRegexpMatcher) regexp() string {
|
||||
re := matcher.Regexp
|
||||
if len(matcher.Args) > 0 {
|
||||
re = fmt.Sprintf(matcher.Regexp, matcher.Args...)
|
||||
}
|
||||
return re
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
type MatchXMLMatcher struct {
|
||||
XMLToMatch any
|
||||
}
|
||||
|
||||
func (matcher *MatchXMLMatcher) Match(actual any) (success bool, err error) {
|
||||
actualString, expectedString, err := matcher.formattedPrint(actual)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
aval, err := parseXmlContent(actualString)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Actual '%s' should be valid XML, but it is not.\nUnderlying error:%s", actualString, err)
|
||||
}
|
||||
|
||||
eval, err := parseXmlContent(expectedString)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Expected '%s' should be valid XML, but it is not.\nUnderlying error:%s", expectedString, err)
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(aval, eval), nil
|
||||
}
|
||||
|
||||
func (matcher *MatchXMLMatcher) FailureMessage(actual any) (message string) {
|
||||
actualString, expectedString, _ := matcher.formattedPrint(actual)
|
||||
return fmt.Sprintf("Expected\n%s\nto match XML of\n%s", actualString, expectedString)
|
||||
}
|
||||
|
||||
func (matcher *MatchXMLMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
actualString, expectedString, _ := matcher.formattedPrint(actual)
|
||||
return fmt.Sprintf("Expected\n%s\nnot to match XML of\n%s", actualString, expectedString)
|
||||
}
|
||||
|
||||
func (matcher *MatchXMLMatcher) formattedPrint(actual any) (actualString, expectedString string, err error) {
|
||||
var ok bool
|
||||
actualString, ok = toString(actual)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
expectedString, ok = toString(matcher.XMLToMatch)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.XMLToMatch, 1))
|
||||
}
|
||||
return actualString, expectedString, nil
|
||||
}
|
||||
|
||||
func parseXmlContent(content string) (*xmlNode, error) {
|
||||
allNodes := []*xmlNode{}
|
||||
|
||||
dec := newXmlDecoder(strings.NewReader(content))
|
||||
for {
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("failed to decode next token: %v", err) // untested section
|
||||
}
|
||||
|
||||
lastNodeIndex := len(allNodes) - 1
|
||||
var lastNode *xmlNode
|
||||
if len(allNodes) > 0 {
|
||||
lastNode = allNodes[lastNodeIndex]
|
||||
} else {
|
||||
lastNode = &xmlNode{}
|
||||
}
|
||||
|
||||
switch tok := tok.(type) {
|
||||
case xml.StartElement:
|
||||
attrs := attributesSlice(tok.Attr)
|
||||
sort.Sort(attrs)
|
||||
allNodes = append(allNodes, &xmlNode{XMLName: tok.Name, XMLAttr: tok.Attr})
|
||||
case xml.EndElement:
|
||||
if len(allNodes) > 1 {
|
||||
allNodes[lastNodeIndex-1].Nodes = append(allNodes[lastNodeIndex-1].Nodes, lastNode)
|
||||
allNodes = allNodes[:lastNodeIndex]
|
||||
}
|
||||
case xml.CharData:
|
||||
lastNode.Content = append(lastNode.Content, tok.Copy()...)
|
||||
case xml.Comment:
|
||||
lastNode.Comments = append(lastNode.Comments, tok.Copy()) // untested section
|
||||
case xml.ProcInst:
|
||||
lastNode.ProcInsts = append(lastNode.ProcInsts, tok.Copy())
|
||||
}
|
||||
}
|
||||
|
||||
if len(allNodes) == 0 {
|
||||
return nil, errors.New("found no nodes")
|
||||
}
|
||||
firstNode := allNodes[0]
|
||||
trimParentNodesContentSpaces(firstNode)
|
||||
|
||||
return firstNode, nil
|
||||
}
|
||||
|
||||
func newXmlDecoder(reader io.Reader) *xml.Decoder {
|
||||
dec := xml.NewDecoder(reader)
|
||||
dec.CharsetReader = charset.NewReaderLabel
|
||||
return dec
|
||||
}
|
||||
|
||||
func trimParentNodesContentSpaces(node *xmlNode) {
|
||||
if len(node.Nodes) > 0 {
|
||||
node.Content = bytes.TrimSpace(node.Content)
|
||||
for _, childNode := range node.Nodes {
|
||||
trimParentNodesContentSpaces(childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type xmlNode struct {
|
||||
XMLName xml.Name
|
||||
Comments []xml.Comment
|
||||
ProcInsts []xml.ProcInst
|
||||
XMLAttr []xml.Attr
|
||||
Content []byte
|
||||
Nodes []*xmlNode
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
type MatchYAMLMatcher struct {
|
||||
YAMLToMatch any
|
||||
firstFailurePath []any
|
||||
}
|
||||
|
||||
func (matcher *MatchYAMLMatcher) Match(actual any) (success bool, err error) {
|
||||
actualString, expectedString, err := matcher.toStrings(actual)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var aval any
|
||||
var eval any
|
||||
|
||||
if err := yaml.Unmarshal([]byte(actualString), &aval); err != nil {
|
||||
return false, fmt.Errorf("Actual '%s' should be valid YAML, but it is not.\nUnderlying error:%s", actualString, err)
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(expectedString), &eval); err != nil {
|
||||
return false, fmt.Errorf("Expected '%s' should be valid YAML, but it is not.\nUnderlying error:%s", expectedString, err)
|
||||
}
|
||||
|
||||
var equal bool
|
||||
equal, matcher.firstFailurePath = deepEqual(aval, eval)
|
||||
return equal, nil
|
||||
}
|
||||
|
||||
func (matcher *MatchYAMLMatcher) FailureMessage(actual any) (message string) {
|
||||
actualString, expectedString, _ := matcher.toNormalisedStrings(actual)
|
||||
return formattedMessage(format.Message(actualString, "to match YAML of", expectedString), matcher.firstFailurePath)
|
||||
}
|
||||
|
||||
func (matcher *MatchYAMLMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
actualString, expectedString, _ := matcher.toNormalisedStrings(actual)
|
||||
return formattedMessage(format.Message(actualString, "not to match YAML of", expectedString), matcher.firstFailurePath)
|
||||
}
|
||||
|
||||
func (matcher *MatchYAMLMatcher) toNormalisedStrings(actual any) (actualFormatted, expectedFormatted string, err error) {
|
||||
actualString, expectedString, err := matcher.toStrings(actual)
|
||||
return normalise(actualString), normalise(expectedString), err
|
||||
}
|
||||
|
||||
func normalise(input string) string {
|
||||
var val any
|
||||
err := yaml.Unmarshal([]byte(input), &val)
|
||||
if err != nil {
|
||||
panic(err) // unreachable since Match already calls Unmarshal
|
||||
}
|
||||
output, err := yaml.Marshal(val)
|
||||
if err != nil {
|
||||
panic(err) // untested section, unreachable since we Unmarshal above
|
||||
}
|
||||
return strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
func (matcher *MatchYAMLMatcher) toStrings(actual any) (actualFormatted, expectedFormatted string, err error) {
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
expectedString, ok := toString(matcher.YAMLToMatch)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.YAMLToMatch, 1))
|
||||
}
|
||||
|
||||
return actualString, expectedString, nil
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
type NotMatcher struct {
|
||||
Matcher types.GomegaMatcher
|
||||
}
|
||||
|
||||
func (m *NotMatcher) Match(actual any) (bool, error) {
|
||||
success, err := m.Matcher.Match(actual)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !success, nil
|
||||
}
|
||||
|
||||
func (m *NotMatcher) FailureMessage(actual any) (message string) {
|
||||
return m.Matcher.NegatedFailureMessage(actual) // works beautifully
|
||||
}
|
||||
|
||||
func (m *NotMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return m.Matcher.FailureMessage(actual) // works beautifully
|
||||
}
|
||||
|
||||
func (m *NotMatcher) MatchMayChangeInTheFuture(actual any) bool {
|
||||
return types.MatchMayChangeInTheFuture(m.Matcher, actual) // just return m.Matcher's value
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
type OrMatcher struct {
|
||||
Matchers []types.GomegaMatcher
|
||||
|
||||
// state
|
||||
firstSuccessfulMatcher types.GomegaMatcher
|
||||
}
|
||||
|
||||
func (m *OrMatcher) Match(actual any) (success bool, err error) {
|
||||
m.firstSuccessfulMatcher = nil
|
||||
for _, matcher := range m.Matchers {
|
||||
success, err := matcher.Match(actual)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success {
|
||||
m.firstSuccessfulMatcher = matcher
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *OrMatcher) FailureMessage(actual any) (message string) {
|
||||
// not the most beautiful list of matchers, but not bad either...
|
||||
return format.Message(actual, fmt.Sprintf("To satisfy at least one of these matchers: %s", m.Matchers))
|
||||
}
|
||||
|
||||
func (m *OrMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return m.firstSuccessfulMatcher.NegatedFailureMessage(actual)
|
||||
}
|
||||
|
||||
func (m *OrMatcher) MatchMayChangeInTheFuture(actual any) bool {
|
||||
/*
|
||||
Example with 3 matchers: A, B, C
|
||||
|
||||
Match evaluates them: F, T, <?> => T
|
||||
So match is currently T, what should MatchMayChangeInTheFuture() return?
|
||||
Seems like it only depends on B, since currently B MUST change to allow the result to become F
|
||||
|
||||
Match eval: F, F, F => F
|
||||
So match is currently F, what should MatchMayChangeInTheFuture() return?
|
||||
Seems to depend on ANY of them being able to change to T.
|
||||
*/
|
||||
|
||||
if m.firstSuccessfulMatcher != nil {
|
||||
// one of the matchers succeeded.. it must be able to change in order to affect the result
|
||||
return types.MatchMayChangeInTheFuture(m.firstSuccessfulMatcher, actual)
|
||||
} else {
|
||||
// so all matchers failed.. Any one of them changing would change the result.
|
||||
for _, matcher := range m.Matchers {
|
||||
if types.MatchMayChangeInTheFuture(matcher, actual) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false // none of were going to change
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type PanicMatcher struct {
|
||||
Expected any
|
||||
object any
|
||||
}
|
||||
|
||||
func (matcher *PanicMatcher) Match(actual any) (success bool, err error) {
|
||||
if actual == nil {
|
||||
return false, fmt.Errorf("PanicMatcher expects a non-nil actual.")
|
||||
}
|
||||
|
||||
actualType := reflect.TypeOf(actual)
|
||||
if actualType.Kind() != reflect.Func {
|
||||
return false, fmt.Errorf("PanicMatcher expects a function. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
if !(actualType.NumIn() == 0 && actualType.NumOut() == 0) {
|
||||
return false, fmt.Errorf("PanicMatcher expects a function with no arguments and no return value. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
success = false
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
matcher.object = e
|
||||
|
||||
if matcher.Expected == nil {
|
||||
success = true
|
||||
return
|
||||
}
|
||||
|
||||
valueMatcher, valueIsMatcher := matcher.Expected.(omegaMatcher)
|
||||
if !valueIsMatcher {
|
||||
valueMatcher = &EqualMatcher{Expected: matcher.Expected}
|
||||
}
|
||||
|
||||
success, err = valueMatcher.Match(e)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("PanicMatcher's value matcher failed with:\n%s%s", format.Indent, err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
reflect.ValueOf(actual).Call([]reflect.Value{})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (matcher *PanicMatcher) FailureMessage(actual any) (message string) {
|
||||
if matcher.Expected == nil {
|
||||
// We wanted any panic to occur, but none did.
|
||||
return format.Message(actual, "to panic")
|
||||
}
|
||||
|
||||
if matcher.object == nil {
|
||||
// We wanted a panic with a specific value to occur, but none did.
|
||||
switch matcher.Expected.(type) {
|
||||
case omegaMatcher:
|
||||
return format.Message(actual, "to panic with a value matching", matcher.Expected)
|
||||
default:
|
||||
return format.Message(actual, "to panic with", matcher.Expected)
|
||||
}
|
||||
}
|
||||
|
||||
// We got a panic, but the value isn't what we expected.
|
||||
switch matcher.Expected.(type) {
|
||||
case omegaMatcher:
|
||||
return format.Message(
|
||||
actual,
|
||||
fmt.Sprintf(
|
||||
"to panic with a value matching\n%s\nbut panicked with\n%s",
|
||||
format.Object(matcher.Expected, 1),
|
||||
format.Object(matcher.object, 1),
|
||||
),
|
||||
)
|
||||
default:
|
||||
return format.Message(
|
||||
actual,
|
||||
fmt.Sprintf(
|
||||
"to panic with\n%s\nbut panicked with\n%s",
|
||||
format.Object(matcher.Expected, 1),
|
||||
format.Object(matcher.object, 1),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *PanicMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
if matcher.Expected == nil {
|
||||
// We didn't want any panic to occur, but one did.
|
||||
return format.Message(actual, fmt.Sprintf("not to panic, but panicked with\n%s", format.Object(matcher.object, 1)))
|
||||
}
|
||||
|
||||
// We wanted a to ensure a panic with a specific value did not occur, but it did.
|
||||
switch matcher.Expected.(type) {
|
||||
case omegaMatcher:
|
||||
return format.Message(
|
||||
actual,
|
||||
fmt.Sprintf(
|
||||
"not to panic with a value matching\n%s\nbut panicked with\n%s",
|
||||
format.Object(matcher.Expected, 1),
|
||||
format.Object(matcher.object, 1),
|
||||
),
|
||||
)
|
||||
default:
|
||||
return format.Message(actual, "not to panic with", matcher.Expected)
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// untested sections: 3
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type ReceiveMatcher struct {
|
||||
Args []any
|
||||
receivedValue reflect.Value
|
||||
channelClosed bool
|
||||
}
|
||||
|
||||
func (matcher *ReceiveMatcher) Match(actual any) (success bool, err error) {
|
||||
if !isChan(actual) {
|
||||
return false, fmt.Errorf("ReceiveMatcher expects a channel. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
channelType := reflect.TypeOf(actual)
|
||||
channelValue := reflect.ValueOf(actual)
|
||||
|
||||
if channelType.ChanDir() == reflect.SendDir {
|
||||
return false, fmt.Errorf("ReceiveMatcher matcher cannot be passed a send-only channel. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
var subMatcher omegaMatcher
|
||||
var hasSubMatcher bool
|
||||
var resultReference any
|
||||
|
||||
// Valid arg formats are as follows, always with optional POINTER before
|
||||
// optional MATCHER:
|
||||
// - Receive()
|
||||
// - Receive(POINTER)
|
||||
// - Receive(MATCHER)
|
||||
// - Receive(POINTER, MATCHER)
|
||||
args := matcher.Args
|
||||
if len(args) > 0 {
|
||||
arg := args[0]
|
||||
_, isSubMatcher := arg.(omegaMatcher)
|
||||
if !isSubMatcher && reflect.ValueOf(arg).Kind() == reflect.Ptr {
|
||||
// Consume optional POINTER arg first, if it ain't no matcher ;)
|
||||
resultReference = arg
|
||||
args = args[1:]
|
||||
}
|
||||
}
|
||||
if len(args) > 0 {
|
||||
arg := args[0]
|
||||
subMatcher, hasSubMatcher = arg.(omegaMatcher)
|
||||
if !hasSubMatcher {
|
||||
// At this point we assume the dev user wanted to assign a received
|
||||
// value, so [POINTER,]MATCHER.
|
||||
return false, fmt.Errorf("Cannot assign a value from the channel:\n%s\nTo:\n%s\nYou need to pass a pointer!", format.Object(actual, 1), format.Object(arg, 1))
|
||||
}
|
||||
// Consume optional MATCHER arg.
|
||||
args = args[1:]
|
||||
}
|
||||
if len(args) > 0 {
|
||||
// If there are still args present, reject all.
|
||||
return false, errors.New("Receive matcher expects at most an optional pointer and/or an optional matcher")
|
||||
}
|
||||
|
||||
winnerIndex, value, open := reflect.Select([]reflect.SelectCase{
|
||||
{Dir: reflect.SelectRecv, Chan: channelValue},
|
||||
{Dir: reflect.SelectDefault},
|
||||
})
|
||||
|
||||
var closed bool
|
||||
var didReceive bool
|
||||
if winnerIndex == 0 {
|
||||
closed = !open
|
||||
didReceive = open
|
||||
}
|
||||
matcher.channelClosed = closed
|
||||
|
||||
if closed {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if hasSubMatcher {
|
||||
if !didReceive {
|
||||
return false, nil
|
||||
}
|
||||
matcher.receivedValue = value
|
||||
if match, err := subMatcher.Match(matcher.receivedValue.Interface()); err != nil || !match {
|
||||
return match, err
|
||||
}
|
||||
// if we received a match, then fall through in order to handle an
|
||||
// optional assignment of the received value to the specified reference.
|
||||
}
|
||||
|
||||
if didReceive {
|
||||
if resultReference != nil {
|
||||
outValue := reflect.ValueOf(resultReference)
|
||||
|
||||
if value.Type().AssignableTo(outValue.Elem().Type()) {
|
||||
outValue.Elem().Set(value)
|
||||
return true, nil
|
||||
}
|
||||
if value.Type().Kind() == reflect.Interface && value.Elem().Type().AssignableTo(outValue.Elem().Type()) {
|
||||
outValue.Elem().Set(value.Elem())
|
||||
return true, nil
|
||||
} else {
|
||||
return false, fmt.Errorf("Cannot assign a value from the channel:\n%s\nType:\n%s\nTo:\n%s", format.Object(actual, 1), format.Object(value.Interface(), 1), format.Object(resultReference, 1))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (matcher *ReceiveMatcher) FailureMessage(actual any) (message string) {
|
||||
var matcherArg any
|
||||
if len(matcher.Args) > 0 {
|
||||
matcherArg = matcher.Args[len(matcher.Args)-1]
|
||||
}
|
||||
subMatcher, hasSubMatcher := (matcherArg).(omegaMatcher)
|
||||
|
||||
closedAddendum := ""
|
||||
if matcher.channelClosed {
|
||||
closedAddendum = " The channel is closed."
|
||||
}
|
||||
|
||||
if hasSubMatcher {
|
||||
if matcher.receivedValue.IsValid() {
|
||||
return subMatcher.FailureMessage(matcher.receivedValue.Interface())
|
||||
}
|
||||
return "When passed a matcher, ReceiveMatcher's channel *must* receive something."
|
||||
}
|
||||
return format.Message(actual, "to receive something."+closedAddendum)
|
||||
}
|
||||
|
||||
func (matcher *ReceiveMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
var matcherArg any
|
||||
if len(matcher.Args) > 0 {
|
||||
matcherArg = matcher.Args[len(matcher.Args)-1]
|
||||
}
|
||||
subMatcher, hasSubMatcher := (matcherArg).(omegaMatcher)
|
||||
|
||||
closedAddendum := ""
|
||||
if matcher.channelClosed {
|
||||
closedAddendum = " The channel is closed."
|
||||
}
|
||||
|
||||
if hasSubMatcher {
|
||||
if matcher.receivedValue.IsValid() {
|
||||
return subMatcher.NegatedFailureMessage(matcher.receivedValue.Interface())
|
||||
}
|
||||
return "When passed a matcher, ReceiveMatcher's channel *must* receive something."
|
||||
}
|
||||
return format.Message(actual, "not to receive anything."+closedAddendum)
|
||||
}
|
||||
|
||||
func (matcher *ReceiveMatcher) MatchMayChangeInTheFuture(actual any) bool {
|
||||
if !isChan(actual) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !matcher.channelClosed
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type SatisfyMatcher struct {
|
||||
Predicate any
|
||||
|
||||
// cached type
|
||||
predicateArgType reflect.Type
|
||||
}
|
||||
|
||||
func NewSatisfyMatcher(predicate any) *SatisfyMatcher {
|
||||
if predicate == nil {
|
||||
panic("predicate cannot be nil")
|
||||
}
|
||||
predicateType := reflect.TypeOf(predicate)
|
||||
if predicateType.Kind() != reflect.Func {
|
||||
panic("predicate must be a function")
|
||||
}
|
||||
if predicateType.NumIn() != 1 {
|
||||
panic("predicate must have 1 argument")
|
||||
}
|
||||
if predicateType.NumOut() != 1 || predicateType.Out(0).Kind() != reflect.Bool {
|
||||
panic("predicate must return bool")
|
||||
}
|
||||
|
||||
return &SatisfyMatcher{
|
||||
Predicate: predicate,
|
||||
predicateArgType: predicateType.In(0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SatisfyMatcher) Match(actual any) (success bool, err error) {
|
||||
// prepare a parameter to pass to the predicate
|
||||
var param reflect.Value
|
||||
if actual != nil && reflect.TypeOf(actual).AssignableTo(m.predicateArgType) {
|
||||
// The dynamic type of actual is compatible with the predicate argument.
|
||||
param = reflect.ValueOf(actual)
|
||||
|
||||
} else if actual == nil && m.predicateArgType.Kind() == reflect.Interface {
|
||||
// The dynamic type of actual is unknown, so there's no way to make its
|
||||
// reflect.Value. Create a nil of the predicate argument, which is known.
|
||||
param = reflect.Zero(m.predicateArgType)
|
||||
|
||||
} else {
|
||||
return false, fmt.Errorf("predicate expects '%s' but we have '%T'", m.predicateArgType, actual)
|
||||
}
|
||||
|
||||
// call the predicate with `actual`
|
||||
fn := reflect.ValueOf(m.Predicate)
|
||||
result := fn.Call([]reflect.Value{param})
|
||||
return result[0].Bool(), nil
|
||||
}
|
||||
|
||||
func (m *SatisfyMatcher) FailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to satisfy predicate", m.Predicate)
|
||||
}
|
||||
|
||||
func (m *SatisfyMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return format.Message(actual, "to not satisfy predicate", m.Predicate)
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// untested sections: 5
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func formattedMessage(comparisonMessage string, failurePath []any) string {
|
||||
var diffMessage string
|
||||
if len(failurePath) == 0 {
|
||||
diffMessage = ""
|
||||
} else {
|
||||
diffMessage = fmt.Sprintf("\n\nfirst mismatched key: %s", formattedFailurePath(failurePath))
|
||||
}
|
||||
return fmt.Sprintf("%s%s", comparisonMessage, diffMessage)
|
||||
}
|
||||
|
||||
func formattedFailurePath(failurePath []any) string {
|
||||
formattedPaths := []string{}
|
||||
for i := len(failurePath) - 1; i >= 0; i-- {
|
||||
switch p := failurePath[i].(type) {
|
||||
case int:
|
||||
formattedPaths = append(formattedPaths, fmt.Sprintf(`[%d]`, p))
|
||||
default:
|
||||
if i != len(failurePath)-1 {
|
||||
formattedPaths = append(formattedPaths, ".")
|
||||
}
|
||||
formattedPaths = append(formattedPaths, fmt.Sprintf(`"%s"`, p))
|
||||
}
|
||||
}
|
||||
return strings.Join(formattedPaths, "")
|
||||
}
|
||||
|
||||
func deepEqual(a any, b any) (bool, []any) {
|
||||
var errorPath []any
|
||||
if reflect.TypeOf(a) != reflect.TypeOf(b) {
|
||||
return false, errorPath
|
||||
}
|
||||
|
||||
switch a.(type) {
|
||||
case []any:
|
||||
if len(a.([]any)) != len(b.([]any)) {
|
||||
return false, errorPath
|
||||
}
|
||||
|
||||
for i, v := range a.([]any) {
|
||||
elementEqual, keyPath := deepEqual(v, b.([]any)[i])
|
||||
if !elementEqual {
|
||||
return false, append(keyPath, i)
|
||||
}
|
||||
}
|
||||
return true, errorPath
|
||||
|
||||
case map[any]any:
|
||||
if len(a.(map[any]any)) != len(b.(map[any]any)) {
|
||||
return false, errorPath
|
||||
}
|
||||
|
||||
for k, v1 := range a.(map[any]any) {
|
||||
v2, ok := b.(map[any]any)[k]
|
||||
if !ok {
|
||||
return false, errorPath
|
||||
}
|
||||
elementEqual, keyPath := deepEqual(v1, v2)
|
||||
if !elementEqual {
|
||||
return false, append(keyPath, k)
|
||||
}
|
||||
}
|
||||
return true, errorPath
|
||||
|
||||
case map[string]any:
|
||||
if len(a.(map[string]any)) != len(b.(map[string]any)) {
|
||||
return false, errorPath
|
||||
}
|
||||
|
||||
for k, v1 := range a.(map[string]any) {
|
||||
v2, ok := b.(map[string]any)[k]
|
||||
if !ok {
|
||||
return false, errorPath
|
||||
}
|
||||
elementEqual, keyPath := deepEqual(v1, v2)
|
||||
if !elementEqual {
|
||||
return false, append(keyPath, k)
|
||||
}
|
||||
}
|
||||
return true, errorPath
|
||||
|
||||
default:
|
||||
return a == b, errorPath
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
type formattedGomegaError interface {
|
||||
FormattedGomegaError() string
|
||||
}
|
||||
|
||||
type SucceedMatcher struct {
|
||||
}
|
||||
|
||||
func (matcher *SucceedMatcher) Match(actual any) (success bool, err error) {
|
||||
// is purely nil?
|
||||
if actual == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// must be an 'error' type
|
||||
if !isError(actual) {
|
||||
return false, fmt.Errorf("Expected an error-type. Got:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
// must be nil (or a pointer to a nil)
|
||||
return isNil(actual), nil
|
||||
}
|
||||
|
||||
func (matcher *SucceedMatcher) FailureMessage(actual any) (message string) {
|
||||
var fgErr formattedGomegaError
|
||||
if errors.As(actual.(error), &fgErr) {
|
||||
return fgErr.FormattedGomegaError()
|
||||
}
|
||||
return fmt.Sprintf("Expected success, but got an error:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
func (matcher *SucceedMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||
return "Expected failure, but got no error."
|
||||
}
|
||||
Generated
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
package bipartitegraph
|
||||
|
||||
import "fmt"
|
||||
|
||||
import . "github.com/onsi/gomega/matchers/support/goraph/node"
|
||||
import . "github.com/onsi/gomega/matchers/support/goraph/edge"
|
||||
|
||||
type BipartiteGraph struct {
|
||||
Left NodeOrderedSet
|
||||
Right NodeOrderedSet
|
||||
Edges EdgeSet
|
||||
}
|
||||
|
||||
func NewBipartiteGraph(leftValues, rightValues []any, neighbours func(any, any) (bool, error)) (*BipartiteGraph, error) {
|
||||
left := NodeOrderedSet{}
|
||||
for i, v := range leftValues {
|
||||
left = append(left, Node{ID: i, Value: v})
|
||||
}
|
||||
|
||||
right := NodeOrderedSet{}
|
||||
for j, v := range rightValues {
|
||||
right = append(right, Node{ID: j + len(left), Value: v})
|
||||
}
|
||||
|
||||
edges := EdgeSet{}
|
||||
for i, leftValue := range leftValues {
|
||||
for j, rightValue := range rightValues {
|
||||
neighbours, err := neighbours(leftValue, rightValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error determining adjacency for %v and %v: %s", leftValue, rightValue, err.Error())
|
||||
}
|
||||
|
||||
if neighbours {
|
||||
edges = append(edges, Edge{Node1: left[i].ID, Node2: right[j].ID})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &BipartiteGraph{left, right, edges}, nil
|
||||
}
|
||||
|
||||
// FreeLeftRight returns left node values and right node values
|
||||
// of the BipartiteGraph's nodes which are not part of the given edges.
|
||||
func (bg *BipartiteGraph) FreeLeftRight(edges EdgeSet) (leftValues, rightValues []any) {
|
||||
for _, node := range bg.Left {
|
||||
if edges.Free(node) {
|
||||
leftValues = append(leftValues, node.Value)
|
||||
}
|
||||
}
|
||||
for _, node := range bg.Right {
|
||||
if edges.Free(node) {
|
||||
rightValues = append(rightValues, node.Value)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
Generated
Vendored
+171
@@ -0,0 +1,171 @@
|
||||
package bipartitegraph
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
. "github.com/onsi/gomega/matchers/support/goraph/edge"
|
||||
. "github.com/onsi/gomega/matchers/support/goraph/node"
|
||||
"github.com/onsi/gomega/matchers/support/goraph/util"
|
||||
)
|
||||
|
||||
// LargestMatching implements the Hopcroft–Karp algorithm taking as input a bipartite graph
|
||||
// and outputting a maximum cardinality matching, i.e. a set of as many edges as possible
|
||||
// with the property that no two edges share an endpoint.
|
||||
func (bg *BipartiteGraph) LargestMatching() (matching EdgeSet) {
|
||||
paths := bg.maximalDisjointSLAPCollection(matching)
|
||||
|
||||
for len(paths) > 0 {
|
||||
for _, path := range paths {
|
||||
matching = matching.SymmetricDifference(path)
|
||||
}
|
||||
paths = bg.maximalDisjointSLAPCollection(matching)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (bg *BipartiteGraph) maximalDisjointSLAPCollection(matching EdgeSet) (result []EdgeSet) {
|
||||
guideLayers := bg.createSLAPGuideLayers(matching)
|
||||
if len(guideLayers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
used := make(map[int]bool)
|
||||
|
||||
for _, u := range guideLayers[len(guideLayers)-1] {
|
||||
slap, found := bg.findDisjointSLAP(u, matching, guideLayers, used)
|
||||
if found {
|
||||
for _, edge := range slap {
|
||||
used[edge.Node1] = true
|
||||
used[edge.Node2] = true
|
||||
}
|
||||
result = append(result, slap)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (bg *BipartiteGraph) findDisjointSLAP(
|
||||
start Node,
|
||||
matching EdgeSet,
|
||||
guideLayers []NodeOrderedSet,
|
||||
used map[int]bool,
|
||||
) ([]Edge, bool) {
|
||||
return bg.findDisjointSLAPHelper(start, EdgeSet{}, len(guideLayers)-1, matching, guideLayers, used)
|
||||
}
|
||||
|
||||
func (bg *BipartiteGraph) findDisjointSLAPHelper(
|
||||
currentNode Node,
|
||||
currentSLAP EdgeSet,
|
||||
currentLevel int,
|
||||
matching EdgeSet,
|
||||
guideLayers []NodeOrderedSet,
|
||||
used map[int]bool,
|
||||
) (EdgeSet, bool) {
|
||||
used[currentNode.ID] = true
|
||||
|
||||
if currentLevel == 0 {
|
||||
return currentSLAP, true
|
||||
}
|
||||
|
||||
for _, nextNode := range guideLayers[currentLevel-1] {
|
||||
if used[nextNode.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
edge, found := bg.Edges.FindByNodes(currentNode, nextNode)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
if matching.Contains(edge) == util.Odd(currentLevel) {
|
||||
continue
|
||||
}
|
||||
|
||||
currentSLAP = append(currentSLAP, edge)
|
||||
slap, found := bg.findDisjointSLAPHelper(nextNode, currentSLAP, currentLevel-1, matching, guideLayers, used)
|
||||
if found {
|
||||
return slap, true
|
||||
}
|
||||
currentSLAP = currentSLAP[:len(currentSLAP)-1]
|
||||
}
|
||||
|
||||
used[currentNode.ID] = false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (bg *BipartiteGraph) createSLAPGuideLayers(matching EdgeSet) (guideLayers []NodeOrderedSet) {
|
||||
used := make(map[int]bool)
|
||||
currentLayer := NodeOrderedSet{}
|
||||
|
||||
for _, node := range bg.Left {
|
||||
if matching.Free(node) {
|
||||
used[node.ID] = true
|
||||
currentLayer = append(currentLayer, node)
|
||||
}
|
||||
}
|
||||
|
||||
if len(currentLayer) == 0 {
|
||||
return []NodeOrderedSet{}
|
||||
}
|
||||
guideLayers = append(guideLayers, currentLayer)
|
||||
|
||||
done := false
|
||||
|
||||
for !done {
|
||||
lastLayer := currentLayer
|
||||
currentLayer = NodeOrderedSet{}
|
||||
|
||||
if util.Odd(len(guideLayers)) {
|
||||
for _, leftNode := range lastLayer {
|
||||
for _, rightNode := range bg.Right {
|
||||
if used[rightNode.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
edge, found := bg.Edges.FindByNodes(leftNode, rightNode)
|
||||
if !found || matching.Contains(edge) {
|
||||
continue
|
||||
}
|
||||
|
||||
currentLayer = append(currentLayer, rightNode)
|
||||
used[rightNode.ID] = true
|
||||
|
||||
if matching.Free(rightNode) {
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, rightNode := range lastLayer {
|
||||
for _, leftNode := range bg.Left {
|
||||
if used[leftNode.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
edge, found := bg.Edges.FindByNodes(leftNode, rightNode)
|
||||
if !found || !matching.Contains(edge) {
|
||||
continue
|
||||
}
|
||||
|
||||
currentLayer = append(currentLayer, leftNode)
|
||||
used[leftNode.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(currentLayer) == 0 {
|
||||
return []NodeOrderedSet{}
|
||||
}
|
||||
if done { // if last layer - into last layer must be only 'free' nodes
|
||||
currentLayer = slices.DeleteFunc(currentLayer, func(in Node) bool {
|
||||
return !matching.Free(in)
|
||||
})
|
||||
}
|
||||
guideLayers = append(guideLayers, currentLayer)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package edge
|
||||
|
||||
import (
|
||||
. "github.com/onsi/gomega/matchers/support/goraph/node"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Edge struct {
|
||||
Node1 int
|
||||
Node2 int
|
||||
}
|
||||
|
||||
type EdgeSet []Edge
|
||||
|
||||
func (ec EdgeSet) Free(node Node) bool {
|
||||
for _, e := range ec {
|
||||
if e.Node1 == node.ID || e.Node2 == node.ID {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ec EdgeSet) Contains(edge Edge) bool {
|
||||
return slices.Contains(ec, edge)
|
||||
}
|
||||
|
||||
func (ec EdgeSet) FindByNodes(node1, node2 Node) (Edge, bool) {
|
||||
for _, e := range ec {
|
||||
if (e.Node1 == node1.ID && e.Node2 == node2.ID) || (e.Node1 == node2.ID && e.Node2 == node1.ID) {
|
||||
return e, true
|
||||
}
|
||||
}
|
||||
|
||||
return Edge{}, false
|
||||
}
|
||||
|
||||
func (ec EdgeSet) SymmetricDifference(ec2 EdgeSet) EdgeSet {
|
||||
edgesToInclude := make(map[Edge]bool)
|
||||
|
||||
for _, e := range ec {
|
||||
edgesToInclude[e] = true
|
||||
}
|
||||
|
||||
for _, e := range ec2 {
|
||||
edgesToInclude[e] = !edgesToInclude[e]
|
||||
}
|
||||
|
||||
result := EdgeSet{}
|
||||
for e, include := range edgesToInclude {
|
||||
if include {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package node
|
||||
|
||||
type Node struct {
|
||||
ID int
|
||||
Value any
|
||||
}
|
||||
|
||||
type NodeOrderedSet []Node
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package util
|
||||
|
||||
import "math"
|
||||
|
||||
func Odd(n int) bool {
|
||||
return math.Mod(float64(n), 2.0) == 1.0
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
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/
|
||||
*/
|
||||
|
||||
// untested sections: 11
|
||||
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/matchers/internal/miter"
|
||||
)
|
||||
|
||||
type omegaMatcher interface {
|
||||
Match(actual any) (success bool, err error)
|
||||
FailureMessage(actual any) (message string)
|
||||
NegatedFailureMessage(actual any) (message string)
|
||||
}
|
||||
|
||||
func isBool(a any) bool {
|
||||
return reflect.TypeOf(a).Kind() == reflect.Bool
|
||||
}
|
||||
|
||||
func isNumber(a any) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
kind := reflect.TypeOf(a).Kind()
|
||||
return reflect.Int <= kind && kind <= reflect.Float64
|
||||
}
|
||||
|
||||
func isInteger(a any) bool {
|
||||
kind := reflect.TypeOf(a).Kind()
|
||||
return reflect.Int <= kind && kind <= reflect.Int64
|
||||
}
|
||||
|
||||
func isUnsignedInteger(a any) bool {
|
||||
kind := reflect.TypeOf(a).Kind()
|
||||
return reflect.Uint <= kind && kind <= reflect.Uint64
|
||||
}
|
||||
|
||||
func isFloat(a any) bool {
|
||||
kind := reflect.TypeOf(a).Kind()
|
||||
return reflect.Float32 <= kind && kind <= reflect.Float64
|
||||
}
|
||||
|
||||
func toInteger(a any) int64 {
|
||||
if isInteger(a) {
|
||||
return reflect.ValueOf(a).Int()
|
||||
} else if isUnsignedInteger(a) {
|
||||
return int64(reflect.ValueOf(a).Uint())
|
||||
} else if isFloat(a) {
|
||||
return int64(reflect.ValueOf(a).Float())
|
||||
}
|
||||
panic(fmt.Sprintf("Expected a number! Got <%T> %#v", a, a))
|
||||
}
|
||||
|
||||
func toUnsignedInteger(a any) uint64 {
|
||||
if isInteger(a) {
|
||||
return uint64(reflect.ValueOf(a).Int())
|
||||
} else if isUnsignedInteger(a) {
|
||||
return reflect.ValueOf(a).Uint()
|
||||
} else if isFloat(a) {
|
||||
return uint64(reflect.ValueOf(a).Float())
|
||||
}
|
||||
panic(fmt.Sprintf("Expected a number! Got <%T> %#v", a, a))
|
||||
}
|
||||
|
||||
func toFloat(a any) float64 {
|
||||
if isInteger(a) {
|
||||
return float64(reflect.ValueOf(a).Int())
|
||||
} else if isUnsignedInteger(a) {
|
||||
return float64(reflect.ValueOf(a).Uint())
|
||||
} else if isFloat(a) {
|
||||
return reflect.ValueOf(a).Float()
|
||||
}
|
||||
panic(fmt.Sprintf("Expected a number! Got <%T> %#v", a, a))
|
||||
}
|
||||
|
||||
func isError(a any) bool {
|
||||
_, ok := a.(error)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isChan(a any) bool {
|
||||
if isNil(a) {
|
||||
return false
|
||||
}
|
||||
return reflect.TypeOf(a).Kind() == reflect.Chan
|
||||
}
|
||||
|
||||
func isMap(a any) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
return reflect.TypeOf(a).Kind() == reflect.Map
|
||||
}
|
||||
|
||||
func isArrayOrSlice(a any) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
switch reflect.TypeOf(a).Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isString(a any) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
return reflect.TypeOf(a).Kind() == reflect.String
|
||||
}
|
||||
|
||||
func toString(a any) (string, bool) {
|
||||
aString, isString := a.(string)
|
||||
if isString {
|
||||
return aString, true
|
||||
}
|
||||
|
||||
aBytes, isBytes := a.([]byte)
|
||||
if isBytes {
|
||||
return string(aBytes), true
|
||||
}
|
||||
|
||||
aStringer, isStringer := a.(fmt.Stringer)
|
||||
if isStringer {
|
||||
return aStringer.String(), true
|
||||
}
|
||||
|
||||
aJSONRawMessage, isJSONRawMessage := a.(json.RawMessage)
|
||||
if isJSONRawMessage {
|
||||
return string(aJSONRawMessage), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func lengthOf(a any) (int, bool) {
|
||||
if a == nil {
|
||||
return 0, false
|
||||
}
|
||||
switch reflect.TypeOf(a).Kind() {
|
||||
case reflect.Map, reflect.Array, reflect.String, reflect.Chan, reflect.Slice:
|
||||
return reflect.ValueOf(a).Len(), true
|
||||
case reflect.Func:
|
||||
if !miter.IsIter(a) {
|
||||
return 0, false
|
||||
}
|
||||
var l int
|
||||
if miter.IsSeq2(a) {
|
||||
miter.IterateKV(a, func(k, v reflect.Value) bool { l++; return true })
|
||||
} else {
|
||||
miter.IterateV(a, func(v reflect.Value) bool { l++; return true })
|
||||
}
|
||||
return l, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
func capOf(a any) (int, bool) {
|
||||
if a == nil {
|
||||
return 0, false
|
||||
}
|
||||
switch reflect.TypeOf(a).Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Slice:
|
||||
return reflect.ValueOf(a).Cap(), true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(a any) bool {
|
||||
if a == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch reflect.TypeOf(a).Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return reflect.ValueOf(a).IsNil()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
type WithTransformMatcher struct {
|
||||
// input
|
||||
Transform any // must be a function of one parameter that returns one value and an optional error
|
||||
Matcher types.GomegaMatcher
|
||||
|
||||
// cached value
|
||||
transformArgType reflect.Type
|
||||
|
||||
// state
|
||||
transformedValue any
|
||||
}
|
||||
|
||||
// reflect.Type for error
|
||||
var errorT = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
func NewWithTransformMatcher(transform any, matcher types.GomegaMatcher) *WithTransformMatcher {
|
||||
if transform == nil {
|
||||
panic("transform function cannot be nil")
|
||||
}
|
||||
txType := reflect.TypeOf(transform)
|
||||
if txType.NumIn() != 1 {
|
||||
panic("transform function must have 1 argument")
|
||||
}
|
||||
if numout := txType.NumOut(); numout != 1 {
|
||||
if numout != 2 || !txType.Out(1).AssignableTo(errorT) {
|
||||
panic("transform function must either have 1 return value, or 1 return value plus 1 error value")
|
||||
}
|
||||
}
|
||||
|
||||
return &WithTransformMatcher{
|
||||
Transform: transform,
|
||||
Matcher: matcher,
|
||||
transformArgType: reflect.TypeOf(transform).In(0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WithTransformMatcher) Match(actual any) (bool, error) {
|
||||
// prepare a parameter to pass to the Transform function
|
||||
var param reflect.Value
|
||||
if actual != nil && reflect.TypeOf(actual).AssignableTo(m.transformArgType) {
|
||||
// The dynamic type of actual is compatible with the transform argument.
|
||||
param = reflect.ValueOf(actual)
|
||||
|
||||
} else if actual == nil && m.transformArgType.Kind() == reflect.Interface {
|
||||
// The dynamic type of actual is unknown, so there's no way to make its
|
||||
// reflect.Value. Create a nil of the transform argument, which is known.
|
||||
param = reflect.Zero(m.transformArgType)
|
||||
|
||||
} else {
|
||||
return false, fmt.Errorf("Transform function expects '%s' but we have '%T'", m.transformArgType, actual)
|
||||
}
|
||||
|
||||
// call the Transform function with `actual`
|
||||
fn := reflect.ValueOf(m.Transform)
|
||||
result := fn.Call([]reflect.Value{param})
|
||||
if len(result) == 2 {
|
||||
if !result[1].IsNil() {
|
||||
return false, fmt.Errorf("Transform function failed: %s", result[1].Interface().(error).Error())
|
||||
}
|
||||
}
|
||||
m.transformedValue = result[0].Interface() // expect exactly one value
|
||||
|
||||
return m.Matcher.Match(m.transformedValue)
|
||||
}
|
||||
|
||||
func (m *WithTransformMatcher) FailureMessage(_ any) (message string) {
|
||||
return m.Matcher.FailureMessage(m.transformedValue)
|
||||
}
|
||||
|
||||
func (m *WithTransformMatcher) NegatedFailureMessage(_ any) (message string) {
|
||||
return m.Matcher.NegatedFailureMessage(m.transformedValue)
|
||||
}
|
||||
|
||||
func (m *WithTransformMatcher) MatchMayChangeInTheFuture(_ any) bool {
|
||||
// TODO: Maybe this should always just return true? (Only an issue for non-deterministic transformers.)
|
||||
//
|
||||
// Querying the next matcher is fine if the transformer always will return the same value.
|
||||
// But if the transformer is non-deterministic and returns a different value each time, then there
|
||||
// is no point in querying the next matcher, since it can only comment on the last transformed value.
|
||||
return types.MatchMayChangeInTheFuture(m.Matcher, m.transformedValue)
|
||||
}
|
||||
Reference in New Issue
Block a user