126 lines
2.6 KiB
Go
126 lines
2.6 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/gamemaster/internal/config"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type fakeComponent struct {
|
|
runErr error
|
|
shutdownErr error
|
|
runHook func(context.Context) error
|
|
shutdownHook func(context.Context) error
|
|
runCount atomic.Int32
|
|
downCount atomic.Int32
|
|
blockForCtx bool
|
|
}
|
|
|
|
func (component *fakeComponent) Run(ctx context.Context) error {
|
|
component.runCount.Add(1)
|
|
if component.runHook != nil {
|
|
return component.runHook(ctx)
|
|
}
|
|
if component.blockForCtx {
|
|
<-ctx.Done()
|
|
return ctx.Err()
|
|
}
|
|
|
|
return component.runErr
|
|
}
|
|
|
|
func (component *fakeComponent) Shutdown(ctx context.Context) error {
|
|
component.downCount.Add(1)
|
|
if component.shutdownHook != nil {
|
|
return component.shutdownHook(ctx)
|
|
}
|
|
|
|
return component.shutdownErr
|
|
}
|
|
|
|
func newCfg() config.Config {
|
|
return config.Config{ShutdownTimeout: time.Second}
|
|
}
|
|
|
|
func TestAppRunWithoutComponentsBlocksUntilContextDone(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := New(newCfg())
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
require.NoError(t, app.Run(ctx))
|
|
}
|
|
|
|
func TestAppRunReturnsOnContextCancel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
component := &fakeComponent{blockForCtx: true}
|
|
app := New(newCfg(), component)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() {
|
|
time.Sleep(10 * time.Millisecond)
|
|
cancel()
|
|
}()
|
|
|
|
require.NoError(t, app.Run(ctx))
|
|
assert.EqualValues(t, 1, component.runCount.Load())
|
|
assert.EqualValues(t, 1, component.downCount.Load())
|
|
}
|
|
|
|
func TestAppRunPropagatesComponentFailure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
failure := errors.New("boom")
|
|
component := &fakeComponent{runErr: failure}
|
|
app := New(newCfg(), component)
|
|
|
|
err := app.Run(context.Background())
|
|
require.Error(t, err)
|
|
require.ErrorIs(t, err, failure)
|
|
assert.EqualValues(t, 1, component.downCount.Load())
|
|
}
|
|
|
|
func TestAppRunFailsOnNilContext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := New(newCfg())
|
|
var ctx context.Context
|
|
require.Error(t, app.Run(ctx))
|
|
}
|
|
|
|
func TestAppRunFailsOnNonPositiveShutdownTimeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := New(config.Config{}, &fakeComponent{})
|
|
require.Error(t, app.Run(context.Background()))
|
|
}
|
|
|
|
func TestAppRunFailsOnNilComponent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := New(newCfg(), nil)
|
|
require.Error(t, app.Run(context.Background()))
|
|
}
|
|
|
|
func TestAppRunFlagsCleanExitBeforeShutdown(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
component := &fakeComponent{}
|
|
app := New(newCfg(), component)
|
|
|
|
err := app.Run(context.Background())
|
|
require.Error(t, err)
|
|
require.True(t, strings.Contains(err.Error(), "exited without error"))
|
|
}
|