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") }