-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcall.go
209 lines (185 loc) · 6.12 KB
/
call.go
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package vermock
import (
"errors"
"fmt"
"reflect"
"testing"
)
// Callable defines an interface for delegates to call test functions.
type Callable interface {
Call(testing.TB, CallCount, []reflect.Value) []reflect.Value
}
// MultiCallable defines an interface for Callable objects that can be called
// multiple times.
type MultiCallable interface {
MultiCallable() bool
}
// Callables is a slice of Callable objects.
type Callables []Callable
// Len returns the number of Callables in the slice.
func (c Callables) Len() int {
return len(c)
}
// Cap returns the capacity of the slice of Callables.
func (c Callables) Cap() int {
return cap(c)
}
// Append adds one or more Callables to the slice.
func (c Callables) Append(callable ...Callable) Callables {
return append(c, callable...)
}
// Call invokes the Callable at the given index with the given arguments.
// Panics if the index is out of range and the last Callable is not a
// MultiCallable.
func (c Callables) Call(t testing.TB, index CallCount, in []reflect.Value) []reflect.Value {
if int(index) < len(c) {
return c[index].Call(t, index, in)
}
if c.MultiCallable() {
return c[len(c)-1].Call(t, index, in)
}
panic(fmt.Sprintf("Callables.Call: index out of range [%d] with length %d", index, len(c)))
}
// MultiCallable returns true if the last Callable in the slice is a
// MultiCallable.
func (c Callables) MultiCallable() bool {
if len(c) == 0 {
return false
}
if m, ok := c[len(c)-1].(MultiCallable); ok {
return m.MultiCallable()
}
return false
}
// Value is a Callable that wraps a reflect.Value.
type Value struct {
reflect.Value
ordered
}
// Call invokes the Callable with the given arguments. If the Callable is variadic,
// the last argument must be passed as a slice, otherwise this method panics.
func (v Value) Call(t testing.TB, i CallCount, in []reflect.Value) []reflect.Value {
fn := v.Value
if fn.Kind() != reflect.Func {
panic(fmt.Sprintf("Value.Call: expected func, got %T", v))
}
if fn.Type().NumIn() == len(in)+1 {
in = append([]reflect.Value{reflect.ValueOf(t)}, in...)
}
if fn.Type().IsVariadic() {
return fn.CallSlice(in)
} else {
return fn.Call(in)
}
}
// multi is a Callable that wraps a reflect.Value and implements MultiCallable.
type multi Value
// MultiCallable returns true.
func (v multi) MultiCallable() bool { return true }
// Call invokes the Callable with the given arguments.
func (v multi) Call(t testing.TB, i CallCount, in []reflect.Value) []reflect.Value {
funcType := v.Value.Type()
if funcType.NumIn() > 0 && funcType.In(0) == reflect.TypeOf(i) ||
funcType.NumIn() > 1 && funcType.In(1) == reflect.TypeOf(i) {
in = append([]reflect.Value{reflect.ValueOf(i)}, in...)
}
return Value(v).Call(t, i, in)
}
// errType is the type of the error interface.
var errType = reflect.TypeOf((*error)(nil)).Elem()
// CallDelegate calls the next Callable of the Delegate with the given name and
// given arguments. If the delegate is variadic then the last argument must be
// a slice, otherwise this function panics. If the next Callable does not
// exist or the last Callable is not MultiCallable, then the mock object will
// be marked as failed. In the case of a fail and if the delegate function
// returns an error as its last return value, then the error will be set and
// returned otherwise the function returns zero values for all of the return
// values.
func CallDelegate[T any](key *T, name string, outTypes []reflect.Type, in ...reflect.Value) (out []reflect.Value) {
mock := registry[key]
t := mock.TB
t.Helper()
delegate := delegateByName(mock, name)
delegate.Lock()
defer delegate.Unlock()
if int(delegate.callCount) >= delegate.Len() && !delegate.MultiCallable() {
msg := "unexpected call to " + name
t.Error(msg)
out = make([]reflect.Value, 0, len(outTypes))
for _, typ := range outTypes {
out = append(out, reflect.Zero(typ))
}
// set last out to error
if i := len(out) - 1; i >= 0 && outTypes[i].Implements(errType) {
out[i] = reflect.ValueOf(errors.New(msg))
}
return
}
var (
fn Value
ok bool
)
if int(delegate.callCount) < delegate.Len() {
fn, ok = delegate.Callables[delegate.callCount].(Value)
} else {
fn, ok = delegate.Callables[delegate.Len()-1].(Value)
}
if fn.inOrder {
mock.ordinal++
}
if ok && fn.ordinal != mock.ordinal {
err := fmt.Sprintf("out of order call to %s: expected %d, got %d", name, fn.ordinal, mock.ordinal)
t.Error(err)
}
t.Logf("call to %s: %d/%d", name, delegate.callCount, mock.ordinal)
defer func() { delegate.callCount++ }()
return delegate.Call(t, delegate.callCount, in)
}
// toValues converts the given values to reflect.Values.
func toValues(in ...any) (out []reflect.Value) {
out = make([]reflect.Value, len(in))
for i, v := range in {
out[i] = reflect.ValueOf(v)
}
return
}
// doCall calls the next Callable of the Delegate with the given name and given
// arguments and sets the given out values to the return values of the Callable.
// If the types of the return values do not match the types of the out values,
// or if the number of return values does not match the number of out values,
// then the last out value will be set to an error if it is assignable to an
// error type otherwise this function will panic.
func doCall[T any](key *T, name string, in []reflect.Value, out []reflect.Value) {
registry[key].Helper()
outTypes := make([]reflect.Type, len(out))
for i := range out {
outTypes[i] = out[i].Type().Elem()
}
results := CallDelegate(key, name, outTypes, in...)
last := len(outTypes) - 1
var err error
if len(results) != len(outTypes) {
err = fmt.Errorf("unexpected number of results: expected %d, got %d", len(outTypes), len(results))
}
for i := range out {
if err != nil {
break
}
if !results[i].IsZero() {
if results[i].Type().AssignableTo(outTypes[i]) {
out[i].Elem().Set(results[i])
} else {
err = fmt.Errorf("unexpected type %T for result parameter %T", results[i].Interface(), out[i].Interface())
}
}
}
if err != nil {
registry[key].Error(err)
t2 := outTypes[last]
if reflect.TypeOf(err).ConvertibleTo(t2) {
out[last].Elem().Set(reflect.ValueOf(err).Convert(t2))
} else {
panic(err)
}
}
}