3
3
package matchers
4
4
5
5
import (
6
+ "errors"
6
7
"fmt"
7
8
"reflect"
8
9
@@ -11,44 +12,157 @@ import (
11
12
12
13
type ContainElementMatcher struct {
13
14
Element interface {}
15
+ Result []interface {}
14
16
}
15
17
16
18
func (matcher * ContainElementMatcher ) Match (actual interface {}) (success bool , err error ) {
17
19
if ! isArrayOrSlice (actual ) && ! isMap (actual ) {
18
20
return false , fmt .Errorf ("ContainElement matcher expects an array/slice/map. Got:\n %s" , format .Object (actual , 1 ))
19
21
}
20
22
23
+ var actualT reflect.Type
24
+ var result reflect.Value
25
+ switch l := len (matcher .Result ); {
26
+ case l > 1 :
27
+ return false , errors .New ("ContainElement matcher expects at most a single optional pointer to store its findings at" )
28
+ case l == 1 :
29
+ if reflect .ValueOf (matcher .Result [0 ]).Kind () != reflect .Ptr {
30
+ return false , fmt .Errorf ("ContainElement matcher expects a non-nil pointer to store its findings at. Got\n %s" ,
31
+ format .Object (matcher .Result [0 ], 1 ))
32
+ }
33
+ actualT = reflect .TypeOf (actual )
34
+ resultReference := matcher .Result [0 ]
35
+ result = reflect .ValueOf (resultReference ).Elem () // what ResultReference points to, to stash away our findings
36
+ switch result .Kind () {
37
+ case reflect .Array :
38
+ return false , fmt .Errorf ("ContainElement cannot return findings. Need *%s, got *%s" ,
39
+ reflect .SliceOf (actualT .Elem ()).String (), result .Type ().String ())
40
+ case reflect .Slice :
41
+ if ! isArrayOrSlice (actual ) {
42
+ return false , fmt .Errorf ("ContainElement cannot return findings. Need *%s, got *%s" ,
43
+ reflect .MapOf (actualT .Key (), actualT .Elem ()).String (), result .Type ().String ())
44
+ }
45
+ if ! actualT .Elem ().AssignableTo (result .Type ().Elem ()) {
46
+ return false , fmt .Errorf ("ContainElement cannot return findings. Need *%s, got *%s" ,
47
+ actualT .String (), result .Type ().String ())
48
+ }
49
+ case reflect .Map :
50
+ if ! isMap (actual ) {
51
+ return false , fmt .Errorf ("ContainElement cannot return findings. Need *%s, got *%s" ,
52
+ actualT .String (), result .Type ().String ())
53
+ }
54
+ if ! actualT .AssignableTo (result .Type ()) {
55
+ return false , fmt .Errorf ("ContainElement cannot return findings. Need *%s, got *%s" ,
56
+ actualT .String (), result .Type ().String ())
57
+ }
58
+ default :
59
+ if ! actualT .Elem ().AssignableTo (result .Type ()) {
60
+ return false , fmt .Errorf ("ContainElement cannot return findings. Need *%s, got *%s" ,
61
+ actualT .Elem ().String (), result .Type ().String ())
62
+ }
63
+ }
64
+ }
65
+
21
66
elemMatcher , elementIsMatcher := matcher .Element .(omegaMatcher )
22
67
if ! elementIsMatcher {
23
68
elemMatcher = & EqualMatcher {Expected : matcher .Element }
24
69
}
25
70
26
71
value := reflect .ValueOf (actual )
27
72
var valueAt func (int ) interface {}
73
+
74
+ var getFindings func () reflect.Value
75
+ var foundAt func (int )
76
+
28
77
if isMap (actual ) {
29
78
keys := value .MapKeys ()
30
79
valueAt = func (i int ) interface {} {
31
80
return value .MapIndex (keys [i ]).Interface ()
32
81
}
82
+ if result .Kind () != reflect .Invalid {
83
+ fm := reflect .MakeMap (actualT )
84
+ getFindings = func () reflect.Value {
85
+ return fm
86
+ }
87
+ foundAt = func (i int ) {
88
+ fm .SetMapIndex (keys [i ], value .MapIndex (keys [i ]))
89
+ }
90
+ }
33
91
} else {
34
92
valueAt = func (i int ) interface {} {
35
93
return value .Index (i ).Interface ()
36
94
}
95
+ if result .Kind () != reflect .Invalid {
96
+ var f reflect.Value
97
+ if result .Kind () == reflect .Slice {
98
+ f = reflect .MakeSlice (result .Type (), 0 , 0 )
99
+ } else {
100
+ f = reflect .MakeSlice (reflect .SliceOf (result .Type ()), 0 , 0 )
101
+ }
102
+ getFindings = func () reflect.Value {
103
+ return f
104
+ }
105
+ foundAt = func (i int ) {
106
+ f = reflect .Append (f , value .Index (i ))
107
+ }
108
+ }
37
109
}
38
110
39
111
var lastError error
40
112
for i := 0 ; i < value .Len (); i ++ {
41
- success , err := elemMatcher .Match (valueAt (i ))
113
+ elem := valueAt (i )
114
+ success , err := elemMatcher .Match (elem )
42
115
if err != nil {
43
116
lastError = err
44
117
continue
45
118
}
46
119
if success {
47
- return true , nil
120
+ if result .Kind () == reflect .Invalid {
121
+ return true , nil
122
+ }
123
+ foundAt (i )
48
124
}
49
125
}
50
126
51
- return false , lastError
127
+ // when the expectation isn't interested in the findings except for success
128
+ // or non-success, then we're done here and return the last matcher error
129
+ // seen, if any, as well as non-success.
130
+ if result .Kind () == reflect .Invalid {
131
+ return false , lastError
132
+ }
133
+
134
+ // pick up any findings the test is interested in as it specified a non-nil
135
+ // result reference. However, the expection always is that there are at
136
+ // least one or multiple findings. So, if a result is expected, but we had
137
+ // no findings, then this is an error.
138
+ findings := getFindings ()
139
+ if findings .Len () == 0 {
140
+ return false , lastError
141
+ }
142
+
143
+ // there's just a single finding and the result is neither a slice nor a map
144
+ // (so it's a scalar): pick the one and only finding and return it in the
145
+ // place the reference points to.
146
+ if findings .Len () == 1 && ! isArrayOrSlice (result .Interface ()) && ! isMap (result .Interface ()) {
147
+ if isMap (actual ) {
148
+ miter := findings .MapRange ()
149
+ miter .Next ()
150
+ result .Set (miter .Value ())
151
+ } else {
152
+ result .Set (findings .Index (0 ))
153
+ }
154
+ return true , nil
155
+ }
156
+
157
+ // at least one or even multiple findings and a the result references a
158
+ // slice or a map, so all we need to do is to store our findings where the
159
+ // reference points to.
160
+ if ! findings .Type ().AssignableTo (result .Type ()) {
161
+ return false , fmt .Errorf ("ContainElement cannot return multiple findings. Need *%s, got *%s" ,
162
+ findings .Type ().String (), result .Type ().String ())
163
+ }
164
+ result .Set (findings )
165
+ return true , nil
52
166
}
53
167
54
168
func (matcher * ContainElementMatcher ) FailureMessage (actual interface {}) (message string ) {
0 commit comments