diff options
| author | James O'Doherty <james@theodohertyfamily.com> | 2026-05-29 20:35:31 -0400 |
|---|---|---|
| committer | James O'Doherty <james@theodohertyfamily.com> | 2026-05-29 20:35:31 -0400 |
| commit | d4cec92f5690a60b3509ab718bdea72dc520110e (patch) | |
| tree | b29218a4fee4bbf3b2f4bf25a161f2a74bb98b85 /tests/e2e/resource_exhaustion_test.go | |
| parent | 4ddd0d2ffc7073f2d55ffb6777e3a168af0051f0 (diff) | |
feat: implement robust namespace lifecycle and resilience suite
- Replace marker-file pinning with kernel bind-mount anchors for reliable namespace persistence.
- Implement atomic "last-man-out" cleanup sequence using ProfileLock, preventing namespace leaks and race conditions.
- Add comprehensive resilience test suite covering:
- Crash recovery from stale runtime state.
- Host network change stability.
- Configuration hot-swap session persistence.
- Resource exhaustion and high-churn lifecycle stress.
- Align documentation and test expectations with rootless session-based persistence.
- Fix argument integrity and isolation leaks.
- Ensure 100% pass rate for all E2E and integration tests.
Diffstat (limited to 'tests/e2e/resource_exhaustion_test.go')
| -rw-r--r-- | tests/e2e/resource_exhaustion_test.go | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/tests/e2e/resource_exhaustion_test.go b/tests/e2e/resource_exhaustion_test.go new file mode 100644 index 0000000..3e60cdb --- /dev/null +++ b/tests/e2e/resource_exhaustion_test.go @@ -0,0 +1,64 @@ +package e2e + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" +) + +// TestResourceExhaustion ensures that repeatedly starting and stopping +// tunnels does not leak mounts, file descriptors, or namespaces. +func TestResourceExhaustion(t *testing.T) { + binaryPath, err := GetBinaryPath() + if err != nil { + t.Skipf("Skipping test: %v", err) + } + + tmpRuntimeDir := t.TempDir() + tmpConfigDir := t.TempDir() + profile := "stress-test" + + profilesDir := filepath.Join(tmpConfigDir, "wg-wrap", "profiles") + if err := os.MkdirAll(profilesDir, 0755); err != nil { + t.Fatal(err) + } + profileConfPath := filepath.Join(profilesDir, profile+".conf") + conf := `[Interface] +Address = 10.0.0.2/24 +PrivateKey = 0000000000000000000000000000000000000000000000000000000000000000 +[Peer] +PublicKey = 0000000000000000000000000000000000000000000000000000000000000000 +AllowedIPs = 0.0.0.0/0 +Endpoint = 1.1.1.1:51820 +` + if err := os.WriteFile(profileConfPath, []byte(conf), 0644); err != nil { + t.Fatal(err) + } + + // We run a burst of short-lived commands to stress the lock and cleanup logic. + iterations := 50 + for i := 0; i < iterations; i++ { + cmd := exec.Command(binaryPath, "--profile", profile, "--", "true") + cmd.Env = append(os.Environ(), + fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), + fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), + ) + if err := cmd.Run(); err != nil { + t.Fatalf("Iteration %d failed: %v", i, err) + } + } + + // After all iterations, the pin file should be gone. + nsPath := filepath.Join(tmpRuntimeDir, "profiles", profile+".ns") + if _, err := os.Stat(nsPath); err == nil { + t.Errorf("BUG: Namespace pin file %s still exists after %d iterations", nsPath, iterations) + } + + // PIDs directory should be empty or gone. + pidsDir := filepath.Join(tmpRuntimeDir, "profiles", profile, "pids") + if files, err := os.ReadDir(pidsDir); err == nil && len(files) > 0 { + t.Errorf("BUG: PIDs directory not empty after stress test: %d files remaining", len(files)) + } +} |
