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/config_hotswap_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/config_hotswap_test.go')
| -rw-r--r-- | tests/e2e/config_hotswap_test.go | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/tests/e2e/config_hotswap_test.go b/tests/e2e/config_hotswap_test.go new file mode 100644 index 0000000..6dfcec5 --- /dev/null +++ b/tests/e2e/config_hotswap_test.go @@ -0,0 +1,88 @@ +package e2e + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// TestConfigHotSwap verifies that changing the configuration file on disk +// does not affect an active session. A process joining an existing session +// should use the established tunnel's state, not the updated file. +func TestConfigHotSwap(t *testing.T) { + binaryPath, err := GetBinaryPath() + if err != nil { + t.Skipf("Skipping test: %v", err) + } + + tmpRuntimeDir := t.TempDir() + tmpConfigDir := t.TempDir() + profile := "hotswap-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") + + // 1. Initial configuration + conf1 := `[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(conf1), 0644); err != nil { + t.Fatal(err) + } + + // Start a process to establish the session + cmdA := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "5") + cmdA.Env = append(os.Environ(), + fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), + fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), + ) + if err := cmdA.Start(); err != nil { + t.Fatalf("Failed to start Process A: %v", err) + } + defer func() { _ = cmdA.Process.Kill() }() + + pidsDir := filepath.Join(tmpRuntimeDir, "profiles", profile, "pids") + waitForPids(t, pidsDir, 1) + + // 2. "Hot-Swap" the configuration file while the tunnel is active. + // We change the endpoint to something obviously different. + conf2 := `[Interface] +Address = 10.0.0.2/24 +PrivateKey = 0000000000000000000000000000000000000000000000000000000000000000 +[Peer] +PublicKey = 0000000000000000000000000000000000000000000000000000000000000000 +AllowedIPs = 0.0.0.0/0 +Endpoint = 8.8.8.8:51820 +` + if err := os.WriteFile(profileConfPath, []byte(conf2), 0644); err != nil { + t.Fatal(err) + } + + // 3. Launch a second process. It should join the existing session + // regardless of the fact that the .conf file has changed. + cmdB := exec.Command(binaryPath, "--profile", profile, "--", "ls") + cmdB.Env = append(os.Environ(), + fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), + fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), + ) + + out, err := cmdB.CombinedOutput() + if err != nil { + t.Fatalf("Process B failed to join session after config change: %v\nOutput: %s", err, string(out)) + } + + if !strings.Contains(string(out), "Joining active WireGuard tunnel") { + t.Errorf("Expected Process B to join active tunnel, but it re-initialized. Output: %s", string(out)) + } +} |
