From 3e7386b38c2770d9693605ef77b310f930fa38f9 Mon Sep 17 00:00:00 2001 From: Matthias Riegler Date: Fri, 21 Jul 2023 21:28:10 +0200 Subject: [PATCH] chore: add state tests Signed-off-by: Matthias Riegler --- internal/agent/agent.go | 2 +- internal/agent/state.go | 24 +++-- internal/agent/state_test.go | 192 +++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 9 deletions(-) create mode 100644 internal/agent/state_test.go diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 7d22069..fa5f41a 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -98,7 +98,7 @@ type ComputeBladeAgent interface { type computeBladeAgentImpl struct { opts ComputeBladeAgentConfig blade hal.ComputeBladeHal - state *computebladeState + state ComputebladeState edgeLedEngine ledengine.LedEngine topLedEngine ledengine.LedEngine diff --git a/internal/agent/state.go b/internal/agent/state.go index 3cc54cb..aa1a77c 100644 --- a/internal/agent/state.go +++ b/internal/agent/state.go @@ -16,7 +16,15 @@ var ( }, []string{"state"}) ) -type computebladeState struct { +type ComputebladeState interface { + RegisterEvent(event Event) + IdentifyActive() bool + WaitForIdentifyConfirm(ctx context.Context) error + CriticalActive() bool + WaitForCriticalClear(ctx context.Context) error +} + +type computebladeStateImpl struct { mutex sync.Mutex // identifyActive indicates whether the blade is currently in identify mode @@ -27,14 +35,14 @@ type computebladeState struct { criticalConfirmChan chan struct{} } -func NewComputeBladeState() *computebladeState { - return &computebladeState{ +func NewComputeBladeState() ComputebladeState { + return &computebladeStateImpl{ identifyConfirmChan: make(chan struct{}), criticalConfirmChan: make(chan struct{}), } } -func (s *computebladeState) RegisterEvent(event Event) { +func (s *computebladeStateImpl) RegisterEvent(event Event) { s.mutex.Lock() defer s.mutex.Unlock() switch event { @@ -75,11 +83,11 @@ func (s *computebladeState) RegisterEvent(event Event) { } } -func (s *computebladeState) IdentifyActive() bool { +func (s *computebladeStateImpl) IdentifyActive() bool { return s.identifyActive } -func (s *computebladeState) WaitForIdentifyConfirm(ctx context.Context) error { +func (s *computebladeStateImpl) WaitForIdentifyConfirm(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() @@ -88,11 +96,11 @@ func (s *computebladeState) WaitForIdentifyConfirm(ctx context.Context) error { } } -func (s *computebladeState) CriticalActive() bool { +func (s *computebladeStateImpl) CriticalActive() bool { return s.criticalActive } -func (s *computebladeState) WaitForCriticalConfirm(ctx context.Context) error { +func (s *computebladeStateImpl) WaitForCriticalClear(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() diff --git a/internal/agent/state_test.go b/internal/agent/state_test.go new file mode 100644 index 0000000..e08cd72 --- /dev/null +++ b/internal/agent/state_test.go @@ -0,0 +1,192 @@ +package agent_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/xvzf/computeblade-agent/internal/agent" +) + +func TestNewComputeBladeState(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + assert.NotNil(t, state) +} + +func TestComputeBladeState_RegisterEventIdentify(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + + // Identify event + state.RegisterEvent(agent.IdentifyEvent) + assert.True(t, state.IdentifyActive()) + state.RegisterEvent(agent.IdentifyConfirmEvent) + assert.False(t, state.IdentifyActive()) +} + +func TestComputeBladeState_RegisterEventCritical(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + + // critical event + state.RegisterEvent(agent.CriticalEvent) + assert.True(t, state.CriticalActive()) + state.RegisterEvent(agent.CriticalResetEvent) + assert.False(t, state.CriticalActive()) +} + +func TestComputeBladeState_RegisterEventMixed(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + + // Send a bunch of events + state.RegisterEvent(agent.CriticalEvent) + state.RegisterEvent(agent.CriticalResetEvent) + state.RegisterEvent(agent.NoopEvent) + state.RegisterEvent(agent.CriticalEvent) + state.RegisterEvent(agent.NoopEvent) + state.RegisterEvent(agent.IdentifyEvent) + state.RegisterEvent(agent.IdentifyEvent) + state.RegisterEvent(agent.CriticalResetEvent) + state.RegisterEvent(agent.IdentifyEvent) + + assert.False(t, state.CriticalActive()) + assert.True(t, state.IdentifyActive()) +} + +func TestComputeBladeState_WaitForIdentifyConfirm_NoTimeout(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + + // send identify event + t.Log("Setting identify event") + state.RegisterEvent(agent.IdentifyEvent) + assert.True(t, state.IdentifyActive()) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + ctx := context.Background() + + // Block until identify status is cleared + t.Log("Waiting for identify confirm") + err := state.WaitForIdentifyConfirm(ctx) + assert.NoError(t, err) + }() + + // Give goroutine time to start + time.Sleep(50 * time.Millisecond) + + // confirm event + state.RegisterEvent(agent.IdentifyConfirmEvent) + t.Log("Identify event confirmed") + + wg.Wait() +} + +func TestComputeBladeState_WaitForIdentifyConfirm_Timeout(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + + // send identify event + t.Log("Setting identify event") + state.RegisterEvent(agent.IdentifyEvent) + assert.True(t, state.IdentifyActive()) + + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + // Block until identify status is cleared + t.Log("Waiting for identify confirm") + err := state.WaitForIdentifyConfirm(ctx) + assert.ErrorIs(t, err, context.DeadlineExceeded) + }() + + // Give goroutine time to start. + time.Sleep(50 * time.Millisecond) + + // confirm event + state.RegisterEvent(agent.IdentifyConfirmEvent) + t.Log("Identify event confirmed") + + wg.Wait() +} + +func TestComputeBladeState_WaitForCriticalClear_NoTimeout(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + + // send critical event + t.Log("Setting critical event") + state.RegisterEvent(agent.CriticalEvent) + assert.True(t, state.CriticalActive()) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + ctx := context.Background() + + // Block until critical status is cleared + t.Log("Waiting for critical confirm") + err := state.WaitForCriticalClear(ctx) + assert.NoError(t, err) + }() + + // Give goroutine time to start + time.Sleep(50 * time.Millisecond) + + // confirm event + state.RegisterEvent(agent.CriticalResetEvent) + t.Log("critical event confirmed") + + wg.Wait() +} + +func TestComputeBladeState_WaitForCriticalClear_Timeout(t *testing.T) { + t.Parallel() + + state := agent.NewComputeBladeState() + + // send critical event + t.Log("Setting critical event") + state.RegisterEvent(agent.CriticalEvent) + assert.True(t, state.CriticalActive()) + + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + // Block until critical status is cleared + t.Log("Waiting for critical confirm") + err := state.WaitForCriticalClear(ctx) + assert.ErrorIs(t, err, context.DeadlineExceeded) + }() + + // Give goroutine time to start. + time.Sleep(50 * time.Millisecond) + + // confirm event + state.RegisterEvent(agent.CriticalResetEvent) + t.Log("critical event confirmed") + + wg.Wait() +}