package runtime import ( "context" "runtime" "testing" "time" "git.cloudinit.dev/coreci/orca/internal/jobspec" ) // TestProcessRuntime_PrepareIsNoop verifies Prepare is a no-op. func TestProcessRuntime_PrepareIsNoop(t *testing.T) { p := NewProcessRuntime() a := alloc("process", "", "/bin/true") if err := p.Prepare(context.Background(), a); err != nil { t.Fatalf("Prepare: %v", err) } } // TestProcessRuntime_PrepareNilSpec exercises the nil-spec branch // indirectly — Prepare is a no-op regardless of input. func TestProcessRuntime_PrepareNilSpec(t *testing.T) { p := NewProcessRuntime() if err := p.Prepare(context.Background(), &Alloc{ID: "x"}); err != nil { t.Fatalf("Prepare (nil spec) should still be no-op: %v", err) } } // TestProcessRuntime_StartStopStatus runs a real long-lived process // (sleep) and verifies Start -> Status(running) -> Stop -> Status(stopped). func TestProcessRuntime_StartStopStatus(t *testing.T) { if _, err := sleepBin(); err != nil { t.Skipf("sleep binary not available: %v", err) } p := NewProcessRuntime() sleepCmd, _ := sleepBin() a := alloc("process", "", sleepCmd+" 30") ctx, cancel := withTimeout(10 * time.Second) defer cancel() pid, err := p.Start(ctx, a) if err != nil { t.Fatalf("Start: %v", err) } if pid <= 0 { t.Fatalf("pid = %d, want > 0", pid) } st, err := p.Status(context.Background(), a) if err != nil { t.Fatalf("Status: %v", err) } if st != StateRunning { t.Errorf("Status = %q, want running", st) } // Verify PID lookup. got, err := p.PID(a.ID) if err != nil || got != pid { t.Errorf("PID = %d/%v, want %d", got, err, pid) } stopCtx, cancelStop := withTimeout(15 * time.Second) defer cancelStop() if err := p.Stop(stopCtx, a); err != nil { t.Fatalf("Stop: %v", err) } st2, _ := p.Status(context.Background(), a) if st2 != StateStopped { t.Errorf("Status after Stop = %q, want stopped", st2) } } // TestProcessRuntime_StopNotStarted verifies Stop is idempotent on a // never-started alloc. func TestProcessRuntime_StopNotStarted(t *testing.T) { p := NewProcessRuntime() a := alloc("process", "", "/bin/true") ctx, cancel := withTimeout(2 * time.Second) defer cancel() if err := p.Stop(ctx, a); err != nil { t.Errorf("Stop on never-started alloc should be no-op, got %v", err) } } // TestProcessRuntime_StatusPending verifies Status returns pending for // an alloc that was never started. func TestProcessRuntime_StatusPending(t *testing.T) { p := NewProcessRuntime() a := alloc("process", "", "/bin/true") st, err := p.Status(context.Background(), a) if err != nil { t.Fatalf("Status: %v", err) } if st != StatePending { t.Errorf("Status = %q, want pending", st) } } // TestProcessRuntime_StartNoCommand verifies Start errors on missing // command. func TestProcessRuntime_StartNoCommand(t *testing.T) { p := NewProcessRuntime() a := &Alloc{ID: "x", Spec: nil, Runtime: "process"} if _, err := p.Start(context.Background(), a); err == nil { t.Error("Start with nil spec should error") } } // TestProcessRuntime_StartEmptyCommand verifies Start errors when // the command parses to zero argv. func TestProcessRuntime_StartEmptyCommand(t *testing.T) { p := NewProcessRuntime() a := &Alloc{ ID: "e", Runtime: "process", Spec: &jobspec.WorkloadSpec{ Runtime: &jobspec.RuntimeBlock{Command: " "}, }, } _, err := p.Start(context.Background(), a) if err == nil { t.Error("Start with empty command should error") } } // TestProcessRuntime_StartBadBinary verifies Start propagates exec // errors for a missing binary. func TestProcessRuntime_StartBadBinary(t *testing.T) { p := NewProcessRuntime() a := alloc("process", "", "/no/such/binary/here") _, err := p.Start(context.Background(), a) if err == nil { t.Error("Start with missing binary should error") } } // TestProcessRuntime_StopOnFinishedProcess verifies Stop on a process // that already exited (e.g. /bin/true) is a no-op (no error). func TestProcessRuntime_StopOnFinishedProcess(t *testing.T) { p := NewProcessRuntime() a := alloc("process", "", "/bin/true") _, _ = p.Start(context.Background(), a) // Give /bin/true time to exit. time.Sleep(200 * time.Millisecond) ctx, cancel := withTimeout(5 * time.Second) defer cancel() if err := p.Stop(ctx, a); err != nil { t.Errorf("Stop on exited process should be no-op, got %v", err) } } // TestProcessRuntime_PIDUnknown verifies PID lookup errors for an // unknown alloc. func TestProcessRuntime_PIDUnknown(t *testing.T) { p := NewProcessRuntime() if _, err := p.PID("nope"); err == nil { t.Error("PID unknown should error") } } // sleepBin returns the sleep command path ("sleep") on this OS. func sleepBin() (string, error) { // /bin/sleep exists on Linux; on other platforms fall back to // "sleep" (resolved via PATH). if runtime.GOOS == "linux" { return "/bin/sleep", nil } return "sleep", nil }