github.com/koko1123/flow-go-1@v0.29.6/module/component/run_component_test.go (about) 1 package component_test 2 3 import ( 4 "context" 5 "errors" 6 "math" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 13 "github.com/koko1123/flow-go-1/module/component" 14 "github.com/koko1123/flow-go-1/module/irrecoverable" 15 ) 16 17 var ErrFatal = errors.New("fatal") 18 var ErrCouldNotCreateComponent = errors.New("failed to create component") 19 20 var _ component.Component = (*StartupErroringComponent)(nil) 21 var _ component.Component = (*RunningErroringComponent)(nil) 22 var _ component.Component = (*ShutdownErroringComponent)(nil) 23 var _ component.Component = (*ConcurrentErroringComponent)(nil) 24 25 type StartupErroringComponent struct { 26 ready chan struct{} 27 done chan struct{} 28 } 29 30 func NewStartupErroringComponent() *StartupErroringComponent { 31 return &StartupErroringComponent{ 32 ready: make(chan struct{}), 33 done: make(chan struct{}), 34 } 35 } 36 37 func (c *StartupErroringComponent) Start(ctx irrecoverable.SignalerContext) { 38 go func() { 39 defer close(c.done) 40 41 // throw fatal error during startup 42 ctx.Throw(ErrFatal) 43 44 // close(c.ready) 45 }() 46 } 47 48 func (c *StartupErroringComponent) Ready() <-chan struct{} { 49 return c.ready 50 } 51 52 func (c *StartupErroringComponent) Done() <-chan struct{} { 53 return c.done 54 } 55 56 type RunningErroringComponent struct { 57 ready chan struct{} 58 done chan struct{} 59 duration time.Duration 60 } 61 62 func NewRunningErroringComponent(duration time.Duration) *RunningErroringComponent { 63 return &RunningErroringComponent{ 64 ready: make(chan struct{}), 65 done: make(chan struct{}), 66 duration: duration, 67 } 68 } 69 70 func (c *RunningErroringComponent) Start(ctx irrecoverable.SignalerContext) { 71 go func() { 72 defer close(c.done) 73 close(c.ready) 74 75 // do some work... 76 select { 77 case <-time.After(c.duration): 78 case <-ctx.Done(): 79 return 80 } 81 82 // throw fatal error 83 ctx.Throw(ErrFatal) 84 }() 85 } 86 87 func (c *RunningErroringComponent) Ready() <-chan struct{} { 88 return c.ready 89 } 90 91 func (c *RunningErroringComponent) Done() <-chan struct{} { 92 return c.done 93 } 94 95 type ShutdownErroringComponent struct { 96 ready sync.WaitGroup 97 done sync.WaitGroup 98 started chan struct{} 99 duration time.Duration 100 } 101 102 func NewShutdownErroringComponent(duration time.Duration) *ShutdownErroringComponent { 103 return &ShutdownErroringComponent{ 104 started: make(chan struct{}), 105 duration: duration, 106 } 107 } 108 109 func (c *ShutdownErroringComponent) Start(ctx irrecoverable.SignalerContext) { 110 c.ready.Add(2) 111 c.done.Add(2) 112 113 go func() { 114 c.ready.Done() 115 defer c.done.Done() 116 117 // do some work... 118 select { 119 case <-time.After(c.duration): 120 case <-ctx.Done(): 121 return 122 } 123 124 // encounter fatal error 125 ctx.Throw(ErrFatal) 126 }() 127 128 go func() { 129 c.ready.Done() 130 defer c.done.Done() 131 132 // wait for shutdown signal triggered by fatal error 133 <-ctx.Done() 134 135 // encounter error during shutdown 136 ctx.Throw(ErrFatal) 137 }() 138 139 close(c.started) 140 } 141 142 func (c *ShutdownErroringComponent) Ready() <-chan struct{} { 143 ready := make(chan struct{}) 144 go func() { 145 <-c.started 146 c.ready.Wait() 147 close(ready) 148 }() 149 return ready 150 } 151 152 func (c *ShutdownErroringComponent) Done() <-chan struct{} { 153 done := make(chan struct{}) 154 go func() { 155 <-c.started 156 c.done.Wait() 157 close(done) 158 }() 159 return done 160 } 161 162 type ConcurrentErroringComponent struct { 163 ready sync.WaitGroup 164 done sync.WaitGroup 165 started chan struct{} 166 duration time.Duration 167 } 168 169 func NewConcurrentErroringComponent(duration time.Duration) *ConcurrentErroringComponent { 170 return &ConcurrentErroringComponent{ 171 started: make(chan struct{}), 172 duration: duration, 173 } 174 } 175 176 func (c *ConcurrentErroringComponent) Start(ctx irrecoverable.SignalerContext) { 177 c.ready.Add(2) 178 c.done.Add(2) 179 180 for i := 0; i < 2; i++ { 181 go func() { 182 c.ready.Done() 183 defer c.done.Done() 184 185 // do some work... 186 select { 187 case <-time.After(c.duration): 188 case <-ctx.Done(): 189 return 190 } 191 192 // encounter fatal error 193 ctx.Throw(ErrFatal) 194 }() 195 } 196 197 close(c.started) 198 } 199 200 func (c *ConcurrentErroringComponent) Ready() <-chan struct{} { 201 ready := make(chan struct{}) 202 go func() { 203 <-c.started 204 c.ready.Wait() 205 close(ready) 206 }() 207 return ready 208 } 209 210 func (c *ConcurrentErroringComponent) Done() <-chan struct{} { 211 done := make(chan struct{}) 212 go func() { 213 <-c.started 214 c.done.Wait() 215 close(done) 216 }() 217 return done 218 } 219 220 type NonErroringComponent struct { 221 ready chan struct{} 222 done chan struct{} 223 duration time.Duration 224 } 225 226 func NewNonErroringComponent(duration time.Duration) *NonErroringComponent { 227 return &NonErroringComponent{ 228 ready: make(chan struct{}), 229 done: make(chan struct{}), 230 duration: duration, 231 } 232 } 233 234 func (c *NonErroringComponent) Start(ctx irrecoverable.SignalerContext) { 235 go func() { 236 defer close(c.done) 237 238 // do some work... 239 select { 240 case <-time.After(c.duration): 241 case <-ctx.Done(): 242 return 243 } 244 245 // exit gracefully 246 }() 247 } 248 249 func (c *NonErroringComponent) Ready() <-chan struct{} { 250 return c.ready 251 } 252 253 func (c *NonErroringComponent) Done() <-chan struct{} { 254 return c.done 255 } 256 257 // tests that after starting a component that generates, an expected error is received 258 func TestRunComponentStartupError(t *testing.T) { 259 componentFactory := func() (component.Component, error) { 260 return NewStartupErroringComponent(), nil 261 } 262 263 called := false 264 onError := func(err error) component.ErrorHandlingResult { 265 called = true 266 require.ErrorIs(t, err, ErrFatal) //check that really got the fatal error we were expecting 267 return component.ErrorHandlingStop //stop component after receiving the error (don't restart it) 268 } 269 270 //irrelevant what context we use - test won't be using it 271 err := component.RunComponent(context.Background(), componentFactory, onError) 272 require.ErrorIs(t, err, ErrFatal) 273 require.True(t, called) 274 } 275 276 // tests repeatedly restarting a component during an error that occurs while shutting down 277 func TestRunComponentShutdownError(t *testing.T) { 278 componentFactory := func() (component.Component, error) { 279 //shutdown the component after some time - simulate an error during shutdown 280 return NewShutdownErroringComponent(100 * time.Millisecond), nil 281 } 282 283 fatals := 0 284 onError := func(err error) component.ErrorHandlingResult { 285 fatals++ 286 require.ErrorIs(t, err, ErrFatal) 287 if fatals < 3 { //restart component after first and second error 288 return component.ErrorHandlingRestart 289 } else { //stop component after third error 290 return component.ErrorHandlingStop 291 } 292 } 293 294 //irrelevant what context we use - test won't be using it 295 err := component.RunComponent(context.Background(), componentFactory, onError) 296 require.ErrorIs(t, err, ErrFatal) 297 require.Equal(t, 3, fatals) 298 } 299 300 func TestRunComponentConcurrentError(t *testing.T) { 301 ctx, cancel := context.WithCancel(context.Background()) 302 defer cancel() 303 304 componentFactory := func() (component.Component, error) { 305 return NewConcurrentErroringComponent(100 * time.Millisecond), nil 306 } 307 308 fatals := 0 309 onError := func(err error) component.ErrorHandlingResult { 310 fatals++ 311 require.ErrorIs(t, err, ErrFatal) 312 if fatals < 2 { 313 return component.ErrorHandlingRestart 314 } else { 315 return component.ErrorHandlingStop 316 } 317 } 318 319 err := component.RunComponent(ctx, componentFactory, onError) 320 require.ErrorIs(t, err, ErrFatal) 321 require.Equal(t, 2, fatals) 322 } 323 324 func TestRunComponentNoError(t *testing.T) { 325 ctx, cancel := context.WithCancel(context.Background()) 326 defer cancel() 327 328 componentFactory := func() (component.Component, error) { 329 return NewNonErroringComponent(100 * time.Millisecond), nil 330 } 331 332 onError := func(err error) component.ErrorHandlingResult { 333 require.Fail(t, "error handler should not have been called") 334 return component.ErrorHandlingStop 335 } 336 337 err := component.RunComponent(ctx, componentFactory, onError) 338 require.NoError(t, err) 339 } 340 341 func TestRunComponentCancel(t *testing.T) { 342 componentFactory := func() (component.Component, error) { 343 return NewNonErroringComponent(math.MaxInt64), nil 344 } 345 346 onError := func(err error) component.ErrorHandlingResult { 347 require.Fail(t, "error handler should not have been called") 348 return component.ErrorHandlingStop 349 } 350 351 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 352 defer cancel() 353 354 err := component.RunComponent(ctx, componentFactory, onError) 355 require.ErrorIs(t, err, ctx.Err()) 356 } 357 358 func TestRunComponentFactoryError(t *testing.T) { 359 componentFactory := func() (component.Component, error) { 360 return nil, ErrCouldNotCreateComponent 361 } 362 363 onError := func(err error) component.ErrorHandlingResult { 364 require.Fail(t, "error handler should not have been called") 365 return component.ErrorHandlingStop 366 } 367 368 ctx, cancel := context.WithCancel(context.Background()) 369 defer cancel() 370 371 err := component.RunComponent(ctx, componentFactory, onError) 372 require.ErrorIs(t, err, ErrCouldNotCreateComponent) 373 }