//go:build linux && !android package server import ( "os" "os/exec" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // The start time is what tells a recorded process apart from whatever later // inherits its PID, so the field offset has to be right and the value stable. func TestProcessStartTimeIsStable(t *testing.T) { if _, err := os.Stat("/proc/self/stat"); err != nil { t.Skip("no procfs") } pid := os.Getpid() first, err := processStartTime(pid) require.NoError(t, err) assert.NotZero(t, first, "a running process has a non-zero start time") second, err := processStartTime(pid) require.NoError(t, err) assert.Equal(t, first, second, "start time must not move for a live process") } // A comm containing spaces and parentheses must not shift the field offsets, // which is why parsing starts from the last ')' rather than splitting the line. func TestProcessStartTimeToleratesOddCommName(t *testing.T) { if _, err := os.Stat("/proc/self/stat"); err != nil { t.Skip("no procfs") } sh, err := exec.LookPath("sh") if err != nil { t.Skip("no shell") } // argv[0] becomes the comm, truncated to 15 chars by the kernel. cmd := exec.Command(sh, "-c", "sleep 30") cmd.Args[0] = "a (b) c" require.NoError(t, cmd.Start()) t.Cleanup(func() { _ = cmd.Process.Kill() _, _ = cmd.Process.Wait() }) got, err := processStartTime(cmd.Process.Pid) require.NoError(t, err) assert.NotZero(t, got) } // A PID that never existed must not be reported as ours, and neither must a // record that carries no start time to compare against. func TestIsOurProcessRefusesUnidentifiableRecords(t *testing.T) { assert.False(t, isOurProcess(sessionProcess{PID: -1}, "xvfb:50")) pid := os.Getpid() assert.False(t, isOurProcess(sessionProcess{PID: pid}, "xvfb:50"), "a record with no recorded start time cannot be matched and must be refused") } // describeProcess captures enough to match the process back to itself. func TestDescribeProcessRoundTrips(t *testing.T) { if _, err := os.Stat("/proc/self/stat"); err != nil { t.Skip("no procfs") } proc := describeProcess(os.Getpid()) assert.Equal(t, os.Getpid(), proc.PID) assert.NotZero(t, proc.StartTime) start, err := processStartTime(proc.PID) require.NoError(t, err) assert.Equal(t, start, proc.StartTime) } // A process recorded with its own command is recognised by that command, even // when it is not one of the known desktop names. The test binary stands in for // an xsessions entry or the xterm fallback: its desc says "desktop" but its name // matches nothing on the list. func TestIsOurProcessMatchesRecordedCommand(t *testing.T) { if _, err := os.Stat("/proc/self/cmdline"); err != nil { t.Skip("no procfs") } proc := describeProcess(os.Getpid()) require.NotEmpty(t, proc.Command, "the command name must be recorded") assert.True(t, isOurProcess(proc, "desktop:50"), "a record naming its command must match the live process by that command") proc.Command = "something-else" assert.False(t, isOurProcess(proc, "xvfb:50"), "a recorded command that does not match, and no known name, must be refused") } func TestCommandName(t *testing.T) { assert.Equal(t, "xterm", commandName([]byte("/usr/bin/xterm\x00-geometry\x0080x24\x00"))) assert.Equal(t, "Xvfb", commandName([]byte("Xvfb\x00:50\x00"))) assert.Equal(t, ".", commandName(nil)) }