1// Copyright 2016-2020 The Libsacloud Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package api
16
17import (
18	"fmt"
19	"testing"
20	"time"
21
22	"github.com/stretchr/testify/assert"
23)
24
25func TestPolling(t *testing.T) {
26
27	t.Run("Normal: Should be no error", func(t *testing.T) {
28		f := func() (bool, interface{}, error) {
29			return true, "", nil
30		}
31
32		c, p, err := poll(f, 10*time.Second)
33		for {
34			select {
35			case <-c:
36				return
37			case <-p:
38				// noop
39			case <-err:
40				t.Fatal("Invalid chan state: err chan received")
41			}
42		}
43
44	})
45
46	t.Run("Use progress: Should be no error", func(t *testing.T) {
47		counter := 0
48		expect := 2
49
50		f := func() (bool, interface{}, error) {
51			if counter == expect {
52				return true, "", nil
53			}
54			return false, "", nil
55		}
56
57		c, p, err := poll(f, 5*3*time.Second)
58		for {
59			select {
60			case <-c:
61				return
62			case <-p:
63				counter++
64			case <-err:
65				t.Fatal("Invalid chan state: err chan received")
66			}
67		}
68	})
69
70	t.Run("Timeout: Should return error", func(t *testing.T) {
71		counter := 0
72		f := func() (bool, interface{}, error) {
73			return false, "", nil
74		}
75
76		c, p, err := poll(f, 5*time.Second)
77		for {
78			select {
79			case <-c:
80				t.Fatal("Invalid chan state: complete chan received")
81			case <-p:
82				counter++
83			case <-err:
84				assert.Equal(t, 1, counter)
85				return
86			}
87		}
88	})
89
90	t.Run("Handler raise error: Should return error", func(t *testing.T) {
91		f := func() (bool, interface{}, error) {
92			return false, nil, fmt.Errorf("test")
93		}
94
95		c, p, err := poll(f, 5*time.Second)
96		for {
97			select {
98			case <-c:
99				t.Fatal("Invalid chan state: complete chan received")
100			case <-p:
101				t.Fatal("Invalid chan state: complete chan received")
102			case <-err:
103				return
104			}
105		}
106	})
107}
108
109func TestBlockingPoll(t *testing.T) {
110
111	t.Run("Normal: should be no error", func(t *testing.T) {
112		f := func() (bool, interface{}, error) {
113			return true, "", nil
114		}
115		done := false
116		go func() {
117			time.AfterFunc(10*time.Second, func() {
118				if done {
119					return
120				}
121				t.Fatal("Invalid timeout")
122			})
123		}()
124		err := blockingPoll(f, 5*time.Second)
125		assert.NoError(t, err)
126		done = true
127	})
128
129	t.Run("Timeout: should return error", func(t *testing.T) {
130		f := func() (bool, interface{}, error) {
131			time.Sleep(1 * time.Minute)
132			return true, "", nil
133		}
134		done := false
135		go func() {
136			time.AfterFunc(10*time.Second, func() {
137				if done {
138					return
139				}
140				t.Fatal("Invalid timeout")
141			})
142		}()
143		err := blockingPoll(f, 1*time.Second)
144		assert.Error(t, err)
145		done = true
146	})
147}
148
149type mockHasAvailableAndFailed struct {
150	available bool
151	failed    bool
152}
153
154func (m *mockHasAvailableAndFailed) IsAvailable() bool {
155	return m.available
156}
157func (m *mockHasAvailableAndFailed) IsFailed() bool {
158	return m.failed
159}
160
161func TestWaitingForAvailableFunc(t *testing.T) {
162
163	t.Run("No retry: should no error", func(t *testing.T) {
164		readFunc := func() (hasAvailable, error) {
165			v := &mockHasAvailableAndFailed{
166				available: true,
167				failed:    false,
168			}
169			return v, nil
170		}
171		maxRetry := 1
172
173		f := waitingForAvailableFunc(readFunc, maxRetry)
174		err := blockingPoll(f, 5*time.Second)
175
176		assert.NoError(t, err)
177
178	})
179
180	t.Run("Ignore error while maxRetry", func(t *testing.T) {
181		counter := 0
182		maxRetry := 2
183		readFunc := func() (hasAvailable, error) {
184			counter++
185			if counter < maxRetry {
186				return nil, fmt.Errorf("dummy readFunc error")
187			}
188			return &mockHasAvailableAndFailed{available: true}, nil
189		}
190
191		f := waitingForAvailableFunc(readFunc, maxRetry)
192		err := blockingPoll(f, 1*time.Minute)
193
194		assert.NoError(t, err)
195	})
196
197	t.Run("Raise error when counter become over maxRetry", func(t *testing.T) {
198		counter := 0
199		maxRetry := 2
200		readFunc := func() (hasAvailable, error) {
201			counter++
202			if counter == 1 {
203				return &mockHasAvailableAndFailed{available: false}, nil
204			}
205			return nil, fmt.Errorf("dummy readFunc error")
206		}
207
208		f := waitingForAvailableFunc(readFunc, maxRetry)
209		err := blockingPoll(f, 1*time.Minute)
210
211		assert.Error(t, err)
212		assert.Equal(t, maxRetry, counter-1) // 一回正常値を返した分を引く
213	})
214
215	t.Run("Raise error when instance is failed", func(t *testing.T) {
216		counter := 0
217		maxRetry := 2
218		readFunc := func() (hasAvailable, error) {
219			counter++
220			return &mockHasAvailableAndFailed{failed: true}, nil
221		}
222
223		f := waitingForAvailableFunc(readFunc, maxRetry)
224		err := blockingPoll(f, 1*time.Minute)
225
226		assert.Error(t, err)
227		assert.True(t, counter < maxRetry)
228	})
229}
230