-
Notifications
You must be signed in to change notification settings - Fork 27
/
command.go
180 lines (156 loc) · 4.98 KB
/
command.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
// Copyright 2014 Rafael Dantas Justo. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.
package redigomock
import (
"fmt"
"reflect"
"sync"
)
// response struct that represents single response from `Do` call.
type response struct {
response interface{} // Response to send back when this command/arguments are called
err error // Error to send back when this command/arguments are called
panicVal interface{} // Panic to throw when this command/arguments are called
}
// ResponseHandler dynamic handles the response for the provided arguments.
type ResponseHandler func(args []interface{}) (interface{}, error)
// Cmd stores the registered information about a command to return it later
// when request by a command execution
type Cmd struct {
// name and args must not be mutated after creation.
name string // Name of the command
args []interface{} // Arguments of the command
responses []response // Slice of returned responses
called bool // State for this command called or not
mu sync.Mutex // hold while accessing responses and called
}
// cmdHash stores a unique identifier of the command
type cmdHash string
// equal verify if a command/arguments is related to a registered command
func equal(commandName string, args []interface{}, cmd *Cmd) bool {
if commandName != cmd.name || len(args) != len(cmd.args) {
return false
}
for pos := range cmd.args {
if implementsFuzzy(cmd.args[pos]) && implementsFuzzy(args[pos]) {
if reflect.TypeOf(cmd.args[pos]) != reflect.TypeOf(args[pos]) {
return false
}
} else if implementsFuzzy(cmd.args[pos]) || implementsFuzzy(args[pos]) {
return false
} else {
if !reflect.DeepEqual(cmd.args[pos], args[pos]) {
return false
}
}
}
return true
}
// match check if provided arguments can be matched with any registered
// commands
func match(commandName string, args []interface{}, cmd *Cmd) bool {
if commandName != cmd.name || len(args) != len(cmd.args) {
return false
}
for pos := range cmd.args {
if implementsFuzzy(cmd.args[pos]) {
if !cmd.args[pos].(FuzzyMatcher).Match(args[pos]) {
return false
}
} else if !reflect.DeepEqual(cmd.args[pos], args[pos]) {
return false
}
}
return true
}
// Expect sets a response for this command. Every time a Do or Receive method
// is executed for a registered command this response or error will be
// returned. Expect call returns a pointer to Cmd struct, so you can chain
// Expect calls. Chained responses will be returned on subsequent calls
// matching this commands arguments in FIFO order
func (c *Cmd) Expect(resp interface{}) *Cmd {
c.expect(response{resp, nil, nil})
return c
}
// expect appends the argument to the response-slice, holding the lock
func (c *Cmd) expect(resp response) {
c.mu.Lock()
defer c.mu.Unlock()
c.responses = append(c.responses, resp)
}
// ExpectMap works in the same way of the Expect command, but has a key/value
// input to make it easier to build test environments
func (c *Cmd) ExpectMap(resp map[string]string) *Cmd {
var values []interface{}
for key, value := range resp {
values = append(values, []byte(key))
values = append(values, []byte(value))
}
c.expect(response{values, nil, nil})
return c
}
// ExpectError allows you to force an error when executing a
// command/arguments
func (c *Cmd) ExpectError(err error) *Cmd {
c.expect(response{nil, err, nil})
return c
}
// ExpectPanic allows you to force a panic when executing a
// command/arguments
func (c *Cmd) ExpectPanic(msg interface{}) *Cmd {
c.expect(response{nil, nil, msg})
return c
}
// ExpectSlice makes it easier to expect slice value
// e.g - HMGET command
func (c *Cmd) ExpectSlice(resp ...interface{}) *Cmd {
ifaces := []interface{}{}
ifaces = append(ifaces, resp...)
c.expect(response{ifaces, nil, nil})
return c
}
// ExpectStringSlice makes it easier to expect a slice of strings, plays nicely
// with redigo.Strings
func (c *Cmd) ExpectStringSlice(resp ...string) *Cmd {
ifaces := []interface{}{}
for _, r := range resp {
ifaces = append(ifaces, []byte(r))
}
c.expect(response{ifaces, nil, nil})
return c
}
// Handle registers a function to handle the incoming arguments, generating an
// on-the-fly response.
func (c *Cmd) Handle(fn ResponseHandler) *Cmd {
c.expect(response{fn, nil, nil})
return c
}
// hash generates a unique identifier for the command
func (c *Cmd) hash() cmdHash {
output := c.name
for _, arg := range c.args {
output += fmt.Sprintf("%v", arg)
}
return cmdHash(output)
}
// Called returns true if the command-mock was ever called.
func (c *Cmd) Called() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.called
}
// getResponse marks the command as used, and gets the next response to return.
func (c *Cmd) getResponse() *response {
c.mu.Lock()
defer c.mu.Unlock()
c.called = true
if len(c.responses) == 0 {
return nil
}
resp := c.responses[0]
if len(c.responses) > 1 {
c.responses = c.responses[1:]
}
return &resp
}