Files
Archduino/internal/engine/engine_test.go

147 lines
3.8 KiB
Go

package engine
import (
"context"
"errors"
"testing"
"github.com/MDietermann/ArduinoArchSetup/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)
apply func() error
verify func() error
}
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) {
if s.detect != nil {
return s.detect()
}
return nil, nil
}
func (s scriptedModule) Plan(ctx context.Context, _ module.State) (module.Plan, error) {
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")
}
}