- Activate / Deactivate via flox activate --detach (with graceful SIGTERM→SIGKILL) - Non-privileged IsAlive process check with Linux /proc fallback - Dev-mode stub when flox is missing or FLOXENV_DEV_MODE=1 - Unit and integration tests Closes #16
141 lines
3.4 KiB
Go
141 lines
3.4 KiB
Go
package floxenv
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNewRegistry_DetectsDevMode(t *testing.T) {
|
|
r := NewRegistry()
|
|
if _, err := exec.LookPath(defaultFloxPath); err == nil && os.Getenv(devModeEnvVar) != "1" {
|
|
if r.IsDevMode() {
|
|
t.Fatal("expected dev mode false when flox is present and FLOXENV_DEV_MODE unset")
|
|
}
|
|
} else {
|
|
if !r.IsDevMode() {
|
|
t.Fatal("expected dev mode true when flox missing or FLOXENV_DEV_MODE=1")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestActivate_Stub(t *testing.T) {
|
|
// Force dev mode for a deterministic test.
|
|
t.Setenv(devModeEnvVar, "1")
|
|
|
|
tmpDir := t.TempDir()
|
|
manifest := filepath.Join(tmpDir, "manifest.toml")
|
|
if err := os.WriteFile(manifest, []byte("[install]\n\n"), 0600); err != nil {
|
|
t.Fatalf("write manifest: %v", err)
|
|
}
|
|
|
|
r := NewRegistry()
|
|
envID, pid, err := r.Activate(manifest)
|
|
if err != nil {
|
|
t.Fatalf("activate failed: %v", err)
|
|
}
|
|
if envID == "" {
|
|
t.Fatal("expected non-empty envID")
|
|
}
|
|
if pid <= 0 {
|
|
t.Fatalf("expected positive pid, got %d", pid)
|
|
}
|
|
defer func() {
|
|
_ = r.Deactivate(envID, pid)
|
|
}()
|
|
|
|
if !IsAlive(pid) {
|
|
t.Fatalf("expected pid %d to be alive after stub activation", pid)
|
|
}
|
|
}
|
|
|
|
func TestDeactivate_GracefulShutdown(t *testing.T) {
|
|
t.Setenv(devModeEnvVar, "1")
|
|
|
|
tmpDir := t.TempDir()
|
|
manifest := filepath.Join(tmpDir, "manifest.toml")
|
|
_ = os.WriteFile(manifest, []byte(""), 0600)
|
|
|
|
r := NewRegistry()
|
|
envID, pid, err := r.Activate(manifest)
|
|
if err != nil {
|
|
t.Fatalf("activate failed: %v", err)
|
|
}
|
|
|
|
// Deactivate should succeed.
|
|
if err := r.Deactivate(envID, pid); err != nil {
|
|
t.Fatalf("deactivate failed: %v", err)
|
|
}
|
|
|
|
// Allow scheduler to reap.
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
if IsAlive(pid) {
|
|
t.Fatalf("expected pid %d to be dead after deactivate", pid)
|
|
}
|
|
|
|
// Registry entry should be removed.
|
|
for _, info := range r.List() {
|
|
if info.EnvID == envID {
|
|
t.Fatalf("registry still holds envID %s after deactivation", envID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeactivate_SigkillFallback(t *testing.T) {
|
|
t.Setenv(devModeEnvVar, "1")
|
|
|
|
// Start a child that ignores SIGTERM.
|
|
cmd := exec.Command("sh", "-c", `
|
|
trap '' TERM
|
|
sleep 3600
|
|
`)
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("start ignore-sigterm process: %v", err)
|
|
}
|
|
pid := cmd.Process.Pid
|
|
|
|
r := NewRegistry()
|
|
envID := "forced-ignore-term"
|
|
r.m[envID] = Info{EnvID: envID, PID: pid, ManifestPath: "/tmp/dummy", StartedAt: time.Now()}
|
|
|
|
err := r.Deactivate(envID, pid)
|
|
if err != nil {
|
|
// A timeout is acceptable here; the key assertion is that the process
|
|
// is no longer alive after Deactivate returns.
|
|
if !errors.Is(err, ErrTimeout) {
|
|
t.Fatalf("unexpected deactivation error: %v", err)
|
|
}
|
|
}
|
|
|
|
// Allow the kernel time to reap.
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if IsAlive(pid) {
|
|
t.Fatalf("expected pid %d to be dead after deactivate, but it is still alive", pid)
|
|
}
|
|
}
|
|
|
|
func TestIsAlive_InvalidPid(t *testing.T) {
|
|
if IsAlive(0) {
|
|
t.Fatal("expected pid 0 to be reported dead")
|
|
}
|
|
if IsAlive(-1) {
|
|
t.Fatal("expected pid -1 to be reported dead")
|
|
}
|
|
// PID 1 should exist on Linux, but in restricted containers it may not.
|
|
// We only assert that the function does not panic.
|
|
_ = IsAlive(1)
|
|
}
|
|
|
|
func isTimeout(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
return err.Error() == ErrTimeout.Error() || err.Error() == (ErrTimeout.Error()+": envID=forced-ignore-term pid=0")
|
|
}
|