package namespace import ( "os" "os/exec" "path/filepath" "strconv" "testing" ) func TestLifecycleReferenceCounting(t *testing.T) { // Use a temporary directory to avoid polluting the system tmpDir := t.TempDir() SetRuntimeBaseDir(tmpDir) profile := "test-vpn" t.Run("RegisterAndUnregister", func(t *testing.T) { err := RegisterProcess(profile) if err != nil { t.Fatalf("failed to register: %v", err) } pidsDir := GetPidsDirPath(profile) pidFile := filepath.Join(pidsDir, strconv.Itoa(os.Getpid())) if _, err := os.Stat(pidFile); os.IsNotExist(err) { t.Errorf("PID file should exist at %s", pidFile) } err = UnregisterProcess(profile) if err != nil { t.Fatalf("failed to unregister: %v", err) } if _, err := os.Stat(pidFile); err == nil { t.Errorf("PID file should have been removed at %s", pidFile) } }) t.Run("PruneStalePids", func(t *testing.T) { pidsDir := GetPidsDirPath(profile) if err := os.MkdirAll(pidsDir, 0755); err != nil { t.Fatal(err) } // Create a fake PID file for a process that definitely doesn't exist // Using a very high PID or -1 usually works, but let's use a known invalid one. fakePid := "9999999" fakePidFile := filepath.Join(pidsDir, fakePid) if err := os.WriteFile(fakePidFile, []byte(""), 0644); err != nil { t.Fatal(err) } // Also register the current process so it stays RegisterProcess(profile) err := PruneStalePids(profile) if err != nil { t.Fatalf("prune failed: %v", err) } if _, err := os.Stat(fakePidFile); err == nil { t.Errorf("Stale PID file %s should have been pruned", fakePidFile) } // Current process should still be there currentPidFile := filepath.Join(pidsDir, strconv.Itoa(os.Getpid())) if _, err := os.Stat(currentPidFile); os.IsNotExist(err) { t.Errorf("Current PID file %s should not have been pruned", currentPidFile) } UnregisterProcess(profile) }) t.Run("IsLastProcess", func(t *testing.T) { pidsDir := GetPidsDirPath(profile) os.RemoveAll(pidsDir) // Reset // Case 1: No processes (should return true as it's a clean state) isLast, err := IsLastProcess(profile) if err != nil || !isLast { t.Errorf("Expected IsLastProcess to be true for empty profile, got %v, err: %v", isLast, err) } // Case 2: Only ourselves RegisterProcess(profile) isLast, err = IsLastProcess(profile) if err != nil || !isLast { t.Errorf("Expected IsLastProcess to be true for single process, got %v, err: %v", isLast, err) } // Case 3: Ourselves + another active process // To test this, we'll actually start a dummy process. cmd := exec.Command("sleep", "1") if err := cmd.Start(); err != nil { t.Fatalf("failed to start sleep process: %v", err) } defer cmd.Process.Kill() // Manually add the sleep process PID to the tracking os.WriteFile(filepath.Join(pidsDir, strconv.Itoa(cmd.Process.Pid)), []byte(""), 0644) isLast, err = IsLastProcess(profile) if err != nil || isLast { t.Errorf("Expected IsLastProcess to be false with two active processes, got %v, err: %v", isLast, err) } UnregisterProcess(profile) }) }