-
Notifications
You must be signed in to change notification settings - Fork 4
/
impl_test.go
334 lines (256 loc) · 7.38 KB
/
impl_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
/*
* Copyright (c) 2019-present unTill Pro, Ltd. and Contributors
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package services
import (
"context"
"errors"
"fmt"
"log"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/untillpro/godif"
)
var lastCtx context.Context
func TestBasicUsage(t *testing.T) {
/*
Using Run()
- Provide few services using `godif`
- Invoke Run()
Note that
- Run waits till either Terminate() invoked somewhere or SIGTERM received
*/
// For testing purposes
var wg sync.WaitGroup
wg.Add(2)
// Declare two services
s1 := &MyService{Name: "Service1", Wg: &wg}
s2 := &MyService{Name: "Service2", Wg: &wg}
godif.ProvideSliceElement(&Services, s1)
godif.ProvideSliceElement(&Services, s2)
// Call Terminate() when all services started
go func() {
wg.Wait()
Terminate()
}()
// Run starts all services and waits for Terminate() or SIGTERM
err := Run()
require.Nil(t, err, err)
// No started services
assert.Equal(t, 0, len(started))
}
func TestUsageResolveAndStart(t *testing.T) {
/*
Using ResolveAndStart/StopAndReset
- Provide few services using `godif`
- Invoke ResolveAndStart()
- Finally invoke StopAndReset()
*/
// Register services
s1 := &MyService{Name: "Service1"}
s2 := &MyService{Name: "Service2"}
godif.ProvideSliceElement(&Services, s1)
godif.ProvideSliceElement(&Services, s2)
// Resolve and start services
ctx, err := ResolveAndStart()
// StopAndReset can be invokes many times
defer StopAndReset(ctx)
require.Nil(t, err)
// Check service state
assert.Equal(t, 1, s1.State)
assert.Equal(t, 1, s2.State)
//Make sure that value provided by service exist in ctx
assert.Equal(t, 0, lastCtx.Value(ctxKeyType("Service1")).(int))
assert.Equal(t, 1000, lastCtx.Value(ctxKeyType("Service2")).(int))
assert.Nil(t, lastCtx.Value(ctxKeyType("Service3")))
// Stop services and reset godif
StopAndReset(ctx)
assert.Equal(t, 0, s1.State)
assert.Equal(t, 0, s2.State)
}
func TestUsageVerbose(t *testing.T) {
/*
Using SetVerbose
- By default Services are started/stopped using verbose output using log.Println
- SetVerbose(false) changes this behavour
*/
oldVerbose := SetVerbose(false)
defer SetVerbose(oldVerbose)
s1 := &MyService{Name: "Service1"}
s2 := &MyService{Name: "Service2"}
godif.ProvideSliceElement(&Services, s1)
godif.ProvideSliceElement(&Services, s2)
ctx, _ := ResolveAndStart()
defer StopAndReset(ctx)
}
func TestRunFailedStart(t *testing.T) {
var wg sync.WaitGroup
wg.Add(2)
s1 := &MyService{Name: "Service1", Wg: &wg}
s2 := &MyService{Name: "Service2", Failstart: true, Wg: &wg}
s3 := &MyService{Name: "Service3"}
godif.ProvideSliceElement(&Services, s1)
godif.ProvideSliceElement(&Services, s2)
godif.ProvideSliceElement(&Services, s3)
go func() {
wg.Wait()
Terminate()
}()
err := Run()
assert.NotNil(t, err, err)
assert.Equal(t, 0, s1.State)
assert.Equal(t, true, s1.StartInvoked)
assert.Equal(t, 0, s2.State)
assert.Equal(t, true, s2.StartInvoked)
assert.Equal(t, 0, s3.State)
assert.Equal(t, false, s3.StartInvoked)
}
func TestFailedStart(t *testing.T) {
s1 := &MyService{Name: "Service1"}
s2 := &MyService{Name: "Service2", Failstart: true}
godif.ProvideSliceElement(&Services, s1)
godif.ProvideSliceElement(&Services, s2)
Declare()
// Resolve all
errs := godif.ResolveAll()
defer godif.Reset()
assert.Nil(t, errs)
fmt.Println("errs=", errs)
// Start services
var err error
fmt.Println("### Before Start")
ctx := context.Background()
ctx, err = StartServices(ctx)
defer StopServices(ctx)
fmt.Println("### After Start")
assert.NotNil(t, err)
fmt.Println("err=", err)
assert.True(t, strings.Contains(err.Error(), "Service2"))
assert.False(t, strings.Contains(err.Error(), "Service1"))
assert.Equal(t, 1, s1.State)
assert.Equal(t, 0, s2.State)
}
func TestContextStartStopOrder(t *testing.T) {
ctxKey := ctxKeyType("root")
initialCtx := context.WithValue(context.Background(), ctxKey, "rootValue")
var services []IService
for i := 0; i < 100; i++ {
s := MyService{Name: fmt.Sprint("Service", i)}
services = append(services, &s)
}
finalCtx, startedServices, err := Start(initialCtx, services, false)
defer Stop(finalCtx, startedServices, false)
require.Equal(t, len(services), len(startedServices))
require.Nil(t, err)
// Check that initial context is kept
require.Equal(t, "rootValue", finalCtx.Value(ctxKeyType("root")))
// Check that services contexts are kept
for idx := range startedServices {
require.Equal(t, idx*1000, finalCtx.Value(ctxKeyType(fmt.Sprint("Service", idx))))
}
}
func TestStartStopOrder(t *testing.T) {
var services []*MyService
runningServices = 0
prevVerbose := SetVerbose(false)
defer SetVerbose(prevVerbose)
for i := 0; i < 100; i++ {
s := &MyService{Name: fmt.Sprint("Service", i)}
services = append(services, s)
godif.ProvideSliceElement(&Services, s)
}
// Resolve and start services
ctx, err := ResolveAndStart()
defer StopAndReset(ctx)
require.Nil(t, err)
for i, s := range services {
assert.Equal(t, i, s.runningServiceNumber)
}
StopServices(ctx)
for _, s := range services {
assert.Equal(t, 0, s.runningServiceNumber)
}
}
func TestPanicedStart(t *testing.T) {
prevVerbose := SetVerbose(true)
defer SetVerbose(prevVerbose)
var wg sync.WaitGroup
wg.Add(2)
s1 := &MyService{Name: "Service1", Wg: &wg}
s2 := &MyService{Name: "Service2", PanicData: "somethingwrong", Wg: &wg}
s3 := &MyService{Name: "Service3"}
godif.ProvideSliceElement(&Services, s1)
godif.ProvideSliceElement(&Services, s2)
godif.ProvideSliceElement(&Services, s3)
go func() {
wg.Wait()
Terminate()
}()
err := Run()
assert.NotNil(t, err, err)
p, ok := err.(*EPanic)
log.Println("Panic: ", p)
assert.True(t, ok, "err is not EPanic", err)
assert.Equal(t, "somethingwrong", p.PanicData)
assert.Equal(t, "Service2", p.PanicedService.(*MyService).Name)
assert.Equal(t, 0, s1.State)
assert.Equal(t, true, s1.StartInvoked)
assert.Equal(t, 0, s2.State)
assert.Equal(t, true, s2.StartInvoked)
assert.Equal(t, 0, s3.State)
assert.Equal(t, false, s3.StartInvoked)
}
// Start s.e.
func (s *MyService) Start(ctx context.Context) (context.Context, error) {
s.StartInvoked = true
if s.Failstart {
fmt.Println(s.Name, "Start fails")
return ctx, errors.New(s.Name + ":" + "Start fails")
}
if s.PanicData != nil {
panic(s.PanicData)
}
s.State++
s.runningServiceNumber = runningServices
runningServices++
ctx = context.WithValue(ctx, ctxKeyType(s.Name), s.runningServiceNumber*1000)
if nil != s.Wg {
s.Wg.Done()
}
lastCtx = ctx
return ctx, nil
}
// Stop s.e.
func (s *MyService) Stop(ctx context.Context) {
s.State--
runningServices--
s.runningServiceNumber -= runningServices
}
func (s *MyService) String() string {
return "I'm service " + s.Name
}
var Missed func()
func Test_FailedResolve(t *testing.T) {
godif.Require(&Missed)
err := Run()
require.NotNil(t, err, err)
}
var runningServices = 0
// MyService for testing purposes
type MyService struct {
Name string
State int // 0 (stopped), 1 (started)
Failstart bool
PanicData interface{}
CtxValue interface{}
Wg *sync.WaitGroup
runningServiceNumber int // assgined from runningServices
StartInvoked bool
}
type ctxKeyType string