test_system.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  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. """Tests for system APIS."""
  6. import datetime
  7. import enum
  8. import errno
  9. import os
  10. import pprint
  11. import shutil
  12. import signal
  13. import socket
  14. import sys
  15. import time
  16. from unittest import mock
  17. import psutil
  18. from psutil import AIX
  19. from psutil import BSD
  20. from psutil import FREEBSD
  21. from psutil import LINUX
  22. from psutil import MACOS
  23. from psutil import NETBSD
  24. from psutil import OPENBSD
  25. from psutil import POSIX
  26. from psutil import SUNOS
  27. from psutil import WINDOWS
  28. from psutil._common import broadcast_addr
  29. from psutil.tests import AARCH64
  30. from psutil.tests import ASCII_FS
  31. from psutil.tests import CI_TESTING
  32. from psutil.tests import GITHUB_ACTIONS
  33. from psutil.tests import GLOBAL_TIMEOUT
  34. from psutil.tests import HAS_BATTERY
  35. from psutil.tests import HAS_CPU_FREQ
  36. from psutil.tests import HAS_GETLOADAVG
  37. from psutil.tests import HAS_NET_IO_COUNTERS
  38. from psutil.tests import HAS_SENSORS_BATTERY
  39. from psutil.tests import HAS_SENSORS_FANS
  40. from psutil.tests import HAS_SENSORS_TEMPERATURES
  41. from psutil.tests import MACOS_12PLUS
  42. from psutil.tests import PYPY
  43. from psutil.tests import UNICODE_SUFFIX
  44. from psutil.tests import PsutilTestCase
  45. from psutil.tests import check_net_address
  46. from psutil.tests import pytest
  47. from psutil.tests import retry_on_failure
  48. # ===================================================================
  49. # --- System-related API tests
  50. # ===================================================================
  51. class TestProcessIter(PsutilTestCase):
  52. def test_pid_presence(self):
  53. assert os.getpid() in [x.pid for x in psutil.process_iter()]
  54. sproc = self.spawn_subproc()
  55. assert sproc.pid in [x.pid for x in psutil.process_iter()]
  56. p = psutil.Process(sproc.pid)
  57. p.kill()
  58. p.wait()
  59. assert sproc.pid not in [x.pid for x in psutil.process_iter()]
  60. def test_no_duplicates(self):
  61. ls = list(psutil.process_iter())
  62. assert sorted(ls, key=lambda x: x.pid) == sorted(
  63. set(ls), key=lambda x: x.pid
  64. )
  65. def test_emulate_nsp(self):
  66. list(psutil.process_iter()) # populate cache
  67. for x in range(2):
  68. with mock.patch(
  69. 'psutil.Process.as_dict',
  70. side_effect=psutil.NoSuchProcess(os.getpid()),
  71. ):
  72. assert not list(psutil.process_iter(attrs=["cpu_times"]))
  73. psutil.process_iter.cache_clear() # repeat test without cache
  74. def test_emulate_access_denied(self):
  75. list(psutil.process_iter()) # populate cache
  76. for x in range(2):
  77. with mock.patch(
  78. 'psutil.Process.as_dict',
  79. side_effect=psutil.AccessDenied(os.getpid()),
  80. ):
  81. with pytest.raises(psutil.AccessDenied):
  82. list(psutil.process_iter(attrs=["cpu_times"]))
  83. psutil.process_iter.cache_clear() # repeat test without cache
  84. def test_attrs(self):
  85. for p in psutil.process_iter(attrs=['pid']):
  86. assert list(p.info.keys()) == ['pid']
  87. # yield again
  88. for p in psutil.process_iter(attrs=['pid']):
  89. assert list(p.info.keys()) == ['pid']
  90. with pytest.raises(ValueError):
  91. list(psutil.process_iter(attrs=['foo']))
  92. with mock.patch(
  93. "psutil._psplatform.Process.cpu_times",
  94. side_effect=psutil.AccessDenied(0, ""),
  95. ) as m:
  96. for p in psutil.process_iter(attrs=["pid", "cpu_times"]):
  97. assert p.info['cpu_times'] is None
  98. assert p.info['pid'] >= 0
  99. assert m.called
  100. with mock.patch(
  101. "psutil._psplatform.Process.cpu_times",
  102. side_effect=psutil.AccessDenied(0, ""),
  103. ) as m:
  104. flag = object()
  105. for p in psutil.process_iter(
  106. attrs=["pid", "cpu_times"], ad_value=flag
  107. ):
  108. assert p.info['cpu_times'] is flag
  109. assert p.info['pid'] >= 0
  110. assert m.called
  111. def test_cache_clear(self):
  112. list(psutil.process_iter()) # populate cache
  113. assert psutil._pmap
  114. psutil.process_iter.cache_clear()
  115. assert not psutil._pmap
  116. class TestProcessAPIs(PsutilTestCase):
  117. @pytest.mark.skipif(
  118. PYPY and WINDOWS,
  119. reason="spawn_subproc() unreliable on PYPY + WINDOWS",
  120. )
  121. def test_wait_procs(self):
  122. def callback(p):
  123. pids.append(p.pid)
  124. pids = []
  125. sproc1 = self.spawn_subproc()
  126. sproc2 = self.spawn_subproc()
  127. sproc3 = self.spawn_subproc()
  128. procs = [psutil.Process(x.pid) for x in (sproc1, sproc2, sproc3)]
  129. with pytest.raises(ValueError):
  130. psutil.wait_procs(procs, timeout=-1)
  131. with pytest.raises(TypeError):
  132. psutil.wait_procs(procs, callback=1)
  133. t = time.time()
  134. gone, alive = psutil.wait_procs(procs, timeout=0.01, callback=callback)
  135. assert time.time() - t < 0.5
  136. assert not gone
  137. assert len(alive) == 3
  138. assert not pids
  139. for p in alive:
  140. assert not hasattr(p, 'returncode')
  141. @retry_on_failure(30)
  142. def test_1(procs, callback):
  143. gone, alive = psutil.wait_procs(
  144. procs, timeout=0.03, callback=callback
  145. )
  146. assert len(gone) == 1
  147. assert len(alive) == 2
  148. return gone, alive
  149. sproc3.terminate()
  150. gone, alive = test_1(procs, callback)
  151. assert sproc3.pid in [x.pid for x in gone]
  152. if POSIX:
  153. assert gone.pop().returncode == -signal.SIGTERM
  154. else:
  155. assert gone.pop().returncode == 1
  156. assert pids == [sproc3.pid]
  157. for p in alive:
  158. assert not hasattr(p, 'returncode')
  159. @retry_on_failure(30)
  160. def test_2(procs, callback):
  161. gone, alive = psutil.wait_procs(
  162. procs, timeout=0.03, callback=callback
  163. )
  164. assert len(gone) == 3
  165. assert len(alive) == 0
  166. return gone, alive
  167. sproc1.terminate()
  168. sproc2.terminate()
  169. gone, alive = test_2(procs, callback)
  170. assert set(pids) == {sproc1.pid, sproc2.pid, sproc3.pid}
  171. for p in gone:
  172. assert hasattr(p, 'returncode')
  173. @pytest.mark.skipif(
  174. PYPY and WINDOWS,
  175. reason="spawn_subproc() unreliable on PYPY + WINDOWS",
  176. )
  177. def test_wait_procs_no_timeout(self):
  178. sproc1 = self.spawn_subproc()
  179. sproc2 = self.spawn_subproc()
  180. sproc3 = self.spawn_subproc()
  181. procs = [psutil.Process(x.pid) for x in (sproc1, sproc2, sproc3)]
  182. for p in procs:
  183. p.terminate()
  184. psutil.wait_procs(procs)
  185. def test_pid_exists(self):
  186. sproc = self.spawn_subproc()
  187. assert psutil.pid_exists(sproc.pid)
  188. p = psutil.Process(sproc.pid)
  189. p.kill()
  190. p.wait()
  191. assert not psutil.pid_exists(sproc.pid)
  192. assert not psutil.pid_exists(-1)
  193. assert psutil.pid_exists(0) == (0 in psutil.pids())
  194. def test_pid_exists_2(self):
  195. pids = psutil.pids()
  196. for pid in pids:
  197. try:
  198. assert psutil.pid_exists(pid)
  199. except AssertionError:
  200. # in case the process disappeared in meantime fail only
  201. # if it is no longer in psutil.pids()
  202. time.sleep(0.1)
  203. assert pid not in psutil.pids()
  204. pids = range(max(pids) + 15000, max(pids) + 16000)
  205. for pid in pids:
  206. assert not psutil.pid_exists(pid)
  207. class TestMiscAPIs(PsutilTestCase):
  208. def test_boot_time(self):
  209. bt = psutil.boot_time()
  210. assert isinstance(bt, float)
  211. assert bt > 0
  212. assert bt < time.time()
  213. @pytest.mark.skipif(
  214. CI_TESTING and not psutil.users(), reason="unreliable on CI"
  215. )
  216. def test_users(self):
  217. users = psutil.users()
  218. assert users
  219. for user in users:
  220. with self.subTest(user=user):
  221. assert user.name
  222. assert isinstance(user.name, str)
  223. assert isinstance(user.terminal, (str, type(None)))
  224. if user.host is not None:
  225. assert isinstance(user.host, (str, type(None)))
  226. user.terminal # noqa: B018
  227. user.host # noqa: B018
  228. assert user.started > 0.0
  229. datetime.datetime.fromtimestamp(user.started)
  230. if WINDOWS or OPENBSD:
  231. assert user.pid is None
  232. else:
  233. psutil.Process(user.pid)
  234. def test_os_constants(self):
  235. names = [
  236. "POSIX",
  237. "WINDOWS",
  238. "LINUX",
  239. "MACOS",
  240. "FREEBSD",
  241. "OPENBSD",
  242. "NETBSD",
  243. "BSD",
  244. "SUNOS",
  245. ]
  246. for name in names:
  247. assert isinstance(getattr(psutil, name), bool), name
  248. if os.name == 'posix':
  249. assert psutil.POSIX
  250. assert not psutil.WINDOWS
  251. names.remove("POSIX")
  252. if "linux" in sys.platform.lower():
  253. assert psutil.LINUX
  254. names.remove("LINUX")
  255. elif "bsd" in sys.platform.lower():
  256. assert psutil.BSD
  257. assert [psutil.FREEBSD, psutil.OPENBSD, psutil.NETBSD].count(
  258. True
  259. ) == 1
  260. names.remove("BSD")
  261. names.remove("FREEBSD")
  262. names.remove("OPENBSD")
  263. names.remove("NETBSD")
  264. elif (
  265. "sunos" in sys.platform.lower()
  266. or "solaris" in sys.platform.lower()
  267. ):
  268. assert psutil.SUNOS
  269. names.remove("SUNOS")
  270. elif "darwin" in sys.platform.lower():
  271. assert psutil.MACOS
  272. names.remove("MACOS")
  273. else:
  274. assert psutil.WINDOWS
  275. assert not psutil.POSIX
  276. names.remove("WINDOWS")
  277. # assert all other constants are set to False
  278. for name in names:
  279. assert not getattr(psutil, name), name
  280. class TestMemoryAPIs(PsutilTestCase):
  281. def test_virtual_memory(self):
  282. mem = psutil.virtual_memory()
  283. assert mem.total > 0, mem
  284. assert mem.available > 0, mem
  285. assert 0 <= mem.percent <= 100, mem
  286. assert mem.used > 0, mem
  287. assert mem.free >= 0, mem
  288. for name in mem._fields:
  289. value = getattr(mem, name)
  290. if name != 'percent':
  291. assert isinstance(value, int)
  292. if name != 'total':
  293. if not value >= 0:
  294. return pytest.fail(f"{name!r} < 0 ({value})")
  295. if value > mem.total:
  296. return pytest.fail(
  297. f"{name!r} > total (total={mem.total}, {name}={value})"
  298. )
  299. def test_swap_memory(self):
  300. mem = psutil.swap_memory()
  301. assert mem._fields == (
  302. 'total',
  303. 'used',
  304. 'free',
  305. 'percent',
  306. 'sin',
  307. 'sout',
  308. )
  309. assert mem.total >= 0, mem
  310. assert mem.used >= 0, mem
  311. if mem.total > 0:
  312. # likely a system with no swap partition
  313. assert mem.free > 0, mem
  314. else:
  315. assert mem.free == 0, mem
  316. assert 0 <= mem.percent <= 100, mem
  317. assert mem.sin >= 0, mem
  318. assert mem.sout >= 0, mem
  319. class TestCpuAPIs(PsutilTestCase):
  320. def test_cpu_count_logical(self):
  321. logical = psutil.cpu_count()
  322. assert logical is not None
  323. assert logical == len(psutil.cpu_times(percpu=True))
  324. assert logical >= 1
  325. if os.path.exists("/proc/cpuinfo"):
  326. with open("/proc/cpuinfo") as fd:
  327. cpuinfo_data = fd.read()
  328. if "physical id" not in cpuinfo_data:
  329. return pytest.skip("cpuinfo doesn't include physical id")
  330. def test_cpu_count_cores(self):
  331. logical = psutil.cpu_count()
  332. cores = psutil.cpu_count(logical=False)
  333. if cores is None:
  334. return pytest.skip("cpu_count_cores() is None")
  335. if WINDOWS and sys.getwindowsversion()[:2] <= (6, 1): # <= Vista
  336. assert cores is None
  337. else:
  338. assert cores >= 1
  339. assert logical >= cores
  340. def test_cpu_count_none(self):
  341. # https://github.com/giampaolo/psutil/issues/1085
  342. for val in (-1, 0, None):
  343. with mock.patch(
  344. 'psutil._psplatform.cpu_count_logical', return_value=val
  345. ) as m:
  346. assert psutil.cpu_count() is None
  347. assert m.called
  348. with mock.patch(
  349. 'psutil._psplatform.cpu_count_cores', return_value=val
  350. ) as m:
  351. assert psutil.cpu_count(logical=False) is None
  352. assert m.called
  353. def test_cpu_times(self):
  354. # Check type, value >= 0, str().
  355. total = 0
  356. times = psutil.cpu_times()
  357. sum(times)
  358. for cp_time in times:
  359. assert isinstance(cp_time, float)
  360. assert cp_time >= 0.0
  361. total += cp_time
  362. assert round(abs(total - sum(times)), 6) == 0
  363. str(times)
  364. # CPU times are always supposed to increase over time
  365. # or at least remain the same and that's because time
  366. # cannot go backwards.
  367. # Surprisingly sometimes this might not be the case (at
  368. # least on Windows and Linux), see:
  369. # https://github.com/giampaolo/psutil/issues/392
  370. # https://github.com/giampaolo/psutil/issues/645
  371. # if not WINDOWS:
  372. # last = psutil.cpu_times()
  373. # for x in range(100):
  374. # new = psutil.cpu_times()
  375. # for field in new._fields:
  376. # new_t = getattr(new, field)
  377. # last_t = getattr(last, field)
  378. # assert new_t >= last_t
  379. # last = new
  380. def test_cpu_times_time_increases(self):
  381. # Make sure time increases between calls.
  382. t1 = sum(psutil.cpu_times())
  383. stop_at = time.time() + GLOBAL_TIMEOUT
  384. while time.time() < stop_at:
  385. t2 = sum(psutil.cpu_times())
  386. if t2 > t1:
  387. return None
  388. return pytest.fail("time remained the same")
  389. def test_per_cpu_times(self):
  390. # Check type, value >= 0, str().
  391. for times in psutil.cpu_times(percpu=True):
  392. total = 0
  393. sum(times)
  394. for cp_time in times:
  395. assert isinstance(cp_time, float)
  396. assert cp_time >= 0.0
  397. total += cp_time
  398. assert round(abs(total - sum(times)), 6) == 0
  399. str(times)
  400. assert len(psutil.cpu_times(percpu=True)[0]) == len(
  401. psutil.cpu_times(percpu=False)
  402. )
  403. # Note: in theory CPU times are always supposed to increase over
  404. # time or remain the same but never go backwards. In practice
  405. # sometimes this is not the case.
  406. # This issue seemd to be afflict Windows:
  407. # https://github.com/giampaolo/psutil/issues/392
  408. # ...but it turns out also Linux (rarely) behaves the same.
  409. # last = psutil.cpu_times(percpu=True)
  410. # for x in range(100):
  411. # new = psutil.cpu_times(percpu=True)
  412. # for index in range(len(new)):
  413. # newcpu = new[index]
  414. # lastcpu = last[index]
  415. # for field in newcpu._fields:
  416. # new_t = getattr(newcpu, field)
  417. # last_t = getattr(lastcpu, field)
  418. # assert new_t >= last_t
  419. # last = new
  420. def test_per_cpu_times_2(self):
  421. # Simulate some work load then make sure time have increased
  422. # between calls.
  423. tot1 = psutil.cpu_times(percpu=True)
  424. giveup_at = time.time() + GLOBAL_TIMEOUT
  425. while True:
  426. if time.time() >= giveup_at:
  427. return pytest.fail("timeout")
  428. tot2 = psutil.cpu_times(percpu=True)
  429. for t1, t2 in zip(tot1, tot2):
  430. t1, t2 = psutil._cpu_busy_time(t1), psutil._cpu_busy_time(t2)
  431. difference = t2 - t1
  432. if difference >= 0.05:
  433. return None
  434. @pytest.mark.skipif(
  435. (CI_TESTING and OPENBSD) or MACOS, reason="unreliable on OPENBSD + CI"
  436. )
  437. @retry_on_failure(30)
  438. def test_cpu_times_comparison(self):
  439. # Make sure the sum of all per cpu times is almost equal to
  440. # base "one cpu" times. On OpenBSD the sum of per-CPUs is
  441. # higher for some reason.
  442. base = psutil.cpu_times()
  443. per_cpu = psutil.cpu_times(percpu=True)
  444. summed_values = base._make([sum(num) for num in zip(*per_cpu)])
  445. for field in base._fields:
  446. with self.subTest(field=field, base=base, per_cpu=per_cpu):
  447. assert (
  448. abs(getattr(base, field) - getattr(summed_values, field))
  449. < 2
  450. )
  451. def _test_cpu_percent(self, percent, last_ret, new_ret):
  452. try:
  453. assert isinstance(percent, float)
  454. assert percent >= 0.0
  455. assert percent <= 100.0 * psutil.cpu_count()
  456. except AssertionError as err:
  457. raise AssertionError(
  458. "\n{}\nlast={}\nnew={}".format(
  459. err, pprint.pformat(last_ret), pprint.pformat(new_ret)
  460. )
  461. )
  462. def test_cpu_percent(self):
  463. last = psutil.cpu_percent(interval=0.001)
  464. for _ in range(100):
  465. new = psutil.cpu_percent(interval=None)
  466. self._test_cpu_percent(new, last, new)
  467. last = new
  468. with pytest.raises(ValueError):
  469. psutil.cpu_percent(interval=-1)
  470. def test_per_cpu_percent(self):
  471. last = psutil.cpu_percent(interval=0.001, percpu=True)
  472. assert len(last) == psutil.cpu_count()
  473. for _ in range(100):
  474. new = psutil.cpu_percent(interval=None, percpu=True)
  475. for percent in new:
  476. self._test_cpu_percent(percent, last, new)
  477. last = new
  478. with pytest.raises(ValueError):
  479. psutil.cpu_percent(interval=-1, percpu=True)
  480. def test_cpu_times_percent(self):
  481. last = psutil.cpu_times_percent(interval=0.001)
  482. for _ in range(100):
  483. new = psutil.cpu_times_percent(interval=None)
  484. for percent in new:
  485. self._test_cpu_percent(percent, last, new)
  486. self._test_cpu_percent(sum(new), last, new)
  487. last = new
  488. with pytest.raises(ValueError):
  489. psutil.cpu_times_percent(interval=-1)
  490. def test_per_cpu_times_percent(self):
  491. last = psutil.cpu_times_percent(interval=0.001, percpu=True)
  492. assert len(last) == psutil.cpu_count()
  493. for _ in range(100):
  494. new = psutil.cpu_times_percent(interval=None, percpu=True)
  495. for cpu in new:
  496. for percent in cpu:
  497. self._test_cpu_percent(percent, last, new)
  498. self._test_cpu_percent(sum(cpu), last, new)
  499. last = new
  500. def test_per_cpu_times_percent_negative(self):
  501. # see: https://github.com/giampaolo/psutil/issues/645
  502. psutil.cpu_times_percent(percpu=True)
  503. zero_times = [
  504. x._make([0 for x in range(len(x._fields))])
  505. for x in psutil.cpu_times(percpu=True)
  506. ]
  507. with mock.patch('psutil.cpu_times', return_value=zero_times):
  508. for cpu in psutil.cpu_times_percent(percpu=True):
  509. for percent in cpu:
  510. self._test_cpu_percent(percent, None, None)
  511. def test_cpu_stats(self):
  512. # Tested more extensively in per-platform test modules.
  513. infos = psutil.cpu_stats()
  514. assert infos._fields == (
  515. 'ctx_switches',
  516. 'interrupts',
  517. 'soft_interrupts',
  518. 'syscalls',
  519. )
  520. for name in infos._fields:
  521. value = getattr(infos, name)
  522. assert value >= 0
  523. # on AIX, ctx_switches is always 0
  524. if not AIX and name in {'ctx_switches', 'interrupts'}:
  525. assert value > 0
  526. # TODO: remove this once 1892 is fixed
  527. @pytest.mark.skipif(MACOS and AARCH64, reason="skipped due to #1892")
  528. @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported")
  529. def test_cpu_freq(self):
  530. def check_ls(ls):
  531. for nt in ls:
  532. assert nt._fields == ('current', 'min', 'max')
  533. for name in nt._fields:
  534. value = getattr(nt, name)
  535. assert isinstance(value, (int, float))
  536. assert value >= 0
  537. ls = psutil.cpu_freq(percpu=True)
  538. if (FREEBSD or AARCH64) and not ls:
  539. return pytest.skip(
  540. "returns empty list on FreeBSD and Linux aarch64"
  541. )
  542. assert ls, ls
  543. check_ls([psutil.cpu_freq(percpu=False)])
  544. if LINUX:
  545. assert len(ls) == psutil.cpu_count()
  546. @pytest.mark.skipif(not HAS_GETLOADAVG, reason="not supported")
  547. def test_getloadavg(self):
  548. loadavg = psutil.getloadavg()
  549. assert len(loadavg) == 3
  550. for load in loadavg:
  551. assert isinstance(load, float)
  552. assert load >= 0.0
  553. class TestDiskAPIs(PsutilTestCase):
  554. def test_disk_usage(self):
  555. usage = psutil.disk_usage(os.getcwd())
  556. assert usage._fields == ('total', 'used', 'free', 'percent')
  557. assert usage.total > 0, usage
  558. assert usage.used > 0, usage
  559. assert usage.free > 0, usage
  560. assert usage.total > usage.used, usage
  561. assert usage.total > usage.free, usage
  562. assert 0 <= usage.percent <= 100, usage.percent
  563. shutil_usage = shutil.disk_usage(os.getcwd())
  564. tolerance = 5 * 1024 * 1024 # 5MB
  565. assert usage.total == shutil_usage.total
  566. assert abs(usage.free - shutil_usage.free) < tolerance
  567. if not MACOS_12PLUS:
  568. # see https://github.com/giampaolo/psutil/issues/2147
  569. assert abs(usage.used - shutil_usage.used) < tolerance
  570. # if path does not exist OSError ENOENT is expected across
  571. # all platforms
  572. fname = self.get_testfn()
  573. with pytest.raises(FileNotFoundError):
  574. psutil.disk_usage(fname)
  575. @pytest.mark.skipif(not ASCII_FS, reason="not an ASCII fs")
  576. def test_disk_usage_unicode(self):
  577. # See: https://github.com/giampaolo/psutil/issues/416
  578. with pytest.raises(UnicodeEncodeError):
  579. psutil.disk_usage(UNICODE_SUFFIX)
  580. def test_disk_usage_bytes(self):
  581. psutil.disk_usage(b'.')
  582. def test_disk_partitions(self):
  583. def check_ntuple(nt):
  584. assert isinstance(nt.device, str)
  585. assert isinstance(nt.mountpoint, str)
  586. assert isinstance(nt.fstype, str)
  587. assert isinstance(nt.opts, str)
  588. # all = False
  589. ls = psutil.disk_partitions(all=False)
  590. assert ls
  591. for disk in ls:
  592. check_ntuple(disk)
  593. if WINDOWS and 'cdrom' in disk.opts:
  594. continue
  595. if not POSIX:
  596. assert os.path.exists(disk.device), disk
  597. else:
  598. # we cannot make any assumption about this, see:
  599. # http://goo.gl/p9c43
  600. disk.device # noqa: B018
  601. # on modern systems mount points can also be files
  602. assert os.path.exists(disk.mountpoint), disk
  603. assert disk.fstype, disk
  604. # all = True
  605. ls = psutil.disk_partitions(all=True)
  606. assert ls
  607. for disk in psutil.disk_partitions(all=True):
  608. check_ntuple(disk)
  609. if not WINDOWS and disk.mountpoint:
  610. try:
  611. os.stat(disk.mountpoint)
  612. except OSError as err:
  613. if GITHUB_ACTIONS and MACOS and err.errno == errno.EIO:
  614. continue
  615. # http://mail.python.org/pipermail/python-dev/
  616. # 2012-June/120787.html
  617. if err.errno not in {errno.EPERM, errno.EACCES}:
  618. raise
  619. else:
  620. assert os.path.exists(disk.mountpoint), disk
  621. # ---
  622. def find_mount_point(path):
  623. path = os.path.abspath(path)
  624. while not os.path.ismount(path):
  625. path = os.path.dirname(path)
  626. return path.lower()
  627. mount = find_mount_point(__file__)
  628. mounts = [
  629. x.mountpoint.lower()
  630. for x in psutil.disk_partitions(all=True)
  631. if x.mountpoint
  632. ]
  633. assert mount in mounts
  634. @pytest.mark.skipif(
  635. LINUX and not os.path.exists('/proc/diskstats'),
  636. reason="/proc/diskstats not available on this linux version",
  637. )
  638. @pytest.mark.skipif(
  639. CI_TESTING and not psutil.disk_io_counters(), reason="unreliable on CI"
  640. ) # no visible disks
  641. def test_disk_io_counters(self):
  642. def check_ntuple(nt):
  643. assert nt[0] == nt.read_count
  644. assert nt[1] == nt.write_count
  645. assert nt[2] == nt.read_bytes
  646. assert nt[3] == nt.write_bytes
  647. if not (OPENBSD or NETBSD):
  648. assert nt[4] == nt.read_time
  649. assert nt[5] == nt.write_time
  650. if LINUX:
  651. assert nt[6] == nt.read_merged_count
  652. assert nt[7] == nt.write_merged_count
  653. assert nt[8] == nt.busy_time
  654. elif FREEBSD:
  655. assert nt[6] == nt.busy_time
  656. for name in nt._fields:
  657. assert getattr(nt, name) >= 0, nt
  658. ret = psutil.disk_io_counters(perdisk=False)
  659. assert ret is not None, "no disks on this system?"
  660. check_ntuple(ret)
  661. ret = psutil.disk_io_counters(perdisk=True)
  662. # make sure there are no duplicates
  663. assert len(ret) == len(set(ret))
  664. for key in ret:
  665. assert key, key
  666. check_ntuple(ret[key])
  667. def test_disk_io_counters_no_disks(self):
  668. # Emulate a case where no disks are installed, see:
  669. # https://github.com/giampaolo/psutil/issues/1062
  670. with mock.patch(
  671. 'psutil._psplatform.disk_io_counters', return_value={}
  672. ) as m:
  673. assert psutil.disk_io_counters(perdisk=False) is None
  674. assert psutil.disk_io_counters(perdisk=True) == {}
  675. assert m.called
  676. class TestNetAPIs(PsutilTestCase):
  677. @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported")
  678. def test_net_io_counters(self):
  679. def check_ntuple(nt):
  680. assert nt[0] == nt.bytes_sent
  681. assert nt[1] == nt.bytes_recv
  682. assert nt[2] == nt.packets_sent
  683. assert nt[3] == nt.packets_recv
  684. assert nt[4] == nt.errin
  685. assert nt[5] == nt.errout
  686. assert nt[6] == nt.dropin
  687. assert nt[7] == nt.dropout
  688. assert nt.bytes_sent >= 0, nt
  689. assert nt.bytes_recv >= 0, nt
  690. assert nt.packets_sent >= 0, nt
  691. assert nt.packets_recv >= 0, nt
  692. assert nt.errin >= 0, nt
  693. assert nt.errout >= 0, nt
  694. assert nt.dropin >= 0, nt
  695. assert nt.dropout >= 0, nt
  696. ret = psutil.net_io_counters(pernic=False)
  697. check_ntuple(ret)
  698. ret = psutil.net_io_counters(pernic=True)
  699. assert ret != []
  700. for key in ret:
  701. assert key
  702. assert isinstance(key, str)
  703. check_ntuple(ret[key])
  704. @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported")
  705. def test_net_io_counters_no_nics(self):
  706. # Emulate a case where no NICs are installed, see:
  707. # https://github.com/giampaolo/psutil/issues/1062
  708. with mock.patch(
  709. 'psutil._psplatform.net_io_counters', return_value={}
  710. ) as m:
  711. assert psutil.net_io_counters(pernic=False) is None
  712. assert psutil.net_io_counters(pernic=True) == {}
  713. assert m.called
  714. def test_net_if_addrs(self):
  715. nics = psutil.net_if_addrs()
  716. assert nics, nics
  717. nic_stats = psutil.net_if_stats()
  718. # Not reliable on all platforms (net_if_addrs() reports more
  719. # interfaces).
  720. # assert sorted(nics.keys()) == sorted(
  721. # psutil.net_io_counters(pernic=True).keys()
  722. # )
  723. families = {socket.AF_INET, socket.AF_INET6, psutil.AF_LINK}
  724. for nic, addrs in nics.items():
  725. assert isinstance(nic, str)
  726. assert len(set(addrs)) == len(addrs)
  727. for addr in addrs:
  728. assert isinstance(addr.family, int)
  729. assert isinstance(addr.address, str)
  730. assert isinstance(addr.netmask, (str, type(None)))
  731. assert isinstance(addr.broadcast, (str, type(None)))
  732. assert addr.family in families
  733. assert isinstance(addr.family, enum.IntEnum)
  734. if nic_stats[nic].isup:
  735. # Do not test binding to addresses of interfaces
  736. # that are down
  737. if addr.family == socket.AF_INET:
  738. with socket.socket(addr.family) as s:
  739. s.bind((addr.address, 0))
  740. elif addr.family == socket.AF_INET6:
  741. info = socket.getaddrinfo(
  742. addr.address,
  743. 0,
  744. socket.AF_INET6,
  745. socket.SOCK_STREAM,
  746. 0,
  747. socket.AI_PASSIVE,
  748. )[0]
  749. af, socktype, proto, _canonname, sa = info
  750. with socket.socket(af, socktype, proto) as s:
  751. s.bind(sa)
  752. for ip in (
  753. addr.address,
  754. addr.netmask,
  755. addr.broadcast,
  756. addr.ptp,
  757. ):
  758. if ip is not None:
  759. # TODO: skip AF_INET6 for now because I get:
  760. # AddressValueError: Only hex digits permitted in
  761. # u'c6f3%lxcbr0' in u'fe80::c8e0:fff:fe54:c6f3%lxcbr0'
  762. if addr.family != socket.AF_INET6:
  763. check_net_address(ip, addr.family)
  764. # broadcast and ptp addresses are mutually exclusive
  765. if addr.broadcast:
  766. assert addr.ptp is None
  767. elif addr.ptp:
  768. assert addr.broadcast is None
  769. # check broadcast address
  770. if (
  771. addr.broadcast
  772. and addr.netmask
  773. and addr.family in {socket.AF_INET, socket.AF_INET6}
  774. ):
  775. assert addr.broadcast == broadcast_addr(addr)
  776. if BSD or MACOS or SUNOS:
  777. if hasattr(socket, "AF_LINK"):
  778. assert psutil.AF_LINK == socket.AF_LINK
  779. elif LINUX:
  780. assert psutil.AF_LINK == socket.AF_PACKET
  781. elif WINDOWS:
  782. assert psutil.AF_LINK == -1
  783. def test_net_if_addrs_mac_null_bytes(self):
  784. # Simulate that the underlying C function returns an incomplete
  785. # MAC address. psutil is supposed to fill it with null bytes.
  786. # https://github.com/giampaolo/psutil/issues/786
  787. if POSIX:
  788. ret = [('em1', psutil.AF_LINK, '06:3d:29', None, None, None)]
  789. else:
  790. ret = [('em1', -1, '06-3d-29', None, None, None)]
  791. with mock.patch(
  792. 'psutil._psplatform.net_if_addrs', return_value=ret
  793. ) as m:
  794. addr = psutil.net_if_addrs()['em1'][0]
  795. assert m.called
  796. if POSIX:
  797. assert addr.address == '06:3d:29:00:00:00'
  798. else:
  799. assert addr.address == '06-3d-29-00-00-00'
  800. def test_net_if_stats(self):
  801. nics = psutil.net_if_stats()
  802. assert nics, nics
  803. all_duplexes = (
  804. psutil.NIC_DUPLEX_FULL,
  805. psutil.NIC_DUPLEX_HALF,
  806. psutil.NIC_DUPLEX_UNKNOWN,
  807. )
  808. for name, stats in nics.items():
  809. assert isinstance(name, str)
  810. isup, duplex, speed, mtu, flags = stats
  811. assert isinstance(isup, bool)
  812. assert duplex in all_duplexes
  813. assert duplex in all_duplexes
  814. assert speed >= 0
  815. assert mtu >= 0
  816. assert isinstance(flags, str)
  817. @pytest.mark.skipif(
  818. not (LINUX or BSD or MACOS), reason="LINUX or BSD or MACOS specific"
  819. )
  820. def test_net_if_stats_enodev(self):
  821. # See: https://github.com/giampaolo/psutil/issues/1279
  822. with mock.patch(
  823. 'psutil._psplatform.cext.net_if_mtu',
  824. side_effect=OSError(errno.ENODEV, ""),
  825. ) as m:
  826. ret = psutil.net_if_stats()
  827. assert ret == {}
  828. assert m.called
  829. class TestSensorsAPIs(PsutilTestCase):
  830. @pytest.mark.skipif(not HAS_SENSORS_TEMPERATURES, reason="not supported")
  831. def test_sensors_temperatures(self):
  832. temps = psutil.sensors_temperatures()
  833. for name, entries in temps.items():
  834. assert isinstance(name, str)
  835. for entry in entries:
  836. assert isinstance(entry.label, str)
  837. if entry.current is not None:
  838. assert entry.current >= 0
  839. if entry.high is not None:
  840. assert entry.high >= 0
  841. if entry.critical is not None:
  842. assert entry.critical >= 0
  843. @pytest.mark.skipif(not HAS_SENSORS_TEMPERATURES, reason="not supported")
  844. def test_sensors_temperatures_fahreneit(self):
  845. d = {'coretemp': [('label', 50.0, 60.0, 70.0)]}
  846. with mock.patch(
  847. "psutil._psplatform.sensors_temperatures", return_value=d
  848. ) as m:
  849. temps = psutil.sensors_temperatures(fahrenheit=True)['coretemp'][0]
  850. assert m.called
  851. assert temps.current == 122.0
  852. assert temps.high == 140.0
  853. assert temps.critical == 158.0
  854. @pytest.mark.skipif(not HAS_SENSORS_BATTERY, reason="not supported")
  855. @pytest.mark.skipif(not HAS_BATTERY, reason="no battery")
  856. def test_sensors_battery(self):
  857. ret = psutil.sensors_battery()
  858. assert ret.percent >= 0
  859. assert ret.percent <= 100
  860. if ret.secsleft not in {
  861. psutil.POWER_TIME_UNKNOWN,
  862. psutil.POWER_TIME_UNLIMITED,
  863. }:
  864. assert ret.secsleft >= 0
  865. elif ret.secsleft == psutil.POWER_TIME_UNLIMITED:
  866. assert ret.power_plugged
  867. assert isinstance(ret.power_plugged, bool)
  868. @pytest.mark.skipif(not HAS_SENSORS_FANS, reason="not supported")
  869. def test_sensors_fans(self):
  870. fans = psutil.sensors_fans()
  871. for name, entries in fans.items():
  872. assert isinstance(name, str)
  873. for entry in entries:
  874. assert isinstance(entry.label, str)
  875. assert isinstance(entry.current, int)
  876. assert entry.current >= 0