aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/onsi/gomega/matchers/contain_element_matcher.go
blob: 3d45c9ebc696de9a8edd5c8b9503d953020b9a04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// untested sections: 2

package matchers

import (
	"errors"
	"fmt"
	"reflect"

	"github.com/onsi/gomega/format"
)

type ContainElementMatcher struct {
	Element interface{}
	Result  []interface{}
}

func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, err error) {
	if !isArrayOrSlice(actual) && !isMap(actual) {
		return false, fmt.Errorf("ContainElement matcher expects an array/slice/map.  Got:\n%s", format.Object(actual, 1))
	}

	var actualT reflect.Type
	var result reflect.Value
	switch l := len(matcher.Result); {
	case l > 1:
		return false, errors.New("ContainElement matcher expects at most a single optional pointer to store its findings at")
	case l == 1:
		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:
			return false, fmt.Errorf("ContainElement cannot return findings.  Need *%s, got *%s",
				reflect.SliceOf(actualT.Elem()).String(), result.Type().String())
		case reflect.Slice:
			if !isArrayOrSlice(actual) {
				return false, fmt.Errorf("ContainElement cannot return findings.  Need *%s, got *%s",
					reflect.MapOf(actualT.Key(), actualT.Elem()).String(), result.Type().String())
			}
			if !actualT.Elem().AssignableTo(result.Type().Elem()) {
				return false, fmt.Errorf("ContainElement cannot return findings.  Need *%s, got *%s",
					actualT.String(), result.Type().String())
			}
		case reflect.Map:
			if !isMap(actual) {
				return false, fmt.Errorf("ContainElement cannot return findings.  Need *%s, got *%s",
					actualT.String(), result.Type().String())
			}
			if !actualT.AssignableTo(result.Type()) {
				return false, fmt.Errorf("ContainElement cannot return findings.  Need *%s, got *%s",
					actualT.String(), result.Type().String())
			}
		default:
			if !actualT.Elem().AssignableTo(result.Type()) {
				return false, fmt.Errorf("ContainElement cannot return findings.  Need *%s, got *%s",
					actualT.Elem().String(), result.Type().String())
			}
		}
	}

	elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
	if !elementIsMatcher {
		elemMatcher = &EqualMatcher{Expected: matcher.Element}
	}

	value := reflect.ValueOf(actual)
	var valueAt func(int) interface{}

	var getFindings func() reflect.Value
	var foundAt func(int)

	if isMap(actual) {
		keys := value.MapKeys()
		valueAt = func(i int) interface{} {
			return value.MapIndex(keys[i]).Interface()
		}
		if result.Kind() != reflect.Invalid {
			fm := reflect.MakeMap(actualT)
			getFindings = func() reflect.Value {
				return fm
			}
			foundAt = func(i int) {
				fm.SetMapIndex(keys[i], value.MapIndex(keys[i]))
			}
		}
	} else {
		valueAt = func(i int) interface{} {
			return value.Index(i).Interface()
		}
		if result.Kind() != reflect.Invalid {
			var f reflect.Value
			if result.Kind() == reflect.Slice {
				f = reflect.MakeSlice(result.Type(), 0, 0)
			} else {
				f = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)
			}
			getFindings = func() reflect.Value {
				return f
			}
			foundAt = func(i int) {
				f = reflect.Append(f, value.Index(i))
			}
		}
	}

	var lastError error
	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)
		}
	}

	// 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 expection 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 interface{}) (message string) {
	return format.Message(actual, "to contain element matching", matcher.Element)
}

func (matcher *ContainElementMatcher) NegatedFailureMessage(actual interface{}) (message string) {
	return format.Message(actual, "not to contain element matching", matcher.Element)
}