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)
This commit is contained in:
parent
55f7245017
commit
d86f9abc29
15 changed files with 986 additions and 936 deletions
|
|
@ -2,8 +2,6 @@ package process
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRingBuffer_Basics_Good(t *testing.T) {
|
||||
|
|
@ -11,22 +9,22 @@ func TestRingBuffer_Basics_Good(t *testing.T) {
|
|||
rb := NewRingBuffer(10)
|
||||
|
||||
n, err := rb.Write([]byte("hello"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 5, n)
|
||||
assert.Equal(t, "hello", rb.String())
|
||||
assert.Equal(t, 5, rb.Len())
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, 5, n)
|
||||
assertEqual(t, "hello", rb.String())
|
||||
assertEqual(t, 5, rb.Len())
|
||||
})
|
||||
|
||||
t.Run("overflow wraps around", func(t *testing.T) {
|
||||
rb := NewRingBuffer(5)
|
||||
|
||||
_, _ = rb.Write([]byte("hello"))
|
||||
assert.Equal(t, "hello", rb.String())
|
||||
assertEqual(t, "hello", rb.String())
|
||||
|
||||
_, _ = rb.Write([]byte("world"))
|
||||
// Should contain "world" (overwrote "hello")
|
||||
assert.Equal(t, 5, rb.Len())
|
||||
assert.Equal(t, "world", rb.String())
|
||||
assertEqual(t, 5, rb.Len())
|
||||
assertEqual(t, "world", rb.String())
|
||||
})
|
||||
|
||||
t.Run("partial overflow", func(t *testing.T) {
|
||||
|
|
@ -35,27 +33,27 @@ func TestRingBuffer_Basics_Good(t *testing.T) {
|
|||
_, _ = rb.Write([]byte("hello"))
|
||||
_, _ = rb.Write([]byte("worldx"))
|
||||
// Should contain "lloworldx" (11 chars, buffer is 10)
|
||||
assert.Equal(t, 10, rb.Len())
|
||||
assertEqual(t, 10, rb.Len())
|
||||
})
|
||||
|
||||
t.Run("empty buffer", func(t *testing.T) {
|
||||
rb := NewRingBuffer(10)
|
||||
assert.Equal(t, "", rb.String())
|
||||
assert.Equal(t, 0, rb.Len())
|
||||
assert.Nil(t, rb.Bytes())
|
||||
assertEqual(t, "", rb.String())
|
||||
assertEqual(t, 0, rb.Len())
|
||||
assertNil(t, rb.Bytes())
|
||||
})
|
||||
|
||||
t.Run("reset", func(t *testing.T) {
|
||||
rb := NewRingBuffer(10)
|
||||
_, _ = rb.Write([]byte("hello"))
|
||||
rb.Reset()
|
||||
assert.Equal(t, "", rb.String())
|
||||
assert.Equal(t, 0, rb.Len())
|
||||
assertEqual(t, "", rb.String())
|
||||
assertEqual(t, 0, rb.Len())
|
||||
})
|
||||
|
||||
t.Run("cap", func(t *testing.T) {
|
||||
rb := NewRingBuffer(42)
|
||||
assert.Equal(t, 42, rb.Cap())
|
||||
assertEqual(t, 42, rb.Cap())
|
||||
})
|
||||
|
||||
t.Run("bytes returns copy", func(t *testing.T) {
|
||||
|
|
@ -63,11 +61,11 @@ func TestRingBuffer_Basics_Good(t *testing.T) {
|
|||
_, _ = rb.Write([]byte("hello"))
|
||||
|
||||
bytes := rb.Bytes()
|
||||
assert.Equal(t, []byte("hello"), bytes)
|
||||
assertEqual(t, []byte("hello"), bytes)
|
||||
|
||||
// Modifying returned bytes shouldn't affect buffer
|
||||
bytes[0] = 'x'
|
||||
assert.Equal(t, "hello", rb.String())
|
||||
assertEqual(t, "hello", rb.String())
|
||||
})
|
||||
|
||||
t.Run("zero or negative capacity is a no-op", func(t *testing.T) {
|
||||
|
|
@ -75,12 +73,12 @@ func TestRingBuffer_Basics_Good(t *testing.T) {
|
|||
rb := NewRingBuffer(size)
|
||||
|
||||
n, err := rb.Write([]byte("discarded"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len("discarded"), n)
|
||||
assert.Equal(t, 0, rb.Cap())
|
||||
assert.Equal(t, 0, rb.Len())
|
||||
assert.Equal(t, "", rb.String())
|
||||
assert.Nil(t, rb.Bytes())
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, len("discarded"), n)
|
||||
assertEqual(t, 0, rb.Cap())
|
||||
assertEqual(t, 0, rb.Len())
|
||||
assertEqual(t, "", rb.String())
|
||||
assertNil(t, rb.Bytes())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
107
daemon_test.go
107
daemon_test.go
|
|
@ -8,9 +8,6 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDaemon_StartAndStop(t *testing.T) {
|
||||
|
|
@ -23,18 +20,18 @@ func TestDaemon_StartAndStop(t *testing.T) {
|
|||
})
|
||||
|
||||
err := d.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
addr := d.HealthAddr()
|
||||
require.NotEmpty(t, addr)
|
||||
requireNotEmpty(t, addr)
|
||||
|
||||
resp, err := http.Get("http://" + addr + "/health")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
err = d.Stop()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
}
|
||||
|
||||
func TestDaemon_StopMarksNotReadyBeforeShutdownCompletes(t *testing.T) {
|
||||
|
|
@ -55,10 +52,10 @@ func TestDaemon_StopMarksNotReadyBeforeShutdownCompletes(t *testing.T) {
|
|||
})
|
||||
|
||||
err := d.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
addr := d.HealthAddr()
|
||||
require.NotEmpty(t, addr)
|
||||
requireNotEmpty(t, addr)
|
||||
|
||||
healthErr := make(chan error, 1)
|
||||
go func() {
|
||||
|
|
@ -82,7 +79,7 @@ func TestDaemon_StopMarksNotReadyBeforeShutdownCompletes(t *testing.T) {
|
|||
stopDone <- d.Stop()
|
||||
}()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
requireEventually(t, func() bool {
|
||||
return !d.Ready()
|
||||
}, 500*time.Millisecond, 10*time.Millisecond, "daemon should become not ready before shutdown completes")
|
||||
|
||||
|
|
@ -96,14 +93,14 @@ func TestDaemon_StopMarksNotReadyBeforeShutdownCompletes(t *testing.T) {
|
|||
|
||||
select {
|
||||
case err := <-stopDone:
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("daemon stop did not finish after health check unblocked")
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-healthErr:
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("/health request did not finish")
|
||||
}
|
||||
|
|
@ -134,10 +131,10 @@ func TestDaemon_StopUnregistersAfterHealthShutdownCompletes(t *testing.T) {
|
|||
})
|
||||
|
||||
err := d.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
addr := d.HealthAddr()
|
||||
require.NotEmpty(t, addr)
|
||||
requireNotEmpty(t, addr)
|
||||
|
||||
healthErr := make(chan error, 1)
|
||||
go func() {
|
||||
|
|
@ -161,12 +158,12 @@ func TestDaemon_StopUnregistersAfterHealthShutdownCompletes(t *testing.T) {
|
|||
stopDone <- d.Stop()
|
||||
}()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
requireEventually(t, func() bool {
|
||||
return !d.Ready()
|
||||
}, 500*time.Millisecond, 10*time.Millisecond, "daemon should become not ready before shutdown completes")
|
||||
|
||||
_, ok := reg.Get("test-app", "serve")
|
||||
assert.True(t, ok, "daemon should remain registered until health shutdown completes")
|
||||
assertTrue(t, ok, "daemon should remain registered until health shutdown completes")
|
||||
|
||||
select {
|
||||
case err := <-stopDone:
|
||||
|
|
@ -178,19 +175,19 @@ func TestDaemon_StopUnregistersAfterHealthShutdownCompletes(t *testing.T) {
|
|||
|
||||
select {
|
||||
case err := <-stopDone:
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("daemon stop did not finish after health check unblocked")
|
||||
}
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
requireEventually(t, func() bool {
|
||||
_, ok := reg.Get("test-app", "serve")
|
||||
return !ok
|
||||
}, 500*time.Millisecond, 10*time.Millisecond, "daemon should unregister after health shutdown completes")
|
||||
|
||||
select {
|
||||
case err := <-healthErr:
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("/health request did not finish")
|
||||
}
|
||||
|
|
@ -202,12 +199,12 @@ func TestDaemon_DoubleStartFails(t *testing.T) {
|
|||
})
|
||||
|
||||
err := d.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
defer func() { _ = d.Stop() }()
|
||||
|
||||
err = d.Start()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "already running")
|
||||
assertError(t, err)
|
||||
assertContains(t, err.Error(), "already running")
|
||||
}
|
||||
|
||||
func TestDaemon_RunWithoutStartFails(t *testing.T) {
|
||||
|
|
@ -217,16 +214,16 @@ func TestDaemon_RunWithoutStartFails(t *testing.T) {
|
|||
cancel()
|
||||
|
||||
err := d.Run(ctx)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not started")
|
||||
assertError(t, err)
|
||||
assertContains(t, err.Error(), "not started")
|
||||
}
|
||||
|
||||
func TestDaemon_RunNilContextFails(t *testing.T) {
|
||||
d := NewDaemon(DaemonOptions{})
|
||||
|
||||
err := d.Run(nil)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrDaemonContextRequired)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrDaemonContextRequired)
|
||||
}
|
||||
|
||||
func TestDaemon_SetReady(t *testing.T) {
|
||||
|
|
@ -235,37 +232,37 @@ func TestDaemon_SetReady(t *testing.T) {
|
|||
})
|
||||
|
||||
err := d.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
defer func() { _ = d.Stop() }()
|
||||
|
||||
addr := d.HealthAddr()
|
||||
|
||||
resp, _ := http.Get("http://" + addr + "/ready")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assertEqual(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
assert.True(t, d.Ready())
|
||||
assertTrue(t, d.Ready())
|
||||
|
||||
d.SetReady(false)
|
||||
assert.False(t, d.Ready())
|
||||
assertFalse(t, d.Ready())
|
||||
|
||||
resp, _ = http.Get("http://" + addr + "/ready")
|
||||
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
|
||||
assertEqual(t, http.StatusServiceUnavailable, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
func TestDaemon_ReadyWithoutHealthServer(t *testing.T) {
|
||||
d := NewDaemon(DaemonOptions{})
|
||||
assert.False(t, d.Ready())
|
||||
assertFalse(t, d.Ready())
|
||||
}
|
||||
|
||||
func TestDaemon_NoHealthAddrReturnsEmpty(t *testing.T) {
|
||||
d := NewDaemon(DaemonOptions{})
|
||||
assert.Empty(t, d.HealthAddr())
|
||||
assertEqual(t, "", d.HealthAddr())
|
||||
}
|
||||
|
||||
func TestDaemon_DefaultShutdownTimeout(t *testing.T) {
|
||||
d := NewDaemon(DaemonOptions{})
|
||||
assert.Equal(t, 30*time.Second, d.opts.ShutdownTimeout)
|
||||
assertEqual(t, 30*time.Second, d.opts.ShutdownTimeout)
|
||||
}
|
||||
|
||||
func TestDaemon_RunBlocksUntilCancelled(t *testing.T) {
|
||||
|
|
@ -274,7 +271,7 @@ func TestDaemon_RunBlocksUntilCancelled(t *testing.T) {
|
|||
})
|
||||
|
||||
err := d.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
|
|
@ -295,7 +292,7 @@ func TestDaemon_RunBlocksUntilCancelled(t *testing.T) {
|
|||
|
||||
select {
|
||||
case err := <-done:
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Run should return after context cancellation")
|
||||
}
|
||||
|
|
@ -306,16 +303,16 @@ func TestDaemon_StopIdempotent(t *testing.T) {
|
|||
|
||||
// Stop without Start should be a no-op
|
||||
err := d.Stop()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
}
|
||||
|
||||
func TestDaemon_AutoRegisters(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
reg := NewRegistry(filepath.Join(dir, "daemons"))
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
exe, err := os.Executable()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
d := NewDaemon(DaemonOptions{
|
||||
HealthAddr: "127.0.0.1:0",
|
||||
|
|
@ -327,22 +324,22 @@ func TestDaemon_AutoRegisters(t *testing.T) {
|
|||
})
|
||||
|
||||
err = d.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
// Should be registered
|
||||
entry, ok := reg.Get("test-app", "serve")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, os.Getpid(), entry.PID)
|
||||
assert.NotEmpty(t, entry.Health)
|
||||
assert.Equal(t, wd, entry.Project)
|
||||
assert.Equal(t, exe, entry.Binary)
|
||||
requireTrue(t, ok)
|
||||
assertEqual(t, os.Getpid(), entry.PID)
|
||||
assertNotEmpty(t, entry.Health)
|
||||
assertEqual(t, wd, entry.Project)
|
||||
assertEqual(t, exe, entry.Binary)
|
||||
|
||||
// Stop should unregister
|
||||
err = d.Stop()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
_, ok = reg.Get("test-app", "serve")
|
||||
assert.False(t, ok)
|
||||
assertFalse(t, ok)
|
||||
}
|
||||
|
||||
func TestDaemon_StartRollsBackOnRegistryFailure(t *testing.T) {
|
||||
|
|
@ -350,8 +347,8 @@ func TestDaemon_StartRollsBackOnRegistryFailure(t *testing.T) {
|
|||
|
||||
pidPath := filepath.Join(dir, "daemon.pid")
|
||||
regDir := filepath.Join(dir, "registry")
|
||||
require.NoError(t, os.MkdirAll(regDir, 0o755))
|
||||
require.NoError(t, os.Chmod(regDir, 0o555))
|
||||
requireNoError(t, os.MkdirAll(regDir, 0o755))
|
||||
requireNoError(t, os.Chmod(regDir, 0o555))
|
||||
|
||||
d := NewDaemon(DaemonOptions{
|
||||
PIDFile: pidPath,
|
||||
|
|
@ -364,20 +361,20 @@ func TestDaemon_StartRollsBackOnRegistryFailure(t *testing.T) {
|
|||
})
|
||||
|
||||
err := d.Start()
|
||||
require.Error(t, err)
|
||||
requireError(t, err)
|
||||
|
||||
_, statErr := os.Stat(pidPath)
|
||||
assert.True(t, os.IsNotExist(statErr))
|
||||
assertTrue(t, os.IsNotExist(statErr))
|
||||
|
||||
addr := d.HealthAddr()
|
||||
require.NotEmpty(t, addr)
|
||||
requireNotEmpty(t, addr)
|
||||
|
||||
client := &http.Client{Timeout: 250 * time.Millisecond}
|
||||
resp, reqErr := client.Get("http://" + addr + "/health")
|
||||
if resp != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
assert.Error(t, reqErr)
|
||||
assertError(t, reqErr)
|
||||
|
||||
assert.NoError(t, d.Stop())
|
||||
assertNoError(t, d.Stop())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@ package process
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServiceError(t *testing.T) {
|
||||
err := ServiceError("service failed", ErrContextRequired)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "service failed")
|
||||
assert.ErrorIs(t, err, ErrContextRequired)
|
||||
requireError(t, err)
|
||||
assertContains(t, err.Error(), "service failed")
|
||||
assertErrorIs(t, err, ErrContextRequired)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package exec_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -11,8 +12,6 @@ import (
|
|||
"time"
|
||||
|
||||
"dappco.re/go/core/process/exec"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// mockLogger captures log calls for testing
|
||||
|
|
@ -159,7 +158,9 @@ func TestDefaultLogger_IsConcurrentSafe(t *testing.T) {
|
|||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.NotNil(t, exec.DefaultLogger())
|
||||
if exec.DefaultLogger() == nil {
|
||||
t.Fatal("expected non-nil default logger")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommand_UsesDefaultLogger(t *testing.T) {
|
||||
|
|
@ -260,26 +261,42 @@ func TestCommand_Run_Background(t *testing.T) {
|
|||
func TestCommand_NilContextRejected(t *testing.T) {
|
||||
t.Run("start", func(t *testing.T) {
|
||||
err := exec.Command(nil, "echo", "test").Start()
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, exec.ErrCommandContextRequired)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, exec.ErrCommandContextRequired) {
|
||||
t.Fatalf("expected ErrCommandContextRequired, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("run", func(t *testing.T) {
|
||||
err := exec.Command(nil, "echo", "test").Run()
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, exec.ErrCommandContextRequired)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, exec.ErrCommandContextRequired) {
|
||||
t.Fatalf("expected ErrCommandContextRequired, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("output", func(t *testing.T) {
|
||||
_, err := exec.Command(nil, "echo", "test").Output()
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, exec.ErrCommandContextRequired)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, exec.ErrCommandContextRequired) {
|
||||
t.Fatalf("expected ErrCommandContextRequired, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("combined output", func(t *testing.T) {
|
||||
_, err := exec.Command(nil, "echo", "test").CombinedOutput()
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, exec.ErrCommandContextRequired)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, exec.ErrCommandContextRequired) {
|
||||
t.Fatalf("expected ErrCommandContextRequired, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
128
global_test.go
128
global_test.go
|
|
@ -9,8 +9,6 @@ import (
|
|||
"time"
|
||||
|
||||
framework "dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGlobal_DefaultNotInitialized(t *testing.T) {
|
||||
|
|
@ -22,46 +20,46 @@ func TestGlobal_DefaultNotInitialized(t *testing.T) {
|
|||
}
|
||||
}()
|
||||
|
||||
assert.Nil(t, Default())
|
||||
assertNil(t, Default())
|
||||
|
||||
_, err := Start(context.Background(), "echo", "test")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
_, err = Run(context.Background(), "echo", "test")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
_, err = Get("proc-1")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
_, err = Output("proc-1")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
err = Input("proc-1", "test")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
err = CloseStdin("proc-1")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
assert.Nil(t, List())
|
||||
assert.Nil(t, Running())
|
||||
assertNil(t, List())
|
||||
assertNil(t, Running())
|
||||
|
||||
err = Remove("proc-1")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
// Clear is a no-op without a default service.
|
||||
Clear()
|
||||
|
||||
err = Kill("proc-1")
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
err = KillPID(1234)
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
_, err = StartWithOptions(context.Background(), RunOptions{Command: "echo"})
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
|
||||
_, err = RunWithOptions(context.Background(), RunOptions{Command: "echo"})
|
||||
assert.ErrorIs(t, err, ErrServiceNotInitialized)
|
||||
assertErrorIs(t, err, ErrServiceNotInitialized)
|
||||
}
|
||||
|
||||
func newGlobalTestService(t *testing.T) *Service {
|
||||
|
|
@ -69,7 +67,7 @@ func newGlobalTestService(t *testing.T) *Service {
|
|||
c := framework.New()
|
||||
factory := NewService(Options{})
|
||||
raw, err := factory(c)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
return raw.(*Service)
|
||||
}
|
||||
|
||||
|
|
@ -85,13 +83,13 @@ func TestGlobal_SetDefault(t *testing.T) {
|
|||
svc := newGlobalTestService(t)
|
||||
|
||||
err := SetDefault(svc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, svc, Default())
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, svc, Default())
|
||||
})
|
||||
|
||||
t.Run("errors on nil", func(t *testing.T) {
|
||||
err := SetDefault(nil)
|
||||
assert.Error(t, err)
|
||||
assertError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -99,13 +97,13 @@ func TestGlobal_Register(t *testing.T) {
|
|||
c := framework.New()
|
||||
|
||||
result := Register(c)
|
||||
require.True(t, result.OK)
|
||||
requireTrue(t, result.OK)
|
||||
|
||||
svc, ok := result.Value.(*Service)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, svc)
|
||||
assert.NotNil(t, svc.ServiceRuntime)
|
||||
assert.Equal(t, DefaultBufferSize, svc.bufSize)
|
||||
requireTrue(t, ok)
|
||||
requireNotNil(t, svc)
|
||||
assertNotNil(t, svc.ServiceRuntime)
|
||||
assertEqual(t, DefaultBufferSize, svc.bufSize)
|
||||
}
|
||||
|
||||
func TestGlobal_ConcurrentDefault(t *testing.T) {
|
||||
|
|
@ -119,7 +117,7 @@ func TestGlobal_ConcurrentDefault(t *testing.T) {
|
|||
svc := newGlobalTestService(t)
|
||||
|
||||
err := SetDefault(svc)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
|
|
@ -127,8 +125,8 @@ func TestGlobal_ConcurrentDefault(t *testing.T) {
|
|||
go func() {
|
||||
defer wg.Done()
|
||||
s := Default()
|
||||
assert.NotNil(t, s)
|
||||
assert.Equal(t, svc, s)
|
||||
assertNotNil(t, s)
|
||||
assertEqual(t, svc, s)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
|
@ -159,7 +157,7 @@ func TestGlobal_ConcurrentSetDefault(t *testing.T) {
|
|||
wg.Wait()
|
||||
|
||||
final := Default()
|
||||
assert.NotNil(t, final)
|
||||
assertNotNil(t, final)
|
||||
|
||||
found := false
|
||||
for _, svc := range services {
|
||||
|
|
@ -168,7 +166,7 @@ func TestGlobal_ConcurrentSetDefault(t *testing.T) {
|
|||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Default should be one of the set services")
|
||||
assertTrue(t, found, "Default should be one of the set services")
|
||||
}
|
||||
|
||||
func TestGlobal_ConcurrentOperations(t *testing.T) {
|
||||
|
|
@ -182,7 +180,7 @@ func TestGlobal_ConcurrentOperations(t *testing.T) {
|
|||
svc := newGlobalTestService(t)
|
||||
|
||||
err := SetDefault(svc)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var processes []*Process
|
||||
|
|
@ -218,7 +216,7 @@ func TestGlobal_ConcurrentOperations(t *testing.T) {
|
|||
}
|
||||
procMu.Unlock()
|
||||
|
||||
assert.Len(t, processes, 20)
|
||||
assertLen(t, processes, 20)
|
||||
|
||||
var wg2 sync.WaitGroup
|
||||
for _, p := range processes {
|
||||
|
|
@ -226,8 +224,8 @@ func TestGlobal_ConcurrentOperations(t *testing.T) {
|
|||
go func(id string) {
|
||||
defer wg2.Done()
|
||||
got, err := Get(id)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, got)
|
||||
assertNoError(t, err)
|
||||
assertNotNil(t, got)
|
||||
}(p.ID)
|
||||
}
|
||||
wg2.Wait()
|
||||
|
|
@ -247,12 +245,12 @@ func TestGlobal_StartWithOptions(t *testing.T) {
|
|||
Command: "echo",
|
||||
Args: []string{"with", "options"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
assert.Equal(t, 0, proc.ExitCode)
|
||||
assert.Contains(t, proc.Output(), "with options")
|
||||
assertEqual(t, 0, proc.ExitCode)
|
||||
assertContains(t, proc.Output(), "with options")
|
||||
}
|
||||
|
||||
func TestGlobal_RunWithOptions(t *testing.T) {
|
||||
|
|
@ -269,8 +267,8 @@ func TestGlobal_RunWithOptions(t *testing.T) {
|
|||
Command: "echo",
|
||||
Args: []string{"run", "options"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, output, "run options")
|
||||
requireNoError(t, err)
|
||||
assertContains(t, output, "run options")
|
||||
}
|
||||
|
||||
func TestGlobal_Output(t *testing.T) {
|
||||
|
|
@ -284,12 +282,12 @@ func TestGlobal_Output(t *testing.T) {
|
|||
}()
|
||||
|
||||
proc, err := Start(context.Background(), "echo", "global-output")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc.Done()
|
||||
|
||||
output, err := Output(proc.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, output, "global-output")
|
||||
requireNoError(t, err)
|
||||
assertContains(t, output, "global-output")
|
||||
}
|
||||
|
||||
func TestGlobal_InputAndCloseStdin(t *testing.T) {
|
||||
|
|
@ -303,17 +301,17 @@ func TestGlobal_InputAndCloseStdin(t *testing.T) {
|
|||
}()
|
||||
|
||||
proc, err := Start(context.Background(), "cat")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = Input(proc.ID, "global-input\n")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = CloseStdin(proc.ID)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
assert.Contains(t, proc.Output(), "global-input")
|
||||
assertContains(t, proc.Output(), "global-input")
|
||||
}
|
||||
|
||||
func TestGlobal_Wait(t *testing.T) {
|
||||
|
|
@ -327,13 +325,13 @@ func TestGlobal_Wait(t *testing.T) {
|
|||
}()
|
||||
|
||||
proc, err := Start(context.Background(), "echo", "global-wait")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
info, err := Wait(proc.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, proc.ID, info.ID)
|
||||
assert.Equal(t, StatusExited, info.Status)
|
||||
assert.Equal(t, 0, info.ExitCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, proc.ID, info.ID)
|
||||
assertEqual(t, StatusExited, info.Status)
|
||||
assertEqual(t, 0, info.ExitCode)
|
||||
}
|
||||
|
||||
func TestGlobal_Signal(t *testing.T) {
|
||||
|
|
@ -347,10 +345,10 @@ func TestGlobal_Signal(t *testing.T) {
|
|||
}()
|
||||
|
||||
proc, err := Start(context.Background(), "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = Signal(proc.ID, syscall.SIGTERM)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -370,7 +368,7 @@ func TestGlobal_SignalPID(t *testing.T) {
|
|||
}()
|
||||
|
||||
cmd := exec.Command("sleep", "60")
|
||||
require.NoError(t, cmd.Start())
|
||||
requireNoError(t, cmd.Start())
|
||||
|
||||
waitCh := make(chan error, 1)
|
||||
go func() {
|
||||
|
|
@ -388,11 +386,11 @@ func TestGlobal_SignalPID(t *testing.T) {
|
|||
})
|
||||
|
||||
err := SignalPID(cmd.Process.Pid, syscall.SIGTERM)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
select {
|
||||
case err := <-waitCh:
|
||||
require.Error(t, err)
|
||||
requireError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("unmanaged process should have been signalled through the global helper")
|
||||
}
|
||||
|
|
@ -412,17 +410,17 @@ func TestGlobal_Running(t *testing.T) {
|
|||
defer cancel()
|
||||
|
||||
proc, err := Start(ctx, "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
running := Running()
|
||||
assert.Len(t, running, 1)
|
||||
assert.Equal(t, proc.ID, running[0].ID)
|
||||
assertLen(t, running, 1)
|
||||
assertEqual(t, proc.ID, running[0].ID)
|
||||
|
||||
cancel()
|
||||
<-proc.Done()
|
||||
|
||||
running = Running()
|
||||
assert.Len(t, running, 0)
|
||||
assertLen(t, running, 0)
|
||||
}
|
||||
|
||||
func TestGlobal_RemoveAndClear(t *testing.T) {
|
||||
|
|
@ -436,21 +434,21 @@ func TestGlobal_RemoveAndClear(t *testing.T) {
|
|||
}()
|
||||
|
||||
proc, err := Start(context.Background(), "echo", "remove-me")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc.Done()
|
||||
|
||||
err = Remove(proc.ID)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
_, err = Get(proc.ID)
|
||||
require.ErrorIs(t, err, ErrProcessNotFound)
|
||||
requireErrorIs(t, err, ErrProcessNotFound)
|
||||
|
||||
proc2, err := Start(context.Background(), "echo", "clear-me")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc2.Done()
|
||||
|
||||
Clear()
|
||||
|
||||
_, err = Get(proc2.ID)
|
||||
require.ErrorIs(t, err, ErrProcessNotFound)
|
||||
requireErrorIs(t, err, ErrProcessNotFound)
|
||||
}
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -10,7 +10,6 @@ require (
|
|||
dappco.re/go/core/ws v0.4.0
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/stretchr/testify v1.11.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -28,7 +27,6 @@ require (
|
|||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/authz v1.0.6 // indirect
|
||||
|
|
@ -75,7 +73,6 @@ require (
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.18.0 // indirect
|
||||
|
|
|
|||
5
go.sum
5
go.sum
|
|
@ -7,6 +7,7 @@ dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5
|
|||
dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc=
|
||||
dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs=
|
||||
dappco.re/go/core/ws v0.4.0 h1:yEDV9whXyo+GWzBSjuB3NiLiH2bmBPBWD6rydwHyBn8=
|
||||
dappco.re/go/core/ws v0.4.0/go.mod h1:L1rrgW6zU+DztcVBJW2yO5Lm3rGXpyUMOA8OL9zsAok=
|
||||
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
|
||||
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
|
||||
github.com/99designs/gqlgen v0.17.88 h1:neMQDgehMwT1vYIOx/w5ZYPUU/iMNAJzRO44I5Intoc=
|
||||
|
|
@ -249,6 +250,7 @@ golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
|
|
@ -258,6 +260,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -272,6 +275,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
|
@ -280,6 +284,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
|
|
|||
|
|
@ -4,47 +4,44 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHealthServer_Endpoints(t *testing.T) {
|
||||
hs := NewHealthServer("127.0.0.1:0")
|
||||
assert.True(t, hs.Ready())
|
||||
assertTrue(t, hs.Ready())
|
||||
err := hs.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
defer func() { _ = hs.Stop(context.Background()) }()
|
||||
|
||||
addr := hs.Addr()
|
||||
require.NotEmpty(t, addr)
|
||||
requireNotEmpty(t, addr)
|
||||
|
||||
resp, err := http.Get("http://" + addr + "/health")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
resp, err = http.Get("http://" + addr + "/ready")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
hs.SetReady(false)
|
||||
assert.False(t, hs.Ready())
|
||||
assertFalse(t, hs.Ready())
|
||||
|
||||
resp, err = http.Get("http://" + addr + "/ready")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, http.StatusServiceUnavailable, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
func TestHealthServer_Ready(t *testing.T) {
|
||||
hs := NewHealthServer("127.0.0.1:0")
|
||||
|
||||
assert.True(t, hs.Ready())
|
||||
assertTrue(t, hs.Ready())
|
||||
|
||||
hs.SetReady(false)
|
||||
assert.False(t, hs.Ready())
|
||||
assertFalse(t, hs.Ready())
|
||||
}
|
||||
|
||||
func TestHealthServer_WithChecks(t *testing.T) {
|
||||
|
|
@ -53,27 +50,27 @@ func TestHealthServer_WithChecks(t *testing.T) {
|
|||
healthy := true
|
||||
hs.AddCheck(func() error {
|
||||
if !healthy {
|
||||
return assert.AnError
|
||||
return errSentinel
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err := hs.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
defer func() { _ = hs.Stop(context.Background()) }()
|
||||
|
||||
addr := hs.Addr()
|
||||
|
||||
resp, err := http.Get("http://" + addr + "/health")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
healthy = false
|
||||
|
||||
resp, err = http.Get("http://" + addr + "/health")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, http.StatusServiceUnavailable, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
|
|
@ -84,14 +81,14 @@ func TestHealthServer_NilCheckIgnored(t *testing.T) {
|
|||
hs.AddCheck(check)
|
||||
|
||||
err := hs.Start()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
defer func() { _ = hs.Stop(context.Background()) }()
|
||||
|
||||
addr := hs.Addr()
|
||||
|
||||
resp, err := http.Get("http://" + addr + "/health")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
requireNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, resp.StatusCode)
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
|
|
@ -100,49 +97,49 @@ func TestHealthServer_ChecksSnapshotIsStable(t *testing.T) {
|
|||
|
||||
hs.AddCheck(func() error { return nil })
|
||||
snapshot := hs.checksSnapshot()
|
||||
hs.AddCheck(func() error { return assert.AnError })
|
||||
hs.AddCheck(func() error { return errSentinel })
|
||||
|
||||
require.Len(t, snapshot, 1)
|
||||
require.NotNil(t, snapshot[0])
|
||||
requireLen(t, snapshot, 1)
|
||||
requireNotNil(t, snapshot[0])
|
||||
}
|
||||
|
||||
func TestWaitForHealth_Reachable(t *testing.T) {
|
||||
hs := NewHealthServer("127.0.0.1:0")
|
||||
require.NoError(t, hs.Start())
|
||||
requireNoError(t, hs.Start())
|
||||
defer func() { _ = hs.Stop(context.Background()) }()
|
||||
|
||||
ok := WaitForHealth(hs.Addr(), 2_000)
|
||||
assert.True(t, ok)
|
||||
assertTrue(t, ok)
|
||||
}
|
||||
|
||||
func TestWaitForHealth_Unreachable(t *testing.T) {
|
||||
ok := WaitForHealth("127.0.0.1:19999", 500)
|
||||
assert.False(t, ok)
|
||||
assertFalse(t, ok)
|
||||
}
|
||||
|
||||
func TestWaitForReady_Reachable(t *testing.T) {
|
||||
hs := NewHealthServer("127.0.0.1:0")
|
||||
require.NoError(t, hs.Start())
|
||||
requireNoError(t, hs.Start())
|
||||
defer func() { _ = hs.Stop(context.Background()) }()
|
||||
|
||||
ok := WaitForReady(hs.Addr(), 2_000)
|
||||
assert.True(t, ok)
|
||||
assertTrue(t, ok)
|
||||
}
|
||||
|
||||
func TestWaitForReady_Unreachable(t *testing.T) {
|
||||
ok := WaitForReady("127.0.0.1:19999", 500)
|
||||
assert.False(t, ok)
|
||||
assertFalse(t, ok)
|
||||
}
|
||||
|
||||
func TestHealthServer_StopMarksNotReady(t *testing.T) {
|
||||
hs := NewHealthServer("127.0.0.1:0")
|
||||
require.NoError(t, hs.Start())
|
||||
requireNoError(t, hs.Start())
|
||||
|
||||
require.NotEmpty(t, hs.Addr())
|
||||
assert.True(t, hs.Ready())
|
||||
requireNotEmpty(t, hs.Addr())
|
||||
assertTrue(t, hs.Ready())
|
||||
|
||||
require.NoError(t, hs.Stop(context.Background()))
|
||||
requireNoError(t, hs.Stop(context.Background()))
|
||||
|
||||
assert.False(t, hs.Ready())
|
||||
assert.NotEmpty(t, hs.Addr())
|
||||
assertFalse(t, hs.Ready())
|
||||
assertNotEmpty(t, hs.Addr())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,72 +5,70 @@ import (
|
|||
"testing"
|
||||
|
||||
"dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPIDFile_Acquire_Good(t *testing.T) {
|
||||
pidPath := core.JoinPath(t.TempDir(), "test.pid")
|
||||
pid := NewPIDFile(pidPath)
|
||||
err := pid.Acquire()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
data, err := os.ReadFile(pidPath)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, data)
|
||||
requireNoError(t, err)
|
||||
assertNotEmpty(t, data)
|
||||
err = pid.Release()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
_, err = os.Stat(pidPath)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
assertTrue(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestPIDFile_AcquireStale_Good(t *testing.T) {
|
||||
pidPath := core.JoinPath(t.TempDir(), "stale.pid")
|
||||
require.NoError(t, os.WriteFile(pidPath, []byte("999999999"), 0644))
|
||||
requireNoError(t, os.WriteFile(pidPath, []byte("999999999"), 0644))
|
||||
pid := NewPIDFile(pidPath)
|
||||
err := pid.Acquire()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
err = pid.Release()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
}
|
||||
|
||||
func TestPIDFile_CreateDirectory_Good(t *testing.T) {
|
||||
pidPath := core.JoinPath(t.TempDir(), "subdir", "nested", "test.pid")
|
||||
pid := NewPIDFile(pidPath)
|
||||
err := pid.Acquire()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
err = pid.Release()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
}
|
||||
|
||||
func TestPIDFile_Path_Good(t *testing.T) {
|
||||
pid := NewPIDFile("/tmp/test.pid")
|
||||
assert.Equal(t, "/tmp/test.pid", pid.Path())
|
||||
assertEqual(t, "/tmp/test.pid", pid.Path())
|
||||
}
|
||||
|
||||
func TestPIDFile_Release_MissingIsNoop(t *testing.T) {
|
||||
pidPath := core.JoinPath(t.TempDir(), "absent.pid")
|
||||
pid := NewPIDFile(pidPath)
|
||||
require.NoError(t, pid.Release())
|
||||
requireNoError(t, pid.Release())
|
||||
}
|
||||
|
||||
func TestReadPID_Missing_Bad(t *testing.T) {
|
||||
pid, running := ReadPID("/nonexistent/path.pid")
|
||||
assert.Equal(t, 0, pid)
|
||||
assert.False(t, running)
|
||||
assertEqual(t, 0, pid)
|
||||
assertFalse(t, running)
|
||||
}
|
||||
|
||||
func TestReadPID_Invalid_Bad(t *testing.T) {
|
||||
path := core.JoinPath(t.TempDir(), "bad.pid")
|
||||
require.NoError(t, os.WriteFile(path, []byte("notanumber"), 0644))
|
||||
requireNoError(t, os.WriteFile(path, []byte("notanumber"), 0644))
|
||||
pid, running := ReadPID(path)
|
||||
assert.Equal(t, 0, pid)
|
||||
assert.False(t, running)
|
||||
assertEqual(t, 0, pid)
|
||||
assertFalse(t, running)
|
||||
}
|
||||
|
||||
func TestReadPID_Stale_Bad(t *testing.T) {
|
||||
path := core.JoinPath(t.TempDir(), "stale.pid")
|
||||
require.NoError(t, os.WriteFile(path, []byte("999999999"), 0644))
|
||||
requireNoError(t, os.WriteFile(path, []byte("999999999"), 0644))
|
||||
pid, running := ReadPID(path)
|
||||
assert.Equal(t, 999999999, pid)
|
||||
assert.False(t, running)
|
||||
assertEqual(t, 999999999, pid)
|
||||
assertFalse(t, running)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ import (
|
|||
corews "dappco.re/go/core/ws"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -31,33 +29,33 @@ func init() {
|
|||
|
||||
func TestProcessProvider_Name_Good(t *testing.T) {
|
||||
p := processapi.NewProvider(nil, nil, nil)
|
||||
assert.Equal(t, "process", p.Name())
|
||||
assertEqual(t, "process", p.Name())
|
||||
}
|
||||
|
||||
func TestProcessProvider_BasePath_Good(t *testing.T) {
|
||||
p := processapi.NewProvider(nil, nil, nil)
|
||||
assert.Equal(t, "/api/process", p.BasePath())
|
||||
assertEqual(t, "/api/process", p.BasePath())
|
||||
}
|
||||
|
||||
func TestProcessProvider_Channels_Good(t *testing.T) {
|
||||
p := processapi.NewProvider(nil, nil, nil)
|
||||
channels := p.Channels()
|
||||
assert.Contains(t, channels, "process.daemon.started")
|
||||
assert.Contains(t, channels, "process.daemon.stopped")
|
||||
assert.Contains(t, channels, "process.daemon.health")
|
||||
assertContains(t, channels, "process.daemon.started")
|
||||
assertContains(t, channels, "process.daemon.stopped")
|
||||
assertContains(t, channels, "process.daemon.health")
|
||||
}
|
||||
|
||||
func TestProcessProvider_Describe_Good(t *testing.T) {
|
||||
p := processapi.NewProvider(nil, nil, nil)
|
||||
descs := p.Describe()
|
||||
assert.GreaterOrEqual(t, len(descs), 5)
|
||||
assertGreaterOrEqual(t, len(descs), 5)
|
||||
|
||||
// Verify all descriptions have required fields
|
||||
for _, d := range descs {
|
||||
assert.NotEmpty(t, d.Method)
|
||||
assert.NotEmpty(t, d.Path)
|
||||
assert.NotEmpty(t, d.Summary)
|
||||
assert.NotEmpty(t, d.Tags)
|
||||
assertNotEmpty(t, d.Method)
|
||||
assertNotEmpty(t, d.Path)
|
||||
assertNotEmpty(t, d.Summary)
|
||||
assertNotEmpty(t, d.Tags)
|
||||
}
|
||||
|
||||
foundPipelineRoute := false
|
||||
|
|
@ -70,8 +68,8 @@ func TestProcessProvider_Describe_Good(t *testing.T) {
|
|||
foundSignalRoute = true
|
||||
}
|
||||
}
|
||||
assert.True(t, foundPipelineRoute, "pipeline route should be described")
|
||||
assert.True(t, foundSignalRoute, "signal route should be described")
|
||||
assertTrue(t, foundPipelineRoute, "pipeline route should be described")
|
||||
assertTrue(t, foundSignalRoute, "signal route should be described")
|
||||
}
|
||||
|
||||
func TestProcessProvider_ListDaemons_Good(t *testing.T) {
|
||||
|
|
@ -85,18 +83,18 @@ func TestProcessProvider_ListDaemons_Good(t *testing.T) {
|
|||
req, _ := http.NewRequest("GET", "/api/process/daemons", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[[]any]
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Success)
|
||||
requireNoError(t, err)
|
||||
assertTrue(t, resp.Success)
|
||||
}
|
||||
|
||||
func TestProcessProvider_ListDaemons_BroadcastsStarted_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
registry := newTestRegistry(dir)
|
||||
require.NoError(t, registry.Register(process.DaemonEntry{
|
||||
requireNoError(t, registry.Register(process.DaemonEntry{
|
||||
Code: "test",
|
||||
Daemon: "serve",
|
||||
PID: os.Getpid(),
|
||||
|
|
@ -114,7 +112,7 @@ func TestProcessProvider_ListDaemons_BroadcastsStarted_Good(t *testing.T) {
|
|||
conn := connectWS(t, server.URL)
|
||||
defer conn.Close()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
requireEventually(t, func() bool {
|
||||
return hub.ClientCount() == 1
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
|
|
@ -123,16 +121,16 @@ func TestProcessProvider_ListDaemons_BroadcastsStarted_Good(t *testing.T) {
|
|||
req, _ := http.NewRequest("GET", "/api/process/daemons", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
events := readWSEvents(t, conn, "process.daemon.started")
|
||||
started := events["process.daemon.started"]
|
||||
require.NotNil(t, started)
|
||||
requireNotNil(t, started)
|
||||
|
||||
startedData := started.Data.(map[string]any)
|
||||
assert.Equal(t, "test", startedData["code"])
|
||||
assert.Equal(t, "serve", startedData["daemon"])
|
||||
assert.Equal(t, float64(os.Getpid()), startedData["pid"])
|
||||
assertEqual(t, "test", startedData["code"])
|
||||
assertEqual(t, "serve", startedData["daemon"])
|
||||
assertEqual(t, float64(os.Getpid()), startedData["pid"])
|
||||
}
|
||||
|
||||
func TestProcessProvider_GetDaemon_Bad(t *testing.T) {
|
||||
|
|
@ -145,7 +143,7 @@ func TestProcessProvider_GetDaemon_Bad(t *testing.T) {
|
|||
req, _ := http.NewRequest("GET", "/api/process/daemons/test/nonexistent", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assertEqual(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestProcessProvider_HealthCheck_Bad(t *testing.T) {
|
||||
|
|
@ -159,7 +157,7 @@ func TestProcessProvider_HealthCheck_Bad(t *testing.T) {
|
|||
defer healthSrv.Close()
|
||||
|
||||
hostPort := strings.TrimPrefix(healthSrv.URL, "http://")
|
||||
require.NoError(t, registry.Register(process.DaemonEntry{
|
||||
requireNoError(t, registry.Register(process.DaemonEntry{
|
||||
Code: "test",
|
||||
Daemon: "broken",
|
||||
PID: os.Getpid(),
|
||||
|
|
@ -173,40 +171,40 @@ func TestProcessProvider_HealthCheck_Bad(t *testing.T) {
|
|||
req, _ := http.NewRequest("GET", "/api/process/daemons/test/broken/health", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
|
||||
assertEqual(t, http.StatusServiceUnavailable, w.Code)
|
||||
|
||||
var resp goapi.Response[map[string]any]
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
|
||||
assert.Equal(t, false, resp.Data["healthy"])
|
||||
assert.Equal(t, hostPort, resp.Data["address"])
|
||||
assert.Equal(t, "upstream health check failed", resp.Data["reason"])
|
||||
assertEqual(t, false, resp.Data["healthy"])
|
||||
assertEqual(t, hostPort, resp.Data["address"])
|
||||
assertEqual(t, "upstream health check failed", resp.Data["reason"])
|
||||
}
|
||||
|
||||
func TestProcessProvider_RegistersAsRouteGroup_Good(t *testing.T) {
|
||||
p := processapi.NewProvider(nil, nil, nil)
|
||||
|
||||
engine, err := goapi.New()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
engine.Register(p)
|
||||
assert.Len(t, engine.Groups(), 1)
|
||||
assert.Equal(t, "process", engine.Groups()[0].Name())
|
||||
assertLen(t, engine.Groups(), 1)
|
||||
assertEqual(t, "process", engine.Groups()[0].Name())
|
||||
}
|
||||
|
||||
func TestProcessProvider_Channels_RegisterAsStreamGroup_Good(t *testing.T) {
|
||||
p := processapi.NewProvider(nil, nil, nil)
|
||||
|
||||
engine, err := goapi.New()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
engine.Register(p)
|
||||
|
||||
// Engine.Channels() discovers StreamGroups
|
||||
channels := engine.Channels()
|
||||
assert.Contains(t, channels, "process.daemon.started")
|
||||
assertContains(t, channels, "process.daemon.started")
|
||||
}
|
||||
|
||||
func TestProcessProvider_RunPipeline_Good(t *testing.T) {
|
||||
|
|
@ -224,19 +222,19 @@ func TestProcessProvider_RunPipeline_Good(t *testing.T) {
|
|||
]
|
||||
}`)
|
||||
req, err := http.NewRequest("POST", "/api/process/pipelines/run", body)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[process.RunAllResult]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Success)
|
||||
assert.Equal(t, 2, resp.Data.Passed)
|
||||
assert.Len(t, resp.Data.Results, 2)
|
||||
requireNoError(t, err)
|
||||
assertTrue(t, resp.Success)
|
||||
assertEqual(t, 2, resp.Data.Passed)
|
||||
assertLen(t, resp.Data.Results, 2)
|
||||
}
|
||||
|
||||
func TestProcessProvider_RunPipeline_Unavailable(t *testing.T) {
|
||||
|
|
@ -246,18 +244,18 @@ func TestProcessProvider_RunPipeline_Unavailable(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/pipelines/run", strings.NewReader(`{"mode":"all","specs":[]}`))
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
|
||||
assertEqual(t, http.StatusServiceUnavailable, w.Code)
|
||||
}
|
||||
|
||||
func TestProcessProvider_ListProcesses_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "echo", "hello-api")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc.Done()
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
|
|
@ -265,28 +263,28 @@ func TestProcessProvider_ListProcesses_Good(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/process/processes", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[[]process.Info]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
require.Len(t, resp.Data, 1)
|
||||
assert.Equal(t, proc.ID, resp.Data[0].ID)
|
||||
assert.Equal(t, "echo", resp.Data[0].Command)
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
requireLen(t, resp.Data, 1)
|
||||
assertEqual(t, proc.ID, resp.Data[0].ID)
|
||||
assertEqual(t, "echo", resp.Data[0].Command)
|
||||
}
|
||||
|
||||
func TestProcessProvider_ListProcesses_RunningOnly_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
|
||||
runningProc, err := svc.Start(context.Background(), "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
exitedProc, err := svc.Start(context.Background(), "echo", "done")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-exitedProc.Done()
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
|
|
@ -294,20 +292,20 @@ func TestProcessProvider_ListProcesses_RunningOnly_Good(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/process/processes?runningOnly=true", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[[]process.Info]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
require.Len(t, resp.Data, 1)
|
||||
assert.Equal(t, runningProc.ID, resp.Data[0].ID)
|
||||
assert.Equal(t, process.StatusRunning, resp.Data[0].Status)
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
requireLen(t, resp.Data, 1)
|
||||
assertEqual(t, runningProc.ID, resp.Data[0].ID)
|
||||
assertEqual(t, process.StatusRunning, resp.Data[0].Status)
|
||||
|
||||
require.NoError(t, svc.Kill(runningProc.ID))
|
||||
requireNoError(t, svc.Kill(runningProc.ID))
|
||||
<-runningProc.Done()
|
||||
}
|
||||
|
||||
|
|
@ -323,26 +321,26 @@ func TestProcessProvider_StartProcess_Good(t *testing.T) {
|
|||
"killGroup": true
|
||||
}`)
|
||||
req, err := http.NewRequest("POST", "/api/process/processes", body)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[process.Info]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, "sleep", resp.Data.Command)
|
||||
assert.Equal(t, process.StatusRunning, resp.Data.Status)
|
||||
assert.True(t, resp.Data.Running)
|
||||
assert.NotEmpty(t, resp.Data.ID)
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertEqual(t, "sleep", resp.Data.Command)
|
||||
assertEqual(t, process.StatusRunning, resp.Data.Status)
|
||||
assertTrue(t, resp.Data.Running)
|
||||
assertNotEmpty(t, resp.Data.ID)
|
||||
|
||||
managed, err := svc.Get(resp.Data.ID)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, svc.Kill(managed.ID))
|
||||
requireNoError(t, err)
|
||||
requireNoError(t, svc.Kill(managed.ID))
|
||||
select {
|
||||
case <-managed.Done():
|
||||
case <-time.After(5 * time.Second):
|
||||
|
|
@ -360,25 +358,25 @@ func TestProcessProvider_RunProcess_Good(t *testing.T) {
|
|||
"args": ["run-check"]
|
||||
}`)
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/run", body)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[string]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Contains(t, resp.Data, "run-check")
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertContains(t, resp.Data, "run-check")
|
||||
}
|
||||
|
||||
func TestProcessProvider_GetProcess_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "echo", "single")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc.Done()
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
|
|
@ -386,23 +384,23 @@ func TestProcessProvider_GetProcess_Good(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/process/processes/"+proc.ID, nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[process.Info]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, proc.ID, resp.Data.ID)
|
||||
assert.Equal(t, "echo", resp.Data.Command)
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertEqual(t, proc.ID, resp.Data.ID)
|
||||
assertEqual(t, "echo", resp.Data.Command)
|
||||
}
|
||||
|
||||
func TestProcessProvider_GetProcessOutput_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "echo", "output-check")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc.Done()
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
|
|
@ -410,97 +408,97 @@ func TestProcessProvider_GetProcessOutput_Good(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/process/processes/"+proc.ID+"/output", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[string]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Contains(t, resp.Data, "output-check")
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertContains(t, resp.Data, "output-check")
|
||||
}
|
||||
|
||||
func TestProcessProvider_WaitProcess_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "echo", "wait-check")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/wait", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[process.Info]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, proc.ID, resp.Data.ID)
|
||||
assert.Equal(t, process.StatusExited, resp.Data.Status)
|
||||
assert.Equal(t, 0, resp.Data.ExitCode)
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertEqual(t, proc.ID, resp.Data.ID)
|
||||
assertEqual(t, process.StatusExited, resp.Data.Status)
|
||||
assertEqual(t, 0, resp.Data.ExitCode)
|
||||
}
|
||||
|
||||
func TestProcessProvider_WaitProcess_NonZeroExit_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "sh", "-c", "exit 7")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/wait", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, w.Code)
|
||||
assertEqual(t, http.StatusConflict, w.Code)
|
||||
|
||||
var resp goapi.Response[any]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.False(t, resp.Success)
|
||||
require.NotNil(t, resp.Error)
|
||||
assert.Equal(t, "wait_failed", resp.Error.Code)
|
||||
assert.Contains(t, resp.Error.Message, "process exited with code 7")
|
||||
requireNoError(t, err)
|
||||
requireFalse(t, resp.Success)
|
||||
requireNotNil(t, resp.Error)
|
||||
assertEqual(t, "wait_failed", resp.Error.Code)
|
||||
assertContains(t, resp.Error.Message, "process exited with code 7")
|
||||
|
||||
details, ok := resp.Error.Details.(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "exited", details["status"])
|
||||
assert.Equal(t, float64(7), details["exitCode"])
|
||||
assert.Equal(t, proc.ID, details["id"])
|
||||
requireTrue(t, ok)
|
||||
assertEqual(t, "exited", details["status"])
|
||||
assertEqual(t, float64(7), details["exitCode"])
|
||||
assertEqual(t, proc.ID, details["id"])
|
||||
}
|
||||
|
||||
func TestProcessProvider_InputAndCloseStdin_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "cat")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
|
||||
inputReq := strings.NewReader("{\"input\":\"hello-api\\n\"}")
|
||||
inputHTTPReq, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/input", inputReq)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
inputHTTPReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
inputResp := httptest.NewRecorder()
|
||||
r.ServeHTTP(inputResp, inputHTTPReq)
|
||||
|
||||
assert.Equal(t, http.StatusOK, inputResp.Code)
|
||||
assertEqual(t, http.StatusOK, inputResp.Code)
|
||||
|
||||
closeReq, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/close-stdin", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
closeResp := httptest.NewRecorder()
|
||||
r.ServeHTTP(closeResp, closeReq)
|
||||
|
||||
assert.Equal(t, http.StatusOK, closeResp.Code)
|
||||
assertEqual(t, http.StatusOK, closeResp.Code)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -508,36 +506,36 @@ func TestProcessProvider_InputAndCloseStdin_Good(t *testing.T) {
|
|||
t.Fatal("process should have exited after stdin was closed")
|
||||
}
|
||||
|
||||
assert.Contains(t, proc.Output(), "hello-api")
|
||||
assertContains(t, proc.Output(), "hello-api")
|
||||
}
|
||||
|
||||
func TestProcessProvider_KillProcess_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/kill", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[map[string]any]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, true, resp.Data["killed"])
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertEqual(t, true, resp.Data["killed"])
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("process should have been killed")
|
||||
}
|
||||
assert.Equal(t, process.StatusKilled, proc.Status)
|
||||
assertEqual(t, process.StatusKilled, proc.Status)
|
||||
}
|
||||
|
||||
func TestProcessProvider_KillProcess_ByPID_Good(t *testing.T) {
|
||||
|
|
@ -546,7 +544,7 @@ func TestProcessProvider_KillProcess_ByPID_Good(t *testing.T) {
|
|||
r := setupRouter(p)
|
||||
|
||||
cmd := exec.Command("sleep", "60")
|
||||
require.NoError(t, cmd.Start())
|
||||
requireNoError(t, cmd.Start())
|
||||
|
||||
waitCh := make(chan error, 1)
|
||||
go func() {
|
||||
|
|
@ -565,20 +563,20 @@ func TestProcessProvider_KillProcess_ByPID_Good(t *testing.T) {
|
|||
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+strconv.Itoa(cmd.Process.Pid)+"/kill", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[map[string]any]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, true, resp.Data["killed"])
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertEqual(t, true, resp.Data["killed"])
|
||||
|
||||
select {
|
||||
case err := <-waitCh:
|
||||
require.Error(t, err)
|
||||
requireError(t, err)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("unmanaged process should have been killed by PID")
|
||||
}
|
||||
|
|
@ -587,31 +585,31 @@ func TestProcessProvider_KillProcess_ByPID_Good(t *testing.T) {
|
|||
func TestProcessProvider_SignalProcess_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/signal", strings.NewReader(`{"signal":"SIGTERM"}`))
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[map[string]any]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, true, resp.Data["signalled"])
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertEqual(t, true, resp.Data["signalled"])
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("process should have been signalled")
|
||||
}
|
||||
assert.Equal(t, process.StatusKilled, proc.Status)
|
||||
assertEqual(t, process.StatusKilled, proc.Status)
|
||||
}
|
||||
|
||||
func TestProcessProvider_SignalProcess_ByPID_Good(t *testing.T) {
|
||||
|
|
@ -620,7 +618,7 @@ func TestProcessProvider_SignalProcess_ByPID_Good(t *testing.T) {
|
|||
r := setupRouter(p)
|
||||
|
||||
cmd := exec.Command("sleep", "60")
|
||||
require.NoError(t, cmd.Start())
|
||||
requireNoError(t, cmd.Start())
|
||||
|
||||
waitCh := make(chan error, 1)
|
||||
go func() {
|
||||
|
|
@ -639,21 +637,21 @@ func TestProcessProvider_SignalProcess_ByPID_Good(t *testing.T) {
|
|||
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+strconv.Itoa(cmd.Process.Pid)+"/signal", strings.NewReader(`{"signal":"SIGTERM"}`))
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[map[string]any]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, true, resp.Data["signalled"])
|
||||
requireNoError(t, err)
|
||||
requireTrue(t, resp.Success)
|
||||
assertEqual(t, true, resp.Data["signalled"])
|
||||
|
||||
select {
|
||||
case err := <-waitCh:
|
||||
require.Error(t, err)
|
||||
requireError(t, err)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("unmanaged process should have been signalled by PID")
|
||||
}
|
||||
|
|
@ -662,21 +660,21 @@ func TestProcessProvider_SignalProcess_ByPID_Good(t *testing.T) {
|
|||
func TestProcessProvider_SignalProcess_InvalidSignal_Bad(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/signal", strings.NewReader(`{"signal":"NOPE"}`))
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.True(t, proc.IsRunning())
|
||||
assertEqual(t, http.StatusBadRequest, w.Code)
|
||||
assertTrue(t, proc.IsRunning())
|
||||
|
||||
require.NoError(t, svc.Kill(proc.ID))
|
||||
requireNoError(t, svc.Kill(proc.ID))
|
||||
<-proc.Done()
|
||||
}
|
||||
|
||||
|
|
@ -695,35 +693,35 @@ func TestProcessProvider_BroadcastsProcessEvents_Good(t *testing.T) {
|
|||
conn := connectWS(t, server.URL)
|
||||
defer conn.Close()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
requireEventually(t, func() bool {
|
||||
return hub.ClientCount() == 1
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "sh", "-c", "echo live-event")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc.Done()
|
||||
|
||||
events := readWSEvents(t, conn, "process.started", "process.output", "process.exited")
|
||||
|
||||
started := events["process.started"]
|
||||
require.NotNil(t, started)
|
||||
requireNotNil(t, started)
|
||||
startedData := started.Data.(map[string]any)
|
||||
assert.Equal(t, proc.ID, startedData["id"])
|
||||
assert.Equal(t, "sh", startedData["command"])
|
||||
assert.Equal(t, float64(proc.Info().PID), startedData["pid"])
|
||||
assertEqual(t, proc.ID, startedData["id"])
|
||||
assertEqual(t, "sh", startedData["command"])
|
||||
assertEqual(t, float64(proc.Info().PID), startedData["pid"])
|
||||
|
||||
output := events["process.output"]
|
||||
require.NotNil(t, output)
|
||||
requireNotNil(t, output)
|
||||
outputData := output.Data.(map[string]any)
|
||||
assert.Equal(t, proc.ID, outputData["id"])
|
||||
assert.Equal(t, "stdout", outputData["stream"])
|
||||
assert.Contains(t, outputData["line"], "live-event")
|
||||
assertEqual(t, proc.ID, outputData["id"])
|
||||
assertEqual(t, "stdout", outputData["stream"])
|
||||
assertContains(t, outputData["line"], "live-event")
|
||||
|
||||
exited := events["process.exited"]
|
||||
require.NotNil(t, exited)
|
||||
requireNotNil(t, exited)
|
||||
exitedData := exited.Data.(map[string]any)
|
||||
assert.Equal(t, proc.ID, exitedData["id"])
|
||||
assert.Equal(t, float64(0), exitedData["exitCode"])
|
||||
assertEqual(t, proc.ID, exitedData["id"])
|
||||
assertEqual(t, float64(0), exitedData["exitCode"])
|
||||
}
|
||||
|
||||
func TestProcessProvider_BroadcastsKilledEvents_Good(t *testing.T) {
|
||||
|
|
@ -741,14 +739,14 @@ func TestProcessProvider_BroadcastsKilledEvents_Good(t *testing.T) {
|
|||
conn := connectWS(t, server.URL)
|
||||
defer conn.Close()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
requireEventually(t, func() bool {
|
||||
return hub.ClientCount() == 1
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
require.NoError(t, svc.Kill(proc.ID))
|
||||
requireNoError(t, svc.Kill(proc.ID))
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -759,17 +757,17 @@ func TestProcessProvider_BroadcastsKilledEvents_Good(t *testing.T) {
|
|||
events := readWSEvents(t, conn, "process.killed", "process.exited")
|
||||
|
||||
killed := events["process.killed"]
|
||||
require.NotNil(t, killed)
|
||||
requireNotNil(t, killed)
|
||||
killedData := killed.Data.(map[string]any)
|
||||
assert.Equal(t, proc.ID, killedData["id"])
|
||||
assert.Equal(t, "SIGKILL", killedData["signal"])
|
||||
assert.Equal(t, float64(-1), killedData["exitCode"])
|
||||
assertEqual(t, proc.ID, killedData["id"])
|
||||
assertEqual(t, "SIGKILL", killedData["signal"])
|
||||
assertEqual(t, float64(-1), killedData["exitCode"])
|
||||
|
||||
exited := events["process.exited"]
|
||||
require.NotNil(t, exited)
|
||||
requireNotNil(t, exited)
|
||||
exitedData := exited.Data.(map[string]any)
|
||||
assert.Equal(t, proc.ID, exitedData["id"])
|
||||
assert.Equal(t, float64(-1), exitedData["exitCode"])
|
||||
assertEqual(t, proc.ID, exitedData["id"])
|
||||
assertEqual(t, float64(-1), exitedData["exitCode"])
|
||||
}
|
||||
|
||||
func TestProcessProvider_ProcessRoutes_Unavailable(t *testing.T) {
|
||||
|
|
@ -797,9 +795,9 @@ func TestProcessProvider_ProcessRoutes_Unavailable(t *testing.T) {
|
|||
method = "POST"
|
||||
}
|
||||
req, err := http.NewRequest(method, path, nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
|
||||
assertEqual(t, http.StatusServiceUnavailable, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -809,23 +807,23 @@ func TestProcessProvider_RFCListAlias_Good(t *testing.T) {
|
|||
r := setupRouter(p)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "sleep", "0.1")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = svc.Kill(proc.ID)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/api/process/process/list?runningOnly=true", nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[[]string]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Success)
|
||||
assert.Contains(t, resp.Data, proc.ID)
|
||||
requireNoError(t, err)
|
||||
assertTrue(t, resp.Success)
|
||||
assertContains(t, resp.Data, proc.ID)
|
||||
}
|
||||
|
||||
func TestProcessProvider_RFCStartAlias_Good(t *testing.T) {
|
||||
|
|
@ -836,20 +834,20 @@ func TestProcessProvider_RFCStartAlias_Good(t *testing.T) {
|
|||
body := strings.NewReader(`{"command":"sleep","args":["0.1"]}`)
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("POST", "/api/process/process/start", body)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assertEqual(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[string]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Success)
|
||||
assert.NotEmpty(t, resp.Data)
|
||||
requireNoError(t, err)
|
||||
assertTrue(t, resp.Success)
|
||||
assertNotEmpty(t, resp.Data)
|
||||
|
||||
proc, err := svc.Get(resp.Data)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -857,8 +855,8 @@ func TestProcessProvider_RFCStartAlias_Good(t *testing.T) {
|
|||
t.Fatal("RFC alias start should detach from the HTTP request context")
|
||||
}
|
||||
|
||||
assert.Equal(t, process.StatusExited, proc.Status)
|
||||
assert.Equal(t, 0, proc.ExitCode)
|
||||
assertEqual(t, process.StatusExited, proc.Status)
|
||||
assertEqual(t, 0, proc.ExitCode)
|
||||
}
|
||||
|
||||
// -- Test helpers -------------------------------------------------------------
|
||||
|
|
@ -880,7 +878,7 @@ func newTestProcessService(t *testing.T) *process.Service {
|
|||
c := core.New()
|
||||
factory := process.NewService(process.Options{})
|
||||
raw, err := factory(c)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
return raw.(*process.Service)
|
||||
}
|
||||
|
|
@ -890,7 +888,7 @@ func connectWS(t *testing.T, serverURL string) *websocket.Conn {
|
|||
|
||||
wsURL := "ws" + strings.TrimPrefix(serverURL, "http")
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
return conn
|
||||
}
|
||||
|
||||
|
|
@ -906,10 +904,10 @@ func readWSEvents(t *testing.T, conn *websocket.Conn, channels ...string) map[st
|
|||
deadline := time.Now().Add(3 * time.Second)
|
||||
|
||||
for len(events) < len(channels) && time.Now().Before(deadline) {
|
||||
require.NoError(t, conn.SetReadDeadline(time.Now().Add(500*time.Millisecond)))
|
||||
requireNoError(t, conn.SetReadDeadline(time.Now().Add(500*time.Millisecond)))
|
||||
|
||||
_, payload, err := conn.ReadMessage()
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(payload)), "\n") {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
|
|
@ -917,7 +915,7 @@ func readWSEvents(t *testing.T, conn *websocket.Conn, channels ...string) map[st
|
|||
}
|
||||
|
||||
var msg corews.Message
|
||||
require.NoError(t, json.Unmarshal([]byte(line), &msg))
|
||||
requireNoError(t, json.Unmarshal([]byte(line), &msg))
|
||||
|
||||
if _, ok := want[msg.Channel]; ok {
|
||||
events[msg.Channel] = msg
|
||||
|
|
@ -925,6 +923,6 @@ func readWSEvents(t *testing.T, conn *websocket.Conn, channels ...string) map[st
|
|||
}
|
||||
}
|
||||
|
||||
require.Len(t, events, len(channels))
|
||||
requireLen(t, events, len(channels))
|
||||
return events
|
||||
}
|
||||
|
|
|
|||
161
process_test.go
161
process_test.go
|
|
@ -6,9 +6,6 @@ import (
|
|||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ *Process = (*ManagedProcess)(nil)
|
||||
|
|
@ -17,18 +14,18 @@ func TestProcess_Info(t *testing.T) {
|
|||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "hello")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
info := proc.Info()
|
||||
assert.Equal(t, proc.ID, info.ID)
|
||||
assert.Equal(t, "echo", info.Command)
|
||||
assert.Equal(t, []string{"hello"}, info.Args)
|
||||
assert.False(t, info.Running)
|
||||
assert.Equal(t, StatusExited, info.Status)
|
||||
assert.Equal(t, 0, info.ExitCode)
|
||||
assert.Greater(t, info.Duration, time.Duration(0))
|
||||
assertEqual(t, proc.ID, info.ID)
|
||||
assertEqual(t, "echo", info.Command)
|
||||
assertEqual(t, []string{"hello"}, info.Args)
|
||||
assertFalse(t, info.Running)
|
||||
assertEqual(t, StatusExited, info.Status)
|
||||
assertEqual(t, 0, info.ExitCode)
|
||||
assertGreater(t, info.Duration, time.Duration(0))
|
||||
}
|
||||
|
||||
func TestProcess_Info_Pending(t *testing.T) {
|
||||
|
|
@ -39,8 +36,8 @@ func TestProcess_Info_Pending(t *testing.T) {
|
|||
}
|
||||
|
||||
info := proc.Info()
|
||||
assert.Equal(t, StatusPending, info.Status)
|
||||
assert.False(t, info.Running)
|
||||
assertEqual(t, StatusPending, info.Status)
|
||||
assertFalse(t, info.Running)
|
||||
}
|
||||
|
||||
func TestProcess_Info_RunningDuration(t *testing.T) {
|
||||
|
|
@ -50,13 +47,13 @@ func TestProcess_Info_RunningDuration(t *testing.T) {
|
|||
defer cancel()
|
||||
|
||||
proc, err := svc.Start(ctx, "sleep", "10")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
info := proc.Info()
|
||||
assert.True(t, info.Running)
|
||||
assert.Equal(t, StatusRunning, info.Status)
|
||||
assert.Greater(t, info.Duration, time.Duration(0))
|
||||
assertTrue(t, info.Running)
|
||||
assertEqual(t, StatusRunning, info.Status)
|
||||
assertGreater(t, info.Duration, time.Duration(0))
|
||||
|
||||
cancel()
|
||||
<-proc.Done()
|
||||
|
|
@ -66,17 +63,17 @@ func TestProcess_InfoSnapshot(t *testing.T) {
|
|||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "snapshot")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
info := proc.Info()
|
||||
require.NotEmpty(t, info.Args)
|
||||
requireNotEmpty(t, info.Args)
|
||||
|
||||
info.Args[0] = "mutated"
|
||||
|
||||
assert.Equal(t, "snapshot", proc.Args[0])
|
||||
assert.Equal(t, "mutated", info.Args[0])
|
||||
assertEqual(t, "snapshot", proc.Args[0])
|
||||
assertEqual(t, "mutated", info.Args[0])
|
||||
}
|
||||
|
||||
func TestProcess_Output(t *testing.T) {
|
||||
|
|
@ -84,25 +81,25 @@ func TestProcess_Output(t *testing.T) {
|
|||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "hello world")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
output := proc.Output()
|
||||
assert.Contains(t, output, "hello world")
|
||||
assertContains(t, output, "hello world")
|
||||
})
|
||||
|
||||
t.Run("OutputBytes returns copy", func(t *testing.T) {
|
||||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "test")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
bytes := proc.OutputBytes()
|
||||
assert.NotNil(t, bytes)
|
||||
assert.Contains(t, string(bytes), "test")
|
||||
assertNotNil(t, bytes)
|
||||
assertContains(t, string(bytes), "test")
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -114,27 +111,27 @@ func TestProcess_IsRunning(t *testing.T) {
|
|||
defer cancel()
|
||||
|
||||
proc, err := svc.Start(ctx, "sleep", "10")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, proc.IsRunning())
|
||||
assert.True(t, proc.Info().Running)
|
||||
assertTrue(t, proc.IsRunning())
|
||||
assertTrue(t, proc.Info().Running)
|
||||
|
||||
cancel()
|
||||
<-proc.Done()
|
||||
|
||||
assert.False(t, proc.IsRunning())
|
||||
assert.False(t, proc.Info().Running)
|
||||
assertFalse(t, proc.IsRunning())
|
||||
assertFalse(t, proc.Info().Running)
|
||||
})
|
||||
|
||||
t.Run("false after completion", func(t *testing.T) {
|
||||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "done")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
assert.False(t, proc.IsRunning())
|
||||
assertFalse(t, proc.IsRunning())
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -143,20 +140,20 @@ func TestProcess_Wait(t *testing.T) {
|
|||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "ok")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.Wait()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("returns error on failure", func(t *testing.T) {
|
||||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "sh", "-c", "exit 1")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.Wait()
|
||||
assert.Error(t, err)
|
||||
assertError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +162,7 @@ func TestProcess_Done(t *testing.T) {
|
|||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "test")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -184,12 +181,12 @@ func TestProcess_Kill(t *testing.T) {
|
|||
defer cancel()
|
||||
|
||||
proc, err := svc.Start(ctx, "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, proc.IsRunning())
|
||||
assertTrue(t, proc.IsRunning())
|
||||
|
||||
err = proc.Kill()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -198,19 +195,19 @@ func TestProcess_Kill(t *testing.T) {
|
|||
t.Fatal("process should have been killed")
|
||||
}
|
||||
|
||||
assert.Equal(t, StatusKilled, proc.Status)
|
||||
assertEqual(t, StatusKilled, proc.Status)
|
||||
})
|
||||
|
||||
t.Run("noop on completed process", func(t *testing.T) {
|
||||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "done")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
err = proc.Kill()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -220,29 +217,29 @@ func TestProcess_SendInput(t *testing.T) {
|
|||
|
||||
// Use cat to echo back stdin
|
||||
proc, err := svc.Start(context.Background(), "cat")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.SendInput("hello\n")
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
err = proc.CloseStdin()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
assert.Contains(t, proc.Output(), "hello")
|
||||
assertContains(t, proc.Output(), "hello")
|
||||
})
|
||||
|
||||
t.Run("error on completed process", func(t *testing.T) {
|
||||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "done")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
err = proc.SendInput("test")
|
||||
assert.ErrorIs(t, err, ErrProcessNotRunning)
|
||||
assertErrorIs(t, err, ErrProcessNotRunning)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -254,10 +251,10 @@ func TestProcess_Signal(t *testing.T) {
|
|||
defer cancel()
|
||||
|
||||
proc, err := svc.Start(ctx, "sleep", "60")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.Signal(os.Interrupt)
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -266,18 +263,18 @@ func TestProcess_Signal(t *testing.T) {
|
|||
t.Fatal("process should have been terminated by signal")
|
||||
}
|
||||
|
||||
assert.Equal(t, StatusKilled, proc.Status)
|
||||
assertEqual(t, StatusKilled, proc.Status)
|
||||
})
|
||||
|
||||
t.Run("error on completed process", func(t *testing.T) {
|
||||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "echo", "done")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
<-proc.Done()
|
||||
|
||||
err = proc.Signal(os.Interrupt)
|
||||
assert.ErrorIs(t, err, ErrProcessNotRunning)
|
||||
assertErrorIs(t, err, ErrProcessNotRunning)
|
||||
})
|
||||
|
||||
t.Run("signals process group when kill group is enabled", func(t *testing.T) {
|
||||
|
|
@ -289,10 +286,10 @@ func TestProcess_Signal(t *testing.T) {
|
|||
Detach: true,
|
||||
KillGroup: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.Signal(os.Interrupt)
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -311,16 +308,16 @@ func TestProcess_Signal(t *testing.T) {
|
|||
Detach: true,
|
||||
KillGroup: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.Signal(syscall.Signal(0))
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
assert.True(t, proc.IsRunning())
|
||||
assertTrue(t, proc.IsRunning())
|
||||
|
||||
err = proc.Kill()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -335,10 +332,10 @@ func TestProcess_CloseStdin(t *testing.T) {
|
|||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "cat")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.CloseStdin()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
// Process should exit now that stdin is closed
|
||||
select {
|
||||
|
|
@ -353,17 +350,17 @@ func TestProcess_CloseStdin(t *testing.T) {
|
|||
svc, _ := newTestService(t)
|
||||
|
||||
proc, err := svc.Start(context.Background(), "cat")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
// First close
|
||||
err = proc.CloseStdin()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
|
||||
// Second close should be safe (stdin already nil)
|
||||
err = proc.CloseStdin()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +373,7 @@ func TestProcess_Timeout(t *testing.T) {
|
|||
Args: []string{"60"},
|
||||
Timeout: 200 * time.Millisecond,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -385,8 +382,8 @@ func TestProcess_Timeout(t *testing.T) {
|
|||
t.Fatal("process should have been killed by timeout")
|
||||
}
|
||||
|
||||
assert.False(t, proc.IsRunning())
|
||||
assert.Equal(t, StatusKilled, proc.Status)
|
||||
assertFalse(t, proc.IsRunning())
|
||||
assertEqual(t, StatusKilled, proc.Status)
|
||||
})
|
||||
|
||||
t.Run("no timeout when zero", func(t *testing.T) {
|
||||
|
|
@ -397,10 +394,10 @@ func TestProcess_Timeout(t *testing.T) {
|
|||
Args: []string{"fast"},
|
||||
Timeout: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
<-proc.Done()
|
||||
assert.Equal(t, 0, proc.ExitCode)
|
||||
assertEqual(t, 0, proc.ExitCode)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -414,12 +411,12 @@ func TestProcess_Shutdown(t *testing.T) {
|
|||
Args: []string{"60"},
|
||||
GracePeriod: 100 * time.Millisecond,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, proc.IsRunning())
|
||||
assertTrue(t, proc.IsRunning())
|
||||
|
||||
err = proc.Shutdown()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -428,7 +425,7 @@ func TestProcess_Shutdown(t *testing.T) {
|
|||
t.Fatal("shutdown should have completed")
|
||||
}
|
||||
|
||||
assert.Equal(t, StatusKilled, proc.Status)
|
||||
assertEqual(t, StatusKilled, proc.Status)
|
||||
})
|
||||
|
||||
t.Run("immediate kill without grace period", func(t *testing.T) {
|
||||
|
|
@ -438,10 +435,10 @@ func TestProcess_Shutdown(t *testing.T) {
|
|||
Command: "sleep",
|
||||
Args: []string{"60"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = proc.Shutdown()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -463,13 +460,13 @@ func TestProcess_KillGroup(t *testing.T) {
|
|||
Detach: true,
|
||||
KillGroup: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
// Give child time to spawn
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
err = proc.Kill()
|
||||
assert.NoError(t, err)
|
||||
assertNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -478,7 +475,7 @@ func TestProcess_KillGroup(t *testing.T) {
|
|||
t.Fatal("process group should have been killed")
|
||||
}
|
||||
|
||||
assert.Equal(t, StatusKilled, proc.Status)
|
||||
assertEqual(t, StatusKilled, proc.Status)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -492,7 +489,7 @@ func TestProcess_TimeoutWithGrace(t *testing.T) {
|
|||
Timeout: 200 * time.Millisecond,
|
||||
GracePeriod: 100 * time.Millisecond,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
|
|
@ -501,6 +498,6 @@ func TestProcess_TimeoutWithGrace(t *testing.T) {
|
|||
t.Fatal("process should have been killed by timeout")
|
||||
}
|
||||
|
||||
assert.Equal(t, StatusKilled, proc.Status)
|
||||
assertEqual(t, StatusKilled, proc.Status)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
124
program_test.go
124
program_test.go
|
|
@ -2,14 +2,12 @@ package process_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
process "dappco.re/go/core/process"
|
||||
)
|
||||
|
||||
|
|
@ -22,60 +20,94 @@ func testCtx(t *testing.T) context.Context {
|
|||
|
||||
func TestProgram_Find_KnownBinary(t *testing.T) {
|
||||
p := &process.Program{Name: "echo"}
|
||||
require.NoError(t, p.Find())
|
||||
assert.NotEmpty(t, p.Path)
|
||||
if err := p.Find(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if p.Path == "" {
|
||||
t.Fatal("expected non-empty path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Find_UnknownBinary(t *testing.T) {
|
||||
p := &process.Program{Name: "no-such-binary-xyzzy-42"}
|
||||
err := p.Find()
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, process.ErrProgramNotFound)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, process.ErrProgramNotFound) {
|
||||
t.Fatalf("expected ErrProgramNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Find_UsesExistingPath(t *testing.T) {
|
||||
path, err := exec.LookPath("echo")
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
p := &process.Program{Path: path}
|
||||
require.NoError(t, p.Find())
|
||||
assert.Equal(t, path, p.Path)
|
||||
if err := p.Find(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if p.Path != path {
|
||||
t.Fatalf("want %v, got %v", path, p.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Find_PrefersExistingPathOverName(t *testing.T) {
|
||||
path, err := exec.LookPath("echo")
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
p := &process.Program{
|
||||
Name: "no-such-binary-xyzzy-42",
|
||||
Path: path,
|
||||
}
|
||||
|
||||
require.NoError(t, p.Find())
|
||||
assert.Equal(t, path, p.Path)
|
||||
if err := p.Find(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if p.Path != path {
|
||||
t.Fatalf("want %v, got %v", path, p.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Find_EmptyName(t *testing.T) {
|
||||
p := &process.Program{}
|
||||
require.Error(t, p.Find())
|
||||
if err := p.Find(); err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Run_ReturnsOutput(t *testing.T) {
|
||||
p := &process.Program{Name: "echo"}
|
||||
require.NoError(t, p.Find())
|
||||
if err := p.Find(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out, err := p.Run(testCtx(t), "hello")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hello", out)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if out != "hello" {
|
||||
t.Fatalf("want %q, got %q", "hello", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Run_PreservesLeadingWhitespace(t *testing.T) {
|
||||
p := &process.Program{Name: "sh"}
|
||||
require.NoError(t, p.Find())
|
||||
if err := p.Find(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out, err := p.Run(testCtx(t), "-c", "printf ' hello \n'")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, " hello", out)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if out != " hello" {
|
||||
t.Fatalf("want %q, got %q", " hello", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Run_WithoutFind_FallsBackToName(t *testing.T) {
|
||||
|
|
@ -83,46 +115,72 @@ func TestProgram_Run_WithoutFind_FallsBackToName(t *testing.T) {
|
|||
p := &process.Program{Name: "echo"}
|
||||
|
||||
out, err := p.Run(testCtx(t), "fallback")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "fallback", out)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if out != "fallback" {
|
||||
t.Fatalf("want %q, got %q", "fallback", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_RunDir_UsesDirectory(t *testing.T) {
|
||||
p := &process.Program{Name: "pwd"}
|
||||
require.NoError(t, p.Find())
|
||||
if err := p.Find(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
out, err := p.RunDir(testCtx(t), dir)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// Resolve symlinks on both sides for portability (macOS uses /private/ prefix).
|
||||
canonicalDir, err := filepath.EvalSymlinks(dir)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
canonicalOut, err := filepath.EvalSymlinks(out)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, canonicalDir, canonicalOut)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if canonicalDir != canonicalOut {
|
||||
t.Fatalf("want %q, got %q", canonicalDir, canonicalOut)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Run_FailingCommand(t *testing.T) {
|
||||
p := &process.Program{Name: "false"}
|
||||
require.NoError(t, p.Find())
|
||||
if err := p.Find(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
_, err := p.Run(testCtx(t))
|
||||
require.Error(t, err)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_Run_NilContextRejected(t *testing.T) {
|
||||
p := &process.Program{Name: "echo"}
|
||||
|
||||
_, err := p.Run(nil, "test")
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, process.ErrProgramContextRequired)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, process.ErrProgramContextRequired) {
|
||||
t.Fatalf("expected ErrProgramContextRequired, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgram_RunDir_EmptyNameRejected(t *testing.T) {
|
||||
p := &process.Program{}
|
||||
|
||||
_, err := p.RunDir(testCtx(t), "", "test")
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, process.ErrProgramNameRequired)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, process.ErrProgramNameRequired) {
|
||||
t.Fatalf("expected ErrProgramNameRequired, got %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRegistry_RegisterAndGet(t *testing.T) {
|
||||
|
|
@ -26,17 +23,17 @@ func TestRegistry_RegisterAndGet(t *testing.T) {
|
|||
}
|
||||
|
||||
err := reg.Register(entry)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
got, ok := reg.Get("myapp", "worker")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "myapp", got.Code)
|
||||
assert.Equal(t, "worker", got.Daemon)
|
||||
assert.Equal(t, os.Getpid(), got.PID)
|
||||
assert.Equal(t, "healthy", got.Health)
|
||||
assert.Equal(t, "test-project", got.Project)
|
||||
assert.Equal(t, "/usr/bin/worker", got.Binary)
|
||||
assert.Equal(t, started, got.Started)
|
||||
requireTrue(t, ok)
|
||||
assertEqual(t, "myapp", got.Code)
|
||||
assertEqual(t, "worker", got.Daemon)
|
||||
assertEqual(t, os.Getpid(), got.PID)
|
||||
assertEqual(t, "healthy", got.Health)
|
||||
assertEqual(t, "test-project", got.Project)
|
||||
assertEqual(t, "/usr/bin/worker", got.Binary)
|
||||
assertEqual(t, started, got.Started)
|
||||
}
|
||||
|
||||
func TestRegistry_Unregister(t *testing.T) {
|
||||
|
|
@ -50,19 +47,19 @@ func TestRegistry_Unregister(t *testing.T) {
|
|||
}
|
||||
|
||||
err := reg.Register(entry)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
// File should exist
|
||||
path := filepath.Join(dir, "myapp-server.json")
|
||||
_, err = os.Stat(path)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
err = reg.Unregister("myapp", "server")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
// File should be gone
|
||||
_, err = os.Stat(path)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
assertTrue(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestRegistry_UnregisterMissingIsNoop(t *testing.T) {
|
||||
|
|
@ -70,7 +67,7 @@ func TestRegistry_UnregisterMissingIsNoop(t *testing.T) {
|
|||
reg := NewRegistry(dir)
|
||||
|
||||
err := reg.Unregister("missing", "entry")
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
}
|
||||
|
||||
func TestRegistry_List(t *testing.T) {
|
||||
|
|
@ -78,15 +75,15 @@ func TestRegistry_List(t *testing.T) {
|
|||
reg := NewRegistry(dir)
|
||||
|
||||
err := reg.Register(DaemonEntry{Code: "app1", Daemon: "web", PID: os.Getpid()})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
err = reg.Register(DaemonEntry{Code: "app2", Daemon: "api", PID: os.Getpid()})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
entries, err := reg.List()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, entries, 2)
|
||||
assert.Equal(t, "app1", entries[0].Code)
|
||||
assert.Equal(t, "app2", entries[1].Code)
|
||||
requireNoError(t, err)
|
||||
requireLen(t, entries, 2)
|
||||
assertEqual(t, "app1", entries[0].Code)
|
||||
assertEqual(t, "app2", entries[1].Code)
|
||||
}
|
||||
|
||||
func TestRegistry_List_PrunesStale(t *testing.T) {
|
||||
|
|
@ -94,20 +91,20 @@ func TestRegistry_List_PrunesStale(t *testing.T) {
|
|||
reg := NewRegistry(dir)
|
||||
|
||||
err := reg.Register(DaemonEntry{Code: "dead", Daemon: "proc", PID: 999999999})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
// File should exist before listing
|
||||
path := filepath.Join(dir, "dead-proc.json")
|
||||
_, err = os.Stat(path)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
entries, err := reg.List()
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, entries)
|
||||
requireNoError(t, err)
|
||||
assertEmpty(t, entries)
|
||||
|
||||
// Stale file should be removed
|
||||
_, err = os.Stat(path)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
assertTrue(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestRegistry_Get_NotFound(t *testing.T) {
|
||||
|
|
@ -115,8 +112,8 @@ func TestRegistry_Get_NotFound(t *testing.T) {
|
|||
reg := NewRegistry(dir)
|
||||
|
||||
got, ok := reg.Get("nope", "missing")
|
||||
assert.Nil(t, got)
|
||||
assert.False(t, ok)
|
||||
assertNil(t, got)
|
||||
assertFalse(t, ok)
|
||||
}
|
||||
|
||||
func TestRegistry_CreatesDirectory(t *testing.T) {
|
||||
|
|
@ -124,14 +121,14 @@ func TestRegistry_CreatesDirectory(t *testing.T) {
|
|||
reg := NewRegistry(dir)
|
||||
|
||||
err := reg.Register(DaemonEntry{Code: "app", Daemon: "srv", PID: os.Getpid()})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
info, err := os.Stat(dir)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, info.IsDir())
|
||||
requireNoError(t, err)
|
||||
assertTrue(t, info.IsDir())
|
||||
}
|
||||
|
||||
func TestDefaultRegistry(t *testing.T) {
|
||||
reg := DefaultRegistry()
|
||||
assert.NotNil(t, reg)
|
||||
assertNotNil(t, reg)
|
||||
}
|
||||
|
|
|
|||
212
runner_test.go
212
runner_test.go
|
|
@ -5,8 +5,6 @@ import (
|
|||
"testing"
|
||||
|
||||
framework "dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newTestRunner(t *testing.T) *Runner {
|
||||
|
|
@ -15,7 +13,7 @@ func newTestRunner(t *testing.T) *Runner {
|
|||
c := framework.New()
|
||||
factory := NewService(Options{})
|
||||
raw, err := factory(c)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
return NewRunner(raw.(*Service))
|
||||
}
|
||||
|
|
@ -29,12 +27,12 @@ func TestRunner_RunSequential(t *testing.T) {
|
|||
{Name: "second", Command: "echo", Args: []string{"2"}},
|
||||
{Name: "third", Command: "echo", Args: []string{"3"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, result.Success())
|
||||
assert.Equal(t, 3, result.Passed)
|
||||
assert.Equal(t, 0, result.Failed)
|
||||
assert.Equal(t, 0, result.Skipped)
|
||||
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) {
|
||||
|
|
@ -45,18 +43,18 @@ func TestRunner_RunSequential(t *testing.T) {
|
|||
{Name: "fails", Command: "sh", Args: []string{"-c", "exit 1"}},
|
||||
{Name: "third", Command: "echo", Args: []string{"3"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.False(t, result.Success())
|
||||
assert.Equal(t, 1, result.Passed)
|
||||
assert.Equal(t, 1, result.Failed)
|
||||
assert.Equal(t, 1, result.Skipped)
|
||||
require.Len(t, result.Results, 3)
|
||||
assert.Equal(t, 0, result.Results[0].ExitCode)
|
||||
assert.NoError(t, result.Results[0].Error)
|
||||
assert.Equal(t, 1, result.Results[1].ExitCode)
|
||||
assert.NoError(t, result.Results[1].Error)
|
||||
assert.True(t, result.Results[2].Skipped)
|
||||
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) {
|
||||
|
|
@ -67,12 +65,12 @@ func TestRunner_RunSequential(t *testing.T) {
|
|||
{Name: "fails", Command: "sh", Args: []string{"-c", "exit 1"}, AllowFailure: true},
|
||||
{Name: "third", Command: "echo", Args: []string{"3"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
// Still counts as failed but pipeline continues
|
||||
assert.Equal(t, 2, result.Passed)
|
||||
assert.Equal(t, 1, result.Failed)
|
||||
assert.Equal(t, 0, result.Skipped)
|
||||
assertEqual(t, 2, result.Passed)
|
||||
assertEqual(t, 1, result.Failed)
|
||||
assertEqual(t, 0, result.Skipped)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -85,11 +83,11 @@ func TestRunner_RunParallel(t *testing.T) {
|
|||
{Name: "second", Command: "echo", Args: []string{"2"}},
|
||||
{Name: "third", Command: "echo", Args: []string{"3"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, result.Success())
|
||||
assert.Equal(t, 3, result.Passed)
|
||||
assert.Len(t, result.Results, 3)
|
||||
assertTrue(t, result.Success())
|
||||
assertEqual(t, 3, result.Passed)
|
||||
assertLen(t, result.Results, 3)
|
||||
})
|
||||
|
||||
t.Run("failure doesnt stop others", func(t *testing.T) {
|
||||
|
|
@ -100,11 +98,11 @@ func TestRunner_RunParallel(t *testing.T) {
|
|||
{Name: "fails", Command: "sh", Args: []string{"-c", "exit 1"}},
|
||||
{Name: "third", Command: "echo", Args: []string{"3"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.False(t, result.Success())
|
||||
assert.Equal(t, 2, result.Passed)
|
||||
assert.Equal(t, 1, result.Failed)
|
||||
assertFalse(t, result.Success())
|
||||
assertEqual(t, 2, result.Passed)
|
||||
assertEqual(t, 1, result.Failed)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +115,10 @@ func TestRunner_RunAll(t *testing.T) {
|
|||
{Name: "first", Command: "echo", Args: []string{"1"}},
|
||||
{Name: "second", Command: "echo", Args: []string{"2"}, After: []string{"first"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, result.Success())
|
||||
assert.Equal(t, 3, result.Passed)
|
||||
assertTrue(t, result.Success())
|
||||
assertEqual(t, 3, result.Passed)
|
||||
})
|
||||
|
||||
t.Run("skips dependents on failure", func(t *testing.T) {
|
||||
|
|
@ -131,12 +129,12 @@ func TestRunner_RunAll(t *testing.T) {
|
|||
{Name: "second", Command: "echo", Args: []string{"2"}, After: []string{"first"}},
|
||||
{Name: "third", Command: "echo", Args: []string{"3"}, After: []string{"second"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.False(t, result.Success())
|
||||
assert.Equal(t, 0, result.Passed)
|
||||
assert.Equal(t, 1, result.Failed)
|
||||
assert.Equal(t, 2, result.Skipped)
|
||||
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) {
|
||||
|
|
@ -149,10 +147,10 @@ func TestRunner_RunAll(t *testing.T) {
|
|||
{Name: "c", Command: "echo", Args: []string{"c"}},
|
||||
{Name: "final", Command: "echo", Args: []string{"done"}, After: []string{"a", "b", "c"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, result.Success())
|
||||
assert.Equal(t, 4, result.Passed)
|
||||
assertTrue(t, result.Success())
|
||||
assertEqual(t, 4, result.Passed)
|
||||
})
|
||||
|
||||
t.Run("preserves input order", func(t *testing.T) {
|
||||
|
|
@ -165,11 +163,11 @@ func TestRunner_RunAll(t *testing.T) {
|
|||
}
|
||||
|
||||
result, err := runner.RunAll(context.Background(), specs)
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
require.Len(t, result.Results, len(specs))
|
||||
requireLen(t, result.Results, len(specs))
|
||||
for i, res := range result.Results {
|
||||
assert.Equal(t, specs[i].Name, res.Name)
|
||||
assertEqual(t, specs[i].Name, res.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -182,15 +180,15 @@ func TestRunner_RunAll_CircularDeps(t *testing.T) {
|
|||
{Name: "a", Command: "echo", Args: []string{"a"}, After: []string{"b"}},
|
||||
{Name: "b", Command: "echo", Args: []string{"b"}, After: []string{"a"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, result.Success())
|
||||
assert.Equal(t, 0, result.Failed)
|
||||
assert.Equal(t, 2, result.Skipped)
|
||||
assertTrue(t, result.Success())
|
||||
assertEqual(t, 0, result.Failed)
|
||||
assertEqual(t, 2, result.Skipped)
|
||||
for _, res := range result.Results {
|
||||
assert.True(t, res.Skipped)
|
||||
assert.Equal(t, 0, res.ExitCode)
|
||||
assert.Error(t, res.Error)
|
||||
assertTrue(t, res.Skipped)
|
||||
assertEqual(t, 0, res.ExitCode)
|
||||
assertError(t, res.Error)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -200,15 +198,15 @@ func TestRunner_RunAll_CircularDeps(t *testing.T) {
|
|||
result, err := runner.RunAll(context.Background(), []RunSpec{
|
||||
{Name: "a", Command: "echo", Args: []string{"a"}, After: []string{"missing"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.True(t, result.Success())
|
||||
assert.Equal(t, 0, result.Failed)
|
||||
assert.Equal(t, 1, result.Skipped)
|
||||
require.Len(t, result.Results, 1)
|
||||
assert.True(t, result.Results[0].Skipped)
|
||||
assert.Equal(t, 0, result.Results[0].ExitCode)
|
||||
assert.Error(t, result.Results[0].Error)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -223,17 +221,17 @@ func TestRunner_ContextCancellation(t *testing.T) {
|
|||
{Name: "first", Command: "echo", Args: []string{"1"}},
|
||||
{Name: "second", Command: "echo", Args: []string{"2"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, result.Passed)
|
||||
assert.Equal(t, 0, result.Failed)
|
||||
assert.Equal(t, 2, result.Skipped)
|
||||
require.Len(t, result.Results, 2)
|
||||
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 {
|
||||
assert.True(t, res.Skipped)
|
||||
assert.Equal(t, 1, res.ExitCode)
|
||||
assert.Error(t, res.Error)
|
||||
assert.Contains(t, res.Error.Error(), "context canceled")
|
||||
assertTrue(t, res.Skipped)
|
||||
assertEqual(t, 1, res.ExitCode)
|
||||
assertError(t, res.Error)
|
||||
assertContains(t, res.Error.Error(), "context canceled")
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -247,17 +245,17 @@ func TestRunner_ContextCancellation(t *testing.T) {
|
|||
{Name: "first", Command: "echo", Args: []string{"1"}},
|
||||
{Name: "second", Command: "echo", Args: []string{"2"}, After: []string{"first"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireNoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, result.Passed)
|
||||
assert.Equal(t, 0, result.Failed)
|
||||
assert.Equal(t, 2, result.Skipped)
|
||||
require.Len(t, result.Results, 2)
|
||||
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 {
|
||||
assert.True(t, res.Skipped)
|
||||
assert.Equal(t, 1, res.ExitCode)
|
||||
assert.Error(t, res.Error)
|
||||
assert.Contains(t, res.Error.Error(), "context canceled")
|
||||
assertTrue(t, res.Skipped)
|
||||
assertEqual(t, 1, res.ExitCode)
|
||||
assertError(t, res.Error)
|
||||
assertContains(t, res.Error.Error(), "context canceled")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -265,22 +263,22 @@ func TestRunner_ContextCancellation(t *testing.T) {
|
|||
func TestRunResult_Passed(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
r := RunResult{ExitCode: 0}
|
||||
assert.True(t, r.Passed())
|
||||
assertTrue(t, r.Passed())
|
||||
})
|
||||
|
||||
t.Run("non-zero exit", func(t *testing.T) {
|
||||
r := RunResult{ExitCode: 1}
|
||||
assert.False(t, r.Passed())
|
||||
assertFalse(t, r.Passed())
|
||||
})
|
||||
|
||||
t.Run("skipped", func(t *testing.T) {
|
||||
r := RunResult{ExitCode: 0, Skipped: true}
|
||||
assert.False(t, r.Passed())
|
||||
assertFalse(t, r.Passed())
|
||||
})
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
r := RunResult{ExitCode: 0, Error: assert.AnError}
|
||||
assert.False(t, r.Passed())
|
||||
r := RunResult{ExitCode: 0, Error: errSentinel}
|
||||
assertFalse(t, r.Passed())
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -288,32 +286,32 @@ func TestRunner_NilService(t *testing.T) {
|
|||
runner := NewRunner(nil)
|
||||
|
||||
_, err := runner.RunAll(context.Background(), nil)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerNoService)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerNoService)
|
||||
|
||||
_, err = runner.RunSequential(context.Background(), nil)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerNoService)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerNoService)
|
||||
|
||||
_, err = runner.RunParallel(context.Background(), nil)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerNoService)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerNoService)
|
||||
}
|
||||
|
||||
func TestRunner_NilContext(t *testing.T) {
|
||||
runner := newTestRunner(t)
|
||||
|
||||
_, err := runner.RunAll(nil, nil)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerContextRequired)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerContextRequired)
|
||||
|
||||
_, err = runner.RunSequential(nil, nil)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerContextRequired)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerContextRequired)
|
||||
|
||||
_, err = runner.RunParallel(nil, nil)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerContextRequired)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerContextRequired)
|
||||
}
|
||||
|
||||
func TestRunner_InvalidSpecNames(t *testing.T) {
|
||||
|
|
@ -323,32 +321,32 @@ func TestRunner_InvalidSpecNames(t *testing.T) {
|
|||
_, err := runner.RunSequential(context.Background(), []RunSpec{
|
||||
{Name: "", Command: "echo", Args: []string{"a"}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerInvalidSpecName)
|
||||
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{""}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerInvalidDependencyName)
|
||||
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"}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerInvalidDependencyName)
|
||||
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"}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerInvalidDependencyName)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerInvalidDependencyName)
|
||||
})
|
||||
|
||||
t.Run("rejects duplicate names", func(t *testing.T) {
|
||||
|
|
@ -356,8 +354,8 @@ func TestRunner_InvalidSpecNames(t *testing.T) {
|
|||
{Name: "same", Command: "echo", Args: []string{"a"}},
|
||||
{Name: "same", Command: "echo", Args: []string{"b"}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerInvalidSpecName)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerInvalidSpecName)
|
||||
})
|
||||
|
||||
t.Run("rejects duplicate names in parallel mode", func(t *testing.T) {
|
||||
|
|
@ -365,7 +363,7 @@ func TestRunner_InvalidSpecNames(t *testing.T) {
|
|||
{Name: "one", Command: "echo", Args: []string{"a"}},
|
||||
{Name: "one", Command: "echo", Args: []string{"b"}},
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrRunnerInvalidSpecName)
|
||||
requireError(t, err)
|
||||
assertErrorIs(t, err, ErrRunnerInvalidSpecName)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
518
service_test.go
518
service_test.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue