Byte-snake-engine/pipeline_internal_test.go
Torben d378759d58
DEV-1096 - add a new "pipeline" concept (#67)
* add a new "pipeline" concept

- added new Pipeline type which is a series of stages
- added a global registry to facilitate plugin architecture
- 100% test coverage

* Refactor rulesets to provide and use Pipeline

* fix copypasta comments

* fix lint for unused method

* include game over stages in ruleset pipelines

* clean up unused private standard methods

* remove unused private methods in squad ruleset

* remove unused private methods in royale ruleset

* refactor: pipeline clone + return next board state

* YAGNI: remove unused Append

* refactor: improve stage names

* add no-op behavior to stages for initial state

* refactor: no-op decision within stage functions

* remove misleading comment that isn't true

* dont bother checking for init in gameover stages

* remove redundant test

* refactor: provide a combined ruleset/pipeline type

* fix: movement no-op for GameOver check

IsGameOver needs to run pipeline, move snakes needs to no-op for that

* add test coverage

* refactor: improve stage names and use constants

* add Error method

Support error checking before calling Execute()

* update naming to be American style

* panic when overwriting stages in global registry

* rename "Error" method and improve docs

* use testify lib for panic assertion

* remove redundant food stage

* use ruleset-specific logic for game over checks

* re-work Pipeline errors

* rework errors again

* add defensive check for zero length snake

* use old logic which checks current state, not next

* add warning about how PipelineRuleset checks for game over
2022-04-19 15:52:57 -07:00

98 lines
2.6 KiB
Go

package rules
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestPipelineRuleset(t *testing.T) {
r := StageRegistry{
"doesnt_end": mockStageFn(false, nil),
"ends": mockStageFn(true, nil),
}
// Name/Error methods
p := NewPipelineFromRegistry(r, "404doesntexist")
pr := pipelineRuleset{
name: "test",
pipeline: p,
}
require.Equal(t, "test", pr.Name())
require.Equal(t, ErrorStageNotFound, pr.Err())
// test game over when it does end
p = NewPipelineFromRegistry(r, "doesnt_end", "ends")
pr = pipelineRuleset{
name: "test",
pipeline: p,
}
ended, err := pr.IsGameOver(&BoardState{})
require.NoError(t, err)
require.True(t, ended)
// Test game over when it doesn't end
p = NewPipelineFromRegistry(r, "doesnt_end")
pr = pipelineRuleset{
name: "test",
pipeline: p,
}
ended, err = pr.IsGameOver(&BoardState{})
require.NoError(t, err)
require.False(t, ended)
// test a stage that adds food, except on initialization
r.RegisterPipelineStage("add_food", func(bs *BoardState, s Settings, sm []SnakeMove) (bool, error) {
if IsInitialization(bs, s, sm) {
return false, nil
}
bs.Food = append(bs.Food, Point{X: 0, Y: 0})
return false, nil
})
b := &BoardState{}
p = NewPipelineFromRegistry(r, "add_food")
pr = pipelineRuleset{
name: "test",
pipeline: p,
}
require.Empty(t, b.Food)
b, err = pr.ModifyInitialBoardState(b)
require.NoError(t, err)
require.Empty(t, b.Food, "food should not be added on initialisation phase")
b, err = pr.CreateNextBoardState(b, mockSnakeMoves())
require.NoError(t, err)
require.NotEmpty(t, b.Food, "fodo should be added now")
}
func TestPipelineGlobals(t *testing.T) {
oldReg := globalRegistry
globalRegistry = StageRegistry{}
// ensure that we can register a function without errors
RegisterPipelineStage("test", mockStageFn(false, nil))
require.Contains(t, globalRegistry, "test")
// ensure that the global registry panics if you register an existing stage name
require.Panics(t, func() {
RegisterPipelineStage("test", mockStageFn(false, nil))
})
RegisterPipelineStage("other", mockStageFn(true, nil)) // otherwise should not panic
// ensure that we can build a pipeline using the global registry
p := NewPipeline("test", "other")
require.NotNil(t, p)
// ensure that it runs okay too
ended, next, err := p.Execute(&BoardState{}, Settings{}, nil)
require.NoError(t, err)
require.NotNil(t, next)
require.True(t, ended)
globalRegistry = oldReg
}
func mockStageFn(ended bool, err error) StageFunc {
return func(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
return ended, err
}
}