test_process_all.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. #!/usr/bin/env python3
  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. """Iterate over all process PIDs and for each one of them invoke and
  6. test all psutil.Process() methods.
  7. """
  8. import enum
  9. import errno
  10. import multiprocessing
  11. import os
  12. import stat
  13. import time
  14. import traceback
  15. import psutil
  16. from psutil import AIX
  17. from psutil import BSD
  18. from psutil import FREEBSD
  19. from psutil import LINUX
  20. from psutil import MACOS
  21. from psutil import NETBSD
  22. from psutil import OPENBSD
  23. from psutil import OSX
  24. from psutil import POSIX
  25. from psutil import WINDOWS
  26. from psutil.tests import CI_TESTING
  27. from psutil.tests import PYTEST_PARALLEL
  28. from psutil.tests import VALID_PROC_STATUSES
  29. from psutil.tests import PsutilTestCase
  30. from psutil.tests import check_connection_ntuple
  31. from psutil.tests import create_sockets
  32. from psutil.tests import is_namedtuple
  33. from psutil.tests import is_win_secure_system_proc
  34. from psutil.tests import process_namespace
  35. from psutil.tests import pytest
  36. # Cuts the time in half, but (e.g.) on macOS the process pool stays
  37. # alive after join() (multiprocessing bug?), messing up other tests.
  38. USE_PROC_POOL = LINUX and not CI_TESTING and not PYTEST_PARALLEL
  39. def proc_info(pid):
  40. tcase = PsutilTestCase()
  41. def check_exception(exc, proc, name, ppid):
  42. assert exc.pid == pid
  43. if exc.name is not None:
  44. assert exc.name == name
  45. if isinstance(exc, psutil.ZombieProcess):
  46. tcase.assert_proc_zombie(proc)
  47. if exc.ppid is not None:
  48. assert exc.ppid >= 0
  49. assert exc.ppid == ppid
  50. elif isinstance(exc, psutil.NoSuchProcess):
  51. tcase.assert_proc_gone(proc)
  52. str(exc)
  53. repr(exc)
  54. def do_wait():
  55. if pid != 0:
  56. try:
  57. proc.wait(0)
  58. except psutil.Error as exc:
  59. check_exception(exc, proc, name, ppid)
  60. try:
  61. proc = psutil.Process(pid)
  62. except psutil.NoSuchProcess:
  63. tcase.assert_pid_gone(pid)
  64. return {}
  65. try:
  66. d = proc.as_dict(['ppid', 'name'])
  67. except psutil.NoSuchProcess:
  68. tcase.assert_proc_gone(proc)
  69. else:
  70. name, ppid = d['name'], d['ppid']
  71. info = {'pid': proc.pid}
  72. ns = process_namespace(proc)
  73. # We don't use oneshot() because in order not to fool
  74. # check_exception() in case of NSP.
  75. for fun, fun_name in ns.iter(ns.getters, clear_cache=False):
  76. try:
  77. info[fun_name] = fun()
  78. except psutil.Error as exc:
  79. check_exception(exc, proc, name, ppid)
  80. continue
  81. do_wait()
  82. return info
  83. class TestFetchAllProcesses(PsutilTestCase):
  84. """Test which iterates over all running processes and performs
  85. some sanity checks against Process API's returned values.
  86. Uses a process pool to get info about all processes.
  87. """
  88. def setUp(self):
  89. psutil._set_debug(False)
  90. # Using a pool in a CI env may result in deadlock, see:
  91. # https://github.com/giampaolo/psutil/issues/2104
  92. if USE_PROC_POOL:
  93. # The 'fork' method is the only one that does not
  94. # create a "resource_tracker" process. The problem
  95. # when creating this process is that it ignores
  96. # SIGTERM and SIGINT, and this makes "reap_children"
  97. # hang... The following code should run on python-3.4
  98. # and later.
  99. multiprocessing.set_start_method('fork')
  100. self.pool = multiprocessing.Pool()
  101. def tearDown(self):
  102. psutil._set_debug(True)
  103. if USE_PROC_POOL:
  104. self.pool.terminate()
  105. self.pool.join()
  106. def iter_proc_info(self):
  107. # Fixes "can't pickle <function proc_info>: it's not the
  108. # same object as test_process_all.proc_info".
  109. from psutil.tests.test_process_all import proc_info
  110. if USE_PROC_POOL:
  111. return self.pool.imap_unordered(proc_info, psutil.pids())
  112. else:
  113. ls = [proc_info(pid) for pid in psutil.pids()]
  114. return ls
  115. def test_all(self):
  116. failures = []
  117. for info in self.iter_proc_info():
  118. for name, value in info.items():
  119. meth = getattr(self, name)
  120. try:
  121. meth(value, info)
  122. except Exception: # noqa: BLE001
  123. s = '\n' + '=' * 70 + '\n'
  124. s += (
  125. "FAIL: name=test_{}, pid={}, ret={}\ninfo={}\n".format(
  126. name,
  127. info['pid'],
  128. repr(value),
  129. info,
  130. )
  131. )
  132. s += '-' * 70
  133. s += f"\n{traceback.format_exc()}"
  134. s = "\n".join((" " * 4) + i for i in s.splitlines()) + "\n"
  135. failures.append(s)
  136. else:
  137. if value not in (0, 0.0, [], None, '', {}):
  138. assert value, value
  139. if failures:
  140. return pytest.fail(''.join(failures))
  141. def cmdline(self, ret, info):
  142. assert isinstance(ret, list)
  143. for part in ret:
  144. assert isinstance(part, str)
  145. def exe(self, ret, info):
  146. assert isinstance(ret, str)
  147. assert ret.strip() == ret
  148. if ret:
  149. if WINDOWS and not ret.endswith('.exe'):
  150. return # May be "Registry", "MemCompression", ...
  151. assert os.path.isabs(ret), ret
  152. # Note: os.stat() may return False even if the file is there
  153. # hence we skip the test, see:
  154. # http://stackoverflow.com/questions/3112546/os-path-exists-lies
  155. if POSIX and os.path.isfile(ret):
  156. if hasattr(os, 'access') and hasattr(os, "X_OK"):
  157. # XXX: may fail on MACOS
  158. try:
  159. assert os.access(ret, os.X_OK)
  160. except AssertionError:
  161. if os.path.exists(ret) and not CI_TESTING:
  162. raise
  163. def pid(self, ret, info):
  164. assert isinstance(ret, int)
  165. assert ret >= 0
  166. def ppid(self, ret, info):
  167. assert isinstance(ret, int)
  168. assert ret >= 0
  169. proc_info(ret)
  170. def name(self, ret, info):
  171. assert isinstance(ret, str)
  172. if WINDOWS and not ret and is_win_secure_system_proc(info['pid']):
  173. # https://github.com/giampaolo/psutil/issues/2338
  174. return
  175. # on AIX, "<exiting>" processes don't have names
  176. if not AIX:
  177. assert ret, repr(ret)
  178. def create_time(self, ret, info):
  179. assert isinstance(ret, float)
  180. try:
  181. assert ret >= 0
  182. except AssertionError:
  183. # XXX
  184. if OPENBSD and info['status'] == psutil.STATUS_ZOMBIE:
  185. pass
  186. else:
  187. raise
  188. # this can't be taken for granted on all platforms
  189. # assert ret >= psutil.boot_time())
  190. # make sure returned value can be pretty printed
  191. # with strftime
  192. time.strftime("%Y %m %d %H:%M:%S", time.localtime(ret))
  193. def uids(self, ret, info):
  194. assert is_namedtuple(ret)
  195. for uid in ret:
  196. assert isinstance(uid, int)
  197. assert uid >= 0
  198. def gids(self, ret, info):
  199. assert is_namedtuple(ret)
  200. # note: testing all gids as above seems not to be reliable for
  201. # gid == 30 (nodoby); not sure why.
  202. for gid in ret:
  203. assert isinstance(gid, int)
  204. if not MACOS and not NETBSD:
  205. assert gid >= 0
  206. def username(self, ret, info):
  207. assert isinstance(ret, str)
  208. assert ret.strip() == ret
  209. assert ret.strip()
  210. def status(self, ret, info):
  211. assert isinstance(ret, str)
  212. assert ret, ret
  213. assert ret != '?' # XXX
  214. assert ret in VALID_PROC_STATUSES
  215. def io_counters(self, ret, info):
  216. assert is_namedtuple(ret)
  217. for field in ret:
  218. assert isinstance(field, int)
  219. if field != -1:
  220. assert field >= 0
  221. def ionice(self, ret, info):
  222. if LINUX:
  223. assert isinstance(ret.ioclass, int)
  224. assert isinstance(ret.value, int)
  225. assert ret.ioclass >= 0
  226. assert ret.value >= 0
  227. else: # Windows, Cygwin
  228. choices = [
  229. psutil.IOPRIO_VERYLOW,
  230. psutil.IOPRIO_LOW,
  231. psutil.IOPRIO_NORMAL,
  232. psutil.IOPRIO_HIGH,
  233. ]
  234. assert isinstance(ret, int)
  235. assert ret >= 0
  236. assert ret in choices
  237. def num_threads(self, ret, info):
  238. assert isinstance(ret, int)
  239. if WINDOWS and ret == 0 and is_win_secure_system_proc(info['pid']):
  240. # https://github.com/giampaolo/psutil/issues/2338
  241. return
  242. assert ret >= 1
  243. def threads(self, ret, info):
  244. assert isinstance(ret, list)
  245. for t in ret:
  246. assert is_namedtuple(t)
  247. assert t.id >= 0
  248. assert t.user_time >= 0
  249. assert t.system_time >= 0
  250. for field in t:
  251. assert isinstance(field, (int, float))
  252. def cpu_times(self, ret, info):
  253. assert is_namedtuple(ret)
  254. for n in ret:
  255. assert isinstance(n, float)
  256. assert n >= 0
  257. # TODO: check ntuple fields
  258. def cpu_percent(self, ret, info):
  259. assert isinstance(ret, float)
  260. assert 0.0 <= ret <= 100.0, ret
  261. def cpu_num(self, ret, info):
  262. assert isinstance(ret, int)
  263. if FREEBSD and ret == -1:
  264. return
  265. assert ret >= 0
  266. if psutil.cpu_count() == 1:
  267. assert ret == 0
  268. assert ret in list(range(psutil.cpu_count()))
  269. def memory_info(self, ret, info):
  270. assert is_namedtuple(ret)
  271. for value in ret:
  272. assert isinstance(value, int)
  273. assert value >= 0
  274. if WINDOWS:
  275. assert ret.peak_wset >= ret.wset
  276. assert ret.peak_paged_pool >= ret.paged_pool
  277. assert ret.peak_nonpaged_pool >= ret.nonpaged_pool
  278. assert ret.peak_pagefile >= ret.pagefile
  279. def memory_full_info(self, ret, info):
  280. assert is_namedtuple(ret)
  281. total = psutil.virtual_memory().total
  282. for name in ret._fields:
  283. value = getattr(ret, name)
  284. assert isinstance(value, int)
  285. assert value >= 0
  286. if LINUX or (OSX and name in {'vms', 'data'}):
  287. # On Linux there are processes (e.g. 'goa-daemon') whose
  288. # VMS is incredibly high for some reason.
  289. continue
  290. assert value <= total, name
  291. if LINUX:
  292. assert ret.pss >= ret.uss
  293. def open_files(self, ret, info):
  294. assert isinstance(ret, list)
  295. for f in ret:
  296. assert isinstance(f.fd, int)
  297. assert isinstance(f.path, str)
  298. assert f.path.strip() == f.path
  299. if WINDOWS:
  300. assert f.fd == -1
  301. elif LINUX:
  302. assert isinstance(f.position, int)
  303. assert isinstance(f.mode, str)
  304. assert isinstance(f.flags, int)
  305. assert f.position >= 0
  306. assert f.mode in {'r', 'w', 'a', 'r+', 'a+'}
  307. assert f.flags > 0
  308. elif BSD and not f.path:
  309. # XXX see: https://github.com/giampaolo/psutil/issues/595
  310. continue
  311. assert os.path.isabs(f.path), f
  312. try:
  313. st = os.stat(f.path)
  314. except FileNotFoundError:
  315. pass
  316. else:
  317. assert stat.S_ISREG(st.st_mode), f
  318. def num_fds(self, ret, info):
  319. assert isinstance(ret, int)
  320. assert ret >= 0
  321. def net_connections(self, ret, info):
  322. with create_sockets():
  323. assert len(ret) == len(set(ret))
  324. for conn in ret:
  325. assert is_namedtuple(conn)
  326. check_connection_ntuple(conn)
  327. def cwd(self, ret, info):
  328. assert isinstance(ret, str)
  329. assert ret.strip() == ret
  330. if ret:
  331. assert os.path.isabs(ret), ret
  332. try:
  333. st = os.stat(ret)
  334. except OSError as err:
  335. if WINDOWS and psutil._psplatform.is_permission_err(err):
  336. pass
  337. # directory has been removed in mean time
  338. elif err.errno != errno.ENOENT:
  339. raise
  340. else:
  341. assert stat.S_ISDIR(st.st_mode)
  342. def memory_percent(self, ret, info):
  343. assert isinstance(ret, float)
  344. assert 0 <= ret <= 100, ret
  345. def is_running(self, ret, info):
  346. assert isinstance(ret, bool)
  347. def cpu_affinity(self, ret, info):
  348. assert isinstance(ret, list)
  349. assert ret != []
  350. cpus = list(range(psutil.cpu_count()))
  351. for n in ret:
  352. assert isinstance(n, int)
  353. assert n in cpus
  354. def terminal(self, ret, info):
  355. assert isinstance(ret, (str, type(None)))
  356. if ret is not None:
  357. assert os.path.isabs(ret), ret
  358. assert os.path.exists(ret), ret
  359. def memory_maps(self, ret, info):
  360. for nt in ret:
  361. assert isinstance(nt.addr, str)
  362. assert isinstance(nt.perms, str)
  363. assert isinstance(nt.path, str)
  364. for fname in nt._fields:
  365. value = getattr(nt, fname)
  366. if fname == 'path':
  367. if value.startswith(("[", "anon_inode:")): # linux
  368. continue
  369. if BSD and value == "pvclock": # seen on FreeBSD
  370. continue
  371. assert os.path.isabs(nt.path), nt.path
  372. # commented as on Linux we might get
  373. # '/foo/bar (deleted)'
  374. # assert os.path.exists(nt.path), nt.path
  375. elif fname == 'addr':
  376. assert value, repr(value)
  377. elif fname == 'perms':
  378. if not WINDOWS:
  379. assert value, repr(value)
  380. else:
  381. assert isinstance(value, int)
  382. assert value >= 0
  383. def num_handles(self, ret, info):
  384. assert isinstance(ret, int)
  385. assert ret >= 0
  386. def nice(self, ret, info):
  387. assert isinstance(ret, int)
  388. if POSIX:
  389. assert -20 <= ret <= 20, ret
  390. else:
  391. priorities = [
  392. getattr(psutil, x)
  393. for x in dir(psutil)
  394. if x.endswith('_PRIORITY_CLASS')
  395. ]
  396. assert ret in priorities
  397. assert isinstance(ret, enum.IntEnum)
  398. def num_ctx_switches(self, ret, info):
  399. assert is_namedtuple(ret)
  400. for value in ret:
  401. assert isinstance(value, int)
  402. assert value >= 0
  403. def rlimit(self, ret, info):
  404. assert isinstance(ret, tuple)
  405. assert len(ret) == 2
  406. assert ret[0] >= -1
  407. assert ret[1] >= -1
  408. def environ(self, ret, info):
  409. assert isinstance(ret, dict)
  410. for k, v in ret.items():
  411. assert isinstance(k, str)
  412. assert isinstance(v, str)
  413. class TestPidsRange(PsutilTestCase):
  414. """Given pid_exists() return value for a range of PIDs which may or
  415. may not exist, make sure that psutil.Process() and psutil.pids()
  416. agree with pid_exists(). This guarantees that the 3 APIs are all
  417. consistent with each other. See:
  418. https://github.com/giampaolo/psutil/issues/2359
  419. XXX - Note about Windows: it turns out there are some "hidden" PIDs
  420. which are not returned by psutil.pids() and are also not revealed
  421. by taskmgr.exe and ProcessHacker, still they can be instantiated by
  422. psutil.Process() and queried. One of such PIDs is "conhost.exe".
  423. Running as_dict() for it reveals that some Process() APIs
  424. erroneously raise NoSuchProcess, so we know we have problem there.
  425. Let's ignore this for now, since it's quite a corner case (who even
  426. imagined hidden PIDs existed on Windows?).
  427. """
  428. def setUp(self):
  429. psutil._set_debug(False)
  430. def tearDown(self):
  431. psutil._set_debug(True)
  432. def test_it(self):
  433. def is_linux_tid(pid):
  434. try:
  435. f = open(f"/proc/{pid}/status", "rb") # noqa: SIM115
  436. except FileNotFoundError:
  437. return False
  438. else:
  439. with f:
  440. for line in f:
  441. if line.startswith(b"Tgid:"):
  442. tgid = int(line.split()[1])
  443. # If tgid and pid are different then we're
  444. # dealing with a process TID.
  445. return tgid != pid
  446. raise ValueError("'Tgid' line not found")
  447. def check(pid):
  448. # In case of failure retry up to 3 times in order to avoid
  449. # race conditions, especially when running in a CI
  450. # environment where PIDs may appear and disappear at any
  451. # time.
  452. x = 3
  453. while True:
  454. exists = psutil.pid_exists(pid)
  455. try:
  456. if exists:
  457. psutil.Process(pid)
  458. if not WINDOWS: # see docstring
  459. assert pid in psutil.pids()
  460. else:
  461. # On OpenBSD thread IDs can be instantiated,
  462. # and oneshot() succeeds, but other APIs fail
  463. # with EINVAL.
  464. if not OPENBSD:
  465. with pytest.raises(psutil.NoSuchProcess):
  466. psutil.Process(pid)
  467. if not WINDOWS: # see docstring
  468. assert pid not in psutil.pids()
  469. except (psutil.Error, AssertionError):
  470. x -= 1
  471. if x == 0:
  472. raise
  473. else:
  474. return
  475. for pid in range(1, 3000):
  476. if LINUX and is_linux_tid(pid):
  477. # On Linux a TID (thread ID) can be passed to the
  478. # Process class and is querable like a PID (process
  479. # ID). Skip it.
  480. continue
  481. check(pid)