__init__.py 62 KB

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