Files
wehub-resource-sync 36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:06 +08:00

191 lines
6.2 KiB
Go

//go:build linux || darwin || freebsd || windows
package target
import (
"net"
"os"
"os/exec"
"runtime"
"slices"
"testing"
"time"
)
// Integration smoke tests for the target package's platform-specific
// resolution paths. They spin up real OS state (a sleeper child, a TCP
// listener) the test can identify by PID, then confirm the resolver finds it.
// startSleeper spawns a long-running child process suitable for name-resolution
// assertions. Returns the child's PID and process name; the caller must invoke
// the returned cleanup func to kill it.
func startSleeper(t *testing.T) (pid int, name string, cleanup func()) {
t.Helper()
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
// `ping -n N 127.0.0.1` is a reliable cross-version Windows sleeper
// that doesn't require a TTY (unlike `timeout /nobreak`).
cmd = exec.Command("ping", "-n", "60", "127.0.0.1")
name = "ping"
default:
cmd = exec.Command("sleep", "60")
name = "sleep"
}
if err := cmd.Start(); err != nil {
t.Skipf("could not spawn %s for integration test: %v", name, err)
}
return cmd.Process.Pid, name, func() {
_ = cmd.Process.Kill()
_, _ = cmd.Process.Wait()
}
}
// TestIntegration_ResolveNameFindsSpawnedChild proves the name resolver talks
// to the real OS process table. We can't ResolveName against the test binary
// itself because every platform's name resolver excludes self+ancestors —
// that's a feature (so `witr bash` doesn't always match the user's own
// shell), but it forces this test to use a spawned child instead.
func TestIntegration_ResolveNameFindsSpawnedChild(t *testing.T) {
childPID, name, cleanup := startSleeper(t)
defer cleanup()
// Give the OS a beat to register the child. ToolHelp32 / /proc updates
// are usually instant, but CI runners can be sluggish.
var pids []int
var lastErr error
for i := 0; i < 10; i++ {
var err error
pids, err = ResolveName(name, true)
if err == nil && slices.Contains(pids, childPID) {
return
}
lastErr = err
time.Sleep(100 * time.Millisecond)
}
t.Errorf("ResolveName(%q) did not find spawned child PID %d after retries; got %v (last err: %v)",
name, childPID, pids, lastErr)
}
// TestIntegration_ResolveNameFindsGrep is a regression test for the removed
// "grep" name-matching exclusion. witr reads the process table directly (no
// `ps | grep` pipeline), so a real grep process must be resolvable by name —
// previously it was silently filtered out on every Unix platform.
func TestIntegration_ResolveNameFindsGrep(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("grep is not available on Windows")
}
if _, err := exec.LookPath("grep"); err != nil {
t.Skipf("grep not found in PATH: %v", err)
}
// grep with no file argument blocks reading stdin; keeping the pipe open
// holds it alive for the duration of the lookup.
cmd := exec.Command("grep", "witr-regression-pattern")
stdin, err := cmd.StdinPipe()
if err != nil {
t.Fatalf("StdinPipe: %v", err)
}
if err := cmd.Start(); err != nil {
t.Skipf("could not spawn grep: %v", err)
}
childPID := cmd.Process.Pid
defer func() {
_ = stdin.Close()
_ = cmd.Process.Kill()
_, _ = cmd.Process.Wait()
}()
var pids []int
var lastErr error
for i := 0; i < 10; i++ {
pids, lastErr = ResolveName("grep", true)
if lastErr == nil && slices.Contains(pids, childPID) {
return
}
time.Sleep(100 * time.Millisecond)
}
t.Errorf("ResolveName(%q) did not find spawned grep PID %d after retries; got %v (last err: %v)",
"grep", childPID, pids, lastErr)
}
// TestIntegration_ResolvePortFindsLoopbackListener binds a real TCP listener
// on a random localhost port and asserts ResolvePort attributes it to the
// test process. This drives the platform's port-resolution machinery end to
// end (netstat/lsof/sockstat/Win32 netstat plus /proc fd scan).
func TestIntegration_ResolvePortFindsLoopbackListener(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen: %v", err)
}
defer ln.Close()
port := ln.Addr().(*net.TCPAddr).Port
self := os.Getpid()
pids, err := ResolvePort(port)
if err != nil {
t.Fatalf("ResolvePort(%d): %v", port, err)
}
if !slices.Contains(pids, self) {
t.Errorf("ResolvePort(%d) = %v, missing self PID %d", port, pids, self)
}
}
// TestIntegration_ResolvePortNonexistent asserts the resolver errors cleanly
// on a port nothing has bound. Picking a random ephemeral port and closing
// the listener gives us a port we know nobody else owns.
func TestIntegration_ResolvePortNonexistent(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen: %v", err)
}
port := ln.Addr().(*net.TCPAddr).Port
ln.Close() // free the port immediately
// The kernel may keep the port in TIME_WAIT briefly. Loop a few times
// until the port is genuinely unowned.
for i := 0; i < 10; i++ {
_, err := ResolvePort(port)
if err != nil {
return // expected
}
time.Sleep(50 * time.Millisecond)
}
t.Errorf("ResolvePort(%d) returned nil error for an unbound port", port)
}
// TestIntegration_ResolveFileSelf creates a file, holds it open, and confirms
// the platform's file resolver attributes it to the test process — exercising
// /proc/fd (Linux), lsof (macOS), fstat (FreeBSD), and the Restart Manager
// (Windows). macOS/FreeBSD lean on external tools that may be absent or quirky
// in minimal CI images, so a miss there is tolerated rather than failed.
func TestIntegration_ResolveFileSelf(t *testing.T) {
f, err := os.CreateTemp("", "witr-file-*.tmp")
if err != nil {
t.Fatalf("CreateTemp: %v", err)
}
defer func() { _ = os.Remove(f.Name()) }()
defer f.Close()
if _, err := f.WriteString("witr"); err != nil {
t.Fatalf("write temp file: %v", err)
}
self := os.Getpid()
var pids []int
var lastErr error
for i := 0; i < 10; i++ {
pids, lastErr = ResolveFile(f.Name())
if lastErr == nil && slices.Contains(pids, self) {
return
}
time.Sleep(100 * time.Millisecond)
}
if runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" {
t.Skipf("ResolveFile on %s did not attribute the file to self (got %v, err %v); tolerated", runtime.GOOS, pids, lastErr)
}
t.Errorf("ResolveFile(%q) did not find self PID %d; got %v (last err: %v)", f.Name(), self, pids, lastErr)
}