You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gopsutil/process/process_test.go

693 lines
20 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause
package process
import (
"bufio"
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"os/user"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/shirou/gopsutil/v4/internal/common"
)
var mu sync.Mutex
func skipIfNotImplementedErr(t *testing.T, err error) {
if errors.Is(err, common.ErrNotImplementedError) {
t.Skip("not implemented")
}
}
func testGetProcess() Process {
checkPid := os.Getpid() // process.test
ret, _ := NewProcess(int32(checkPid))
return *ret
}
func TestPids(t *testing.T) {
ret, err := Pids()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
assert.NotEmptyf(t, ret, "could not get pids %v", ret)
}
func TestPid_exists(t *testing.T) {
checkPid := os.Getpid()
ret, err := PidExists(int32(checkPid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
assert.Truef(t, ret, "could not get process exists: %v", ret)
}
func TestNewProcess(t *testing.T) {
checkPid := os.Getpid()
ret, err := NewProcess(int32(checkPid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
empty := &Process{}
if runtime.GOOS != "windows" { // Windows pid is 0
assert.NotSamef(t, empty, ret, "error %v", ret)
}
}
func TestMemoryMaps(t *testing.T) {
checkPid := os.Getpid()
ret, err := NewProcess(int32(checkPid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
// ungrouped memory maps
mmaps, err := ret.MemoryMaps(false)
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "memory map get error %v", err)
empty := MemoryMapsStat{}
for _, m := range *mmaps {
assert.NotEqualf(t, m, empty, "memory map get error %v", m)
}
// grouped memory maps
mmaps, err = ret.MemoryMaps(true)
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "memory map get error %v", err)
assert.Lenf(t, *mmaps, 1, "grouped memory maps length (%v) is not equal to 1", len(*mmaps))
assert.NotEqualf(t, (*mmaps)[0], empty, "memory map is empty")
}
func TestMemoryInfo(t *testing.T) {
p := testGetProcess()
v, err := p.MemoryInfo()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting memory info error %v", err)
empty := MemoryInfoStat{}
if v == nil || *v == empty {
t.Errorf("could not get memory info %v", v)
}
}
func TestCmdLine(t *testing.T) {
p := testGetProcess()
v, err := p.Cmdline()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting cmdline error %v", err)
assert.Containsf(t, v, "process.test", "invalid cmd line %v", v)
}
func TestCmdLineSlice(t *testing.T) {
p := testGetProcess()
v, err := p.CmdlineSlice()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting cmdline slice error %v", err)
assert.Truef(t, reflect.DeepEqual(v, os.Args), "returned cmdline slice not as expected:\nexp: %v\ngot: %v", os.Args, v)
}
func TestPpid(t *testing.T) {
p := testGetProcess()
v, err := p.Ppid()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting ppid error %v", err)
assert.NotZerof(t, v, "return value is 0 %v", v)
expected := os.Getppid()
assert.Equalf(t, int32(expected), v, "return value is %v, expected %v", v, expected)
}
func TestStatus(t *testing.T) {
p := testGetProcess()
v, err := p.Status()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting status error %v", err)
assert.NotEmptyf(t, v, "could not get state")
if v[0] != Running && v[0] != Sleep {
t.Errorf("got wrong state, %v", v)
}
}
func TestTerminal(t *testing.T) {
p := testGetProcess()
_, err := p.Terminal()
skipIfNotImplementedErr(t, err)
assert.NoErrorf(t, err, "getting terminal error %v", err)
}
func TestIOCounters(t *testing.T) {
p := testGetProcess()
v, err := p.IOCounters()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting iocounter error %v", err)
empty := &IOCountersStat{}
assert.NotSamef(t, v, empty, "error %v", v)
}
func TestNumCtx(t *testing.T) {
p := testGetProcess()
_, err := p.NumCtxSwitches()
skipIfNotImplementedErr(t, err)
assert.NoErrorf(t, err, "getting numctx error %v", err)
}
func TestNice(t *testing.T) {
p := testGetProcess()
// https://github.com/shirou/gopsutil/issues/1532
if os.Getenv("CI") == "true" && runtime.GOOS == "darwin" {
t.Skip("Skip CI")
}
n, err := p.Nice()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting nice error %v", err)
if runtime.GOOS != "windows" && n != 0 && n != 20 && n != 8 {
t.Errorf("invalid nice: %d", n)
}
}
func TestGroups(t *testing.T) {
p := testGetProcess()
v, err := p.Groups()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting groups error %v", err)
if len(v) == 0 {
t.Skip("Groups is empty")
}
}
func TestNumThread(t *testing.T) {
p := testGetProcess()
n, err := p.NumThreads()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting NumThread error %v", err)
assert.GreaterOrEqualf(t, n, int32(0), "invalid NumThread: %d", n)
}
func TestThreads(t *testing.T) {
p := testGetProcess()
n, err := p.NumThreads()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting NumThread error %v", err)
assert.GreaterOrEqualf(t, n, int32(0), "invalid NumThread: %d", n)
ts, err := p.Threads()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting Threads error %v", err)
assert.Equalf(t, len(ts), int(n), "unexpected number of threads: %v vs %v", len(ts), n)
}
func TestName(t *testing.T) {
p := testGetProcess()
n, err := p.Name()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting name error %v", err)
assert.Containsf(t, n, "process.test", "invalid Name %s", n)
}
// #nosec G204
func TestLong_Name_With_Spaces(t *testing.T) {
tmpdir, err := os.MkdirTemp("", "")
require.NoErrorf(t, err, "unable to create temp dir %v", err)
defer os.RemoveAll(tmpdir) // clean up
tmpfilepath := filepath.Join(tmpdir, "loooong name with spaces.go")
tmpfile, err := os.Create(tmpfilepath)
require.NoErrorf(t, err, "unable to create temp file %v", err)
tmpfilecontent := []byte("package main\nimport(\n\"time\"\n)\nfunc main(){\nfor range time.Tick(time.Second) {}\n}")
if _, err := tmpfile.Write(tmpfilecontent); err != nil {
tmpfile.Close()
t.Fatalf("unable to write temp file %v", err)
}
require.NoErrorf(t, tmpfile.Close(), "unable to close temp file")
err = exec.Command("go", "build", "-o", tmpfile.Name()+".exe", tmpfile.Name()).Run()
require.NoErrorf(t, err, "unable to build temp file %v", err)
cmd := exec.Command(tmpfile.Name() + ".exe")
require.NoError(t, cmd.Start())
time.Sleep(100 * time.Millisecond)
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
n, err := p.Name()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting name error %v", err)
basename := filepath.Base(tmpfile.Name() + ".exe")
require.Equalf(t, basename, n, "%s != %s", basename, n)
cmd.Process.Kill()
}
// #nosec G204
func TestLong_Name(t *testing.T) {
tmpdir, err := os.MkdirTemp("", "")
require.NoErrorf(t, err, "unable to create temp dir %v", err)
defer os.RemoveAll(tmpdir) // clean up
tmpfilepath := filepath.Join(tmpdir, "looooooooooooooooooooong.go")
tmpfile, err := os.Create(tmpfilepath)
require.NoErrorf(t, err, "unable to create temp file %v", err)
tmpfilecontent := []byte("package main\nimport(\n\"time\"\n)\nfunc main(){\nfor range time.Tick(time.Second) {}\n}")
if _, err := tmpfile.Write(tmpfilecontent); err != nil {
tmpfile.Close()
t.Fatalf("unable to write temp file %v", err)
}
require.NoErrorf(t, tmpfile.Close(), "unable to close temp file")
err = exec.Command("go", "build", "-o", tmpfile.Name()+".exe", tmpfile.Name()).Run()
require.NoErrorf(t, err, "unable to build temp file %v", err)
cmd := exec.Command(tmpfile.Name() + ".exe")
require.NoError(t, cmd.Start())
time.Sleep(100 * time.Millisecond)
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
n, err := p.Name()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting name error %v", err)
basename := filepath.Base(tmpfile.Name() + ".exe")
require.Equalf(t, basename, n, "%s != %s", basename, n)
cmd.Process.Kill()
}
func TestName_Against_Python(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("only applies to posix")
}
py3Path, err := exec.LookPath("python3")
if err != nil {
t.Skipf("python3 not found: %s", err)
}
if out, err := exec.Command(py3Path, "-c", "import psutil").CombinedOutput(); err != nil {
t.Skipf("psutil not found for %s: %s", py3Path, out)
}
tmpdir, err := os.MkdirTemp("", "")
require.NoErrorf(t, err, "unable to create temp dir %v", err)
defer os.RemoveAll(tmpdir) // clean up
tmpfilepath := filepath.Join(tmpdir, "looooooooooooooooooooong.py")
tmpfile, err := os.Create(tmpfilepath)
require.NoErrorf(t, err, "unable to create temp file %v", err)
tmpfilecontent := []byte("#!" + py3Path + "\nimport psutil, time\nprint(psutil.Process().name(), flush=True)\nwhile True:\n\ttime.sleep(1)")
if _, err := tmpfile.Write(tmpfilecontent); err != nil {
tmpfile.Close()
t.Fatalf("unable to write temp file %v", err)
}
require.NoErrorf(t, tmpfile.Chmod(0o744), "unable to chmod u+x temp file")
require.NoErrorf(t, tmpfile.Close(), "unable to close temp file")
cmd := exec.Command(tmpfilepath)
outPipe, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(outPipe)
cmd.Start()
defer cmd.Process.Kill()
scanner.Scan()
pyName := scanner.Text() // first line printed by py3 script, its name
t.Logf("pyName %s", pyName)
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting process error %v", err)
name, err := p.Name()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting name error %v", err)
require.Equalf(t, pyName, name, "psutil and gopsutil process.Name() results differ: expected %s, got %s", pyName, name)
}
func TestExe(t *testing.T) {
p := testGetProcess()
n, err := p.Exe()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting Exe error %v", err)
assert.Containsf(t, n, "process.test", "invalid Exe %s", n)
}
func TestCpuPercent(t *testing.T) {
p := testGetProcess()
_, err := p.Percent(0)
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
duration := time.Duration(1000) * time.Microsecond
time.Sleep(duration)
percent, err := p.Percent(0)
require.NoError(t, err)
numcpu := runtime.NumCPU()
// if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO
require.GreaterOrEqualf(t, percent, 0.0, "CPUPercent value is invalid: %f, %d", percent, numcpu)
}
func TestCpuPercentLoop(t *testing.T) {
p := testGetProcess()
numcpu := runtime.NumCPU()
for i := 0; i < 2; i++ {
duration := time.Duration(100) * time.Microsecond
percent, err := p.Percent(duration)
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
// if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO
require.GreaterOrEqualf(t, percent, 0.0, "CPUPercent value is invalid: %f, %d", percent, numcpu)
}
}
func TestCreateTime(t *testing.T) {
if os.Getenv("CI") == "true" {
t.Skip("Skip CI")
}
p := testGetProcess()
c, err := p.CreateTime()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
assert.GreaterOrEqualf(t, c, 1420000000, "process created time is wrong.")
gotElapsed := time.Since(time.Unix(int64(c/1000), 0))
maxElapsed := time.Duration(20 * time.Second)
assert.Lessf(t, gotElapsed, maxElapsed, "this process has not been running for %v", gotElapsed)
}
func TestParent(t *testing.T) {
p := testGetProcess()
c, err := p.Parent()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
require.NotNilf(t, c, "could not get parent")
require.NotZerof(t, c.Pid, "wrong parent pid")
}
func TestConnections(t *testing.T) {
p := testGetProcess()
addr, err := net.ResolveTCPAddr("tcp", "localhost:0") // dynamically get a random open port from OS
require.NoErrorf(t, err, "unable to resolve localhost: %v", err)
l, err := net.ListenTCP(addr.Network(), addr)
require.NoErrorf(t, err, "unable to listen on %v: %v", addr, err)
defer l.Close()
tcpServerAddr := l.Addr().String()
tcpServerAddrIP := strings.Split(tcpServerAddr, ":")[0]
tcpServerAddrPort, err := strconv.ParseUint(strings.Split(tcpServerAddr, ":")[1], 10, 32)
require.NoErrorf(t, err, "unable to parse tcpServerAddr port: %v", err)
serverEstablished := make(chan struct{})
go func() { // TCP listening goroutine
conn, err := l.Accept()
if err != nil {
panic(err)
}
defer conn.Close()
serverEstablished <- struct{}{}
_, err = io.ReadAll(conn)
if err != nil {
panic(err)
}
}()
conn, err := net.Dial("tcp", tcpServerAddr)
require.NoErrorf(t, err, "unable to dial %v: %v", tcpServerAddr, err)
defer conn.Close()
// Rarely the call to net.Dial returns before the server connection is
// established. Wait so that the test doesn't fail.
<-serverEstablished
c, err := p.Connections()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
require.NotEmptyf(t, c, "no connections found")
serverConnections := 0
for _, connection := range c {
if connection.Laddr.IP == tcpServerAddrIP && connection.Laddr.Port == uint32(tcpServerAddrPort) && connection.Raddr.Port != 0 {
require.Equalf(t, "ESTABLISHED", connection.Status, "expected server connection to be ESTABLISHED, have %+v", connection)
serverConnections++
}
}
clientConnections := 0
for _, connection := range c {
if connection.Raddr.IP == tcpServerAddrIP && connection.Raddr.Port == uint32(tcpServerAddrPort) {
require.Equalf(t, "ESTABLISHED", connection.Status, "expected client connection to be ESTABLISHED, have %+v", connection)
clientConnections++
}
}
// two established connections, one for the server, the other for the client
require.Equalf(t, 1, serverConnections, "expected 1 server connection, have %d.\nDetails: %+v", serverConnections, c)
// two established connections, one for the server, the other for the client
require.Equalf(t, 1, clientConnections, "expected 1 server connection, have %d.\nDetails: %+v", clientConnections, c)
}
func TestChildren(t *testing.T) {
p := testGetProcess()
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("ping", "localhost", "-n", "4")
} else {
cmd = exec.Command("sleep", "3")
}
require.NoError(t, cmd.Start())
time.Sleep(100 * time.Millisecond)
c, err := p.Children()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
require.NotEmptyf(t, c, "children is empty")
found := false
for _, child := range c {
if child.Pid == int32(cmd.Process.Pid) {
found = true
break
}
}
assert.Truef(t, found, "could not find child %d", cmd.Process.Pid)
}
func TestUsername(t *testing.T) {
myPid := os.Getpid()
currentUser, _ := user.Current()
myUsername := currentUser.Username
process, _ := NewProcess(int32(myPid))
pidUsername, err := process.Username()
skipIfNotImplementedErr(t, err)
assert.Equal(t, myUsername, pidUsername)
t.Log(pidUsername)
}
func TestCPUTimes(t *testing.T) {
pid := os.Getpid()
process, err := NewProcess(int32(pid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
spinSeconds := 0.2
cpuTimes0, err := process.Times()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
// Spin for a duration of spinSeconds
t0 := time.Now()
tGoal := t0.Add(time.Duration(spinSeconds*1000) * time.Millisecond)
require.NoError(t, err)
for time.Now().Before(tGoal) {
// This block intentionally left blank
}
cpuTimes1, err := process.Times()
require.NoError(t, err)
if cpuTimes0 == nil || cpuTimes1 == nil {
t.FailNow()
}
measuredElapsed := cpuTimes1.Total() - cpuTimes0.Total()
message := fmt.Sprintf("Measured %fs != spun time of %fs\ncpuTimes0=%v\ncpuTimes1=%v",
measuredElapsed, spinSeconds, cpuTimes0, cpuTimes1)
assert.Greaterf(t, measuredElapsed, float64(spinSeconds)/5, message)
assert.Lessf(t, measuredElapsed, float64(spinSeconds)*5, message)
}
func TestOpenFiles(t *testing.T) {
fp, err := os.Open("process_test.go")
require.NoError(t, err)
defer func() {
assert.NoError(t, fp.Close())
}()
pid := os.Getpid()
p, err := NewProcess(int32(pid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
v, err := p.OpenFiles()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
assert.NotEmpty(t, v) // test always open files.
for _, vv := range v {
assert.NotEqual(t, "", vv.Path)
}
}
func TestKill(t *testing.T) {
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("ping", "localhost", "-n", "4")
} else {
cmd = exec.Command("sleep", "3")
}
require.NoError(t, cmd.Start())
time.Sleep(100 * time.Millisecond)
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
err = p.Kill()
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
cmd.Wait()
}
func TestIsRunning(t *testing.T) {
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("ping", "localhost", "-n", "2")
} else {
cmd = exec.Command("sleep", "1")
}
cmd.Start()
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
running, err := p.IsRunning()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "IsRunning error: %v", err)
require.Truef(t, running, "process should be found running")
cmd.Wait()
running, err = p.IsRunning()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "IsRunning error: %v", err)
require.Falsef(t, running, "process should NOT be found running")
}
// #nosec G204
func TestEnviron(t *testing.T) {
tmpdir, err := os.MkdirTemp("", "")
require.NoErrorf(t, err, "unable to create temp dir %v", err)
defer os.RemoveAll(tmpdir) // clean up
tmpfilepath := filepath.Join(tmpdir, "test.go")
tmpfile, err := os.Create(tmpfilepath)
require.NoErrorf(t, err, "unable to create temp file %v", err)
tmpfilecontent := []byte("package main\nimport(\n\"time\"\n)\nfunc main(){\nfor range time.Tick(time.Second) {}\n}")
if _, err := tmpfile.Write(tmpfilecontent); err != nil {
tmpfile.Close()
t.Fatalf("unable to write temp file %v", err)
}
require.NoErrorf(t, tmpfile.Close(), "unable to close temp file")
err = exec.Command("go", "build", "-o", tmpfile.Name()+".exe", tmpfile.Name()).Run()
require.NoErrorf(t, err, "unable to build temp file %v", err)
cmd := exec.Command(tmpfile.Name() + ".exe")
cmd.Env = []string{"testkey=envvalue"}
require.NoError(t, cmd.Start())
defer cmd.Process.Kill()
time.Sleep(100 * time.Millisecond)
p, err := NewProcess(int32(cmd.Process.Pid))
skipIfNotImplementedErr(t, err)
require.NoError(t, err)
envs, err := p.Environ()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting environ error %v", err)
var envvarFound bool
for _, envvar := range envs {
if envvar == "testkey=envvalue" {
envvarFound = true
break
}
}
assert.Truef(t, envvarFound, "environment variable not found")
}
func TestCwd(t *testing.T) {
myPid := os.Getpid()
currentWorkingDirectory, _ := os.Getwd()
process, _ := NewProcess(int32(myPid))
pidCwd, err := process.Cwd()
skipIfNotImplementedErr(t, err)
require.NoErrorf(t, err, "getting cwd error %v", err)
pidCwd = strings.TrimSuffix(pidCwd, string(os.PathSeparator))
assert.Equal(t, currentWorkingDirectory, pidCwd)
t.Log(pidCwd)
}
func BenchmarkNewProcess(b *testing.B) {
checkPid := os.Getpid()
for i := 0; i < b.N; i++ {
NewProcess(int32(checkPid))
}
}
func BenchmarkProcessName(b *testing.B) {
p := testGetProcess()
for i := 0; i < b.N; i++ {
p.Name()
}
}
func BenchmarkProcessPpid(b *testing.B) {
p := testGetProcess()
for i := 0; i < b.N; i++ {
p.Ppid()
}
}
func BenchmarkProcesses(b *testing.B) {
for i := 0; i < b.N; i++ {
ps, err := Processes()
require.NoError(b, err)
require.NotEmpty(b, ps)
}
}