github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/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  }