github.com/gavinw2006/hashicorp-terraform@v0.11.12-beta1/helper/resource/state_test.go (about) 1 package resource 2 3 import ( 4 "errors" 5 "strings" 6 "sync/atomic" 7 "testing" 8 "time" 9 ) 10 11 func FailedStateRefreshFunc() StateRefreshFunc { 12 return func() (interface{}, string, error) { 13 return nil, "", errors.New("failed") 14 } 15 } 16 17 func TimeoutStateRefreshFunc() StateRefreshFunc { 18 return func() (interface{}, string, error) { 19 time.Sleep(100 * time.Second) 20 return nil, "", errors.New("failed") 21 } 22 } 23 24 func SuccessfulStateRefreshFunc() StateRefreshFunc { 25 return func() (interface{}, string, error) { 26 return struct{}{}, "running", nil 27 } 28 } 29 30 type StateGenerator struct { 31 position int 32 stateSequence []string 33 } 34 35 func (r *StateGenerator) NextState() (int, string, error) { 36 p, v := r.position, "" 37 if len(r.stateSequence)-1 >= p { 38 v = r.stateSequence[p] 39 } else { 40 return -1, "", errors.New("No more states available") 41 } 42 43 r.position += 1 44 45 return p, v, nil 46 } 47 48 func NewStateGenerator(sequence []string) *StateGenerator { 49 r := &StateGenerator{} 50 r.stateSequence = sequence 51 52 return r 53 } 54 55 func InconsistentStateRefreshFunc() StateRefreshFunc { 56 sequence := []string{ 57 "done", "replicating", 58 "done", "done", "done", 59 "replicating", 60 "done", "done", "done", 61 } 62 63 r := NewStateGenerator(sequence) 64 65 return func() (interface{}, string, error) { 66 idx, s, err := r.NextState() 67 if err != nil { 68 return nil, "", err 69 } 70 71 return idx, s, nil 72 } 73 } 74 75 func UnknownPendingStateRefreshFunc() StateRefreshFunc { 76 sequence := []string{ 77 "unknown1", "unknown2", "done", 78 } 79 80 r := NewStateGenerator(sequence) 81 82 return func() (interface{}, string, error) { 83 idx, s, err := r.NextState() 84 if err != nil { 85 return nil, "", err 86 } 87 88 return idx, s, nil 89 } 90 } 91 92 func TestWaitForState_inconsistent_positive(t *testing.T) { 93 conf := &StateChangeConf{ 94 Pending: []string{"replicating"}, 95 Target: []string{"done"}, 96 Refresh: InconsistentStateRefreshFunc(), 97 Timeout: 90 * time.Millisecond, 98 PollInterval: 10 * time.Millisecond, 99 ContinuousTargetOccurence: 3, 100 } 101 102 idx, err := conf.WaitForState() 103 104 if err != nil { 105 t.Fatalf("err: %s", err) 106 } 107 108 if idx != 4 { 109 t.Fatalf("Expected index 4, given %d", idx.(int)) 110 } 111 } 112 113 func TestWaitForState_inconsistent_negative(t *testing.T) { 114 refreshCount := int64(0) 115 f := InconsistentStateRefreshFunc() 116 refresh := func() (interface{}, string, error) { 117 atomic.AddInt64(&refreshCount, 1) 118 return f() 119 } 120 121 conf := &StateChangeConf{ 122 Pending: []string{"replicating"}, 123 Target: []string{"done"}, 124 Refresh: refresh, 125 Timeout: 85 * time.Millisecond, 126 PollInterval: 10 * time.Millisecond, 127 ContinuousTargetOccurence: 4, 128 } 129 130 _, err := conf.WaitForState() 131 132 if err == nil { 133 t.Fatal("Expected timeout error. No error returned.") 134 } 135 136 // we can't guarantee the exact number of refresh calls in the tests by 137 // timing them, but we want to make sure the test at least went through th 138 // required states. 139 if atomic.LoadInt64(&refreshCount) < 6 { 140 t.Fatal("refreshed called too few times") 141 } 142 143 expectedErr := "timeout while waiting for state to become 'done'" 144 if !strings.HasPrefix(err.Error(), expectedErr) { 145 t.Fatalf("error prefix doesn't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) 146 } 147 } 148 149 func TestWaitForState_timeout(t *testing.T) { 150 old := refreshGracePeriod 151 refreshGracePeriod = 5 * time.Millisecond 152 defer func() { 153 refreshGracePeriod = old 154 }() 155 156 conf := &StateChangeConf{ 157 Pending: []string{"pending", "incomplete"}, 158 Target: []string{"running"}, 159 Refresh: TimeoutStateRefreshFunc(), 160 Timeout: 1 * time.Millisecond, 161 } 162 163 obj, err := conf.WaitForState() 164 165 if err == nil { 166 t.Fatal("Expected timeout error. No error returned.") 167 } 168 169 expectedErr := "timeout while waiting for state to become 'running' (timeout: 1ms)" 170 if err.Error() != expectedErr { 171 t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) 172 } 173 174 if obj != nil { 175 t.Fatalf("should not return obj") 176 } 177 } 178 179 // Make sure a timeout actually cancels the refresh goroutine and waits for its 180 // return. 181 func TestWaitForState_cancel(t *testing.T) { 182 // make this refresh func block until we cancel it 183 cancel := make(chan struct{}) 184 refresh := func() (interface{}, string, error) { 185 <-cancel 186 return nil, "pending", nil 187 } 188 conf := &StateChangeConf{ 189 Pending: []string{"pending", "incomplete"}, 190 Target: []string{"running"}, 191 Refresh: refresh, 192 Timeout: 10 * time.Millisecond, 193 PollInterval: 10 * time.Second, 194 } 195 196 var obj interface{} 197 var err error 198 199 waitDone := make(chan struct{}) 200 go func() { 201 defer close(waitDone) 202 obj, err = conf.WaitForState() 203 }() 204 205 // make sure WaitForState is blocked 206 select { 207 case <-waitDone: 208 t.Fatal("WaitForState returned too early") 209 case <-time.After(10 * time.Millisecond): 210 } 211 212 // unlock the refresh function 213 close(cancel) 214 // make sure WaitForState returns 215 select { 216 case <-waitDone: 217 case <-time.After(time.Second): 218 t.Fatal("WaitForState didn't return after refresh finished") 219 } 220 221 if err == nil { 222 t.Fatal("Expected timeout error. No error returned.") 223 } 224 225 expectedErr := "timeout while waiting for state to become 'running'" 226 if !strings.HasPrefix(err.Error(), expectedErr) { 227 t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) 228 } 229 230 if obj != nil { 231 t.Fatalf("should not return obj") 232 } 233 234 } 235 236 func TestWaitForState_success(t *testing.T) { 237 conf := &StateChangeConf{ 238 Pending: []string{"pending", "incomplete"}, 239 Target: []string{"running"}, 240 Refresh: SuccessfulStateRefreshFunc(), 241 Timeout: 200 * time.Second, 242 } 243 244 obj, err := conf.WaitForState() 245 if err != nil { 246 t.Fatalf("err: %s", err) 247 } 248 if obj == nil { 249 t.Fatalf("should return obj") 250 } 251 } 252 253 func TestWaitForState_successUnknownPending(t *testing.T) { 254 conf := &StateChangeConf{ 255 Target: []string{"done"}, 256 Refresh: UnknownPendingStateRefreshFunc(), 257 Timeout: 200 * time.Second, 258 } 259 260 obj, err := conf.WaitForState() 261 if err != nil { 262 t.Fatalf("err: %s", err) 263 } 264 if obj == nil { 265 t.Fatalf("should return obj") 266 } 267 } 268 269 func TestWaitForState_successEmpty(t *testing.T) { 270 conf := &StateChangeConf{ 271 Pending: []string{"pending", "incomplete"}, 272 Target: []string{}, 273 Refresh: func() (interface{}, string, error) { 274 return nil, "", nil 275 }, 276 Timeout: 200 * time.Second, 277 } 278 279 obj, err := conf.WaitForState() 280 if err != nil { 281 t.Fatalf("err: %s", err) 282 } 283 if obj != nil { 284 t.Fatalf("obj should be nil") 285 } 286 } 287 288 func TestWaitForState_failureEmpty(t *testing.T) { 289 conf := &StateChangeConf{ 290 Pending: []string{"pending", "incomplete"}, 291 Target: []string{}, 292 NotFoundChecks: 1, 293 Refresh: func() (interface{}, string, error) { 294 return 42, "pending", nil 295 }, 296 PollInterval: 10 * time.Millisecond, 297 Timeout: 100 * time.Millisecond, 298 } 299 300 _, err := conf.WaitForState() 301 if err == nil { 302 t.Fatal("Expected timeout error. Got none.") 303 } 304 expectedErr := "timeout while waiting for resource to be gone (last state: 'pending', timeout: 100ms)" 305 if err.Error() != expectedErr { 306 t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) 307 } 308 } 309 310 func TestWaitForState_failure(t *testing.T) { 311 conf := &StateChangeConf{ 312 Pending: []string{"pending", "incomplete"}, 313 Target: []string{"running"}, 314 Refresh: FailedStateRefreshFunc(), 315 Timeout: 200 * time.Second, 316 } 317 318 obj, err := conf.WaitForState() 319 if err == nil { 320 t.Fatal("Expected error. No error returned.") 321 } 322 expectedErr := "failed" 323 if err.Error() != expectedErr { 324 t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) 325 } 326 if obj != nil { 327 t.Fatalf("should not return obj") 328 } 329 }