summaryrefslogtreecommitdiff
path: root/internal/namespace/lifecycle_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/namespace/lifecycle_test.go')
-rw-r--r--internal/namespace/lifecycle_test.go110
1 files changed, 110 insertions, 0 deletions
diff --git a/internal/namespace/lifecycle_test.go b/internal/namespace/lifecycle_test.go
new file mode 100644
index 0000000..981cfd4
--- /dev/null
+++ b/internal/namespace/lifecycle_test.go
@@ -0,0 +1,110 @@
+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)
+ })
+}