github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/lease/util_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lease_test 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/core/lease" 16 coretesting "github.com/juju/juju/testing" 17 ) 18 19 // Secretary implements lease.Secretary for testing purposes. 20 type Secretary struct{} 21 22 // CheckLease is part of the lease.Secretary interface. 23 func (Secretary) CheckLease(key lease.Key) error { 24 return checkName(key.Lease) 25 } 26 27 // CheckHolder is part of the lease.Secretary interface. 28 func (Secretary) CheckHolder(name string) error { 29 return checkName(name) 30 } 31 32 func checkName(name string) error { 33 if name == "INVALID" { 34 return errors.NotValidf("name") 35 } 36 return nil 37 } 38 39 // CheckDuration is part of the lease.Secretary interface. 40 func (Secretary) CheckDuration(duration time.Duration) error { 41 if duration != time.Minute { 42 return errors.NotValidf("time") 43 } 44 return nil 45 } 46 47 // Store implements corelease.Store for testing purposes. 48 type Store struct { 49 mu sync.Mutex 50 autoexpire bool 51 leases map[lease.Key]lease.Info 52 expect []call 53 failed chan error 54 runningCalls int 55 done chan struct{} 56 } 57 58 // NewStore initializes and returns a new store configured to report 59 // the supplied leases and expect the supplied calls. 60 func NewStore(autoexpire bool, leases map[lease.Key]lease.Info, expect []call) *Store { 61 if leases == nil { 62 leases = make(map[lease.Key]lease.Info) 63 } 64 done := make(chan struct{}) 65 if len(expect) == 0 { 66 close(done) 67 } 68 return &Store{ 69 leases: leases, 70 expect: expect, 71 done: done, 72 failed: make(chan error, 1000), 73 autoexpire: autoexpire, 74 } 75 } 76 77 // Wait will return when all expected calls have been made, or fail the test 78 // if they don't happen within a second. (You control the clock; your tests 79 // should pass in *way* less than 10 seconds of wall-clock time.) 80 func (store *Store) Wait(c *gc.C) { 81 select { 82 case <-store.done: 83 select { 84 case err := <-store.failed: 85 c.Errorf(err.Error()) 86 default: 87 } 88 case <-time.After(coretesting.LongWait): 89 store.mu.Lock() 90 remaining := make([]string, len(store.expect)) 91 for i := range store.expect { 92 remaining[i] = store.expect[i].method 93 } 94 store.mu.Unlock() 95 c.Errorf("Store test took way too long, still expecting %v", remaining) 96 } 97 } 98 99 // Autoexpire is part of the lease.Store interface. 100 func (store *Store) Autoexpire() bool { 101 return store.autoexpire 102 } 103 104 // Leases is part of the lease.Store interface. 105 func (store *Store) Leases(keys ...lease.Key) map[lease.Key]lease.Info { 106 filter := make(map[lease.Key]bool) 107 filtering := len(keys) > 0 108 if filtering { 109 for _, key := range keys { 110 filter[key] = true 111 } 112 } 113 114 store.mu.Lock() 115 defer store.mu.Unlock() 116 result := make(map[lease.Key]lease.Info) 117 for k, v := range store.leases { 118 if filtering && !filter[k] { 119 continue 120 } 121 result[k] = v 122 } 123 return result 124 } 125 126 func (store *Store) closeIfEmpty() { 127 // This must be called with the lock held. 128 if store.runningCalls > 1 { 129 // The last one to leave should turn out the lights. 130 return 131 } 132 if len(store.expect) == 0 || len(store.failed) > 0 { 133 close(store.done) 134 } 135 } 136 137 // call implements the bulk of the lease.Store interface. 138 func (store *Store) call(method string, args []interface{}) error { 139 store.mu.Lock() 140 defer store.mu.Unlock() 141 142 store.runningCalls++ 143 defer func() { 144 store.runningCalls-- 145 }() 146 147 select { 148 case <-store.done: 149 err := errors.Errorf("Store method called after test complete: %s %v", method, args) 150 store.failed <- err 151 return err 152 default: 153 } 154 defer store.closeIfEmpty() 155 156 if len(store.expect) < 1 { 157 err := errors.Errorf("store.%s called but was not expected", method) 158 store.failed <- err 159 return err 160 } 161 expect := store.expect[0] 162 store.expect = store.expect[1:] 163 if expect.parallelCallback != nil { 164 store.mu.Unlock() 165 expect.parallelCallback(&store.mu, store.leases) 166 store.mu.Lock() 167 } 168 if expect.callback != nil { 169 expect.callback(store.leases) 170 } 171 172 if method == expect.method { 173 if ok, _ := jc.DeepEqual(args, expect.args); ok { 174 return expect.err 175 } 176 } 177 err := errors.Errorf("unexpected Store call:\n actual: %s %v\n expect: %s %v", 178 method, args, expect.method, expect.args, 179 ) 180 store.failed <- err 181 return err 182 } 183 184 // ClaimLease is part of the corelease.Store interface. 185 func (store *Store) ClaimLease(key lease.Key, request lease.Request) error { 186 return store.call("ClaimLease", []interface{}{key, request}) 187 } 188 189 // ExtendLease is part of the corelease.Store interface. 190 func (store *Store) ExtendLease(key lease.Key, request lease.Request) error { 191 return store.call("ExtendLease", []interface{}{key, request}) 192 } 193 194 // ExpireLease is part of the corelease.Store interface. 195 func (store *Store) ExpireLease(key lease.Key) error { 196 return store.call("ExpireLease", []interface{}{key}) 197 } 198 199 // Refresh is part of the lease.Store interface. 200 func (store *Store) Refresh() error { 201 return store.call("Refresh", nil) 202 } 203 204 // PinLease is part of the corelease.Store interface. 205 func (store *Store) PinLease(key lease.Key, entity string) error { 206 return store.call("PinLease", []interface{}{key, entity}) 207 } 208 209 // UnpinLease is part of the corelease.Store interface. 210 func (store *Store) UnpinLease(key lease.Key, entity string) error { 211 return store.call("UnpinLease", []interface{}{key, entity}) 212 } 213 214 func (store *Store) Pinned() map[lease.Key][]string { 215 store.call("Pinned", nil) 216 return map[lease.Key][]string{ 217 { 218 Namespace: "namespace", 219 ModelUUID: "modelUUID", 220 Lease: "redis", 221 }: {names.NewMachineTag("0").String()}, 222 { 223 Namespace: "ignored-namespace", 224 ModelUUID: "ignored modelUUID", 225 Lease: "lolwut", 226 }: {names.NewMachineTag("666").String()}, 227 } 228 } 229 230 // call defines a expected method call on a Store; it encodes: 231 type call struct { 232 233 // method is the name of the method. 234 method string 235 236 // args is the expected arguments. 237 args []interface{} 238 239 // err is the error to return. 240 err error 241 242 // callback, if non-nil, will be passed the internal leases dict; for 243 // modification, if desired. Otherwise you can use it to, e.g., assert 244 // clock time. 245 callback func(leases map[lease.Key]lease.Info) 246 247 // parallelCallback is like callback, but is also passed the 248 // lock. It's for testing calls that happen in parallel, where one 249 // might take longer than another. Any update to the leases dict 250 // must only happen while the lock is held. 251 parallelCallback func(mu *sync.Mutex, leases map[lease.Key]lease.Info) 252 } 253 254 func key(args ...string) lease.Key { 255 result := lease.Key{ 256 Namespace: "namespace", 257 ModelUUID: "modelUUID", 258 Lease: "lease", 259 } 260 if len(args) == 1 { 261 result.Lease = args[0] 262 } else if len(args) == 3 { 263 result.Namespace = args[0] 264 result.ModelUUID = args[1] 265 result.Lease = args[2] 266 } 267 return result 268 }