__init__.py 63 KB

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