blob: c8354ad6a44cc81e2d2d6f104e433695a8f9b184 [file] [edit]
#!/usr/bin/env python3
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Windows specific tests."""
import ctypes
import datetime
import os
import platform
import re
import signal
import socket
import subprocess
import time
import warnings
from unittest import mock
import psutil
from psutil import WINDOWS
from psutil import _psutil
from . import HAS_BATTERY
from . import PYPY
from . import TOLERANCE_DISK_USAGE
from . import TOLERANCE_SYS_MEM
from . import PsutilTestCase
from . import isolated
from . import pytest
from . import retry_on_failure
from . import serial
from . import sh
from . import skipif
from . import spawn_subproc
from . import terminate
if WINDOWS and not PYPY:
import ctypes
import ctypes.wintypes
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import win32api # requires "pip install pywin32"
import win32con
import win32process
import wmi # requires "pip install wmi" / "make install-pydeps-test"
if WINDOWS:
from psutil._pswindows import ERROR_PARTIAL_COPY
from psutil._pswindows import convert_oserror
@skipif(not WINDOWS, reason="WINDOWS only")
@skipif(PYPY, reason="pywin32 not available on PYPY")
class WindowsTestCase(PsutilTestCase):
def OpenProcess(self, pid=None):
handle = win32api.OpenProcess(
win32con.PROCESS_QUERY_INFORMATION,
win32con.FALSE,
pid or os.getpid(),
)
self.addCleanup(win32api.CloseHandle, handle)
return handle
# Note used but could be useful in the future
def is_bash_env():
env = os.environ
if "MSYSTEM" in env or "MINGW_PREFIX" in env or "MINGW_CHOST" in env:
return True
return "bash" in env.get("SHELL", "")
def powershell(cmd):
"""Run a powershell command and return its output.
Example usage:
>>> powershell(
"Get-CIMInstance Win32_PageFileUsage | Select AllocatedBaseSize"
)
"""
exe = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
cmdline = [
exe,
"-ExecutionPolicy",
"Bypass",
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-WindowStyle",
"Hidden",
"-Command",
cmd,
]
return sh(cmdline)
def wmic(path, what, converter=int):
"""Currently not used, but available just in case. Usage:
>>> wmic("Win32_OperatingSystem", "FreePhysicalMemory")
2134124534
"""
out = sh(f"wmic path {path} get {what}").strip()
data = "".join(out.splitlines()[1:]).strip() # get rid of the header
if converter is not None:
if "," in what:
return tuple(converter(x) for x in data.split())
else:
return converter(data)
else:
return data
# ===================================================================
# System APIs
# ===================================================================
class TestCpuCount(WindowsTestCase):
@skipif(
'NUMBER_OF_PROCESSORS' not in os.environ,
reason="env var not available",
)
def test_against_NUMBER_OF_PROCESSORS(self):
# Will likely fail on many-cores systems:
# https://stackoverflow.com/questions/31209256
num_cpus = int(os.environ['NUMBER_OF_PROCESSORS'])
assert num_cpus == psutil.cpu_count()
def test_against_GetSystemInfo(self):
# Will likely fail on many-cores systems:
# https://stackoverflow.com/questions/31209256
assert psutil.cpu_count() == win32api.GetSystemInfo()[5]
def test_against_wmi(self):
w = wmi.WMI()
procs = sum(
proc.NumberOfLogicalProcessors for proc in w.Win32_Processor()
)
assert psutil.cpu_count() == procs
def test_cores_against_wmi(self):
w = wmi.WMI()
cores = sum(proc.NumberOfCores for proc in w.Win32_Processor())
assert psutil.cpu_count(logical=False) == cores
def test_against_cpu_times(self):
assert psutil.cpu_count() == len(psutil.cpu_times(percpu=True))
def test_irq_field(self):
t = psutil.cpu_times()
assert t.irq >= 0
with pytest.warns(DeprecationWarning, match="interrupt"):
assert t.interrupt == t.irq
class TestCpuFreq(WindowsTestCase):
def test_cpu_freq(self):
w = wmi.WMI()
proc = w.Win32_Processor()[0]
assert abs(proc.CurrentClockSpeed - psutil.cpu_freq().current) < 100
assert abs(proc.MaxClockSpeed - psutil.cpu_freq().max) < 100
class TestCpuStats(WindowsTestCase):
@retry_on_failure
def test_ctx_switches(self):
w = wmi.WMI().Win32_PerfRawData_PerfOS_System()[0]
wmi_value = int(w.ContextSwitchesPersec)
psutil_value = psutil.cpu_stats().ctx_switches
assert abs(psutil_value - wmi_value) < 1000
@retry_on_failure
def test_interrupts(self):
# Interrupts are summed across all CPUs; use _Total from
# Win32_PerfRawData_PerfOS_Processor.
w = wmi.WMI().Win32_PerfRawData_PerfOS_Processor(Name="_Total")[0]
wmi_value = int(w.InterruptsPersec)
psutil_value = psutil.cpu_stats().interrupts
assert abs(psutil_value - wmi_value) < 1000
def test_soft_interrupts(self):
# Always 0 on Windows.
assert psutil.cpu_stats().soft_interrupts == 0
class TestVirtualMemory(WindowsTestCase):
def test_total(self):
w = wmi.WMI().Win32_ComputerSystem()[0]
assert int(w.TotalPhysicalMemory) == psutil.virtual_memory().total
@retry_on_failure
def test_free(self):
w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0]
assert (
abs(int(w.AvailableBytes) - psutil.virtual_memory().free)
< TOLERANCE_SYS_MEM
)
@retry_on_failure
def test_wired(self):
w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0]
assert (
abs(int(w.PoolNonpagedBytes) - psutil.virtual_memory().wired)
< TOLERANCE_SYS_MEM
)
@retry_on_failure
def test_available(self):
w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0]
assert (
abs(int(w.AvailableBytes) - psutil.virtual_memory().available)
< TOLERANCE_SYS_MEM
)
@retry_on_failure
def test_used(self):
w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0]
total = psutil.virtual_memory().total
wmi_used = total - int(w.AvailableBytes)
assert abs(psutil.virtual_memory().used - wmi_used) < TOLERANCE_SYS_MEM
class TestSwapMemory(WindowsTestCase):
def test_total(self):
w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0]
assert (
int(w.CommitLimit) - psutil.virtual_memory().total
== psutil.swap_memory().total
)
if psutil.swap_memory().total == 0:
assert psutil.swap_memory().free == 0
assert psutil.swap_memory().used == 0
def test_percent(self):
if psutil.swap_memory().total > 0:
w = wmi.WMI().Win32_PerfRawData_PerfOS_PagingFile(Name="_Total")[0]
# calculate swap usage to percent
percentSwap = int(w.PercentUsage) * 100 / int(w.PercentUsage_Base)
# exact percent may change but should be reasonable
# assert within +/- 5% and between 0 and 100%
assert psutil.swap_memory().percent >= 0
assert abs(psutil.swap_memory().percent - percentSwap) < 5
assert psutil.swap_memory().percent <= 100
class TestNetAPIs(WindowsTestCase):
def test_net_io_counters_nic_names(self):
out = sh('ipconfig /all')
nics = psutil.net_io_counters(pernic=True).keys()
for nic in nics:
if "pseudo-interface" in nic.replace(' ', '-').lower():
continue
if nic not in out:
return pytest.fail(
f"{nic!r} nic wasn't found in 'ipconfig /all' output"
)
@retry_on_failure
def test_net_io_counters(self):
wmi_nics = [
(nic.Name, int(nic.BytesReceivedPerSec), int(nic.BytesSentPerSec))
for nic in wmi.WMI().Win32_PerfRawData_Tcpip_NetworkInterface()
]
tolerance = 1 * 1024 * 1024 # 1 MB
for name, io in psutil.net_io_counters(pernic=True).items():
if io.bytes_recv < tolerance and io.bytes_sent < tolerance:
# WMI does not list NICs with no traffic, e.g. the
# loopback pseudo-interface.
continue
for _, wmi_recv, wmi_sent in wmi_nics:
if (
abs(io.bytes_recv - wmi_recv) < tolerance
and abs(io.bytes_sent - wmi_sent) < tolerance
):
break
else:
return pytest.fail(
f"no WMI match for {name!r} ({io!r}); wmi={wmi_nics!r}"
)
def test_net_if_addrs(self):
ps_addrs = set()
for addrs in psutil.net_if_addrs().values():
for addr in addrs:
if addr.family == socket.AF_INET:
ps_addrs.add(addr.address)
out = powershell(
"(Get-NetIPAddress -AddressFamily IPv4).IPAddress -join ','"
)
win_addrs = set(out.strip().split(','))
assert win_addrs == ps_addrs
@serial
def test_net_connections(self):
# Compare listening TCP ports; they're stable unlike active
# connections.
ps_ports = {
c.laddr.port
for c in psutil.net_connections(kind='tcp')
if c.status == psutil.CONN_LISTEN
}
out = powershell(
"(Get-NetTCPConnection -State Listen).LocalPort -join ','"
)
win_ports = {int(p) for p in out.strip().split(',') if p.strip()}
assert ps_ports == win_ports
def test_net_if_stats(self):
ps_names = set(_psutil.net_if_stats())
wmi_adapters = wmi.WMI().Win32_NetworkAdapter()
wmi_names = set()
for wmi_adapter in wmi_adapters:
wmi_names.add(wmi_adapter.Name)
wmi_names.add(wmi_adapter.NetConnectionID)
assert (
ps_names & wmi_names
), f"no common entries in {ps_names}, {wmi_names}"
class TestDiskApis(WindowsTestCase):
@retry_on_failure
def test_disk_partitions(self):
ps_parts = psutil.disk_partitions(all=True)
wmi_parts = wmi.WMI().Win32_LogicalDisk()
for ps_part in ps_parts:
for wmi_part in wmi_parts:
if ps_part.device.replace('\\', '') == wmi_part.DeviceID:
if not ps_part.mountpoint:
# this is usually a CD-ROM with no disk inserted
break
if 'cdrom' in ps_part.opts:
break
if ps_part.mountpoint.startswith('A:'):
break # floppy
try:
usage = psutil.disk_usage(ps_part.mountpoint)
except FileNotFoundError:
# usually this is the floppy
break
assert usage.total == int(wmi_part.Size)
wmi_free = int(wmi_part.FreeSpace)
assert usage.free == wmi_free
# 10 MB tolerance
if abs(usage.free - wmi_free) > 10 * 1024 * 1024:
return pytest.fail(
f"psutil={usage.free}, wmi={wmi_free}"
)
break
else:
return pytest.fail(f"can't find partition {ps_part!r}")
def test_disk_partitions_mountpoint(self):
win = [
x + '\\'
for x in win32api.GetLogicalDriveStrings().split("\\\x00")
if x and not x.startswith('A:')
]
ps = [
x.mountpoint
for x in psutil.disk_partitions(all=True)
if not x.mountpoint.startswith('A:')
]
assert win == ps
@retry_on_failure
def test_disk_usage(self):
for disk in psutil.disk_partitions():
if 'cdrom' in disk.opts:
continue
win = win32api.GetDiskFreeSpaceEx(disk.mountpoint)
ps = psutil.disk_usage(disk.mountpoint)
assert abs(win[0] - ps.free) < TOLERANCE_DISK_USAGE
assert abs(win[1] - ps.total) < TOLERANCE_DISK_USAGE
assert ps.used == ps.total - ps.free
@retry_on_failure
def test_disk_io_counters(self):
stats = psutil.disk_io_counters()
w = wmi.WMI().Win32_PerfRawData_PerfDisk_PhysicalDisk(Name="_Total")[0]
tolerance = 10 * 1024 * 1024
assert abs(stats.read_bytes - int(w.DiskReadBytesPersec)) < tolerance
assert abs(stats.write_bytes - int(w.DiskWriteBytesPersec)) < tolerance
assert abs(stats.read_count - int(w.DiskReadsPersec)) < 1000
assert abs(stats.write_count - int(w.DiskWritesPersec)) < 1000
class TestOtherSystemAPIs(WindowsTestCase):
# @skipif(wmi is None, reason="wmi module is not installed")
# def test__UPTIME(self):
# # _UPTIME constant is not public but it is used internally
# # as value to return for pid 0 creation time.
# # WMI behaves the same.
# w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
# p = psutil.Process(0)
# wmic_create = str(w.CreationDate.split('.')[0])
# psutil_create = time.strftime("%Y%m%d%H%M%S",
# time.localtime(p.create_time()))
def test_convert_dos_path_drive(self):
winpath = 'C:\\Windows\\Temp'
driveletter = 'C:'
# Mocked NT device path for C:
devicepath = '\\Device\\HarddiskVolume1'
# Path returned by RtlDosPathNameToNtPathName
ntpath1 = '\\??\\C:\\Windows\\Temp'
# Mocked normalized NT path
ntpath2 = '\\Device\\HarddiskVolume1\\Windows\\Temp'
devices = {devicepath: driveletter}
with mock.patch.object(
_psutil, 'QueryDosDevice', side_effect=devices.get
) as m:
assert psutil._pswindows.convert_dos_path(ntpath1) == winpath
assert psutil._pswindows.convert_dos_path(ntpath2) == winpath
assert m.called
def test_convert_dos_path_unc(self):
# UNC path
winpath = '\\\\localhost\\C$\\Windows\\Temp'
# Path returned by RtlDosPathNameToNtPathName
ntpath1 = '\\??\\UNC\\localhost\\C$\\Windows\\Temp'
# Normalized NT path
ntpath2 = '\\Device\\Mup\\localhost\\C$\\Windows\\Temp'
assert psutil._pswindows.convert_dos_path(winpath) == winpath
assert psutil._pswindows.convert_dos_path(ntpath1) == winpath
assert psutil._pswindows.convert_dos_path(ntpath2) == winpath
def test_boot_time(self):
wmi_os = wmi.WMI().Win32_OperatingSystem()
wmi_btime_str = wmi_os[0].LastBootUpTime.split('.')[0]
wmi_btime_dt = datetime.datetime.strptime(
wmi_btime_str, "%Y%m%d%H%M%S"
)
psutil_dt = datetime.datetime.fromtimestamp(psutil.boot_time())
diff = abs((wmi_btime_dt - psutil_dt).total_seconds())
assert diff <= 5, (psutil_dt, wmi_btime_dt)
def test_users(self):
current = win32api.GetUserName()
assert current in {u.name for u in psutil.users()}
# ===================================================================
# sensors_battery()
# ===================================================================
class TestSensorsBattery(WindowsTestCase):
def test_has_battery(self):
if win32api.GetPwrCapabilities()['SystemBatteriesPresent']:
assert psutil.sensors_battery() is not None
else:
assert psutil.sensors_battery() is None
@skipif(not HAS_BATTERY, reason="no battery")
def test_percent(self):
w = wmi.WMI()
battery_wmi = w.query('select * from Win32_Battery')[0]
battery_psutil = psutil.sensors_battery()
assert (
abs(battery_psutil.percent - battery_wmi.EstimatedChargeRemaining)
< 1
)
@skipif(not HAS_BATTERY, reason="no battery")
def test_power_plugged(self):
w = wmi.WMI()
battery_wmi = w.query('select * from Win32_Battery')[0]
battery_psutil = psutil.sensors_battery()
# Status codes:
# https://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx
assert battery_psutil.power_plugged == (battery_wmi.BatteryStatus == 2)
@skipif(not HAS_BATTERY, reason="no battery")
def test_secsleft(self):
class SYSTEM_POWER_STATUS(ctypes.Structure):
_fields_ = [
('ACLineStatus', ctypes.c_byte),
('BatteryFlag', ctypes.c_byte),
('BatteryLifePercent', ctypes.c_byte),
('SystemStatusFlag', ctypes.c_byte),
('BatteryLifeTime', ctypes.c_ulong),
('BatteryFullLifeTime', ctypes.c_ulong),
]
status = SYSTEM_POWER_STATUS()
ctypes.windll.kernel32.GetSystemPowerStatus(ctypes.byref(status))
bat = psutil.sensors_battery()
if status.ACLineStatus == 1 or status.BatteryFlag & 8:
# plugged/charging
assert bat.secsleft == psutil.POWER_TIME_UNLIMITED
elif status.BatteryLifeTime == 0xFFFFFFFF:
assert bat.secsleft == psutil.POWER_TIME_UNKNOWN
else:
assert bat.secsleft == status.BatteryLifeTime
def test_emulate_secsleft_unknown(self):
with mock.patch.object(
_psutil,
"sensors_battery",
return_value=(0, 0, 50, -1),
) as m:
bat = psutil.sensors_battery()
assert m.called
assert bat.secsleft == psutil.POWER_TIME_UNKNOWN
def test_emulate_no_battery(self):
with mock.patch.object(
_psutil,
"sensors_battery",
return_value=(0, 128, 0, 0),
) as m:
assert psutil.sensors_battery() is None
assert m.called
def test_emulate_power_connected(self):
with mock.patch.object(
_psutil, "sensors_battery", return_value=(1, 0, 0, 0)
) as m:
assert (
psutil.sensors_battery().secsleft
== psutil.POWER_TIME_UNLIMITED
)
assert m.called
def test_emulate_power_charging(self):
with mock.patch.object(
_psutil, "sensors_battery", return_value=(0, 8, 0, 0)
) as m:
assert (
psutil.sensors_battery().secsleft
== psutil.POWER_TIME_UNLIMITED
)
assert m.called
def test_emulate_secs_left_unknown(self):
with mock.patch.object(
_psutil,
"sensors_battery",
return_value=(0, 0, 0, -1),
) as m:
assert (
psutil.sensors_battery().secsleft == psutil.POWER_TIME_UNKNOWN
)
assert m.called
# ===================================================================
# Process APIs
# ===================================================================
class TestProcess(WindowsTestCase):
@classmethod
def setUpClass(cls):
cls.pid = spawn_subproc().pid
@classmethod
def tearDownClass(cls):
terminate(cls.pid)
def test_issue_24(self):
p = psutil.Process(0)
with pytest.raises(psutil.AccessDenied):
p.kill()
def test_special_pid(self):
p = psutil.Process(4)
assert p.name() == 'System'
# use __str__ to access all common Process properties to check
# that nothing strange happens
str(p)
p.username()
assert p.create_time() >= 0.0
try:
rss, _vms = p.memory_info()[:2]
except psutil.AccessDenied:
# expected on Windows Vista and Windows 7
if platform.uname()[1] not in {'vista', 'win-7', 'win7'}:
raise
else:
assert rss > 0
def test_send_signal(self):
p = psutil.Process(self.pid)
with pytest.raises(ValueError):
p.send_signal(signal.SIGINT)
def test_num_handles_increment(self):
p = psutil.Process(os.getpid())
before = p.num_handles()
handle = self.OpenProcess()
after = p.num_handles()
assert after == before + 1
win32api.CloseHandle(handle)
assert p.num_handles() == before
@isolated
def test_ctrl_signals(self):
p = psutil.Process(self.spawn_subproc().pid)
p.send_signal(signal.CTRL_C_EVENT)
p.send_signal(signal.CTRL_BREAK_EVENT)
p.kill()
p.wait()
with pytest.raises(psutil.NoSuchProcess):
p.send_signal(signal.CTRL_C_EVENT)
with pytest.raises(psutil.NoSuchProcess):
p.send_signal(signal.CTRL_BREAK_EVENT)
def test_username(self):
name = win32api.GetUserNameEx(win32con.NameSamCompatible)
if name.endswith('$'):
# When running as a service account (most likely to be
# NetworkService), these user name calculations don't produce the
# same result, causing the test to fail.
return pytest.skip('running as service account')
assert psutil.Process().username() == name
def test_cmdline(self):
win = re.sub(r"[ ]+", " ", win32api.GetCommandLine()).strip()
ps = " ".join(psutil.Process().cmdline())
# The PyWin32 command line may retain quotes around argv[0] if they
# were used unnecessarily, while psutil will omit them. So remove
# the first 2 quotes from win if not in ps.
# A path to an executable will not contain quotes, so this is safe.
win = win.replace('"', "")
ps = ps.replace('"', "")
assert win == ps
# XXX - occasional failures
# def test_cpu_times(self):
# a = psutil.Process().cpu_times()
# b = win32process.GetProcessTimes(self.OpenProcess())
# assert abs(a.user - b['UserTime'] / 10000000.0) < 0.2
# assert abs(a.user - b['KernelTime'] / 10000000.0) < 0.2
def test_nice(self):
win = win32process.GetPriorityClass(self.OpenProcess())
ps = psutil.Process().nice()
assert ps == win
def test_memory_info(self):
win = win32process.GetProcessMemoryInfo(self.OpenProcess(self.pid))
ps = psutil.Process(self.pid).memory_info()
assert ps.rss == win["WorkingSetSize"]
assert ps.vms == win["PagefileUsage"]
assert ps.peak_rss == win["PeakWorkingSetSize"]
assert ps.peak_vms == win["PeakPagefileUsage"]
with pytest.warns(DeprecationWarning):
assert ps.num_page_faults == win["PageFaultCount"]
def test_memory_info_deprecated_fields(self):
win = win32process.GetProcessMemoryInfo(self.OpenProcess(self.pid))
ps = psutil.Process(self.pid).memory_info()
# old aliases
with pytest.warns(DeprecationWarning, match="wset is deprecated"):
assert ps.wset == ps.rss
with pytest.warns(DeprecationWarning, match="peak_wset is deprecated"):
assert ps.peak_wset == ps.peak_rss
with pytest.warns(DeprecationWarning, match="pagefile is deprecated"):
assert ps.pagefile == ps.vms
with pytest.warns(DeprecationWarning, match="private is deprecated"):
assert ps.private == ps.vms
with pytest.warns(
DeprecationWarning, match="peak_pagefile is deprecated"
):
assert ps.peak_pagefile == ps.peak_vms
# fields moved to memory_info_ex()
with pytest.warns(
DeprecationWarning, match="paged_pool is deprecated"
):
assert ps.paged_pool == win['QuotaPagedPoolUsage']
with pytest.warns(
DeprecationWarning, match="nonpaged_pool is deprecated"
):
assert ps.nonpaged_pool == win['QuotaNonPagedPoolUsage']
with pytest.warns(
DeprecationWarning, match="peak_paged_pool is deprecated"
):
assert ps.peak_paged_pool == win['QuotaPeakPagedPoolUsage']
with pytest.warns(
DeprecationWarning, match="peak_nonpaged_pool is deprecated"
):
assert ps.peak_nonpaged_pool == win['QuotaPeakNonPagedPoolUsage']
# field moved to pages_fault()
with pytest.warns(
DeprecationWarning, match="num_page_faults is deprecated"
):
assert ps.num_page_faults == win['PageFaultCount']
# test ntuple's __getattr__ override
with pytest.raises(AttributeError, match="foo"):
ps.foo # noqa: B018
def test_memory_info_ex(self):
win = win32process.GetProcessMemoryInfo(self.OpenProcess(self.pid))
ps = psutil.Process(self.pid).memory_info_ex()
assert ps.paged_pool == win["QuotaPagedPoolUsage"]
assert ps.nonpaged_pool == win["QuotaNonPagedPoolUsage"]
assert ps.peak_paged_pool == win["QuotaPeakPagedPoolUsage"]
assert ps.peak_nonpaged_pool == win["QuotaPeakNonPagedPoolUsage"]
assert ps.virtual > 0
assert ps.peak_virtual > 0
assert ps.peak_virtual >= ps.virtual
assert ps.peak_paged_pool >= ps.paged_pool
assert ps.peak_nonpaged_pool >= ps.nonpaged_pool
def test_wait(self):
p = psutil.Process(self.pid)
p.terminate()
ps = p.wait()
win = win32process.GetExitCodeProcess(self.OpenProcess(self.pid))
assert ps == win
def test_num_threads(self):
ps = psutil.Process(self.pid).num_threads()
win = int(powershell(f"(Get-Process -Id {self.pid}).Threads.Count"))
assert ps == win
def test_cpu_affinity(self):
def from_bitmask(x):
return [i for i in range(64) if (1 << i) & x]
win = from_bitmask(
win32process.GetProcessAffinityMask(self.OpenProcess(self.pid))[0]
)
ps = psutil.Process(self.pid).cpu_affinity()
assert ps == win
def test_io_counters(self):
win = win32process.GetProcessIoCounters(self.OpenProcess())
ps = psutil.Process().io_counters()
assert ps.read_count == win['ReadOperationCount']
assert ps.write_count == win['WriteOperationCount']
assert ps.read_bytes == win['ReadTransferCount']
assert ps.write_bytes == win['WriteTransferCount']
assert ps.other_count == win['OtherOperationCount']
assert ps.other_bytes == win['OtherTransferCount']
def test_num_handles(self):
PROCESS_QUERY_INFORMATION = 0x400
handle = ctypes.windll.kernel32.OpenProcess(
PROCESS_QUERY_INFORMATION, 0, self.pid
)
self.addCleanup(ctypes.windll.kernel32.CloseHandle, handle)
hndcnt = ctypes.wintypes.DWORD()
ctypes.windll.kernel32.GetProcessHandleCount(
handle, ctypes.byref(hndcnt)
)
win = hndcnt.value
ps = psutil.Process(self.pid).num_handles()
assert ps == win
def test_error_partial_copy(self):
# https://github.com/giampaolo/psutil/issues/875
exc = OSError()
exc.winerror = 299
with mock.patch.object(_psutil, "proc_cwd", side_effect=exc):
with mock.patch("time.sleep") as m:
p = psutil.Process()
with pytest.raises(psutil.AccessDenied):
p.cwd()
assert m.call_count >= 5
def test_exe(self):
# NtQuerySystemInformation succeeds if process is gone. Make sure
# it raises NSP for a non existent pid.
pid = psutil.pids()[-1] + 99999
proc = psutil._psplatform.Process(pid)
with pytest.raises(psutil.NoSuchProcess):
proc.exe()
@retry_on_failure
def test_page_faults(self):
# memory_info() value comes from GetProcessMemoryInfo ->
# PageFaultCount. page_faults() value comes from
# NtQuerySystemInformation -> HardFaultCount / PageFaultCount
p = psutil.Process()
mem = p.memory_info()
pfaults = p.page_faults()
tol = 500
with pytest.warns(
DeprecationWarning, match="num_page_faults is deprecated"
):
assert mem.num_page_faults == pytest.approx(pfaults.minor, abs=tol)
class TestProcessWMI(WindowsTestCase):
"""Compare Process API results with WMI."""
@classmethod
def setUpClass(cls):
cls.pid = spawn_subproc().pid
@classmethod
def tearDownClass(cls):
terminate(cls.pid)
def test_name(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
assert p.name() == w.Caption
def test_exe(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
# Note: wmi reports the exe as a lower case string.
# Being Windows paths case-insensitive we ignore that.
assert p.exe().lower() == w.ExecutablePath.lower()
def test_cmdline(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
assert ' '.join(p.cmdline()) == w.CommandLine.replace('"', '')
def test_username(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
domain, _, username = w.GetOwner()
username = f"{domain}\\{username}"
assert p.username() == username
@retry_on_failure
def test_memory_rss(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
rss = p.memory_info().rss
assert rss == int(w.WorkingSetSize)
@retry_on_failure
def test_memory_vms(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
vms = p.memory_info().vms
# http://msdn.microsoft.com/en-us/library/aa394372(VS.85).aspx
# ...claims that PageFileUsage is represented in Kilo
# bytes but funnily enough on certain platforms bytes are
# returned instead.
wmi_usage = int(w.PageFileUsage)
if vms not in {wmi_usage, wmi_usage * 1024}:
return pytest.fail(f"wmi={wmi_usage}, psutil={vms}")
def test_cpu_times(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
ps = p.cpu_times()
assert abs(ps.user - int(w.UserModeTime) / 1e7) < 0.1
assert abs(ps.system - int(w.KernelModeTime) / 1e7) < 0.1
def test_ppid(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
assert p.ppid() == int(w.ParentProcessId)
def test_create_time(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
wmic_create = str(w.CreationDate.split('.')[0])
psutil_create = time.strftime(
"%Y%m%d%H%M%S", time.localtime(p.create_time())
)
assert wmic_create == psutil_create
# ---
@skipif(not WINDOWS, reason="WINDOWS only")
class TestDualProcessImplementation(PsutilTestCase):
"""Certain APIs on Windows have 2 internal implementations, one
based on documented Windows APIs, another one based
NtQuerySystemInformation() which gets called as fallback in
case the first fails because of limited permission error.
Here we test that the two methods return the exact same value,
see:
https://github.com/giampaolo/psutil/issues/304.
"""
@classmethod
def setUpClass(cls):
cls.pid = spawn_subproc().pid
@classmethod
def tearDownClass(cls):
terminate(cls.pid)
def test_memory_info(self):
mem_1 = psutil.Process(self.pid).memory_info()
with mock.patch.object(
_psutil,
"proc_memory_info",
side_effect=PermissionError,
) as fun:
mem_2 = psutil.Process(self.pid).memory_info()
assert len(mem_1) == len(mem_2)
for i in range(len(mem_1)):
assert mem_1[i] >= 0
assert mem_2[i] >= 0
assert abs(mem_1[i] - mem_2[i]) < 512
assert fun.called
def test_create_time(self):
ctime = psutil.Process(self.pid).create_time()
with mock.patch.object(
_psutil,
"proc_times",
side_effect=PermissionError,
) as fun:
assert psutil.Process(self.pid).create_time() == ctime
assert fun.called
def test_cpu_times(self):
cpu_times_1 = psutil.Process(self.pid).cpu_times()
with mock.patch.object(
_psutil,
"proc_times",
side_effect=PermissionError,
) as fun:
cpu_times_2 = psutil.Process(self.pid).cpu_times()
assert fun.called
assert abs(cpu_times_1.user - cpu_times_2.user) < 0.01
assert abs(cpu_times_1.system - cpu_times_2.system) < 0.01
def test_io_counters(self):
io_counters_1 = psutil.Process(self.pid).io_counters()
with mock.patch.object(
_psutil,
"proc_io_counters",
side_effect=PermissionError,
) as fun:
io_counters_2 = psutil.Process(self.pid).io_counters()
for i in range(len(io_counters_1)):
assert abs(io_counters_1[i] - io_counters_2[i]) < 5
assert fun.called
def test_num_handles(self):
num_handles = psutil.Process(self.pid).num_handles()
with mock.patch.object(
_psutil,
"proc_num_handles",
side_effect=PermissionError,
) as fun:
assert psutil.Process(self.pid).num_handles() == num_handles
assert fun.called
def test_cmdline(self):
for pid in psutil.pids():
try:
a = _psutil.proc_cmdline(pid, use_peb=True)
b = _psutil.proc_cmdline(pid, use_peb=False)
except OSError as err:
if err.winerror == ERROR_PARTIAL_COPY:
continue # process is dying
err = convert_oserror(err)
if not isinstance(
err, (psutil.AccessDenied, psutil.NoSuchProcess)
):
raise
else:
assert a == b
@skipif(not WINDOWS, reason="WINDOWS only")
class TestWow64Process(PsutilTestCase):
"""Test reading cmdline, cwd and environ of a 32 bit (WoW64)
process, which requires reading its 32 bit PEB, see
psutil/arch/windows/proc_peb.c.
"""
@staticmethod
def assert_wow64(pid):
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
kernel32 = ctypes.windll.kernel32
handle = kernel32.OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION, False, pid
)
assert handle
try:
wow64 = ctypes.c_int()
assert kernel32.IsWow64Process(handle, ctypes.byref(wow64))
assert wow64.value
finally:
kernel32.CloseHandle(handle)
def setUp(self):
super().setUp()
# any exe in SysWOW64 is 32 bit; the dir only exists on 64 bit
# Windows
exe = os.path.join(
os.environ.get("SYSTEMROOT", r"C:\Windows"),
"SysWOW64",
"findstr.exe",
)
if not os.path.exists(exe):
return pytest.skip("32 bit Windows")
env = os.environ.copy()
env["THINK_OF_A_NUMBER"] = str(os.getpid())
self.args = [exe, "hello"]
# findstr hangs reading stdin until we close it
self.proc = self.spawn_subproc(
self.args, env=env, stdin=subprocess.PIPE
)
self.assert_wow64(self.proc.pid)
def tearDown(self):
super().tearDown()
self.proc.communicate()
def test_cmdline(self):
assert psutil.Process(self.proc.pid).cmdline() == self.args
def test_cwd(self):
assert psutil.Process(self.proc.pid).cwd() == os.getcwd()
def test_environ(self):
environ = psutil.Process(self.proc.pid).environ()
assert environ["THINK_OF_A_NUMBER"] == str(os.getpid())
# ===================================================================
# Windows services
# ===================================================================
@skipif(not WINDOWS, reason="WINDOWS only")
class TestServices(PsutilTestCase):
def test_win_service_iter(self):
valid_statuses = {
"running",
"paused",
"start",
"pause",
"continue",
"stop",
"stopped",
}
valid_start_types = {"automatic", "manual", "disabled"}
valid_statuses = {
"running",
"paused",
"start_pending",
"pause_pending",
"continue_pending",
"stop_pending",
"stopped",
}
for serv in psutil.win_service_iter():
if serv.name() == "WaaSMedicSvc":
# known issue in Windows 11 reading the description
# https://learn.microsoft.com/en-us/answers/questions/1320388/in-windows-11-version-22h2-there-it-shows-(failed
# https://github.com/giampaolo/psutil/issues/2383
continue
data = serv.as_dict()
assert isinstance(data['name'], str)
assert data['name'].strip()
assert isinstance(data['display_name'], str)
assert isinstance(data['username'], str)
assert data['status'] in valid_statuses
if data['pid'] is not None:
psutil.Process(data['pid'])
assert isinstance(data['binpath'], str)
assert isinstance(data['username'], str)
assert isinstance(data['start_type'], str)
assert data['start_type'] in valid_start_types
assert data['status'] in valid_statuses
assert isinstance(data['description'], str)
pid = serv.pid()
if pid is not None:
p = psutil.Process(pid)
assert p.is_running()
# win_service_get
s = psutil.win_service_get(serv.name())
# test __eq__
assert serv == s
def test_win_service_get(self):
ERROR_SERVICE_DOES_NOT_EXIST = _psutil.ERROR_SERVICE_DOES_NOT_EXIST
ERROR_ACCESS_DENIED = _psutil.ERROR_ACCESS_DENIED
name = next(psutil.win_service_iter()).name()
with pytest.raises(psutil.NoSuchProcess) as cm:
psutil.win_service_get(name + '???')
assert cm.value.name == name + '???'
# test NoSuchProcess
service = psutil.win_service_get(name)
exc = OSError(0, "msg", 0)
exc.winerror = ERROR_SERVICE_DOES_NOT_EXIST
with mock.patch.object(
_psutil, "winservice_query_status", side_effect=exc
):
with pytest.raises(psutil.NoSuchProcess):
service.status()
with mock.patch.object(
_psutil, "winservice_query_config", side_effect=exc
):
with pytest.raises(psutil.NoSuchProcess):
service.username()
# test AccessDenied
exc = OSError(0, "msg", 0)
exc.winerror = ERROR_ACCESS_DENIED
with mock.patch.object(
_psutil, "winservice_query_status", side_effect=exc
):
with pytest.raises(psutil.AccessDenied):
service.status()
with mock.patch.object(
_psutil, "winservice_query_config", side_effect=exc
):
with pytest.raises(psutil.AccessDenied):
service.username()
# test __str__ and __repr__
assert service.name() in str(service)
assert service.display_name() in str(service)
assert service.name() in repr(service)
assert service.display_name() in repr(service)