197 lines
5.1 KiB
Go
197 lines
5.1 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"gitlab.hive-brain.de/MDietermann/Archduino/internal/module"
|
|
)
|
|
|
|
// scriptedModule lets tests control what each phase returns.
|
|
type scriptedModule struct {
|
|
id string
|
|
deps []string
|
|
detect func() (module.State, error)
|
|
plan func() (module.Plan, error)
|
|
planForce func() (module.Plan, error)
|
|
apply func() error
|
|
verify func() error
|
|
|
|
detectCalls int
|
|
planCalls int
|
|
planForceCalls int
|
|
}
|
|
|
|
func (s *scriptedModule) Meta() module.Meta { return module.Meta{ID: s.id, Name: s.id, Deps: s.deps} }
|
|
func (s *scriptedModule) Detect(ctx context.Context) (module.State, error) {
|
|
s.detectCalls++
|
|
if s.detect != nil {
|
|
return s.detect()
|
|
}
|
|
return nil, nil
|
|
}
|
|
func (s *scriptedModule) Plan(ctx context.Context, _ module.State) (module.Plan, error) {
|
|
s.planCalls++
|
|
if s.plan != nil {
|
|
return s.plan()
|
|
}
|
|
return module.Plan{}, nil
|
|
}
|
|
func (s *scriptedModule) PlanForce(ctx context.Context) (module.Plan, error) {
|
|
s.planForceCalls++
|
|
if s.planForce != nil {
|
|
return s.planForce()
|
|
}
|
|
if s.plan != nil {
|
|
return s.plan()
|
|
}
|
|
return module.Plan{}, nil
|
|
}
|
|
func (s *scriptedModule) Apply(ctx context.Context, _ module.Plan, _ module.Reporter) error {
|
|
if s.apply != nil {
|
|
return s.apply()
|
|
}
|
|
return nil
|
|
}
|
|
func (s *scriptedModule) Verify(ctx context.Context) error {
|
|
if s.verify != nil {
|
|
return s.verify()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newReg(t *testing.T, mods ...module.Module) *module.Registry {
|
|
t.Helper()
|
|
r := module.NewRegistry()
|
|
for _, m := range mods {
|
|
if err := r.Register(m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
func TestRunAllSucceed(t *testing.T) {
|
|
reg := newReg(t,
|
|
&scriptedModule{id: "a"},
|
|
&scriptedModule{id: "b", deps: []string{"a"}},
|
|
)
|
|
sum, err := Run(context.Background(), reg, RunOptions{Reporter: NewBufferedReporter()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ok, fail, skip := sum.Counts()
|
|
if ok != 2 || fail != 0 || skip != 0 {
|
|
t.Fatalf("counts = (%d,%d,%d)", ok, fail, skip)
|
|
}
|
|
if sum.Results[0].ID != "a" || sum.Results[1].ID != "b" {
|
|
t.Fatalf("order = %v", sum.Results)
|
|
}
|
|
}
|
|
|
|
func TestRunDryRunDoesNotApply(t *testing.T) {
|
|
applied := false
|
|
reg := newReg(t, &scriptedModule{
|
|
id: "a",
|
|
apply: func() error { applied = true; return nil },
|
|
})
|
|
sum, err := Run(context.Background(), reg, RunOptions{DryRun: true, Reporter: NewBufferedReporter()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if applied {
|
|
t.Fatal("Apply was called during dry-run")
|
|
}
|
|
if sum.Results[0].Status != StatusPlanned {
|
|
t.Fatalf("status = %v", sum.Results[0].Status)
|
|
}
|
|
}
|
|
|
|
func TestRunFailureSkipsDescendantsContinuesSiblings(t *testing.T) {
|
|
reg := newReg(t,
|
|
&scriptedModule{id: "root"},
|
|
&scriptedModule{id: "fails", deps: []string{"root"}, apply: func() error { return errors.New("boom") }},
|
|
&scriptedModule{id: "child", deps: []string{"fails"}},
|
|
&scriptedModule{id: "sibling", deps: []string{"root"}},
|
|
)
|
|
sum, err := Run(context.Background(), reg, RunOptions{Reporter: NewBufferedReporter()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status := map[string]ModuleStatus{}
|
|
for _, r := range sum.Results {
|
|
status[r.ID] = r.Status
|
|
}
|
|
if status["root"] != StatusSucceeded {
|
|
t.Errorf("root = %v", status["root"])
|
|
}
|
|
if status["fails"] != StatusFailed {
|
|
t.Errorf("fails = %v", status["fails"])
|
|
}
|
|
if status["child"] != StatusSkipped {
|
|
t.Errorf("child = %v", status["child"])
|
|
}
|
|
if status["sibling"] != StatusSucceeded {
|
|
t.Errorf("sibling = %v (should have run)", status["sibling"])
|
|
}
|
|
}
|
|
|
|
func TestRunHaltOnError(t *testing.T) {
|
|
reg := newReg(t,
|
|
&scriptedModule{id: "a", apply: func() error { return errors.New("nope") }},
|
|
&scriptedModule{id: "b"}, // independent
|
|
)
|
|
sum, err := Run(context.Background(), reg, RunOptions{HaltOnError: true, Reporter: NewBufferedReporter()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// "b" should not appear in results because we halted before reaching it.
|
|
for _, r := range sum.Results {
|
|
if r.ID == "b" {
|
|
t.Fatalf("b was run despite HaltOnError")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunUnknownModule(t *testing.T) {
|
|
reg := module.NewRegistry()
|
|
_, err := Run(context.Background(), reg, RunOptions{Selected: []string{"ghost"}})
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown module")
|
|
}
|
|
}
|
|
|
|
func TestRunForceSkipsDetectAndCallsPlanForce(t *testing.T) {
|
|
mod := &scriptedModule{
|
|
id: "a",
|
|
detect: func() (module.State, error) {
|
|
panic("Detect must not be called when Force=true")
|
|
},
|
|
planForce: func() (module.Plan, error) {
|
|
return module.Plan{Actions: []module.Action{{Description: "forced"}}}, nil
|
|
},
|
|
}
|
|
reg := newReg(t, mod)
|
|
sum, err := Run(context.Background(), reg, RunOptions{Force: true, Reporter: NewBufferedReporter()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if mod.planForceCalls != 1 {
|
|
t.Errorf("planForceCalls = %d, want 1", mod.planForceCalls)
|
|
}
|
|
if mod.detectCalls != 0 {
|
|
t.Errorf("detectCalls = %d, want 0", mod.detectCalls)
|
|
}
|
|
if mod.planCalls != 0 {
|
|
t.Errorf("planCalls = %d, want 0", mod.planCalls)
|
|
}
|
|
ok, fail, skip := sum.Counts()
|
|
if ok != 1 || fail != 0 || skip != 0 {
|
|
t.Fatalf("counts = (%d,%d,%d)", ok, fail, skip)
|
|
}
|
|
if len(sum.Results[0].Plan.Actions) != 1 {
|
|
t.Fatalf("expected 1 action in plan, got %d", len(sum.Results[0].Plan.Actions))
|
|
}
|
|
}
|