@@ -16,132 +16,171 @@ package lock
1616
1717import (
1818 "errors"
19- "reflect"
2019 "testing"
2120
22- "go.etcd.io/etcd/client"
21+ pb "go.etcd.io/etcd/api/v3/mvccpb"
22+ client "go.etcd.io/etcd/client/v3"
23+
24+ "github.com/stretchr/testify/assert"
25+ "github.com/stretchr/testify/require"
2326 "golang.org/x/net/context"
2427)
2528
29+ // testEtcdClient is the struct used to mock the `etcd`
30+ // client
2631type testEtcdClient struct {
27- err error
28- resp * client.Response
32+ err error
33+ putResp * client.PutResponse
34+ getResp * client.GetResponse
2935}
3036
31- func (t * testEtcdClient ) Get (ctx context.Context , key string , opts * client.GetOptions ) (* client.Response , error ) {
32- return t .resp , t .err
33- }
37+ func (t * testEtcdClient ) Put (ctx context.Context , key , val string , opts ... client.OpOption ) (* client.PutResponse , error ) {
38+ return t .putResp , t .err
3439
35- func (t * testEtcdClient ) Set (ctx context.Context , key , value string , opts * client.SetOptions ) (* client.Response , error ) {
36- return t .resp , t .err
3740}
3841
39- func (t * testEtcdClient ) Create (ctx context.Context , key , value string ) (* client.Response , error ) {
40- return t .resp , t .err
42+ func (t * testEtcdClient ) Get (ctx context.Context , key string , opts ... client.OpOption ) (* client.GetResponse , error ) {
43+ return t .getResp , t .err
44+
4145}
4246
4347func TestEtcdLockClientInit (t * testing.T ) {
44- for i , tt := range []struct {
45- ee error
46- want bool
47- group string
48- keypath string
49- }{
50- {nil , false , "" , SemaphorePrefix },
51- {client.Error {Code : client .ErrorCodeNodeExist }, false , "" , SemaphorePrefix },
52- {client.Error {Code : client .ErrorCodeKeyNotFound }, true , "" , SemaphorePrefix },
53- {errors .New ("some random error" ), true , "" , SemaphorePrefix },
54- {client.Error {Code : client .ErrorCodeKeyNotFound }, true , "database" , "coreos.com/updateengine/rebootlock/groups/database/semaphore" },
55- {nil , false , "prod/database" , "coreos.com/updateengine/rebootlock/groups/prod%2Fdatabase/semaphore" },
56- } {
57- elc , got := NewEtcdLockClient (& testEtcdClient {err : tt .ee }, tt .group )
58- if (got != nil ) != tt .want {
59- t .Errorf ("case %d: unexpected error state initializing Client: got %v" , i , got )
60- continue
61- }
62-
63- if got != nil {
64- continue
65- }
66-
67- if elc .keypath != tt .keypath {
68- t .Errorf ("case %d: unexpected etcd key path: got %v want %v" , i , elc .keypath , tt .keypath )
69- }
70- }
71- }
48+ t .Run ("Success" , func (t * testing.T ) {
49+ elc , err := NewEtcdLockClient (& testEtcdClient {
50+ err : nil ,
51+ getResp : & client.GetResponse {Count : 0 },
52+ putResp : & client.PutResponse {},
53+ },
54+ "" ,
55+ )
56+ require .Nil (t , err )
7257
73- func makeResponse (idx int , val string ) * client.Response {
74- return & client.Response {
75- Node : & client.Node {
76- Value : val ,
77- ModifiedIndex : uint64 (idx ),
58+ assert .NotNil (t , elc )
59+ })
60+ t .Run ("SuccessWithExistingValue" , func (t * testing.T ) {
61+ elc , err := NewEtcdLockClient (& testEtcdClient {
62+ err : nil ,
63+ getResp : & client.GetResponse {
64+ Count : 1 ,
65+ Kvs : []* pb.KeyValue {
66+ & pb.KeyValue {
67+ Key : []byte (SemaphorePrefix ),
68+ Value : []byte (`{"semaphore": 1, "max": 2, "holders": ["foo", "bar"]}` ),
69+ },
70+ },
71+ },
72+ putResp : & client.PutResponse {},
7873 },
79- }
74+ "" ,
75+ )
76+ require .Nil (t , err )
77+
78+ assert .NotNil (t , elc )
79+ })
80+ t .Run ("Error" , func (t * testing.T ) {
81+ elc , err := NewEtcdLockClient (& testEtcdClient {
82+ err : errors .New ("connection refused" ),
83+ getResp : & client.GetResponse {Count : 0 },
84+ putResp : & client.PutResponse {},
85+ },
86+ "" ,
87+ )
88+ require .Nil (t , elc )
89+
90+ assert .Equal (t , "unable to init etcd lock client: unable to get semaphore: connection refused" , err .Error ())
91+ })
8092}
8193
8294func TestEtcdLockClientGet (t * testing.T ) {
83- for i , tt := range []struct {
84- ee error
85- er * client.Response
86- ws * Semaphore
87- we bool
88- }{
89- // errors returned from etcd
90- {errors .New ("some error" ), nil , nil , true },
91- {client.Error {Code : client .ErrorCodeKeyNotFound }, nil , nil , true },
92- // bad JSON should cause errors
93- {nil , makeResponse (0 , "asdf" ), nil , true },
94- {nil , makeResponse (0 , `{"semaphore:` ), nil , true },
95- // successful calls
96- {nil , makeResponse (10 , `{"semaphore": 1}` ), & Semaphore {Index : 10 , Semaphore : 1 }, false },
97- {nil , makeResponse (1024 , `{"semaphore": 1, "max": 2, "holders": ["foo", "bar"]}` ), & Semaphore {Index : 1024 , Semaphore : 1 , Max : 2 , Holders : []string {"foo" , "bar" }}, false },
98- // index should be set from etcd, not json!
99- {nil , makeResponse (1234 , `{"semaphore": 89, "index": 4567}` ), & Semaphore {Index : 1234 , Semaphore : 89 }, false },
100- } {
101- elc := & EtcdLockClient {
102- keyapi : & testEtcdClient {
103- err : tt .ee ,
104- resp : tt .er ,
95+ t .Run ("Success" , func (t * testing.T ) {
96+ elc , err := NewEtcdLockClient (& testEtcdClient {
97+ err : nil ,
98+ putResp : & client.PutResponse {},
99+ getResp : & client.GetResponse {
100+ Count : 1 ,
101+ Kvs : []* pb.KeyValue {
102+ & pb.KeyValue {
103+ Key : []byte (SemaphorePrefix ),
104+ // index should be set from etcd, not json (backported from legacy test)
105+ Value : []byte (`{"index": 12, "semaphore": 1, "max": 2, "holders": ["foo", "bar"]}` ),
106+ Version : 1234 ,
107+ },
108+ },
109+ },
110+ },
111+ "" ,
112+ )
113+ require .Nil (t , err )
114+
115+ res , err := elc .Get ()
116+ require .Nil (t , err )
117+
118+ assert .Equal (t , 1 , res .Semaphore )
119+ assert .Equal (t , uint64 (1234 ), res .Index )
120+ })
121+ t .Run ("SuccessNotFound" , func (t * testing.T ) {
122+ elc , err := NewEtcdLockClient (& testEtcdClient {
123+ err : nil ,
124+ getResp : & client.GetResponse {Count : 0 },
125+ putResp : & client.PutResponse {},
126+ },
127+ "" ,
128+ )
129+ require .Nil (t , err )
130+
131+ res , err := elc .Get ()
132+ require .Nil (t , res )
133+ assert .ErrorIs (t , err , ErrNotFound )
134+ })
135+ t .Run ("ErrorWithMalformedJSON" , func (t * testing.T ) {
136+ elc , err := NewEtcdLockClient (& testEtcdClient {
137+ err : nil ,
138+ getResp : & client.GetResponse {
139+ Count : 1 ,
140+ Kvs : []* pb.KeyValue {
141+ & pb.KeyValue {
142+ Key : []byte (SemaphorePrefix ),
143+ // notice the missing `,` in the array
144+ Value : []byte (`{"semaphore": 1, "max": 2, "holders": ["foo" "bar"]}` ),
145+ },
146+ },
105147 },
106- }
107- gs , ge := elc .Get ()
108- if tt .we {
109- if ge == nil {
110- t .Fatalf ("case %d: expected error but got nil!" , i )
111- }
112- } else {
113- if ge != nil {
114- t .Fatalf ("case %d: unexpected error: %v" , i , ge )
115- }
116- }
117- if ! reflect .DeepEqual (gs , tt .ws ) {
118- t .Fatalf ("case %d: bad semaphore: got %#v, want %#v" , i , gs , tt .ws )
119- }
120- }
148+ putResp : & client.PutResponse {},
149+ },
150+ "" ,
151+ )
152+ require .Nil (t , elc )
153+
154+ assert .Equal (t , "unable to init etcd lock client: unable to get semaphore: invalid character '\" ' after array element" , err .Error ())
155+ })
121156}
122157
123158func TestEtcdLockClientSet (t * testing.T ) {
124- for i , tt := range []struct {
125- sem * Semaphore
126- ee error // error returned from etcd
127- want bool // do we expect Set to return an error
128- }{
129- // nil semaphore cannot be set
130- {nil , nil , true },
131- // empty semaphore is OK
132- {& Semaphore {}, nil , false },
133- {& Semaphore {Index : uint64 (1234 )}, nil , false },
134- // all errors returned from etcd should propagate
135- {& Semaphore {}, client.Error {Code : client .ErrorCodeNodeExist }, true },
136- {& Semaphore {}, client.Error {Code : client .ErrorCodeKeyNotFound }, true },
137- {& Semaphore {}, errors .New ("some random error" ), true },
138- } {
139- elc := & EtcdLockClient {
140- keyapi : & testEtcdClient {err : tt .ee },
141- }
142- got := elc .Set (tt .sem )
143- if (got != nil ) != tt .want {
144- t .Errorf ("case %d: unexpected error state calling Set: got %v" , i , got )
145- }
146- }
159+ t .Run ("Success" , func (t * testing.T ) {
160+ elc , err := NewEtcdLockClient (& testEtcdClient {
161+ err : nil ,
162+ getResp : & client.GetResponse {Count : 0 },
163+ putResp : & client.PutResponse {},
164+ },
165+ "" ,
166+ )
167+ require .Nil (t , err )
168+
169+ err = elc .Set (& Semaphore {})
170+ assert .Nil (t , err )
171+
172+ })
173+ t .Run ("ErrorNilSemaphore" , func (t * testing.T ) {
174+ elc , err := NewEtcdLockClient (& testEtcdClient {
175+ err : nil ,
176+ getResp : & client.GetResponse {Count : 0 },
177+ putResp : & client.PutResponse {},
178+ },
179+ "" ,
180+ )
181+ require .Nil (t , err )
182+
183+ err = elc .Set (nil )
184+ assert .Equal (t , "cannot set nil semaphore" , err .Error ())
185+ })
147186}
0 commit comments