go-process/runner_test.go
Codex d86f9abc29 fix(go-process): replace testify with stdlib testing patterns (AX-6)
Removes testify + davecgh/go-spew + pmezard/go-difflib from go.mod;
rewrites assert/require calls across 13 _test.go files
(buffer/daemon/errors/exec/global/health/pidfile/process/program/
registry/runner/service + pkg/api/provider) to stdlib t.Fatalf
patterns. go vet + go test all clean (GOWORK=off).

Closes tasks.lthn.sh/view.php?id=719

Co-authored-by: Codex <noreply@openai.com>
Via-codex-lane: Cyclops-719 dispatch (haiku forwarder, ~17min lane)
2026-04-24 18:03:24 +01:00

369 lines
11 KiB
Go

package process
import (
"context"
"testing"
framework "dappco.re/go/core"
)
func newTestRunner(t *testing.T) *Runner {
t.Helper()
c := framework.New()
factory := NewService(Options{})
raw, err := factory(c)
requireNoError(t, err)
return NewRunner(raw.(*Service))
}
func TestRunner_RunSequential(t *testing.T) {
t.Run("all pass", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunSequential(context.Background(), []RunSpec{
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "second", Command: "echo", Args: []string{"2"}},
{Name: "third", Command: "echo", Args: []string{"3"}},
})
requireNoError(t, err)
assertTrue(t, result.Success())
assertEqual(t, 3, result.Passed)
assertEqual(t, 0, result.Failed)
assertEqual(t, 0, result.Skipped)
})
t.Run("stops on failure", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunSequential(context.Background(), []RunSpec{
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "fails", Command: "sh", Args: []string{"-c", "exit 1"}},
{Name: "third", Command: "echo", Args: []string{"3"}},
})
requireNoError(t, err)
assertFalse(t, result.Success())
assertEqual(t, 1, result.Passed)
assertEqual(t, 1, result.Failed)
assertEqual(t, 1, result.Skipped)
requireLen(t, result.Results, 3)
assertEqual(t, 0, result.Results[0].ExitCode)
assertNoError(t, result.Results[0].Error)
assertEqual(t, 1, result.Results[1].ExitCode)
assertNoError(t, result.Results[1].Error)
assertTrue(t, result.Results[2].Skipped)
})
t.Run("allow failure continues", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunSequential(context.Background(), []RunSpec{
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "fails", Command: "sh", Args: []string{"-c", "exit 1"}, AllowFailure: true},
{Name: "third", Command: "echo", Args: []string{"3"}},
})
requireNoError(t, err)
// Still counts as failed but pipeline continues
assertEqual(t, 2, result.Passed)
assertEqual(t, 1, result.Failed)
assertEqual(t, 0, result.Skipped)
})
}
func TestRunner_RunParallel(t *testing.T) {
t.Run("all run concurrently", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunParallel(context.Background(), []RunSpec{
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "second", Command: "echo", Args: []string{"2"}},
{Name: "third", Command: "echo", Args: []string{"3"}},
})
requireNoError(t, err)
assertTrue(t, result.Success())
assertEqual(t, 3, result.Passed)
assertLen(t, result.Results, 3)
})
t.Run("failure doesnt stop others", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunParallel(context.Background(), []RunSpec{
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "fails", Command: "sh", Args: []string{"-c", "exit 1"}},
{Name: "third", Command: "echo", Args: []string{"3"}},
})
requireNoError(t, err)
assertFalse(t, result.Success())
assertEqual(t, 2, result.Passed)
assertEqual(t, 1, result.Failed)
})
}
func TestRunner_RunAll(t *testing.T) {
t.Run("respects dependencies", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "third", Command: "echo", Args: []string{"3"}, After: []string{"second"}},
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "second", Command: "echo", Args: []string{"2"}, After: []string{"first"}},
})
requireNoError(t, err)
assertTrue(t, result.Success())
assertEqual(t, 3, result.Passed)
})
t.Run("skips dependents on failure", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "first", Command: "sh", Args: []string{"-c", "exit 1"}},
{Name: "second", Command: "echo", Args: []string{"2"}, After: []string{"first"}},
{Name: "third", Command: "echo", Args: []string{"3"}, After: []string{"second"}},
})
requireNoError(t, err)
assertFalse(t, result.Success())
assertEqual(t, 0, result.Passed)
assertEqual(t, 1, result.Failed)
assertEqual(t, 2, result.Skipped)
})
t.Run("parallel independent specs", func(t *testing.T) {
runner := newTestRunner(t)
// These should run in parallel since they have no dependencies
result, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "a", Command: "echo", Args: []string{"a"}},
{Name: "b", Command: "echo", Args: []string{"b"}},
{Name: "c", Command: "echo", Args: []string{"c"}},
{Name: "final", Command: "echo", Args: []string{"done"}, After: []string{"a", "b", "c"}},
})
requireNoError(t, err)
assertTrue(t, result.Success())
assertEqual(t, 4, result.Passed)
})
t.Run("preserves input order", func(t *testing.T) {
runner := newTestRunner(t)
specs := []RunSpec{
{Name: "third", Command: "echo", Args: []string{"3"}, After: []string{"second"}},
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "second", Command: "echo", Args: []string{"2"}, After: []string{"first"}},
}
result, err := runner.RunAll(context.Background(), specs)
requireNoError(t, err)
requireLen(t, result.Results, len(specs))
for i, res := range result.Results {
assertEqual(t, specs[i].Name, res.Name)
}
})
}
func TestRunner_RunAll_CircularDeps(t *testing.T) {
t.Run("circular dependency is skipped with error", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "a", Command: "echo", Args: []string{"a"}, After: []string{"b"}},
{Name: "b", Command: "echo", Args: []string{"b"}, After: []string{"a"}},
})
requireNoError(t, err)
assertTrue(t, result.Success())
assertEqual(t, 0, result.Failed)
assertEqual(t, 2, result.Skipped)
for _, res := range result.Results {
assertTrue(t, res.Skipped)
assertEqual(t, 0, res.ExitCode)
assertError(t, res.Error)
}
})
t.Run("missing dependency is skipped with error", func(t *testing.T) {
runner := newTestRunner(t)
result, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "a", Command: "echo", Args: []string{"a"}, After: []string{"missing"}},
})
requireNoError(t, err)
assertTrue(t, result.Success())
assertEqual(t, 0, result.Failed)
assertEqual(t, 1, result.Skipped)
requireLen(t, result.Results, 1)
assertTrue(t, result.Results[0].Skipped)
assertEqual(t, 0, result.Results[0].ExitCode)
assertError(t, result.Results[0].Error)
})
}
func TestRunner_ContextCancellation(t *testing.T) {
t.Run("run sequential skips pending specs", func(t *testing.T) {
runner := newTestRunner(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
result, err := runner.RunSequential(ctx, []RunSpec{
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "second", Command: "echo", Args: []string{"2"}},
})
requireNoError(t, err)
assertEqual(t, 0, result.Passed)
assertEqual(t, 0, result.Failed)
assertEqual(t, 2, result.Skipped)
requireLen(t, result.Results, 2)
for _, res := range result.Results {
assertTrue(t, res.Skipped)
assertEqual(t, 1, res.ExitCode)
assertError(t, res.Error)
assertContains(t, res.Error.Error(), "context canceled")
}
})
t.Run("run all skips pending specs", func(t *testing.T) {
runner := newTestRunner(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
result, err := runner.RunAll(ctx, []RunSpec{
{Name: "first", Command: "echo", Args: []string{"1"}},
{Name: "second", Command: "echo", Args: []string{"2"}, After: []string{"first"}},
})
requireNoError(t, err)
assertEqual(t, 0, result.Passed)
assertEqual(t, 0, result.Failed)
assertEqual(t, 2, result.Skipped)
requireLen(t, result.Results, 2)
for _, res := range result.Results {
assertTrue(t, res.Skipped)
assertEqual(t, 1, res.ExitCode)
assertError(t, res.Error)
assertContains(t, res.Error.Error(), "context canceled")
}
})
}
func TestRunResult_Passed(t *testing.T) {
t.Run("success", func(t *testing.T) {
r := RunResult{ExitCode: 0}
assertTrue(t, r.Passed())
})
t.Run("non-zero exit", func(t *testing.T) {
r := RunResult{ExitCode: 1}
assertFalse(t, r.Passed())
})
t.Run("skipped", func(t *testing.T) {
r := RunResult{ExitCode: 0, Skipped: true}
assertFalse(t, r.Passed())
})
t.Run("error", func(t *testing.T) {
r := RunResult{ExitCode: 0, Error: errSentinel}
assertFalse(t, r.Passed())
})
}
func TestRunner_NilService(t *testing.T) {
runner := NewRunner(nil)
_, err := runner.RunAll(context.Background(), nil)
requireError(t, err)
assertErrorIs(t, err, ErrRunnerNoService)
_, err = runner.RunSequential(context.Background(), nil)
requireError(t, err)
assertErrorIs(t, err, ErrRunnerNoService)
_, err = runner.RunParallel(context.Background(), nil)
requireError(t, err)
assertErrorIs(t, err, ErrRunnerNoService)
}
func TestRunner_NilContext(t *testing.T) {
runner := newTestRunner(t)
_, err := runner.RunAll(nil, nil)
requireError(t, err)
assertErrorIs(t, err, ErrRunnerContextRequired)
_, err = runner.RunSequential(nil, nil)
requireError(t, err)
assertErrorIs(t, err, ErrRunnerContextRequired)
_, err = runner.RunParallel(nil, nil)
requireError(t, err)
assertErrorIs(t, err, ErrRunnerContextRequired)
}
func TestRunner_InvalidSpecNames(t *testing.T) {
runner := newTestRunner(t)
t.Run("rejects empty names", func(t *testing.T) {
_, err := runner.RunSequential(context.Background(), []RunSpec{
{Name: "", Command: "echo", Args: []string{"a"}},
})
requireError(t, err)
assertErrorIs(t, err, ErrRunnerInvalidSpecName)
})
t.Run("rejects empty dependency names", func(t *testing.T) {
_, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "one", Command: "echo", Args: []string{"a"}, After: []string{""}},
})
requireError(t, err)
assertErrorIs(t, err, ErrRunnerInvalidDependencyName)
})
t.Run("rejects duplicated dependency names", func(t *testing.T) {
_, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "one", Command: "echo", Args: []string{"a"}, After: []string{"two", "two"}},
})
requireError(t, err)
assertErrorIs(t, err, ErrRunnerInvalidDependencyName)
})
t.Run("rejects self dependency", func(t *testing.T) {
_, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "one", Command: "echo", Args: []string{"a"}, After: []string{"one"}},
})
requireError(t, err)
assertErrorIs(t, err, ErrRunnerInvalidDependencyName)
})
t.Run("rejects duplicate names", func(t *testing.T) {
_, err := runner.RunAll(context.Background(), []RunSpec{
{Name: "same", Command: "echo", Args: []string{"a"}},
{Name: "same", Command: "echo", Args: []string{"b"}},
})
requireError(t, err)
assertErrorIs(t, err, ErrRunnerInvalidSpecName)
})
t.Run("rejects duplicate names in parallel mode", func(t *testing.T) {
_, err := runner.RunParallel(context.Background(), []RunSpec{
{Name: "one", Command: "echo", Args: []string{"a"}},
{Name: "one", Command: "echo", Args: []string{"b"}},
})
requireError(t, err)
assertErrorIs(t, err, ErrRunnerInvalidSpecName)
})
}