package e2e import ( "fmt" "os" "os/exec" "path/filepath" "testing" "time" ) func waitForPids(t *testing.T, pidsDir string, expectedCount int) { timeout := time.After(10 * time.Second) tick := time.NewTicker(100 * time.Millisecond) defer tick.Stop() for { select { case <-timeout: files, err := os.ReadDir(pidsDir) if err != nil { t.Fatalf("Failed to read pids dir during timeout: %v", err) } t.Fatalf("Timed out waiting for %d PID files, got %d", expectedCount, len(files)) case <-tick.C: files, err := os.ReadDir(pidsDir) if err != nil { if os.IsNotExist(err) { continue } t.Fatalf("Failed to read pids dir: %v", err) } if len(files) == expectedCount { return } } } } func waitForLifecycle(t *testing.T, binaryPath, runtimeDir, profile string, expectedActive bool) { timeout := time.After(10 * time.Second) tick := time.NewTicker(100 * time.Millisecond) defer tick.Stop() for { select { case <-timeout: t.Fatalf("Timed out waiting for lifecycle state: expected active=%v", expectedActive) case <-tick.C: cmd := exec.Command(binaryPath, "test-lifecycle", "--profile", profile) cmd.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) err := cmd.Run() isActive := err == nil if isActive == expectedActive { return } } } } func TestNamespaceLifecycleAutomation(t *testing.T) { // 1. Setup Environment binaryPath, err := GetBinaryPath() if err != nil { t.Skipf("Skipping test: %v", err) } // 2. Override the runtime base dir to a temporary location tmpRuntimeDir := t.TempDir() profile := "default" pidsDir := filepath.Join(tmpRuntimeDir, "profiles", profile, "pids") // Clean up before starting if err := os.RemoveAll(filepath.Join(tmpRuntimeDir, "profiles", profile)); err != nil { t.Fatalf("failed to remove profile directory: %v", err) } t.Run("ReferenceCounting", func(t *testing.T) { // Start a process that exits quickly cmd1 := exec.Command(binaryPath, "--profile", "default", "--", "sleep", "1.0") cmd1.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir)) if err := cmd1.Start(); err != nil { t.Fatalf("Failed to start cmd1: %v", err) } // Verify PID file exists using polling waitForLifecycle(t, binaryPath, tmpRuntimeDir, "default", true) // Start a second process using the same profile cmd2 := exec.Command(binaryPath, "--profile", "default", "--", "sleep", "1.0") cmd2.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir)) if err := cmd2.Start(); err != nil { t.Fatalf("Failed to start cmd2: %v", err) } waitForPids(t, pidsDir, 2) // Wait for first process to exit naturally (triggering defer) if err := cmd1.Wait(); err != nil { t.Fatalf("cmd1 failed: %v", err) } // Poll for the count to drop back to 1 waitForLifecycle(t, binaryPath, tmpRuntimeDir, "default", true) // Wait for second process to exit naturally if err := cmd2.Wait(); err != nil { t.Fatalf("cmd2 failed: %v", err) } // Verify a clean state (expect 0 files) waitForLifecycle(t, binaryPath, tmpRuntimeDir, "default", false) }) }