12
"github.com/onsi/gomega/format"
13
errorsutil "github.com/onsi/gomega/gstruct/errors"
14
"github.com/onsi/gomega/types"
17
//MatchAllElements succeeds if every element of a slice matches the element matcher it maps to
18
//through the id function, and every element matcher is matched.
19
// idFn := func(element interface{}) string {
20
// return fmt.Sprintf("%v", element)
23
// Expect([]string{"a", "b"}).To(MatchAllElements(idFn, Elements{
27
func MatchAllElements(identifier Identifier, elements Elements) types.GomegaMatcher {
28
return &ElementsMatcher{
29
Identifier: identifier,
34
//MatchAllElementsWithIndex succeeds if every element of a slice matches the element matcher it maps to
35
//through the id with index function, and every element matcher is matched.
36
// idFn := func(index int, element interface{}) string {
37
// return strconv.Itoa(index)
40
// Expect([]string{"a", "b"}).To(MatchAllElements(idFn, Elements{
44
func MatchAllElementsWithIndex(identifier IdentifierWithIndex, elements Elements) types.GomegaMatcher {
45
return &ElementsMatcher{
46
Identifier: identifier,
51
//MatchElements succeeds if each element of a slice matches the element matcher it maps to
52
//through the id function. It can ignore extra elements and/or missing elements.
53
// idFn := func(element interface{}) string {
54
// return fmt.Sprintf("%v", element)
57
// Expect([]string{"a", "b", "c"}).To(MatchElements(idFn, IgnoreExtras, Elements{
61
// Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing, Elements{
67
func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
68
return &ElementsMatcher{
69
Identifier: identifier,
71
IgnoreExtras: options&IgnoreExtras != 0,
72
IgnoreMissing: options&IgnoreMissing != 0,
73
AllowDuplicates: options&AllowDuplicates != 0,
77
//MatchElementsWithIndex succeeds if each element of a slice matches the element matcher it maps to
78
//through the id with index function. It can ignore extra elements and/or missing elements.
79
// idFn := func(index int, element interface{}) string {
80
// return strconv.Itoa(index)
83
// Expect([]string{"a", "b", "c"}).To(MatchElements(idFn, IgnoreExtras, Elements{
87
// Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing, Elements{
93
func MatchElementsWithIndex(identifier IdentifierWithIndex, options Options, elements Elements) types.GomegaMatcher {
94
return &ElementsMatcher{
95
Identifier: identifier,
97
IgnoreExtras: options&IgnoreExtras != 0,
98
IgnoreMissing: options&IgnoreMissing != 0,
99
AllowDuplicates: options&AllowDuplicates != 0,
103
// ElementsMatcher is a NestingMatcher that applies custom matchers to each element of a slice mapped
104
// by the Identifier function.
105
// TODO: Extend this to work with arrays & maps (map the key) as well.
106
type ElementsMatcher struct {
107
// Matchers for each element.
109
// Function mapping an element to the string key identifying its matcher.
112
// Whether to ignore extra elements or consider it an error.
114
// Whether to ignore missing elements or consider it an error.
116
// Whether to key duplicates when matching IDs.
123
// Element ID to matcher.
124
type Elements map[string]types.GomegaMatcher
126
// Function for identifying (mapping) elements.
127
type Identifier func(element interface{}) string
129
// Calls the underlying fucntion with the provided params.
130
// Identifier drops the index.
131
func (i Identifier) WithIndexAndElement(index int, element interface{}) string {
135
// Uses the index and element to generate an element name
136
type IdentifierWithIndex func(index int, element interface{}) string
138
// Calls the underlying fucntion with the provided params.
139
// IdentifierWithIndex uses the index.
140
func (i IdentifierWithIndex) WithIndexAndElement(index int, element interface{}) string {
141
return i(index, element)
144
// Interface for identifing the element
145
type Identify interface {
146
WithIndexAndElement(i int, element interface{}) string
149
// IndexIdentity is a helper function for using an index as
150
// the key in the element map
151
func IndexIdentity(index int, _ interface{}) string {
152
return strconv.Itoa(index)
155
func (m *ElementsMatcher) Match(actual interface{}) (success bool, err error) {
156
if reflect.TypeOf(actual).Kind() != reflect.Slice {
157
return false, fmt.Errorf("%v is type %T, expected slice", actual, actual)
160
m.failures = m.matchElements(actual)
161
if len(m.failures) > 0 {
167
func (m *ElementsMatcher) matchElements(actual interface{}) (errs []error) {
168
// Provide more useful error messages in the case of a panic.
170
if err := recover(); err != nil {
171
errs = append(errs, fmt.Errorf("panic checking %+v: %v\n%s", actual, err, debug.Stack()))
175
val := reflect.ValueOf(actual)
176
elements := map[string]bool{}
177
for i := 0; i < val.Len(); i++ {
178
element := val.Index(i).Interface()
179
id := m.Identifier.WithIndexAndElement(i, element)
181
if !m.AllowDuplicates {
182
errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
188
matcher, expected := m.Elements[id]
191
errs = append(errs, fmt.Errorf("unexpected element %s", id))
196
match, err := matcher.Match(element)
202
if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
203
err = errorsutil.AggregateError(nesting.Failures())
205
err = errors.New(matcher.FailureMessage(element))
208
errs = append(errs, errorsutil.Nest(fmt.Sprintf("[%s]", id), err))
211
for id := range m.Elements {
212
if !elements[id] && !m.IgnoreMissing {
213
errs = append(errs, fmt.Errorf("missing expected element %s", id))
220
func (m *ElementsMatcher) FailureMessage(actual interface{}) (message string) {
221
failure := errorsutil.AggregateError(m.failures)
222
return format.Message(actual, fmt.Sprintf("to match elements: %v", failure))
225
func (m *ElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
226
return format.Message(actual, "not to match elements")
229
func (m *ElementsMatcher) Failures() []error {