From 29621ecbd1e77e6e1a70b6b3ea8fbe3a56e47df3 Mon Sep 17 00:00:00 2001 From: James O'Doherty Date: Sat, 13 Jun 2026 11:51:04 -0400 Subject: refactor: implement dependency injection and enable parallel testing This commit refactors the core system operations to use a manager-based dependency injection pattern, eliminating global state and resolving data races in the test suite. Architecture: - Introduced NetworkManager and NetworkOps interface in internal/network to abstract netlink calls. - Introduced MountOps and FileSystem interfaces in internal/namespace to abstract mount and filesystem operations. - Introduced TunnelManager in internal/wireguard to coordinate tunnel lifecycle using the new abstractions. - Updated internal/cli and internal/manager to use these managers. Testing: - Restored t.Parallel() to unit tests in internal/network and internal/wireguard. - Implemented setupParallelEnv and an enhanced mockFS in wireguard_unit_test.go to ensure complete test isolation. - Added bootstrap_test.go to verify launcher preparation logic in internal/namespace without requiring syscall.Exec. - Resolved data races in internal/network tests. CLI: - Added support for -h, --help, and -help flags for the main command. Verification: - Passed all tests (unit, integration, E2E). - Verified zero data races with 'go test -race'. - Passed golangci-lint and go vet. --- internal/namespace/bootstrap_test.go | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 internal/namespace/bootstrap_test.go (limited to 'internal/namespace/bootstrap_test.go') diff --git a/internal/namespace/bootstrap_test.go b/internal/namespace/bootstrap_test.go new file mode 100644 index 0000000..eff04e2 --- /dev/null +++ b/internal/namespace/bootstrap_test.go @@ -0,0 +1,111 @@ +//go:build linux + +package namespace + +import ( + "os" + "strings" + "testing" +) + +func TestPrepareBootstrap(t *testing.T) { + t.Parallel() + + // We are testing the environment and argument preparation. + // This will actually open some FDs (memfd, netns, socket), + // which is fine for an integration-style unit test. + config, err := PrepareBootstrap() + if err != nil { + t.Fatalf("PrepareBootstrap failed: %v", err) + } + + // 1. Verify ExecPath is correct (should be a fd in /proc/self/fd) + if !strings.HasPrefix(config.ExecPath, "/proc/self/fd/") { + t.Errorf("expected ExecPath to be in /proc/self/fd/, got %s", config.ExecPath) + } + + // 2. Verify Arguments are preserved and expanded + if len(config.Args) == 0 || config.Args[0] == "" { + t.Error("args should not be empty") + } + + // 3. Verify Host NetNS FD is present in Env + foundNetNs := false + for _, env := range config.Env { + if strings.HasPrefix(env, "WG_WRAP_HOST_NETNS_FD=") { + foundNetNs = true + break + } + } + if !foundNetNs { + t.Error("WG_WRAP_HOST_NETNS_FD missing from environment") + } + + // 4. Verify Host Socket FD is present in Env + foundSocket := false + for _, env := range config.Env { + if strings.HasPrefix(env, "WG_WRAP_HOST_SOCKET_FD=") { + foundSocket = true + break + } + } + if !foundSocket { + t.Error("WG_WRAP_HOST_SOCKET_FD missing from environment") + } + + // 5. Verify FDs are tracked for cleanup + if len(config.Fds) < 2 { + t.Errorf("expected at least 2 FDs (launcher, netns), got %d", len(config.Fds)) + } +} + +func TestPrepareBootstrapJoin(t *testing.T) { + t.Parallel() + + targetPid := 1234 + config, err := PrepareBootstrapJoin(targetPid) + if err != nil { + t.Fatalf("PrepareBootstrapJoin failed: %v", err) + } + + // 1. Verify Join PID is present in Env + foundJoinPid := false + expectedPidEnv := "WG_WRAP_JOIN_PID=1234" + for _, env := range config.Env { + if env == expectedPidEnv { + foundJoinPid = true + break + } + } + if !foundJoinPid { + t.Errorf("expected %s in environment", expectedPidEnv) + } + + // 2. Verify Joined flag is present + foundJoined := false + for _, env := range config.Env { + if env == "WG_WRAP_JOINED=1" { + foundJoined = true + break + } + } + if !foundJoined { + t.Error("WG_WRAP_JOINED=1 missing from environment") + } +} + +func TestPrepareBootstrap_NullByteValidation(t *testing.T) { + // Temporarily inject a null byte into os.Args to test validation + // Note: os.Args is a slice, so we can modify it. + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + + os.Args = append(os.Args, "bad\x00arg") + + _, err := PrepareBootstrap() + if err == nil { + t.Error("expected error when argument contains null byte, got nil") + } else if !strings.Contains(err.Error(), "contains null byte") { + t.Errorf("expected null byte error, got: %v", err) + } +} -- cgit v1.2.3