From 3b56ccecf46b83fa9b0e4b6c54be6ffda395910c Mon Sep 17 00:00:00 2001 From: James O'Doherty Date: Fri, 22 May 2026 11:12:21 -0400 Subject: Implement automatic namespace lifecycle cleanup with last-man-out reference counting --- tests/e2e/lifecycle_test.go | 92 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/e2e/lifecycle_test.go (limited to 'tests/e2e/lifecycle_test.go') diff --git a/tests/e2e/lifecycle_test.go b/tests/e2e/lifecycle_test.go new file mode 100644 index 0000000..45890fa --- /dev/null +++ b/tests/e2e/lifecycle_test.go @@ -0,0 +1,92 @@ +package e2e + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" +) + +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 := "e2e-lifecycle-test" + pidsDir := filepath.Join(tmpRuntimeDir, "profiles", profile, "pids") + + // Clean up before starting + os.RemoveAll(filepath.Join(tmpRuntimeDir, "profiles", profile)) + + t.Run("ReferenceCounting", func(t *testing.T) { + // Start a process that exits quickly + cmd1 := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "0.1") + cmd1.Env = append(os.Environ(), fmt.Sprintf("WG_WRAP_RUNTIME_DIR=%s", tmpRuntimeDir)) + if err := cmd1.Start(); err != nil { + t.Fatalf("Failed to start cmd1: %v", err) + } + + // Allow a moment for the bootstrap loop to complete and register the PID + time.Sleep(500 * time.Millisecond) + + // Verify PID file exists + files, err := os.ReadDir(pidsDir) + if err != nil { + t.Fatalf("Failed to read pids dir: %v", err) + } + if len(files) != 1 { + t.Errorf("Expected 1 PID file, got %d", len(files)) + } + + // Start a second process using the same profile + cmd2 := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "0.1") + cmd2.Env = append(os.Environ(), fmt.Sprintf("WG_WRAP_RUNTIME_DIR=%s", tmpRuntimeDir)) + if err := cmd2.Start(); err != nil { + t.Fatalf("Failed to start cmd2: %v", err) + } + time.Sleep(500 * time.Millisecond) + + files, err = os.ReadDir(pidsDir) + if err != nil { + t.Fatalf("Failed to read pids dir: %v", err) + } + if len(files) != 2 { + t.Errorf("Expected 2 PID files, got %d", len(files)) + } + + // Wait for first process to exit naturally (triggering defer) + if err := cmd1.Wait(); err != nil { + t.Fatalf("cmd1 failed: %v", err) + } + time.Sleep(500 * time.Millisecond) + + files, err = os.ReadDir(pidsDir) + if err != nil { + t.Fatalf("Failed to read pids dir: %v", err) + } + if len(files) != 1 { + t.Errorf("Expected 1 PID file after first exit, got %d", len(files)) + } + + // Wait for second process to exit naturally + if err := cmd2.Wait(); err != nil { + t.Fatalf("cmd2 failed: %v", err) + } + time.Sleep(500 * time.Millisecond) + + // Verify a clean state + files, err = os.ReadDir(pidsDir) + if err != nil && !os.IsNotExist(err) { + t.Fatalf("Failed to read pids dir: %v", err) + } + if err == nil && len(files) != 0 { + t.Errorf("Expected 0 PID files after all exits, got %d", len(files)) + } + }) +} -- cgit v1.2.3