Codebase list golang-github-go-kit-kit / 9624407
Add unit tests for etcd Client and registrar jeremie stordeur 7 years ago
2 changed file(s) with 360 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
00 package etcd
11
22 import (
3 "errors"
4 "reflect"
35 "testing"
46 "time"
57
8 etcd "github.com/coreos/etcd/client"
69 "golang.org/x/net/context"
710 )
811
6164 t.Errorf("expected error: %v", err)
6265 }
6366 }
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 }
0 package etcd
1
2 import (
3 "bytes"
4 "errors"
5 "testing"
6
7 "github.com/go-kit/kit/log"
8 )
9
10 // testClient is a basic implementation of Client
11 type testClient struct {
12 registerRes error // value returned when Register or Deregister is called
13 }
14
15 func (tc *testClient) GetEntries(prefix string) ([]string, error) {
16 return nil, nil
17 }
18
19 func (tc *testClient) WatchPrefix(prefix string, ch chan struct{}) {
20 return
21 }
22
23 func (tc *testClient) Register(s Service) error {
24 return tc.registerRes
25 }
26
27 func (tc *testClient) Deregister(s Service) error {
28 return tc.registerRes
29 }
30
31 // default service used to build registrar in our tests
32 var testService = Service{"testKey", "testValue", nil}
33
34 // NewRegistar should return a registar with a logger using the service key and value
35 func TestNewRegistar(t *testing.T) {
36 c := Client(&testClient{nil})
37 buf := &bytes.Buffer{}
38 logger := log.NewLogfmtLogger(buf)
39 r := NewRegistrar(
40 c,
41 testService,
42 logger,
43 )
44
45 if err := r.logger.Log("msg", "message"); err != nil {
46 t.Fatal(err)
47 }
48 if want, have := "key=testKey value=testValue msg=message\n", buf.String(); want != have {
49 t.Errorf("\nwant: %shave: %s", want, have)
50 }
51 }
52
53 // Register log the error returned by the client or log the successful registration action
54 // table of test cases for method Register
55 var registerTestTable = []struct {
56 registerRes error // value returned by the client on calls to Register
57 log string // expected log by the registrar
58
59 }{
60 // test case: an error is returned by the client
61 {errors.New("regError"), "key=testKey value=testValue err=regError\n"},
62 // test case: registration successful
63 {nil, "key=testKey value=testValue action=register\n"},
64 }
65
66 func TestRegister(t *testing.T) {
67 for _, tc := range registerTestTable {
68 c := Client(&testClient{tc.registerRes})
69 buf := &bytes.Buffer{}
70 logger := log.NewLogfmtLogger(buf)
71 r := NewRegistrar(
72 c,
73 testService,
74 logger,
75 )
76 r.Register()
77 if want, have := tc.log, buf.String(); want != have {
78 t.Fatalf("want %v, have %v", want, have)
79 }
80 }
81 }
82
83 // Deregister log the error returned by the client or log the successful deregistration action
84 // table of test cases for method Deregister
85 var deregisterTestTable = []struct {
86 deregisterRes error // value returned by the client on calls to Deregister
87 log string // expected log by the registrar
88 }{
89 // test case: an error is returned by the client
90 {errors.New("deregError"), "key=testKey value=testValue err=deregError\n"},
91 // test case: deregistration successful
92 {nil, "key=testKey value=testValue action=deregister\n"},
93 }
94
95 func TestDeregister(t *testing.T) {
96 for _, tc := range deregisterTestTable {
97 c := Client(&testClient{tc.deregisterRes})
98 buf := &bytes.Buffer{}
99 logger := log.NewLogfmtLogger(buf)
100 r := NewRegistrar(
101 c,
102 testService,
103 logger,
104 )
105 r.Deregister()
106 if want, have := tc.log, buf.String(); want != have {
107 t.Fatalf("want %v, have %v", want, have)
108 }
109 }
110 }