package e2e import ( "fmt" "os" "os/exec" "path/filepath" "strings" "testing" ) // TestCrashRecovery verifies that wg-wrap can recover from a "dirty" state // where a previous run crashed, leaving behind stale PID and pin files. func TestCrashRecovery(t *testing.T) { binaryPath, err := GetBinaryPath() if err != nil { t.Skipf("Skipping test: %v", err) } tmpRuntimeDir := t.TempDir() tmpConfigDir := t.TempDir() profile := "crash-test" // Setup a valid profile config 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) } // 1. Simulate a "Crash" by creating stale state manually. // We create a pin file and some PID files for processes that aren't actually running. nsPath := filepath.Join(tmpRuntimeDir, "profiles", profile+".ns") profilesRuntimeDir := filepath.Join(tmpRuntimeDir, "profiles") if err := os.MkdirAll(profilesRuntimeDir, 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(nsPath, []byte("stale-pin"), 0644); err != nil { t.Fatal(err) } pidsDir := filepath.Join(tmpRuntimeDir, "profiles", profile, "pids") if err := os.MkdirAll(pidsDir, 0755); err != nil { t.Fatal(err) } // Create a fake PID file for a process that likely doesn't exist (PID 1234567) if err := os.WriteFile(filepath.Join(pidsDir, "1234567"), []byte(""), 0644); err != nil { t.Fatal(err) } // 2. Try to run wg-wrap. // It should see the stale PID, prune it, realize the namespace is actually dead, // and start a fresh tunnel. cmd := exec.Command(binaryPath, "--profile", profile, "--", "ls") cmd.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), ) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("wg-wrap failed to recover from stale state: %v\nOutput: %s", err, string(out)) } // 3. Verify it actually started a tunnel (it should have printed "Initializing...") if !strings.Contains(string(out), "Initializing WireGuard tunnel") { t.Errorf("Expected wg-wrap to initialize a new tunnel, but it didn't. Output: %s", string(out)) } }