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
165 lines
4.3 KiB
Go
165 lines
4.3 KiB
Go
//go:build linux
|
|
|
|
package proc
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
)
|
|
|
|
// These tests anchor on the test process itself (its PID, an fd it holds, a
|
|
// lock it takes, a port it binds) so the assertions are deterministic even
|
|
// though the real /proc has hundreds of unrelated entries.
|
|
|
|
func TestReadExtendedInfoSelf(t *testing.T) {
|
|
mem, _, fds, fdCount, fdLimit, threads, err := ReadExtendedInfo(os.Getpid())
|
|
if err != nil {
|
|
t.Fatalf("ReadExtendedInfo(self): %v", err)
|
|
}
|
|
if mem.RSS == 0 {
|
|
t.Error("RSS should be non-zero for a running process")
|
|
}
|
|
if fdCount == 0 || len(fds) == 0 {
|
|
t.Errorf("fdCount=%d, len(fds)=%d; both should be > 0 (stdin/out/err at minimum)", fdCount, len(fds))
|
|
}
|
|
if fdLimit == 0 {
|
|
t.Error("fdLimit should be > 0")
|
|
}
|
|
if threads == 0 {
|
|
t.Error("threadCount should be > 0 (the Go runtime is multi-threaded)")
|
|
}
|
|
}
|
|
|
|
func TestGetFileContextSelf(t *testing.T) {
|
|
fc := GetFileContext(os.Getpid())
|
|
if fc == nil {
|
|
t.Fatal("GetFileContext(self) = nil")
|
|
}
|
|
if fc.OpenFiles == 0 {
|
|
t.Error("OpenFiles should be > 0")
|
|
}
|
|
if fc.FileLimit == 0 {
|
|
t.Error("FileLimit should be > 0")
|
|
}
|
|
}
|
|
|
|
func TestGetResourceContextSelf(t *testing.T) {
|
|
// We can't assert specific resource numbers (they depend on the host), but
|
|
// the call must always return a non-nil context and exercise every reader.
|
|
if GetResourceContext(os.Getpid()) == nil {
|
|
t.Error("GetResourceContext(self) should never be nil")
|
|
}
|
|
}
|
|
|
|
func TestGetCmdlineSelf(t *testing.T) {
|
|
if cmd := GetCmdline(os.Getpid()); cmd == "" || cmd == "(unknown)" {
|
|
t.Errorf("GetCmdline(self) = %q, want the test binary's command line", cmd)
|
|
}
|
|
if cmd := GetCmdline(0); cmd != "(unknown)" {
|
|
t.Errorf("GetCmdline(0) = %q, want \"(unknown)\" for the unreadable kernel PID", cmd)
|
|
}
|
|
}
|
|
|
|
func TestListProcessSnapshotIncludesSelf(t *testing.T) {
|
|
snap, err := ListProcessSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("ListProcessSnapshot: %v", err)
|
|
}
|
|
self := os.Getpid()
|
|
for _, p := range snap {
|
|
if p.PID == self {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("snapshot of %d processes did not include self PID %d", len(snap), self)
|
|
}
|
|
|
|
func TestResolveChildrenFindsSpawnedChild(t *testing.T) {
|
|
if _, err := ResolveChildren(0); err == nil {
|
|
t.Error("ResolveChildren(0) should reject the invalid PID")
|
|
}
|
|
|
|
c := exec.Command("sleep", "30")
|
|
if err := c.Start(); err != nil {
|
|
t.Skipf("cannot spawn child: %v", err)
|
|
}
|
|
defer func() {
|
|
_ = c.Process.Kill()
|
|
_ = c.Wait()
|
|
}()
|
|
|
|
kids, err := ResolveChildren(os.Getpid())
|
|
if err != nil {
|
|
t.Fatalf("ResolveChildren(self): %v", err)
|
|
}
|
|
for _, k := range kids {
|
|
if k.PID == c.Process.Pid {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("ResolveChildren(self) did not include the spawned child %d (got %d children)", c.Process.Pid, len(kids))
|
|
}
|
|
|
|
func TestListAllOpenFilesFindsHeldFile(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "witr-open.txt")
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
self := os.Getpid()
|
|
for _, of := range ListAllOpenFiles() {
|
|
if of.PID == self && of.Path == path {
|
|
if of.Type != "OPEN" {
|
|
t.Errorf("Type = %q, want OPEN", of.Type)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("ListAllOpenFiles did not include our open file %q owned by PID %d", path, self)
|
|
}
|
|
|
|
func TestListLockedFilesFindsHeldLock(t *testing.T) {
|
|
f, err := os.CreateTemp(t.TempDir(), "witr-lock")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
// Take a real POSIX write lock so it shows up in /proc/locks under our PID.
|
|
lk := syscall.Flock_t{Type: syscall.F_WRLCK, Whence: 0}
|
|
if err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lk); err != nil {
|
|
t.Skipf("cannot place POSIX lock: %v", err)
|
|
}
|
|
|
|
self := os.Getpid()
|
|
for _, l := range ListLockedFiles() {
|
|
if l.PID == self {
|
|
return // our lock surfaced — resolveLockPath/lockProcessName/statKey exercised
|
|
}
|
|
}
|
|
t.Errorf("ListLockedFiles did not include a lock held by self (PID %d)", self)
|
|
}
|
|
|
|
func TestGetSocketStateForPortFindsListener(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
|
|
info := GetSocketStateForPort(port)
|
|
if info == nil {
|
|
t.Fatalf("GetSocketStateForPort(%d) = nil, want our listener", port)
|
|
}
|
|
if info.State != "LISTEN" {
|
|
t.Errorf("socket state = %q, want LISTEN", info.State)
|
|
}
|
|
}
|