summaryrefslogtreecommitdiff
path: root/internal/executor/executor_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor/executor_test.go')
-rw-r--r--internal/executor/executor_test.go72
1 files changed, 72 insertions, 0 deletions
diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go
index 18a79bb..b3e6dae 100644
--- a/internal/executor/executor_test.go
+++ b/internal/executor/executor_test.go
@@ -6,6 +6,7 @@ import (
"log/slog"
"os"
"path/filepath"
+ "strings"
"sync"
"testing"
"time"
@@ -206,6 +207,77 @@ func TestPool_AtCapacity(t *testing.T) {
<-pool.Results() // drain
}
+// logPatherMockRunner is a mockRunner that also implements LogPather,
+// and captures the StdoutPath seen when Run() is called.
+type logPatherMockRunner struct {
+ mockRunner
+ logDir string
+ capturedPath string
+}
+
+func (m *logPatherMockRunner) ExecLogDir(execID string) string {
+ return filepath.Join(m.logDir, execID)
+}
+
+func (m *logPatherMockRunner) Run(ctx context.Context, t *task.Task, e *storage.Execution) error {
+ m.mu.Lock()
+ m.capturedPath = e.StdoutPath
+ m.mu.Unlock()
+ return m.mockRunner.Run(ctx, t, e)
+}
+
+// TestPool_Execute_LogPathsPreSetBeforeRun verifies that when the runner
+// implements LogPather, log paths are set on the execution before Run() is
+// called — so they land in the DB at CreateExecution time, not just at
+// UpdateExecution time.
+func TestPool_Execute_LogPathsPreSetBeforeRun(t *testing.T) {
+ store := testStore(t)
+ runner := &logPatherMockRunner{logDir: t.TempDir()}
+ logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
+ pool := NewPool(2, runner, store, logger)
+
+ tk := makeTask("lp-1")
+ store.CreateTask(tk)
+ if err := pool.Submit(context.Background(), tk); err != nil {
+ t.Fatalf("submit: %v", err)
+ }
+ result := <-pool.Results()
+
+ runner.mu.Lock()
+ captured := runner.capturedPath
+ runner.mu.Unlock()
+
+ if captured == "" {
+ t.Fatal("StdoutPath was empty when Run() was called; expected pre-set path")
+ }
+ if !strings.HasSuffix(captured, "stdout.log") {
+ t.Errorf("expected stdout.log suffix, got: %s", captured)
+ }
+ // Path in the returned execution record should match.
+ if result.Execution.StdoutPath != captured {
+ t.Errorf("execution StdoutPath %q != captured %q", result.Execution.StdoutPath, captured)
+ }
+}
+
+// TestPool_Execute_NoLogPather_PathsEmptyBeforeRun verifies that a runner
+// without LogPather doesn't panic and paths remain empty until Run() sets them.
+func TestPool_Execute_NoLogPather_PathsEmptyBeforeRun(t *testing.T) {
+ store := testStore(t)
+ runner := &mockRunner{} // does NOT implement LogPather
+ logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
+ pool := NewPool(2, runner, store, logger)
+
+ tk := makeTask("nolp-1")
+ store.CreateTask(tk)
+ if err := pool.Submit(context.Background(), tk); err != nil {
+ t.Fatalf("submit: %v", err)
+ }
+ result := <-pool.Results()
+ if result.Err != nil {
+ t.Fatalf("unexpected error: %v", result.Err)
+ }
+}
+
func TestPool_ConcurrentExecution(t *testing.T) {
store := testStore(t)
runner := &mockRunner{delay: 50 * time.Millisecond}