__init__.py 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979
  1. # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Test utilities."""
  5. import atexit
  6. import contextlib
  7. import ctypes
  8. import enum
  9. import errno
  10. import functools
  11. import gc
  12. import importlib
  13. import ipaddress
  14. import os
  15. import platform
  16. import random
  17. import re
  18. import select
  19. import shlex
  20. import shutil
  21. import signal
  22. import socket
  23. import stat
  24. import subprocess
  25. import sys
  26. import tempfile
  27. import textwrap
  28. import threading
  29. import time
  30. import traceback
  31. import unittest
  32. import warnings
  33. from socket import AF_INET
  34. from socket import AF_INET6
  35. from socket import SOCK_STREAM
  36. try:
  37. import pytest
  38. except ImportError:
  39. pytest = None
  40. import psutil
  41. from psutil import AIX
  42. from psutil import LINUX
  43. from psutil import MACOS
  44. from psutil import NETBSD
  45. from psutil import OPENBSD
  46. from psutil import POSIX
  47. from psutil import SUNOS
  48. from psutil import WINDOWS
  49. from psutil._common import bytes2human
  50. from psutil._common import debug
  51. from psutil._common import memoize
  52. from psutil._common import print_color
  53. from psutil._common import supports_ipv6
  54. if POSIX:
  55. from psutil._psposix import wait_pid
  56. # fmt: off
  57. __all__ = [
  58. # constants
  59. 'DEVNULL', 'GLOBAL_TIMEOUT', 'TOLERANCE_SYS_MEM', 'NO_RETRIES',
  60. 'PYPY', 'PYTHON_EXE', 'PYTHON_EXE_ENV', 'ROOT_DIR', 'SCRIPTS_DIR',
  61. 'TESTFN_PREFIX', 'UNICODE_SUFFIX', 'INVALID_UNICODE_SUFFIX',
  62. 'CI_TESTING', 'VALID_PROC_STATUSES', 'TOLERANCE_DISK_USAGE', 'IS_64BIT',
  63. "HAS_CPU_AFFINITY", "HAS_CPU_FREQ", "HAS_ENVIRON", "HAS_PROC_IO_COUNTERS",
  64. "HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT",
  65. "HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS",
  66. "HAS_SENSORS_TEMPERATURES", "HAS_NET_CONNECTIONS_UNIX", "MACOS_11PLUS",
  67. "MACOS_12PLUS", "COVERAGE", 'AARCH64', "PYTEST_PARALLEL",
  68. # subprocesses
  69. 'pyrun', 'terminate', 'reap_children', 'spawn_subproc', 'spawn_zombie',
  70. 'spawn_children_pair',
  71. # threads
  72. 'ThreadTask',
  73. # test utils
  74. 'unittest', 'skip_on_access_denied', 'skip_on_not_implemented',
  75. 'retry_on_failure', 'TestMemoryLeak', 'PsutilTestCase',
  76. 'process_namespace', 'system_namespace',
  77. 'is_win_secure_system_proc', 'fake_pytest',
  78. # fs utils
  79. 'chdir', 'safe_rmpath', 'create_py_exe', 'create_c_exe', 'get_testfn',
  80. # os
  81. 'get_winver', 'kernel_version',
  82. # sync primitives
  83. 'call_until', 'wait_for_pid', 'wait_for_file',
  84. # network
  85. 'check_net_address', 'filter_proc_net_connections',
  86. 'get_free_port', 'bind_socket', 'bind_unix_socket', 'tcp_socketpair',
  87. 'unix_socketpair', 'create_sockets',
  88. # compat
  89. 'reload_module', 'import_module_by_path',
  90. # others
  91. 'warn', 'copyload_shared_lib', 'is_namedtuple',
  92. ]
  93. # fmt: on
  94. # ===================================================================
  95. # --- constants
  96. # ===================================================================
  97. # --- platforms
  98. PYPY = '__pypy__' in sys.builtin_module_names
  99. # whether we're running this test suite on a Continuous Integration service
  100. GITHUB_ACTIONS = 'GITHUB_ACTIONS' in os.environ or 'CIBUILDWHEEL' in os.environ
  101. CI_TESTING = GITHUB_ACTIONS
  102. COVERAGE = 'COVERAGE_RUN' in os.environ
  103. PYTEST_PARALLEL = "PYTEST_XDIST_WORKER" in os.environ # `make test-parallel`
  104. # are we a 64 bit process?
  105. IS_64BIT = sys.maxsize > 2**32
  106. # apparently they're the same
  107. AARCH64 = platform.machine().lower() in {"aarch64", "arm64"}
  108. RISCV64 = platform.machine() == "riscv64"
  109. @memoize
  110. def macos_version():
  111. version_str = platform.mac_ver()[0]
  112. version = tuple(map(int, version_str.split(".")[:2]))
  113. if version == (10, 16):
  114. # When built against an older macOS SDK, Python will report
  115. # macOS 10.16 instead of the real version.
  116. version_str = subprocess.check_output(
  117. [
  118. sys.executable,
  119. "-sS",
  120. "-c",
  121. "import platform; print(platform.mac_ver()[0])",
  122. ],
  123. env={"SYSTEM_VERSION_COMPAT": "0"},
  124. universal_newlines=True,
  125. )
  126. version = tuple(map(int, version_str.split(".")[:2]))
  127. return version
  128. if MACOS:
  129. MACOS_11PLUS = macos_version() > (10, 15)
  130. MACOS_12PLUS = macos_version() >= (12, 0)
  131. else:
  132. MACOS_11PLUS = False
  133. MACOS_12PLUS = False
  134. # --- configurable defaults
  135. # how many times retry_on_failure() decorator will retry
  136. NO_RETRIES = 10
  137. # bytes tolerance for system-wide related tests
  138. TOLERANCE_SYS_MEM = 5 * 1024 * 1024 # 5MB
  139. TOLERANCE_DISK_USAGE = 10 * 1024 * 1024 # 10MB
  140. # the timeout used in functions which have to wait
  141. GLOBAL_TIMEOUT = 5
  142. # be more tolerant if we're on CI in order to avoid false positives
  143. if CI_TESTING:
  144. NO_RETRIES *= 3
  145. GLOBAL_TIMEOUT *= 3
  146. TOLERANCE_SYS_MEM *= 4
  147. TOLERANCE_DISK_USAGE *= 3
  148. # --- file names
  149. # Disambiguate TESTFN with PID for parallel testing.
  150. TESTFN_PREFIX = f"@psutil-{os.getpid()}-"
  151. UNICODE_SUFFIX = "-ƒőő"
  152. # An invalid unicode string.
  153. INVALID_UNICODE_SUFFIX = b"f\xc0\x80".decode('utf8', 'surrogateescape')
  154. ASCII_FS = sys.getfilesystemencoding().lower() in {"ascii", "us-ascii"}
  155. # --- paths
  156. ROOT_DIR = os.environ.get("PSUTIL_ROOT_DIR") or os.path.realpath(
  157. os.path.join(os.path.dirname(__file__), "..", "..")
  158. )
  159. SCRIPTS_DIR = os.path.join(ROOT_DIR, 'scripts')
  160. HERE = os.path.realpath(os.path.dirname(__file__))
  161. # --- support
  162. HAS_CPU_AFFINITY = hasattr(psutil.Process, "cpu_affinity")
  163. HAS_ENVIRON = hasattr(psutil.Process, "environ")
  164. HAS_GETLOADAVG = hasattr(psutil, "getloadavg")
  165. HAS_IONICE = hasattr(psutil.Process, "ionice")
  166. HAS_MEMORY_MAPS = hasattr(psutil.Process, "memory_maps")
  167. HAS_NET_CONNECTIONS_UNIX = POSIX and not SUNOS
  168. HAS_NET_IO_COUNTERS = hasattr(psutil, "net_io_counters")
  169. HAS_PROC_CPU_NUM = hasattr(psutil.Process, "cpu_num")
  170. HAS_PROC_IO_COUNTERS = hasattr(psutil.Process, "io_counters")
  171. HAS_RLIMIT = hasattr(psutil.Process, "rlimit")
  172. HAS_SENSORS_BATTERY = hasattr(psutil, "sensors_battery")
  173. HAS_SENSORS_FANS = hasattr(psutil, "sensors_fans")
  174. HAS_SENSORS_TEMPERATURES = hasattr(psutil, "sensors_temperatures")
  175. HAS_THREADS = hasattr(psutil.Process, "threads")
  176. SKIP_SYSCONS = (MACOS or AIX) and os.getuid() != 0
  177. try:
  178. HAS_BATTERY = HAS_SENSORS_BATTERY and bool(psutil.sensors_battery())
  179. except Exception: # noqa: BLE001
  180. atexit.register(functools.partial(print, traceback.format_exc()))
  181. HAS_BATTERY = False
  182. try:
  183. HAS_CPU_FREQ = hasattr(psutil, "cpu_freq") and bool(psutil.cpu_freq())
  184. except Exception: # noqa: BLE001
  185. atexit.register(functools.partial(print, traceback.format_exc()))
  186. HAS_CPU_FREQ = False
  187. # --- misc
  188. def _get_py_exe():
  189. def attempt(exe):
  190. try:
  191. subprocess.check_call(
  192. [exe, "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
  193. )
  194. except subprocess.CalledProcessError:
  195. return None
  196. else:
  197. return exe
  198. env = os.environ.copy()
  199. # On Windows, starting with python 3.7, virtual environments use a
  200. # venv launcher startup process. This does not play well when
  201. # counting spawned processes, or when relying on the PID of the
  202. # spawned process to do some checks, e.g. connections check per PID.
  203. # Let's use the base python in this case.
  204. base = getattr(sys, "_base_executable", None)
  205. if WINDOWS and sys.version_info >= (3, 7) and base is not None:
  206. # We need to set __PYVENV_LAUNCHER__ to sys.executable for the
  207. # base python executable to know about the environment.
  208. env["__PYVENV_LAUNCHER__"] = sys.executable
  209. return base, env
  210. elif GITHUB_ACTIONS:
  211. return sys.executable, env
  212. elif MACOS:
  213. exe = (
  214. attempt(sys.executable)
  215. or attempt(os.path.realpath(sys.executable))
  216. or attempt(
  217. shutil.which("python{}.{}".format(*sys.version_info[:2]))
  218. )
  219. or attempt(psutil.Process().exe())
  220. )
  221. if not exe:
  222. raise ValueError("can't find python exe real abspath")
  223. return exe, env
  224. else:
  225. exe = os.path.realpath(sys.executable)
  226. assert os.path.exists(exe), exe
  227. return exe, env
  228. PYTHON_EXE, PYTHON_EXE_ENV = _get_py_exe()
  229. DEVNULL = open(os.devnull, 'r+') # noqa: SIM115
  230. atexit.register(DEVNULL.close)
  231. VALID_PROC_STATUSES = [
  232. getattr(psutil, x) for x in dir(psutil) if x.startswith('STATUS_')
  233. ]
  234. AF_UNIX = getattr(socket, "AF_UNIX", object())
  235. _subprocesses_started = set()
  236. _pids_started = set()
  237. # ===================================================================
  238. # --- fake pytest
  239. # ===================================================================
  240. class fake_pytest:
  241. """A class that mimics some basic pytest APIs. This is meant for
  242. when unit tests are run in production, where pytest may not be
  243. installed. Still, the user can test psutil installation via:
  244. $ python3 -m psutil.tests
  245. """
  246. @staticmethod
  247. def _warn_on_exit():
  248. def _warn_on_exit():
  249. warnings.warn(
  250. "Fake pytest module was used. Test results may be inaccurate.",
  251. UserWarning,
  252. stacklevel=1,
  253. )
  254. atexit.register(_warn_on_exit)
  255. @staticmethod
  256. def main(*args, **kw): # noqa: ARG004
  257. """Mimics pytest.main(). It has the same effect as running
  258. `python3 -m unittest -v` from the project root directory.
  259. """
  260. suite = unittest.TestLoader().discover(HERE)
  261. unittest.TextTestRunner(verbosity=2).run(suite)
  262. return suite
  263. @staticmethod
  264. def raises(exc, match=None):
  265. """Mimics `pytest.raises`."""
  266. class ExceptionInfo:
  267. _exc = None
  268. @property
  269. def value(self):
  270. return self._exc
  271. @contextlib.contextmanager
  272. def context(exc, match=None):
  273. einfo = ExceptionInfo()
  274. try:
  275. yield einfo
  276. except exc as err:
  277. if match and not re.search(match, str(err)):
  278. msg = f'"{match}" does not match "{err}"'
  279. raise AssertionError(msg)
  280. einfo._exc = err
  281. else:
  282. raise AssertionError(f"{exc!r} not raised")
  283. return context(exc, match=match)
  284. @staticmethod
  285. def warns(warning, match=None):
  286. """Mimics `pytest.warns`."""
  287. if match:
  288. return unittest.TestCase().assertWarnsRegex(warning, match)
  289. return unittest.TestCase().assertWarns(warning)
  290. @staticmethod
  291. def skip(reason=""):
  292. """Mimics `unittest.SkipTest`."""
  293. raise unittest.SkipTest(reason)
  294. @staticmethod
  295. def fail(reason=""):
  296. """Mimics `pytest.fail`."""
  297. return unittest.TestCase().fail(reason)
  298. class mark:
  299. @staticmethod
  300. def skipif(condition, reason=""):
  301. """Mimics `@pytest.mark.skipif` decorator."""
  302. return unittest.skipIf(condition, reason)
  303. class xdist_group:
  304. """Mimics `@pytest.mark.xdist_group` decorator (no-op)."""
  305. def __init__(self, name=None):
  306. pass
  307. def __call__(self, cls_or_meth):
  308. return cls_or_meth
  309. # to make pytest.fail() exception catchable
  310. fake_pytest.fail.Exception = AssertionError
  311. if pytest is None:
  312. pytest = fake_pytest
  313. # monkey patch future `import pytest` statements
  314. sys.modules["pytest"] = fake_pytest
  315. fake_pytest._warn_on_exit()
  316. # ===================================================================
  317. # --- threads
  318. # ===================================================================
  319. class ThreadTask(threading.Thread):
  320. """A thread task which does nothing expect staying alive."""
  321. def __init__(self):
  322. super().__init__()
  323. self._running = False
  324. self._interval = 0.001
  325. self._flag = threading.Event()
  326. def __repr__(self):
  327. name = self.__class__.__name__
  328. return f"<{name} running={self._running} at {id(self):#x}>"
  329. def __enter__(self):
  330. self.start()
  331. return self
  332. def __exit__(self, *args, **kwargs):
  333. self.stop()
  334. def start(self):
  335. """Start thread and keep it running until an explicit
  336. stop() request. Polls for shutdown every 'timeout' seconds.
  337. """
  338. if self._running:
  339. raise ValueError("already started")
  340. threading.Thread.start(self)
  341. self._flag.wait()
  342. def run(self):
  343. self._running = True
  344. self._flag.set()
  345. while self._running:
  346. time.sleep(self._interval)
  347. def stop(self):
  348. """Stop thread execution and and waits until it is stopped."""
  349. if not self._running:
  350. raise ValueError("already stopped")
  351. self._running = False
  352. self.join()
  353. # ===================================================================
  354. # --- subprocesses
  355. # ===================================================================
  356. def _reap_children_on_err(fun):
  357. @functools.wraps(fun)
  358. def wrapper(*args, **kwargs):
  359. try:
  360. return fun(*args, **kwargs)
  361. except Exception:
  362. reap_children()
  363. raise
  364. return wrapper
  365. @_reap_children_on_err
  366. def spawn_subproc(cmd=None, **kwds):
  367. """Create a python subprocess which does nothing for some secs and
  368. return it as a subprocess.Popen instance.
  369. If "cmd" is specified that is used instead of python.
  370. By default stdin and stdout are redirected to /dev/null.
  371. It also attempts to make sure the process is in a reasonably
  372. initialized state.
  373. The process is registered for cleanup on reap_children().
  374. """
  375. kwds.setdefault("stdin", DEVNULL)
  376. kwds.setdefault("stdout", DEVNULL)
  377. kwds.setdefault("cwd", os.getcwd())
  378. kwds.setdefault("env", PYTHON_EXE_ENV)
  379. if WINDOWS:
  380. # Prevents the subprocess to open error dialogs. This will also
  381. # cause stderr to be suppressed, which is suboptimal in order
  382. # to debug broken tests.
  383. CREATE_NO_WINDOW = 0x8000000
  384. kwds.setdefault("creationflags", CREATE_NO_WINDOW)
  385. if cmd is None:
  386. testfn = get_testfn(dir=os.getcwd())
  387. try:
  388. safe_rmpath(testfn)
  389. pyline = (
  390. "import time;"
  391. f"open(r'{testfn}', 'w').close();"
  392. "[time.sleep(0.1) for x in range(100)];" # 10 secs
  393. )
  394. cmd = [PYTHON_EXE, "-c", pyline]
  395. sproc = subprocess.Popen(cmd, **kwds)
  396. _subprocesses_started.add(sproc)
  397. wait_for_file(testfn, delete=True, empty=True)
  398. finally:
  399. safe_rmpath(testfn)
  400. else:
  401. sproc = subprocess.Popen(cmd, **kwds)
  402. _subprocesses_started.add(sproc)
  403. wait_for_pid(sproc.pid)
  404. return sproc
  405. @_reap_children_on_err
  406. def spawn_children_pair():
  407. """Create a subprocess which creates another one as in:
  408. A (us) -> B (child) -> C (grandchild).
  409. Return a (child, grandchild) tuple.
  410. The 2 processes are fully initialized and will live for 60 secs
  411. and are registered for cleanup on reap_children().
  412. """
  413. tfile = None
  414. testfn = get_testfn(dir=os.getcwd())
  415. try:
  416. s = textwrap.dedent(f"""\
  417. import subprocess, os, sys, time
  418. s = "import os, time;"
  419. s += "f = open('{os.path.basename(testfn)}', 'w');"
  420. s += "f.write(str(os.getpid()));"
  421. s += "f.close();"
  422. s += "[time.sleep(0.1) for x in range(100 * 6)];"
  423. p = subprocess.Popen([r'{PYTHON_EXE}', '-c', s])
  424. p.wait()
  425. """)
  426. # On Windows if we create a subprocess with CREATE_NO_WINDOW flag
  427. # set (which is the default) a "conhost.exe" extra process will be
  428. # spawned as a child. We don't want that.
  429. if WINDOWS:
  430. subp, tfile = pyrun(s, creationflags=0)
  431. else:
  432. subp, tfile = pyrun(s)
  433. child = psutil.Process(subp.pid)
  434. grandchild_pid = int(wait_for_file(testfn, delete=True, empty=False))
  435. _pids_started.add(grandchild_pid)
  436. grandchild = psutil.Process(grandchild_pid)
  437. return (child, grandchild)
  438. finally:
  439. safe_rmpath(testfn)
  440. if tfile is not None:
  441. safe_rmpath(tfile)
  442. def spawn_zombie():
  443. """Create a zombie process and return a (parent, zombie) process tuple.
  444. In order to kill the zombie parent must be terminate()d first, then
  445. zombie must be wait()ed on.
  446. """
  447. assert psutil.POSIX
  448. unix_file = get_testfn()
  449. src = textwrap.dedent(f"""\
  450. import os, sys, time, socket, contextlib
  451. child_pid = os.fork()
  452. if child_pid > 0:
  453. time.sleep(3000)
  454. else:
  455. # this is the zombie process
  456. with socket.socket(socket.AF_UNIX) as s:
  457. s.connect('{unix_file}')
  458. pid = bytes(str(os.getpid()), 'ascii')
  459. s.sendall(pid)
  460. """)
  461. tfile = None
  462. sock = bind_unix_socket(unix_file)
  463. try:
  464. sock.settimeout(GLOBAL_TIMEOUT)
  465. parent, tfile = pyrun(src)
  466. conn, _ = sock.accept()
  467. try:
  468. select.select([conn.fileno()], [], [], GLOBAL_TIMEOUT)
  469. zpid = int(conn.recv(1024))
  470. _pids_started.add(zpid)
  471. zombie = psutil.Process(zpid)
  472. call_until(lambda: zombie.status() == psutil.STATUS_ZOMBIE)
  473. return (parent, zombie)
  474. finally:
  475. conn.close()
  476. finally:
  477. sock.close()
  478. safe_rmpath(unix_file)
  479. if tfile is not None:
  480. safe_rmpath(tfile)
  481. @_reap_children_on_err
  482. def pyrun(src, **kwds):
  483. """Run python 'src' code string in a separate interpreter.
  484. Returns a subprocess.Popen instance and the test file where the source
  485. code was written.
  486. """
  487. kwds.setdefault("stdout", None)
  488. kwds.setdefault("stderr", None)
  489. srcfile = get_testfn()
  490. try:
  491. with open(srcfile, "w") as f:
  492. f.write(src)
  493. subp = spawn_subproc([PYTHON_EXE, f.name], **kwds)
  494. wait_for_pid(subp.pid)
  495. return (subp, srcfile)
  496. except Exception:
  497. safe_rmpath(srcfile)
  498. raise
  499. @_reap_children_on_err
  500. def sh(cmd, **kwds):
  501. """Run cmd in a subprocess and return its output.
  502. raises RuntimeError on error.
  503. """
  504. # Prevents subprocess to open error dialogs in case of error.
  505. flags = 0x8000000 if WINDOWS else 0
  506. kwds.setdefault("stdout", subprocess.PIPE)
  507. kwds.setdefault("stderr", subprocess.PIPE)
  508. kwds.setdefault("universal_newlines", True)
  509. kwds.setdefault("creationflags", flags)
  510. if isinstance(cmd, str):
  511. cmd = shlex.split(cmd)
  512. p = subprocess.Popen(cmd, **kwds)
  513. _subprocesses_started.add(p)
  514. stdout, stderr = p.communicate(timeout=GLOBAL_TIMEOUT)
  515. if p.returncode != 0:
  516. raise RuntimeError(stdout + stderr)
  517. if stderr:
  518. warn(stderr)
  519. if stdout.endswith('\n'):
  520. stdout = stdout[:-1]
  521. return stdout
  522. def terminate(proc_or_pid, sig=signal.SIGTERM, wait_timeout=GLOBAL_TIMEOUT):
  523. """Terminate a process and wait() for it.
  524. Process can be a PID or an instance of psutil.Process(),
  525. subprocess.Popen() or psutil.Popen().
  526. If it's a subprocess.Popen() or psutil.Popen() instance also closes
  527. its stdin / stdout / stderr fds.
  528. PID is wait()ed even if the process is already gone (kills zombies).
  529. Does nothing if the process does not exist.
  530. Return process exit status.
  531. """
  532. def wait(proc, timeout):
  533. proc.wait(timeout)
  534. if WINDOWS and isinstance(proc, subprocess.Popen):
  535. # Otherwise PID may still hang around.
  536. try:
  537. return psutil.Process(proc.pid).wait(timeout)
  538. except psutil.NoSuchProcess:
  539. pass
  540. def sendsig(proc, sig):
  541. # XXX: otherwise the build hangs for some reason.
  542. if MACOS and GITHUB_ACTIONS:
  543. sig = signal.SIGKILL
  544. # If the process received SIGSTOP, SIGCONT is necessary first,
  545. # otherwise SIGTERM won't work.
  546. if POSIX and sig != signal.SIGKILL:
  547. proc.send_signal(signal.SIGCONT)
  548. proc.send_signal(sig)
  549. def term_subprocess_proc(proc, timeout):
  550. try:
  551. sendsig(proc, sig)
  552. except ProcessLookupError:
  553. pass
  554. except OSError as err:
  555. if WINDOWS and err.winerror == 6: # "invalid handle"
  556. pass
  557. raise
  558. return wait(proc, timeout)
  559. def term_psutil_proc(proc, timeout):
  560. try:
  561. sendsig(proc, sig)
  562. except psutil.NoSuchProcess:
  563. pass
  564. return wait(proc, timeout)
  565. def term_pid(pid, timeout):
  566. try:
  567. proc = psutil.Process(pid)
  568. except psutil.NoSuchProcess:
  569. # Needed to kill zombies.
  570. if POSIX:
  571. return wait_pid(pid, timeout)
  572. else:
  573. return term_psutil_proc(proc, timeout)
  574. def flush_popen(proc):
  575. if proc.stdout:
  576. proc.stdout.close()
  577. if proc.stderr:
  578. proc.stderr.close()
  579. # Flushing a BufferedWriter may raise an error.
  580. if proc.stdin:
  581. proc.stdin.close()
  582. p = proc_or_pid
  583. try:
  584. if isinstance(p, int):
  585. return term_pid(p, wait_timeout)
  586. elif isinstance(p, (psutil.Process, psutil.Popen)):
  587. return term_psutil_proc(p, wait_timeout)
  588. elif isinstance(p, subprocess.Popen):
  589. return term_subprocess_proc(p, wait_timeout)
  590. else:
  591. raise TypeError(f"wrong type {p!r}")
  592. finally:
  593. if isinstance(p, (subprocess.Popen, psutil.Popen)):
  594. flush_popen(p)
  595. pid = p if isinstance(p, int) else p.pid
  596. assert not psutil.pid_exists(pid), pid
  597. def reap_children(recursive=False):
  598. """Terminate and wait() any subprocess started by this test suite
  599. and any children currently running, ensuring that no processes stick
  600. around to hog resources.
  601. If recursive is True it also tries to terminate and wait()
  602. all grandchildren started by this process.
  603. """
  604. # Get the children here before terminating them, as in case of
  605. # recursive=True we don't want to lose the intermediate reference
  606. # pointing to the grandchildren.
  607. children = psutil.Process().children(recursive=recursive)
  608. # Terminate subprocess.Popen.
  609. while _subprocesses_started:
  610. subp = _subprocesses_started.pop()
  611. terminate(subp)
  612. # Collect started pids.
  613. while _pids_started:
  614. pid = _pids_started.pop()
  615. terminate(pid)
  616. # Terminate children.
  617. if children:
  618. for p in children:
  619. terminate(p, wait_timeout=None)
  620. _, alive = psutil.wait_procs(children, timeout=GLOBAL_TIMEOUT)
  621. for p in alive:
  622. warn(f"couldn't terminate process {p!r}; attempting kill()")
  623. terminate(p, sig=signal.SIGKILL)
  624. # ===================================================================
  625. # --- OS
  626. # ===================================================================
  627. def kernel_version():
  628. """Return a tuple such as (2, 6, 36)."""
  629. if not POSIX:
  630. raise NotImplementedError("not POSIX")
  631. s = ""
  632. uname = os.uname()[2]
  633. for c in uname:
  634. if c.isdigit() or c == '.':
  635. s += c
  636. else:
  637. break
  638. if not s:
  639. raise ValueError(f"can't parse {uname!r}")
  640. minor = 0
  641. micro = 0
  642. nums = s.split('.')
  643. major = int(nums[0])
  644. if len(nums) >= 2:
  645. minor = int(nums[1])
  646. if len(nums) >= 3:
  647. micro = int(nums[2])
  648. return (major, minor, micro)
  649. def get_winver():
  650. if not WINDOWS:
  651. raise NotImplementedError("not WINDOWS")
  652. wv = sys.getwindowsversion()
  653. sp = wv.service_pack_major or 0
  654. return (wv[0], wv[1], sp)
  655. # ===================================================================
  656. # --- sync primitives
  657. # ===================================================================
  658. class retry:
  659. """A retry decorator."""
  660. def __init__(
  661. self,
  662. exception=Exception,
  663. timeout=None,
  664. retries=None,
  665. interval=0.001,
  666. logfun=None,
  667. ):
  668. if timeout and retries:
  669. raise ValueError("timeout and retries args are mutually exclusive")
  670. self.exception = exception
  671. self.timeout = timeout
  672. self.retries = retries
  673. self.interval = interval
  674. self.logfun = logfun
  675. def __iter__(self):
  676. if self.timeout:
  677. stop_at = time.time() + self.timeout
  678. while time.time() < stop_at:
  679. yield
  680. elif self.retries:
  681. for _ in range(self.retries):
  682. yield
  683. else:
  684. while True:
  685. yield
  686. def sleep(self):
  687. if self.interval is not None:
  688. time.sleep(self.interval)
  689. def __call__(self, fun):
  690. @functools.wraps(fun)
  691. def wrapper(*args, **kwargs):
  692. exc = None
  693. for _ in self:
  694. try:
  695. return fun(*args, **kwargs)
  696. except self.exception as _:
  697. exc = _
  698. if self.logfun is not None:
  699. self.logfun(exc)
  700. self.sleep()
  701. continue
  702. raise exc
  703. # This way the user of the decorated function can change config
  704. # parameters.
  705. wrapper.decorator = self
  706. return wrapper
  707. @retry(
  708. exception=psutil.NoSuchProcess,
  709. logfun=None,
  710. timeout=GLOBAL_TIMEOUT,
  711. interval=0.001,
  712. )
  713. def wait_for_pid(pid):
  714. """Wait for pid to show up in the process list then return.
  715. Used in the test suite to give time the sub process to initialize.
  716. """
  717. if pid not in psutil.pids():
  718. raise psutil.NoSuchProcess(pid)
  719. psutil.Process(pid)
  720. @retry(
  721. exception=(FileNotFoundError, AssertionError),
  722. logfun=None,
  723. timeout=GLOBAL_TIMEOUT,
  724. interval=0.001,
  725. )
  726. def wait_for_file(fname, delete=True, empty=False):
  727. """Wait for a file to be written on disk with some content."""
  728. with open(fname, "rb") as f:
  729. data = f.read()
  730. if not empty:
  731. assert data
  732. if delete:
  733. safe_rmpath(fname)
  734. return data
  735. @retry(
  736. exception=(AssertionError, pytest.fail.Exception),
  737. logfun=None,
  738. timeout=GLOBAL_TIMEOUT,
  739. interval=0.001,
  740. )
  741. def call_until(fun):
  742. """Keep calling function until it evaluates to True."""
  743. ret = fun()
  744. assert ret
  745. return ret
  746. # ===================================================================
  747. # --- fs
  748. # ===================================================================
  749. def safe_rmpath(path):
  750. """Convenience function for removing temporary test files or dirs."""
  751. def retry_fun(fun):
  752. # On Windows it could happen that the file or directory has
  753. # open handles or references preventing the delete operation
  754. # to succeed immediately, so we retry for a while. See:
  755. # https://bugs.python.org/issue33240
  756. stop_at = time.time() + GLOBAL_TIMEOUT
  757. while time.time() < stop_at:
  758. try:
  759. return fun()
  760. except FileNotFoundError:
  761. pass
  762. except OSError as _:
  763. err = _
  764. warn(f"ignoring {err}")
  765. time.sleep(0.01)
  766. raise err
  767. try:
  768. st = os.stat(path)
  769. if stat.S_ISDIR(st.st_mode):
  770. fun = functools.partial(shutil.rmtree, path)
  771. else:
  772. fun = functools.partial(os.remove, path)
  773. if POSIX:
  774. fun()
  775. else:
  776. retry_fun(fun)
  777. except FileNotFoundError:
  778. pass
  779. def safe_mkdir(dir):
  780. """Convenience function for creating a directory."""
  781. try:
  782. os.mkdir(dir)
  783. except FileExistsError:
  784. pass
  785. @contextlib.contextmanager
  786. def chdir(dirname):
  787. """Context manager which temporarily changes the current directory."""
  788. curdir = os.getcwd()
  789. try:
  790. os.chdir(dirname)
  791. yield
  792. finally:
  793. os.chdir(curdir)
  794. def create_py_exe(path):
  795. """Create a Python executable file in the given location."""
  796. assert not os.path.exists(path), path
  797. atexit.register(safe_rmpath, path)
  798. shutil.copyfile(PYTHON_EXE, path)
  799. if POSIX:
  800. st = os.stat(path)
  801. os.chmod(path, st.st_mode | stat.S_IEXEC)
  802. return path
  803. def create_c_exe(path, c_code=None):
  804. """Create a compiled C executable in the given location."""
  805. assert not os.path.exists(path), path
  806. if not shutil.which("gcc"):
  807. return pytest.skip("gcc is not installed")
  808. if c_code is None:
  809. c_code = textwrap.dedent("""
  810. #include <unistd.h>
  811. int main() {
  812. pause();
  813. return 1;
  814. }
  815. """)
  816. else:
  817. assert isinstance(c_code, str), c_code
  818. atexit.register(safe_rmpath, path)
  819. with open(get_testfn(suffix='.c'), "w") as f:
  820. f.write(c_code)
  821. try:
  822. subprocess.check_call(["gcc", f.name, "-o", path])
  823. finally:
  824. safe_rmpath(f.name)
  825. return path
  826. def get_testfn(suffix="", dir=None):
  827. """Return an absolute pathname of a file or dir that did not
  828. exist at the time this call is made. Also schedule it for safe
  829. deletion at interpreter exit. It's technically racy but probably
  830. not really due to the time variant.
  831. """
  832. while True:
  833. name = tempfile.mktemp(prefix=TESTFN_PREFIX, suffix=suffix, dir=dir)
  834. if not os.path.exists(name): # also include dirs
  835. path = os.path.realpath(name) # needed for OSX
  836. atexit.register(safe_rmpath, path)
  837. return path
  838. # ===================================================================
  839. # --- testing
  840. # ===================================================================
  841. class PsutilTestCase(unittest.TestCase):
  842. """Test class providing auto-cleanup wrappers on top of process
  843. test utilities. All test classes should derive from this one, even
  844. if we use pytest.
  845. """
  846. # Print a full path representation of the single unit test being
  847. # run, similar to pytest output. Used only when running tests with
  848. # the unittest runner.
  849. def __str__(self):
  850. fqmod = self.__class__.__module__
  851. if not fqmod.startswith('psutil.'):
  852. fqmod = 'psutil.tests.' + fqmod
  853. return "{}.{}.{}".format(
  854. fqmod,
  855. self.__class__.__name__,
  856. self._testMethodName,
  857. )
  858. def get_testfn(self, suffix="", dir=None):
  859. fname = get_testfn(suffix=suffix, dir=dir)
  860. self.addCleanup(safe_rmpath, fname)
  861. return fname
  862. def spawn_subproc(self, *args, **kwds):
  863. sproc = spawn_subproc(*args, **kwds)
  864. self.addCleanup(terminate, sproc)
  865. return sproc
  866. def spawn_psproc(self, *args, **kwargs):
  867. sproc = self.spawn_subproc(*args, **kwargs)
  868. try:
  869. return psutil.Process(sproc.pid)
  870. except psutil.NoSuchProcess:
  871. self.assert_pid_gone(sproc.pid)
  872. raise
  873. def spawn_children_pair(self):
  874. child1, child2 = spawn_children_pair()
  875. self.addCleanup(terminate, child2)
  876. self.addCleanup(terminate, child1) # executed first
  877. return (child1, child2)
  878. def spawn_zombie(self):
  879. parent, zombie = spawn_zombie()
  880. self.addCleanup(terminate, zombie)
  881. self.addCleanup(terminate, parent) # executed first
  882. return (parent, zombie)
  883. def pyrun(self, *args, **kwds):
  884. sproc, srcfile = pyrun(*args, **kwds)
  885. self.addCleanup(safe_rmpath, srcfile)
  886. self.addCleanup(terminate, sproc) # executed first
  887. return sproc
  888. def _check_proc_exc(self, proc, exc):
  889. assert isinstance(exc, psutil.Error)
  890. assert exc.pid == proc.pid
  891. assert exc.name == proc._name
  892. if exc.name:
  893. assert exc.name
  894. if isinstance(exc, psutil.ZombieProcess):
  895. assert exc.ppid == proc._ppid
  896. if exc.ppid is not None:
  897. assert exc.ppid >= 0
  898. str(exc)
  899. repr(exc)
  900. def assert_pid_gone(self, pid):
  901. try:
  902. proc = psutil.Process(pid)
  903. except psutil.ZombieProcess:
  904. raise AssertionError("wasn't supposed to raise ZombieProcess")
  905. except psutil.NoSuchProcess as exc:
  906. assert exc.pid == pid # noqa: PT017
  907. assert exc.name is None # noqa: PT017
  908. else:
  909. raise AssertionError(f"did not raise NoSuchProcess ({proc})")
  910. assert not psutil.pid_exists(pid), pid
  911. assert pid not in psutil.pids()
  912. assert pid not in [x.pid for x in psutil.process_iter()]
  913. def assert_proc_gone(self, proc):
  914. self.assert_pid_gone(proc.pid)
  915. ns = process_namespace(proc)
  916. for fun, name in ns.iter(ns.all, clear_cache=True):
  917. with self.subTest(proc=str(proc), name=name):
  918. try:
  919. ret = fun()
  920. except psutil.ZombieProcess:
  921. raise
  922. except psutil.NoSuchProcess as exc:
  923. self._check_proc_exc(proc, exc)
  924. else:
  925. msg = (
  926. f"Process.{name}() didn't raise NSP and returned"
  927. f" {ret!r}"
  928. )
  929. raise AssertionError(msg)
  930. proc.wait(timeout=0) # assert not raise TimeoutExpired
  931. def assert_proc_zombie(self, proc):
  932. def assert_in_pids(proc):
  933. if MACOS:
  934. # Even ps does not show zombie PIDs for some reason. Weird...
  935. return
  936. assert proc.pid in psutil.pids()
  937. assert proc.pid in [x.pid for x in psutil.process_iter()]
  938. psutil._pmap = {}
  939. assert proc.pid in [x.pid for x in psutil.process_iter()]
  940. # A zombie process should always be instantiable.
  941. clone = psutil.Process(proc.pid)
  942. # Cloned zombie on Open/NetBSD/illumos/Solaris has null creation
  943. # time, see:
  944. # https://github.com/giampaolo/psutil/issues/2287
  945. # https://github.com/giampaolo/psutil/issues/2593
  946. assert proc == clone
  947. if not (OPENBSD or NETBSD or SUNOS):
  948. assert hash(proc) == hash(clone)
  949. # Its status always be querable.
  950. assert proc.status() == psutil.STATUS_ZOMBIE
  951. # It should be considered 'running'.
  952. assert proc.is_running()
  953. assert psutil.pid_exists(proc.pid)
  954. # as_dict() shouldn't crash.
  955. proc.as_dict()
  956. # It should show up in pids() and process_iter().
  957. assert_in_pids(proc)
  958. # Call all methods.
  959. ns = process_namespace(proc)
  960. for fun, name in ns.iter(ns.all, clear_cache=True):
  961. with self.subTest(proc=str(proc), name=name):
  962. try:
  963. fun()
  964. except (psutil.ZombieProcess, psutil.AccessDenied) as exc:
  965. self._check_proc_exc(proc, exc)
  966. if LINUX:
  967. # https://github.com/giampaolo/psutil/pull/2288
  968. with pytest.raises(psutil.ZombieProcess) as cm:
  969. proc.cmdline()
  970. self._check_proc_exc(proc, cm.value)
  971. with pytest.raises(psutil.ZombieProcess) as cm:
  972. proc.exe()
  973. self._check_proc_exc(proc, cm.value)
  974. with pytest.raises(psutil.ZombieProcess) as cm:
  975. proc.memory_maps()
  976. self._check_proc_exc(proc, cm.value)
  977. # Zombie cannot be signaled or terminated.
  978. proc.suspend()
  979. proc.resume()
  980. proc.terminate()
  981. proc.kill()
  982. assert proc.is_running()
  983. assert psutil.pid_exists(proc.pid)
  984. assert_in_pids(proc)
  985. # Its parent should 'see' it (edit: not true on BSD and MACOS).
  986. # descendants = [x.pid for x in psutil.Process().children(
  987. # recursive=True)]
  988. # assert proc.pid in descendants
  989. # __eq__ can't be relied upon because creation time may not be
  990. # querable.
  991. # assert proc == psutil.Process(proc.pid)
  992. # XXX should we also assume ppid() to be usable? Note: this
  993. # would be an important use case as the only way to get
  994. # rid of a zombie is to kill its parent.
  995. # assert proc == ppid(), os.getpid()
  996. @pytest.mark.skipif(PYPY, reason="unreliable on PYPY")
  997. @pytest.mark.xdist_group(name="serial")
  998. class TestMemoryLeak(PsutilTestCase):
  999. """Test framework class for detecting function memory leaks,
  1000. typically functions implemented in C which forgot to free() memory
  1001. from the heap. It does so by checking whether the process memory
  1002. usage increased before and after calling the function many times.
  1003. Note that this is hard (probably impossible) to do reliably, due
  1004. to how the OS handles memory, the GC and so on (memory can even
  1005. decrease!). In order to avoid false positives, in case of failure
  1006. (mem > 0) we retry the test for up to 5 times, increasing call
  1007. repetitions each time. If the memory keeps increasing then it's a
  1008. failure.
  1009. If available (Linux, OSX, Windows), USS memory is used for comparison,
  1010. since it's supposed to be more precise, see:
  1011. https://gmpy.dev/blog/2016/real-process-memory-and-environ-in-python
  1012. If not, RSS memory is used. mallinfo() on Linux and _heapwalk() on
  1013. Windows may give even more precision, but at the moment are not
  1014. implemented.
  1015. PyPy appears to be completely unstable for this framework, probably
  1016. because of its JIT, so tests on PYPY are skipped.
  1017. Usage:
  1018. class TestLeaks(psutil.tests.TestMemoryLeak):
  1019. def test_fun(self):
  1020. self.execute(some_function)
  1021. """
  1022. # Configurable class attrs.
  1023. times = 200
  1024. warmup_times = 10
  1025. tolerance = 0 # memory
  1026. retries = 10 if CI_TESTING else 5
  1027. verbose = True
  1028. _thisproc = psutil.Process()
  1029. _psutil_debug_orig = bool(os.getenv('PSUTIL_DEBUG'))
  1030. @classmethod
  1031. def setUpClass(cls):
  1032. psutil._set_debug(False) # avoid spamming to stderr
  1033. @classmethod
  1034. def tearDownClass(cls):
  1035. psutil._set_debug(cls._psutil_debug_orig)
  1036. def _get_mem(self):
  1037. # USS is the closest thing we have to "real" memory usage and it
  1038. # should be less likely to produce false positives.
  1039. mem = self._thisproc.memory_full_info()
  1040. return getattr(mem, "uss", mem.rss)
  1041. def _get_num_fds(self):
  1042. if POSIX:
  1043. return self._thisproc.num_fds()
  1044. else:
  1045. return self._thisproc.num_handles()
  1046. def _log(self, msg):
  1047. if self.verbose:
  1048. print_color(msg, color="yellow", file=sys.stderr)
  1049. def _check_fds(self, fun):
  1050. """Makes sure num_fds() (POSIX) or num_handles() (Windows) does
  1051. not increase after calling a function. Used to discover forgotten
  1052. close(2) and CloseHandle syscalls.
  1053. """
  1054. before = self._get_num_fds()
  1055. self.call(fun)
  1056. after = self._get_num_fds()
  1057. diff = after - before
  1058. if diff < 0:
  1059. msg = (
  1060. f"negative diff {diff!r} (gc probably collected a"
  1061. " resource from a previous test)"
  1062. )
  1063. return pytest.fail(msg)
  1064. if diff > 0:
  1065. type_ = "fd" if POSIX else "handle"
  1066. if diff > 1:
  1067. type_ += "s"
  1068. msg = f"{diff} unclosed {type_} after calling {fun!r}"
  1069. return pytest.fail(msg)
  1070. def _call_ntimes(self, fun, times):
  1071. """Get 2 distinct memory samples, before and after having
  1072. called fun repeatedly, and return the memory difference.
  1073. """
  1074. gc.collect(generation=1)
  1075. mem1 = self._get_mem()
  1076. for x in range(times):
  1077. ret = self.call(fun)
  1078. del x, ret
  1079. gc.collect(generation=1)
  1080. mem2 = self._get_mem()
  1081. assert gc.garbage == []
  1082. diff = mem2 - mem1 # can also be negative
  1083. return diff
  1084. def _check_mem(self, fun, times, retries, tolerance):
  1085. messages = []
  1086. prev_mem = 0
  1087. increase = times
  1088. for idx in range(1, retries + 1):
  1089. mem = self._call_ntimes(fun, times)
  1090. msg = "Run #{}: extra-mem={}, per-call={}, calls={}".format(
  1091. idx,
  1092. bytes2human(mem),
  1093. bytes2human(mem / times),
  1094. times,
  1095. )
  1096. messages.append(msg)
  1097. success = mem <= tolerance or mem <= prev_mem
  1098. if success:
  1099. if idx > 1:
  1100. self._log(msg)
  1101. return None
  1102. else:
  1103. if idx == 1:
  1104. print() # noqa: T201
  1105. self._log(msg)
  1106. times += increase
  1107. prev_mem = mem
  1108. return pytest.fail(". ".join(messages))
  1109. # ---
  1110. def call(self, fun):
  1111. return fun()
  1112. def execute(
  1113. self, fun, times=None, warmup_times=None, retries=None, tolerance=None
  1114. ):
  1115. """Test a callable."""
  1116. times = times if times is not None else self.times
  1117. warmup_times = (
  1118. warmup_times if warmup_times is not None else self.warmup_times
  1119. )
  1120. retries = retries if retries is not None else self.retries
  1121. tolerance = tolerance if tolerance is not None else self.tolerance
  1122. try:
  1123. assert times >= 1, "times must be >= 1"
  1124. assert warmup_times >= 0, "warmup_times must be >= 0"
  1125. assert retries >= 0, "retries must be >= 0"
  1126. assert tolerance >= 0, "tolerance must be >= 0"
  1127. except AssertionError as err:
  1128. raise ValueError(str(err))
  1129. self._call_ntimes(fun, warmup_times) # warm up
  1130. self._check_fds(fun)
  1131. self._check_mem(fun, times=times, retries=retries, tolerance=tolerance)
  1132. def execute_w_exc(self, exc, fun, **kwargs):
  1133. """Convenience method to test a callable while making sure it
  1134. raises an exception on every call.
  1135. """
  1136. def call():
  1137. try:
  1138. fun()
  1139. except exc:
  1140. pass
  1141. else:
  1142. return pytest.fail(f"{fun} did not raise {exc}")
  1143. self.execute(call, **kwargs)
  1144. def is_win_secure_system_proc(pid):
  1145. # see: https://github.com/giampaolo/psutil/issues/2338
  1146. @memoize
  1147. def get_procs():
  1148. ret = {}
  1149. out = sh("tasklist.exe /NH /FO csv")
  1150. for line in out.splitlines()[1:]:
  1151. bits = [x.replace('"', "") for x in line.split(",")]
  1152. name, pid = bits[0], int(bits[1])
  1153. ret[pid] = name
  1154. return ret
  1155. try:
  1156. return get_procs()[pid] == "Secure System"
  1157. except KeyError:
  1158. return False
  1159. def _get_eligible_cpu():
  1160. p = psutil.Process()
  1161. if hasattr(p, "cpu_num"):
  1162. return p.cpu_num()
  1163. elif hasattr(p, "cpu_affinity"):
  1164. return random.choice(p.cpu_affinity())
  1165. return 0
  1166. class process_namespace:
  1167. """A container that lists all Process class method names + some
  1168. reasonable parameters to be called with. Utility methods (parent(),
  1169. children(), ...) are excluded.
  1170. >>> ns = process_namespace(psutil.Process())
  1171. >>> for fun, name in ns.iter(ns.getters):
  1172. ... fun()
  1173. """
  1174. utils = [('cpu_percent', (), {}), ('memory_percent', (), {})]
  1175. ignored = [
  1176. ('as_dict', (), {}),
  1177. ('children', (), {'recursive': True}),
  1178. ('connections', (), {}), # deprecated
  1179. ('is_running', (), {}),
  1180. ('oneshot', (), {}),
  1181. ('parent', (), {}),
  1182. ('parents', (), {}),
  1183. ('pid', (), {}),
  1184. ('wait', (0,), {}),
  1185. ]
  1186. getters = [
  1187. ('cmdline', (), {}),
  1188. ('cpu_times', (), {}),
  1189. ('create_time', (), {}),
  1190. ('cwd', (), {}),
  1191. ('exe', (), {}),
  1192. ('memory_full_info', (), {}),
  1193. ('memory_info', (), {}),
  1194. ('name', (), {}),
  1195. ('net_connections', (), {'kind': 'all'}),
  1196. ('nice', (), {}),
  1197. ('num_ctx_switches', (), {}),
  1198. ('num_threads', (), {}),
  1199. ('open_files', (), {}),
  1200. ('ppid', (), {}),
  1201. ('status', (), {}),
  1202. ('threads', (), {}),
  1203. ('username', (), {}),
  1204. ]
  1205. if POSIX:
  1206. getters += [('uids', (), {})]
  1207. getters += [('gids', (), {})]
  1208. getters += [('terminal', (), {})]
  1209. getters += [('num_fds', (), {})]
  1210. if HAS_PROC_IO_COUNTERS:
  1211. getters += [('io_counters', (), {})]
  1212. if HAS_IONICE:
  1213. getters += [('ionice', (), {})]
  1214. if HAS_RLIMIT:
  1215. getters += [('rlimit', (psutil.RLIMIT_NOFILE,), {})]
  1216. if HAS_CPU_AFFINITY:
  1217. getters += [('cpu_affinity', (), {})]
  1218. if HAS_PROC_CPU_NUM:
  1219. getters += [('cpu_num', (), {})]
  1220. if HAS_ENVIRON:
  1221. getters += [('environ', (), {})]
  1222. if WINDOWS:
  1223. getters += [('num_handles', (), {})]
  1224. if HAS_MEMORY_MAPS:
  1225. getters += [('memory_maps', (), {'grouped': False})]
  1226. setters = []
  1227. if POSIX:
  1228. setters += [('nice', (0,), {})]
  1229. else:
  1230. setters += [('nice', (psutil.NORMAL_PRIORITY_CLASS,), {})]
  1231. if HAS_RLIMIT:
  1232. setters += [('rlimit', (psutil.RLIMIT_NOFILE, (1024, 4096)), {})]
  1233. if HAS_IONICE:
  1234. if LINUX:
  1235. setters += [('ionice', (psutil.IOPRIO_CLASS_NONE, 0), {})]
  1236. else:
  1237. setters += [('ionice', (psutil.IOPRIO_NORMAL,), {})]
  1238. if HAS_CPU_AFFINITY:
  1239. setters += [('cpu_affinity', ([_get_eligible_cpu()],), {})]
  1240. killers = [
  1241. ('send_signal', (signal.SIGTERM,), {}),
  1242. ('suspend', (), {}),
  1243. ('resume', (), {}),
  1244. ('terminate', (), {}),
  1245. ('kill', (), {}),
  1246. ]
  1247. if WINDOWS:
  1248. killers += [('send_signal', (signal.CTRL_C_EVENT,), {})]
  1249. killers += [('send_signal', (signal.CTRL_BREAK_EVENT,), {})]
  1250. all = utils + getters + setters + killers
  1251. def __init__(self, proc):
  1252. self._proc = proc
  1253. def iter(self, ls, clear_cache=True):
  1254. """Given a list of tuples yields a set of (fun, fun_name) tuples
  1255. in random order.
  1256. """
  1257. ls = list(ls)
  1258. random.shuffle(ls)
  1259. for fun_name, args, kwds in ls:
  1260. if clear_cache:
  1261. self.clear_cache()
  1262. fun = getattr(self._proc, fun_name)
  1263. fun = functools.partial(fun, *args, **kwds)
  1264. yield (fun, fun_name)
  1265. def clear_cache(self):
  1266. """Clear the cache of a Process instance."""
  1267. self._proc._init(self._proc.pid, _ignore_nsp=True)
  1268. @classmethod
  1269. def test_class_coverage(cls, test_class, ls):
  1270. """Given a TestCase instance and a list of tuples checks that
  1271. the class defines the required test method names.
  1272. """
  1273. for fun_name, _, _ in ls:
  1274. meth_name = 'test_' + fun_name
  1275. if not hasattr(test_class, meth_name):
  1276. msg = (
  1277. f"{test_class.__class__.__name__!r} class should define a"
  1278. f" {meth_name!r} method"
  1279. )
  1280. raise AttributeError(msg)
  1281. @classmethod
  1282. def test(cls):
  1283. this = {x[0] for x in cls.all}
  1284. ignored = {x[0] for x in cls.ignored}
  1285. klass = {x for x in dir(psutil.Process) if x[0] != '_'}
  1286. leftout = (this | ignored) ^ klass
  1287. if leftout:
  1288. raise ValueError(f"uncovered Process class names: {leftout!r}")
  1289. class system_namespace:
  1290. """A container that lists all the module-level, system-related APIs.
  1291. Utilities such as cpu_percent() are excluded. Usage:
  1292. >>> ns = system_namespace
  1293. >>> for fun, name in ns.iter(ns.getters):
  1294. ... fun()
  1295. """
  1296. getters = [
  1297. ('boot_time', (), {}),
  1298. ('cpu_count', (), {'logical': False}),
  1299. ('cpu_count', (), {'logical': True}),
  1300. ('cpu_stats', (), {}),
  1301. ('cpu_times', (), {'percpu': False}),
  1302. ('cpu_times', (), {'percpu': True}),
  1303. ('disk_io_counters', (), {'perdisk': True}),
  1304. ('disk_partitions', (), {'all': True}),
  1305. ('disk_usage', (os.getcwd(),), {}),
  1306. ('net_connections', (), {'kind': 'all'}),
  1307. ('net_if_addrs', (), {}),
  1308. ('net_if_stats', (), {}),
  1309. ('net_io_counters', (), {'pernic': True}),
  1310. ('pid_exists', (os.getpid(),), {}),
  1311. ('pids', (), {}),
  1312. ('swap_memory', (), {}),
  1313. ('users', (), {}),
  1314. ('virtual_memory', (), {}),
  1315. ]
  1316. if HAS_CPU_FREQ:
  1317. if MACOS and AARCH64: # skipped due to #1892
  1318. pass
  1319. else:
  1320. getters += [('cpu_freq', (), {'percpu': True})]
  1321. if HAS_GETLOADAVG:
  1322. getters += [('getloadavg', (), {})]
  1323. if HAS_SENSORS_TEMPERATURES:
  1324. getters += [('sensors_temperatures', (), {})]
  1325. if HAS_SENSORS_FANS:
  1326. getters += [('sensors_fans', (), {})]
  1327. if HAS_SENSORS_BATTERY:
  1328. getters += [('sensors_battery', (), {})]
  1329. if WINDOWS:
  1330. getters += [('win_service_iter', (), {})]
  1331. getters += [('win_service_get', ('alg',), {})]
  1332. ignored = [
  1333. ('process_iter', (), {}),
  1334. ('wait_procs', ([psutil.Process()],), {}),
  1335. ('cpu_percent', (), {}),
  1336. ('cpu_times_percent', (), {}),
  1337. ]
  1338. all = getters
  1339. @staticmethod
  1340. def iter(ls):
  1341. """Given a list of tuples yields a set of (fun, fun_name) tuples
  1342. in random order.
  1343. """
  1344. ls = list(ls)
  1345. random.shuffle(ls)
  1346. for fun_name, args, kwds in ls:
  1347. fun = getattr(psutil, fun_name)
  1348. fun = functools.partial(fun, *args, **kwds)
  1349. yield (fun, fun_name)
  1350. test_class_coverage = process_namespace.test_class_coverage
  1351. def retry_on_failure(retries=NO_RETRIES):
  1352. """Decorator which runs a test function and retries N times before
  1353. actually failing.
  1354. """
  1355. def logfun(exc):
  1356. print(f"{exc!r}, retrying", file=sys.stderr) # noqa: T201
  1357. return retry(
  1358. exception=(AssertionError, pytest.fail.Exception),
  1359. timeout=None,
  1360. retries=retries,
  1361. logfun=logfun,
  1362. )
  1363. def skip_on_access_denied(only_if=None):
  1364. """Decorator to Ignore AccessDenied exceptions."""
  1365. def decorator(fun):
  1366. @functools.wraps(fun)
  1367. def wrapper(*args, **kwargs):
  1368. try:
  1369. return fun(*args, **kwargs)
  1370. except psutil.AccessDenied:
  1371. if only_if is not None:
  1372. if not only_if:
  1373. raise
  1374. return pytest.skip("raises AccessDenied")
  1375. return wrapper
  1376. return decorator
  1377. def skip_on_not_implemented(only_if=None):
  1378. """Decorator to Ignore NotImplementedError exceptions."""
  1379. def decorator(fun):
  1380. @functools.wraps(fun)
  1381. def wrapper(*args, **kwargs):
  1382. try:
  1383. return fun(*args, **kwargs)
  1384. except NotImplementedError:
  1385. if only_if is not None:
  1386. if not only_if:
  1387. raise
  1388. msg = (
  1389. f"{fun.__name__!r} was skipped because it raised"
  1390. " NotImplementedError"
  1391. )
  1392. return pytest.skip(msg)
  1393. return wrapper
  1394. return decorator
  1395. # ===================================================================
  1396. # --- network
  1397. # ===================================================================
  1398. # XXX: no longer used
  1399. def get_free_port(host='127.0.0.1'):
  1400. """Return an unused TCP port. Subject to race conditions."""
  1401. with socket.socket() as sock:
  1402. sock.bind((host, 0))
  1403. return sock.getsockname()[1]
  1404. def bind_socket(family=AF_INET, type=SOCK_STREAM, addr=None):
  1405. """Binds a generic socket."""
  1406. if addr is None and family in {AF_INET, AF_INET6}:
  1407. addr = ("", 0)
  1408. sock = socket.socket(family, type)
  1409. try:
  1410. if os.name not in {'nt', 'cygwin'}:
  1411. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1412. sock.bind(addr)
  1413. if type == socket.SOCK_STREAM:
  1414. sock.listen(5)
  1415. return sock
  1416. except Exception:
  1417. sock.close()
  1418. raise
  1419. def bind_unix_socket(name, type=socket.SOCK_STREAM):
  1420. """Bind a UNIX socket."""
  1421. assert psutil.POSIX
  1422. assert not os.path.exists(name), name
  1423. sock = socket.socket(socket.AF_UNIX, type)
  1424. try:
  1425. sock.bind(name)
  1426. if type == socket.SOCK_STREAM:
  1427. sock.listen(5)
  1428. except Exception:
  1429. sock.close()
  1430. raise
  1431. return sock
  1432. def tcp_socketpair(family, addr=("", 0)):
  1433. """Build a pair of TCP sockets connected to each other.
  1434. Return a (server, client) tuple.
  1435. """
  1436. with socket.socket(family, SOCK_STREAM) as ll:
  1437. ll.bind(addr)
  1438. ll.listen(5)
  1439. addr = ll.getsockname()
  1440. c = socket.socket(family, SOCK_STREAM)
  1441. try:
  1442. c.connect(addr)
  1443. caddr = c.getsockname()
  1444. while True:
  1445. a, addr = ll.accept()
  1446. # check that we've got the correct client
  1447. if addr == caddr:
  1448. return (a, c)
  1449. a.close()
  1450. except OSError:
  1451. c.close()
  1452. raise
  1453. def unix_socketpair(name):
  1454. """Build a pair of UNIX sockets connected to each other through
  1455. the same UNIX file name.
  1456. Return a (server, client) tuple.
  1457. """
  1458. assert psutil.POSIX
  1459. server = client = None
  1460. try:
  1461. server = bind_unix_socket(name, type=socket.SOCK_STREAM)
  1462. server.setblocking(0)
  1463. client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  1464. client.setblocking(0)
  1465. client.connect(name)
  1466. # new = server.accept()
  1467. except Exception:
  1468. if server is not None:
  1469. server.close()
  1470. if client is not None:
  1471. client.close()
  1472. raise
  1473. return (server, client)
  1474. @contextlib.contextmanager
  1475. def create_sockets():
  1476. """Open as many socket families / types as possible."""
  1477. socks = []
  1478. fname1 = fname2 = None
  1479. try:
  1480. socks.extend((
  1481. bind_socket(socket.AF_INET, socket.SOCK_STREAM),
  1482. bind_socket(socket.AF_INET, socket.SOCK_DGRAM),
  1483. ))
  1484. if supports_ipv6():
  1485. socks.extend((
  1486. bind_socket(socket.AF_INET6, socket.SOCK_STREAM),
  1487. bind_socket(socket.AF_INET6, socket.SOCK_DGRAM),
  1488. ))
  1489. if POSIX and HAS_NET_CONNECTIONS_UNIX:
  1490. fname1 = get_testfn()
  1491. fname2 = get_testfn()
  1492. s1, s2 = unix_socketpair(fname1)
  1493. s3 = bind_unix_socket(fname2, type=socket.SOCK_DGRAM)
  1494. for s in (s1, s2, s3):
  1495. socks.append(s)
  1496. yield socks
  1497. finally:
  1498. for s in socks:
  1499. s.close()
  1500. for fname in (fname1, fname2):
  1501. if fname is not None:
  1502. safe_rmpath(fname)
  1503. def check_net_address(addr, family):
  1504. """Check a net address validity. Supported families are IPv4,
  1505. IPv6 and MAC addresses.
  1506. """
  1507. assert isinstance(family, enum.IntEnum), family
  1508. if family == socket.AF_INET:
  1509. octs = [int(x) for x in addr.split('.')]
  1510. assert len(octs) == 4, addr
  1511. for num in octs:
  1512. assert 0 <= num <= 255, addr
  1513. ipaddress.IPv4Address(addr)
  1514. elif family == socket.AF_INET6:
  1515. assert isinstance(addr, str), addr
  1516. ipaddress.IPv6Address(addr)
  1517. elif family == psutil.AF_LINK:
  1518. assert re.match(r'([a-fA-F0-9]{2}[:|\-]?){6}', addr) is not None, addr
  1519. else:
  1520. raise ValueError(f"unknown family {family!r}")
  1521. def check_connection_ntuple(conn):
  1522. """Check validity of a connection namedtuple."""
  1523. def check_ntuple(conn):
  1524. has_pid = len(conn) == 7
  1525. assert len(conn) in {6, 7}, len(conn)
  1526. assert conn[0] == conn.fd, conn.fd
  1527. assert conn[1] == conn.family, conn.family
  1528. assert conn[2] == conn.type, conn.type
  1529. assert conn[3] == conn.laddr, conn.laddr
  1530. assert conn[4] == conn.raddr, conn.raddr
  1531. assert conn[5] == conn.status, conn.status
  1532. if has_pid:
  1533. assert conn[6] == conn.pid, conn.pid
  1534. def check_family(conn):
  1535. assert conn.family in {AF_INET, AF_INET6, AF_UNIX}, conn.family
  1536. assert isinstance(conn.family, enum.IntEnum), conn
  1537. if conn.family == AF_INET:
  1538. # actually try to bind the local socket; ignore IPv6
  1539. # sockets as their address might be represented as
  1540. # an IPv4-mapped-address (e.g. "::127.0.0.1")
  1541. # and that's rejected by bind()
  1542. with socket.socket(conn.family, conn.type) as s:
  1543. try:
  1544. s.bind((conn.laddr[0], 0))
  1545. except OSError as err:
  1546. if err.errno != errno.EADDRNOTAVAIL:
  1547. raise
  1548. elif conn.family == AF_UNIX:
  1549. assert conn.status == psutil.CONN_NONE, conn.status
  1550. def check_type(conn):
  1551. # SOCK_SEQPACKET may happen in case of AF_UNIX socks
  1552. SOCK_SEQPACKET = getattr(socket, "SOCK_SEQPACKET", object())
  1553. assert conn.type in {
  1554. socket.SOCK_STREAM,
  1555. socket.SOCK_DGRAM,
  1556. SOCK_SEQPACKET,
  1557. }, conn.type
  1558. assert isinstance(conn.type, enum.IntEnum), conn
  1559. if conn.type == socket.SOCK_DGRAM:
  1560. assert conn.status == psutil.CONN_NONE, conn.status
  1561. def check_addrs(conn):
  1562. # check IP address and port sanity
  1563. for addr in (conn.laddr, conn.raddr):
  1564. if conn.family in {AF_INET, AF_INET6}:
  1565. assert isinstance(addr, tuple), type(addr)
  1566. if not addr:
  1567. continue
  1568. assert isinstance(addr.port, int), type(addr.port)
  1569. assert 0 <= addr.port <= 65535, addr.port
  1570. check_net_address(addr.ip, conn.family)
  1571. elif conn.family == AF_UNIX:
  1572. assert isinstance(addr, str), type(addr)
  1573. def check_status(conn):
  1574. assert isinstance(conn.status, str), conn.status
  1575. valids = [
  1576. getattr(psutil, x) for x in dir(psutil) if x.startswith('CONN_')
  1577. ]
  1578. assert conn.status in valids, conn.status
  1579. if conn.family in {AF_INET, AF_INET6} and conn.type == SOCK_STREAM:
  1580. assert conn.status != psutil.CONN_NONE, conn.status
  1581. else:
  1582. assert conn.status == psutil.CONN_NONE, conn.status
  1583. check_ntuple(conn)
  1584. check_family(conn)
  1585. check_type(conn)
  1586. check_addrs(conn)
  1587. check_status(conn)
  1588. def filter_proc_net_connections(cons):
  1589. """Our process may start with some open UNIX sockets which are not
  1590. initialized by us, invalidating unit tests.
  1591. """
  1592. new = []
  1593. for conn in cons:
  1594. if POSIX and conn.family == socket.AF_UNIX:
  1595. if MACOS and "/syslog" in conn.raddr:
  1596. debug(f"skipping {conn}")
  1597. continue
  1598. new.append(conn)
  1599. return new
  1600. # ===================================================================
  1601. # --- import utils
  1602. # ===================================================================
  1603. def reload_module(module):
  1604. return importlib.reload(module)
  1605. def import_module_by_path(path):
  1606. name = os.path.splitext(os.path.basename(path))[0]
  1607. spec = importlib.util.spec_from_file_location(name, path)
  1608. mod = importlib.util.module_from_spec(spec)
  1609. spec.loader.exec_module(mod)
  1610. return mod
  1611. # ===================================================================
  1612. # --- others
  1613. # ===================================================================
  1614. def warn(msg):
  1615. """Raise a warning msg."""
  1616. warnings.warn(msg, UserWarning, stacklevel=2)
  1617. def is_namedtuple(x):
  1618. """Check if object is an instance of namedtuple."""
  1619. t = type(x)
  1620. b = t.__bases__
  1621. if len(b) != 1 or b[0] is not tuple:
  1622. return False
  1623. f = getattr(t, '_fields', None)
  1624. if not isinstance(f, tuple):
  1625. return False
  1626. return all(isinstance(n, str) for n in f)
  1627. if POSIX:
  1628. @contextlib.contextmanager
  1629. def copyload_shared_lib(suffix=""):
  1630. """Ctx manager which picks up a random shared CO lib used
  1631. by this process, copies it in another location and loads it
  1632. in memory via ctypes. Return the new absolutized path.
  1633. """
  1634. exe = 'pypy' if PYPY else 'python'
  1635. ext = ".so"
  1636. dst = get_testfn(suffix=suffix + ext)
  1637. libs = [
  1638. x.path
  1639. for x in psutil.Process().memory_maps()
  1640. if os.path.splitext(x.path)[1] == ext and exe in x.path.lower()
  1641. ]
  1642. src = random.choice(libs)
  1643. shutil.copyfile(src, dst)
  1644. try:
  1645. ctypes.CDLL(dst)
  1646. yield dst
  1647. finally:
  1648. safe_rmpath(dst)
  1649. else:
  1650. @contextlib.contextmanager
  1651. def copyload_shared_lib(suffix=""):
  1652. """Ctx manager which picks up a random shared DLL lib used
  1653. by this process, copies it in another location and loads it
  1654. in memory via ctypes.
  1655. Return the new absolutized, normcased path.
  1656. """
  1657. from ctypes import WinError
  1658. from ctypes import wintypes
  1659. ext = ".dll"
  1660. dst = get_testfn(suffix=suffix + ext)
  1661. libs = [
  1662. x.path
  1663. for x in psutil.Process().memory_maps()
  1664. if x.path.lower().endswith(ext)
  1665. and 'python' in os.path.basename(x.path).lower()
  1666. and 'wow64' not in x.path.lower()
  1667. ]
  1668. if PYPY and not libs:
  1669. libs = [
  1670. x.path
  1671. for x in psutil.Process().memory_maps()
  1672. if 'pypy' in os.path.basename(x.path).lower()
  1673. ]
  1674. src = random.choice(libs)
  1675. shutil.copyfile(src, dst)
  1676. cfile = None
  1677. try:
  1678. cfile = ctypes.WinDLL(dst)
  1679. yield dst
  1680. finally:
  1681. # Work around OverflowError:
  1682. # - https://ci.appveyor.com/project/giampaolo/psutil/build/1207/
  1683. # job/o53330pbnri9bcw7
  1684. # - http://bugs.python.org/issue30286
  1685. # - http://stackoverflow.com/questions/23522055
  1686. if cfile is not None:
  1687. FreeLibrary = ctypes.windll.kernel32.FreeLibrary
  1688. FreeLibrary.argtypes = [wintypes.HMODULE]
  1689. ret = FreeLibrary(cfile._handle)
  1690. if ret == 0:
  1691. raise WinError()
  1692. safe_rmpath(dst)
  1693. # ===================================================================
  1694. # --- Exit funs (first is executed last)
  1695. # ===================================================================
  1696. # this is executed first
  1697. @atexit.register
  1698. def cleanup_test_procs():
  1699. reap_children(recursive=True)
  1700. # atexit module does not execute exit functions in case of SIGTERM, which
  1701. # gets sent to test subprocesses, which is a problem if they import this
  1702. # module. With this it will. See:
  1703. # https://gmpy.dev/blog/2016/how-to-always-execute-exit-functions-in-python
  1704. if POSIX:
  1705. signal.signal(signal.SIGTERM, lambda sig, _: sys.exit(sig))