0 | 0 |
package etcd
|
1 | 1 |
|
2 | 2 |
import (
|
|
3 |
"errors"
|
|
4 |
"reflect"
|
3 | 5 |
"testing"
|
4 | 6 |
"time"
|
5 | 7 |
|
|
8 |
etcd "github.com/coreos/etcd/client"
|
6 | 9 |
"golang.org/x/net/context"
|
7 | 10 |
)
|
8 | 11 |
|
|
61 | 64 |
t.Errorf("expected error: %v", err)
|
62 | 65 |
}
|
63 | 66 |
}
|
|
67 |
|
|
68 |
// Mocks of the underlying etcd.KeysAPI interface that is called by the methods we want to test
|
|
69 |
|
|
70 |
// fakeKeysAPI implements etcd.KeysAPI, event and err are channels used to emulate
|
|
71 |
// an etcd event or error, getres will be returned when etcd.KeysAPI.Get is called.
|
|
72 |
type fakeKeysAPI struct {
|
|
73 |
event chan bool
|
|
74 |
err chan bool
|
|
75 |
getres *getResult
|
|
76 |
}
|
|
77 |
|
|
78 |
type getResult struct {
|
|
79 |
resp *etcd.Response
|
|
80 |
err error
|
|
81 |
}
|
|
82 |
|
|
83 |
// Get return the content of getres or nil, nil
|
|
84 |
func (fka *fakeKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
|
|
85 |
if fka.getres == nil {
|
|
86 |
return nil, nil
|
|
87 |
}
|
|
88 |
return fka.getres.resp, fka.getres.err
|
|
89 |
}
|
|
90 |
|
|
91 |
// Set is not used in the tests
|
|
92 |
func (fka *fakeKeysAPI) Set(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) {
|
|
93 |
return nil, nil
|
|
94 |
}
|
|
95 |
|
|
96 |
// Delete is not used in the tests
|
|
97 |
func (fka *fakeKeysAPI) Delete(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error) {
|
|
98 |
return nil, nil
|
|
99 |
}
|
|
100 |
|
|
101 |
// Create is not used in the tests
|
|
102 |
func (fka *fakeKeysAPI) Create(ctx context.Context, key, value string) (*etcd.Response, error) {
|
|
103 |
return nil, nil
|
|
104 |
}
|
|
105 |
|
|
106 |
// CreateInOrder is not used in the tests
|
|
107 |
func (fka *fakeKeysAPI) CreateInOrder(ctx context.Context, dir, value string, opts *etcd.CreateInOrderOptions) (*etcd.Response, error) {
|
|
108 |
return nil, nil
|
|
109 |
}
|
|
110 |
|
|
111 |
// Update is not used in the tests
|
|
112 |
func (fka *fakeKeysAPI) Update(ctx context.Context, key, value string) (*etcd.Response, error) {
|
|
113 |
return nil, nil
|
|
114 |
}
|
|
115 |
|
|
116 |
// Watcher return a fakeWatcher that will forward event and error received on the channels
|
|
117 |
func (fka *fakeKeysAPI) Watcher(key string, opts *etcd.WatcherOptions) etcd.Watcher {
|
|
118 |
return &fakeWatcher{fka.event, fka.err}
|
|
119 |
}
|
|
120 |
|
|
121 |
// fakeWatcher implements etcd.Watcher
|
|
122 |
type fakeWatcher struct {
|
|
123 |
event chan bool
|
|
124 |
err chan bool
|
|
125 |
}
|
|
126 |
|
|
127 |
// Next blocks until an etcd event or error is emulated.
|
|
128 |
// When an event occurs it just return nil response and error.
|
|
129 |
// When an error occur it return a non nil error.
|
|
130 |
func (fw *fakeWatcher) Next(context.Context) (*etcd.Response, error) {
|
|
131 |
for {
|
|
132 |
select {
|
|
133 |
case <-fw.event:
|
|
134 |
return nil, nil
|
|
135 |
case <-fw.err:
|
|
136 |
return nil, errors.New("error from underlying etcd watcher")
|
|
137 |
default:
|
|
138 |
}
|
|
139 |
}
|
|
140 |
}
|
|
141 |
|
|
142 |
// newFakeClient return a new etcd.Client built on top of the mocked interfaces
|
|
143 |
func newFakeClient(event, err chan bool, getres *getResult) Client {
|
|
144 |
return &client{
|
|
145 |
keysAPI: &fakeKeysAPI{event, err, getres},
|
|
146 |
ctx: context.Background(),
|
|
147 |
}
|
|
148 |
}
|
|
149 |
|
|
150 |
// Register should fail when the provided service has an empty key or value
|
|
151 |
func TestRegisterClient(t *testing.T) {
|
|
152 |
client := newFakeClient(nil, nil, nil)
|
|
153 |
|
|
154 |
err := client.Register(Service{Key: "", Value: "value", DeleteOptions: nil})
|
|
155 |
if want, have := ErrNoKey, err; want != have {
|
|
156 |
t.Fatalf("want %v, have %v", want, have)
|
|
157 |
}
|
|
158 |
|
|
159 |
err = client.Register(Service{Key: "key", Value: "", DeleteOptions: nil})
|
|
160 |
if want, have := ErrNoValue, err; want != have {
|
|
161 |
t.Fatalf("want %v, have %v", want, have)
|
|
162 |
}
|
|
163 |
|
|
164 |
err = client.Register(Service{Key: "key", Value: "value", DeleteOptions: nil})
|
|
165 |
if err != nil {
|
|
166 |
t.Fatal(err)
|
|
167 |
}
|
|
168 |
}
|
|
169 |
|
|
170 |
// Deregister should fail if the input service has an empty key
|
|
171 |
func TestDeregisterClient(t *testing.T) {
|
|
172 |
client := newFakeClient(nil, nil, nil)
|
|
173 |
|
|
174 |
err := client.Deregister(Service{Key: "", Value: "value", DeleteOptions: nil})
|
|
175 |
if want, have := ErrNoKey, err; want != have {
|
|
176 |
t.Fatalf("want %v, have %v", want, have)
|
|
177 |
}
|
|
178 |
|
|
179 |
err = client.Deregister(Service{Key: "key", Value: "", DeleteOptions: nil})
|
|
180 |
if err != nil {
|
|
181 |
t.Fatal(err)
|
|
182 |
}
|
|
183 |
}
|
|
184 |
|
|
185 |
// WatchPrefix notify the caller by writing on the channel if an etcd event occurs
|
|
186 |
// or return in case of an underlying error
|
|
187 |
func TestWatchPrefix(t *testing.T) {
|
|
188 |
err := make(chan bool)
|
|
189 |
event := make(chan bool)
|
|
190 |
watchPrefixReturned := make(chan bool, 1)
|
|
191 |
client := newFakeClient(event, err, nil)
|
|
192 |
|
|
193 |
ch := make(chan struct{})
|
|
194 |
go func() {
|
|
195 |
client.WatchPrefix("prefix", ch) // block until an etcd event or error occurs
|
|
196 |
watchPrefixReturned <- true
|
|
197 |
}()
|
|
198 |
|
|
199 |
// WatchPrefix force the caller to read once from the channel before actually
|
|
200 |
// sending notification, emulate that first read.
|
|
201 |
<-ch
|
|
202 |
|
|
203 |
// Emulate an etcd event
|
|
204 |
event <- true
|
|
205 |
if want, have := struct{}{}, <-ch; want != have {
|
|
206 |
t.Fatalf("want %v, have %v", want, have)
|
|
207 |
}
|
|
208 |
|
|
209 |
// Emulate an error, WatchPrefix should return
|
|
210 |
err <- true
|
|
211 |
select {
|
|
212 |
case <-watchPrefixReturned:
|
|
213 |
break
|
|
214 |
case <-time.After(1 * time.Second):
|
|
215 |
t.Fatal("WatchPrefix not returning on errors")
|
|
216 |
}
|
|
217 |
}
|
|
218 |
|
|
219 |
var errKeyAPI = errors.New("emulate error returned by KeysAPI.Get")
|
|
220 |
|
|
221 |
// table of test cases for method GetEntries
|
|
222 |
var getEntriesTestTable = []struct {
|
|
223 |
input getResult // value returned by the underlying etcd.KeysAPI.Get
|
|
224 |
resp []string // response expected in output of GetEntries
|
|
225 |
err error //error expected in output of GetEntries
|
|
226 |
|
|
227 |
}{
|
|
228 |
// test case: an error is returned by etcd.KeysAPI.Get
|
|
229 |
{getResult{nil, errKeyAPI}, nil, errKeyAPI},
|
|
230 |
// test case: return a single leaf node, with an empty value
|
|
231 |
{getResult{&etcd.Response{
|
|
232 |
Action: "get",
|
|
233 |
Node: &etcd.Node{
|
|
234 |
Key: "nodekey",
|
|
235 |
Dir: false,
|
|
236 |
Value: "",
|
|
237 |
Nodes: nil,
|
|
238 |
CreatedIndex: 0,
|
|
239 |
ModifiedIndex: 0,
|
|
240 |
Expiration: nil,
|
|
241 |
TTL: 0,
|
|
242 |
},
|
|
243 |
PrevNode: nil,
|
|
244 |
Index: 0,
|
|
245 |
}, nil}, []string{}, nil},
|
|
246 |
// test case: return a single leaf node, with a value
|
|
247 |
{getResult{&etcd.Response{
|
|
248 |
Action: "get",
|
|
249 |
Node: &etcd.Node{
|
|
250 |
Key: "nodekey",
|
|
251 |
Dir: false,
|
|
252 |
Value: "nodevalue",
|
|
253 |
Nodes: nil,
|
|
254 |
CreatedIndex: 0,
|
|
255 |
ModifiedIndex: 0,
|
|
256 |
Expiration: nil,
|
|
257 |
TTL: 0,
|
|
258 |
},
|
|
259 |
PrevNode: nil,
|
|
260 |
Index: 0,
|
|
261 |
}, nil}, []string{"nodevalue"}, nil},
|
|
262 |
// test case: return a node with two childs
|
|
263 |
{getResult{&etcd.Response{
|
|
264 |
Action: "get",
|
|
265 |
Node: &etcd.Node{
|
|
266 |
Key: "nodekey",
|
|
267 |
Dir: true,
|
|
268 |
Value: "nodevalue",
|
|
269 |
Nodes: []*etcd.Node{
|
|
270 |
&etcd.Node{
|
|
271 |
Key: "childnode1",
|
|
272 |
Dir: false,
|
|
273 |
Value: "childvalue1",
|
|
274 |
Nodes: nil,
|
|
275 |
CreatedIndex: 0,
|
|
276 |
ModifiedIndex: 0,
|
|
277 |
Expiration: nil,
|
|
278 |
TTL: 0,
|
|
279 |
},
|
|
280 |
&etcd.Node{
|
|
281 |
Key: "childnode2",
|
|
282 |
Dir: false,
|
|
283 |
Value: "childvalue2",
|
|
284 |
Nodes: nil,
|
|
285 |
CreatedIndex: 0,
|
|
286 |
ModifiedIndex: 0,
|
|
287 |
Expiration: nil,
|
|
288 |
TTL: 0,
|
|
289 |
},
|
|
290 |
},
|
|
291 |
CreatedIndex: 0,
|
|
292 |
ModifiedIndex: 0,
|
|
293 |
Expiration: nil,
|
|
294 |
TTL: 0,
|
|
295 |
},
|
|
296 |
PrevNode: nil,
|
|
297 |
Index: 0,
|
|
298 |
}, nil}, []string{"childvalue1", "childvalue2"}, nil},
|
|
299 |
}
|
|
300 |
|
|
301 |
func TestGetEntries(t *testing.T) {
|
|
302 |
for _, et := range getEntriesTestTable {
|
|
303 |
client := newFakeClient(nil, nil, &et.input)
|
|
304 |
resp, err := client.GetEntries("prefix")
|
|
305 |
if want, have := et.resp, resp; !reflect.DeepEqual(want, have) {
|
|
306 |
t.Fatalf("want %v, have %v", want, have)
|
|
307 |
}
|
|
308 |
if want, have := et.err, err; want != have {
|
|
309 |
t.Fatalf("want %v, have %v", want, have)
|
|
310 |
}
|
|
311 |
}
|
|
312 |
}
|