From a9a206ae5e4868794ad6c8c4c4d2da256bcfe45e Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 19:11:47 +0200 Subject: [PATCH 01/17] fake pytest class --- psutil/tests/__init__.py | 41 +++++++++++++++++++++++++++++++- psutil/tests/__main__.py | 2 +- psutil/tests/test_bsd.py | 3 +-- psutil/tests/test_connections.py | 3 +-- psutil/tests/test_linux.py | 3 +-- psutil/tests/test_misc.py | 3 +-- psutil/tests/test_posix.py | 3 +-- psutil/tests/test_process.py | 3 +-- psutil/tests/test_process_all.py | 3 +-- psutil/tests/test_system.py | 3 +-- psutil/tests/test_testutils.py | 3 +-- psutil/tests/test_unicode.py | 3 +-- psutil/tests/test_windows.py | 3 +-- 13 files changed, 52 insertions(+), 24 deletions(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 86bd6dca7..1bd7dec63 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -36,7 +36,11 @@ from socket import AF_INET6 from socket import SOCK_STREAM -import pytest + +try: + import pytest +except ImportError: + pytest = None import psutil from psutil import AIX @@ -915,6 +919,41 @@ def get_testfn(suffix="", dir=None): # =================================================================== +class fake_pytest: + + @staticmethod + def main(*args, **kw): # noqa ARG004 + suite = unittest.TestLoader().discover(HERE) + unittest.TextTestRunner(verbosity=2).run(suite) + + @staticmethod + def raises(exc, match=None): + @contextlib.contextmanager + def raises(exc, match=None): + try: + yield + except exc: + if match and not re.search(match, str(exc)): + msg = '"{}" does not match "{}"'.format(match, str(exc)) + raise AssertionError(msg) + else: + raise AssertionError("%r not raised" % exc) + + return raises(exc, match=match) + + class mark: + class xdist_group: + def __init__(self, name=None): + pass + + def __call__(self, cls): + return cls # no-op: just return the class as-is + + +if pytest is None: + pytest = fake_pytest + + class PsutilTestCase(unittest.TestCase): """Test class providing auto-cleanup wrappers on top of process test utilities. diff --git a/psutil/tests/__main__.py b/psutil/tests/__main__.py index f43b751f9..ce6fc24c7 100644 --- a/psutil/tests/__main__.py +++ b/psutil/tests/__main__.py @@ -6,7 +6,7 @@ $ python -m psutil.tests. """ -import pytest +from psutil.tests import pytest pytest.main(["-v", "-s", "--tb=short"]) diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py index 13c877460..3af505b60 100755 --- a/psutil/tests/test_bsd.py +++ b/psutil/tests/test_bsd.py @@ -16,8 +16,6 @@ import time import unittest -import pytest - import psutil from psutil import BSD from psutil import FREEBSD @@ -26,6 +24,7 @@ from psutil.tests import HAS_BATTERY from psutil.tests import TOLERANCE_SYS_MEM from psutil.tests import PsutilTestCase +from psutil.tests import pytest from psutil.tests import retry_on_failure from psutil.tests import sh from psutil.tests import spawn_testproc diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py index 783bf8145..7f29900b2 100755 --- a/psutil/tests/test_connections.py +++ b/psutil/tests/test_connections.py @@ -16,8 +16,6 @@ from socket import SOCK_DGRAM from socket import SOCK_STREAM -import pytest - import psutil from psutil import FREEBSD from psutil import LINUX @@ -38,6 +36,7 @@ from psutil.tests import check_connection_ntuple from psutil.tests import create_sockets from psutil.tests import filter_proc_net_connections +from psutil.tests import pytest from psutil.tests import reap_children from psutil.tests import retry_on_failure from psutil.tests import skip_on_access_denied diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index fddce78d2..5543accf3 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -23,8 +23,6 @@ import unittest import warnings -import pytest - import psutil from psutil import LINUX from psutil._compat import PY3 @@ -46,6 +44,7 @@ from psutil.tests import ThreadTask from psutil.tests import call_until from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import reload_module from psutil.tests import retry_on_failure from psutil.tests import safe_rmpath diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index adafea947..aa55c0569 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -18,8 +18,6 @@ import sys import unittest -import pytest - import psutil import psutil.tests from psutil import POSIX @@ -50,6 +48,7 @@ from psutil.tests import PsutilTestCase from psutil.tests import mock from psutil.tests import process_namespace +from psutil.tests import pytest from psutil.tests import reload_module from psutil.tests import sh from psutil.tests import system_namespace diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index dd2d179dd..5c2c86c9e 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -15,8 +15,6 @@ import time import unittest -import pytest - import psutil from psutil import AIX from psutil import BSD @@ -31,6 +29,7 @@ from psutil.tests import QEMU_USER from psutil.tests import PsutilTestCase from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import retry_on_failure from psutil.tests import sh from psutil.tests import skip_on_access_denied diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index 49a94b7a7..71c940300 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -21,8 +21,6 @@ import types import unittest -import pytest - import psutil from psutil import AIX from psutil import BSD @@ -65,6 +63,7 @@ from psutil.tests import create_py_exe from psutil.tests import mock from psutil.tests import process_namespace +from psutil.tests import pytest from psutil.tests import reap_children from psutil.tests import retry_on_failure from psutil.tests import sh diff --git a/psutil/tests/test_process_all.py b/psutil/tests/test_process_all.py index a6025390e..1550046ba 100755 --- a/psutil/tests/test_process_all.py +++ b/psutil/tests/test_process_all.py @@ -16,8 +16,6 @@ import time import traceback -import pytest - import psutil from psutil import AIX from psutil import BSD @@ -43,6 +41,7 @@ from psutil.tests import is_namedtuple from psutil.tests import is_win_secure_system_proc from psutil.tests import process_namespace +from psutil.tests import pytest # Cuts the time in half, but (e.g.) on macOS the process pool stays diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index 7403326bf..298d2d9e4 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -19,8 +19,6 @@ import time import unittest -import pytest - import psutil from psutil import AIX from psutil import BSD @@ -56,6 +54,7 @@ from psutil.tests import check_net_address from psutil.tests import enum from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import retry_on_failure diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index 64f172c72..360667696 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -16,8 +16,6 @@ import subprocess import unittest -import pytest - import psutil import psutil.tests from psutil import FREEBSD @@ -43,6 +41,7 @@ from psutil.tests import is_namedtuple from psutil.tests import mock from psutil.tests import process_namespace +from psutil.tests import pytest from psutil.tests import reap_children from psutil.tests import retry from psutil.tests import retry_on_failure diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py index 9177df5d7..cc7a5475b 100755 --- a/psutil/tests/test_unicode.py +++ b/psutil/tests/test_unicode.py @@ -79,8 +79,6 @@ import warnings from contextlib import closing -import pytest - import psutil from psutil import BSD from psutil import POSIX @@ -103,6 +101,7 @@ from psutil.tests import copyload_shared_lib from psutil.tests import create_py_exe from psutil.tests import get_testfn +from psutil.tests import pytest from psutil.tests import safe_mkdir from psutil.tests import safe_rmpath from psutil.tests import skip_on_access_denied diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 0a2683dd2..7b5ba7ba1 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -20,8 +20,6 @@ import unittest import warnings -import pytest - import psutil from psutil import WINDOWS from psutil._compat import FileNotFoundError @@ -37,6 +35,7 @@ from psutil.tests import TOLERANCE_SYS_MEM from psutil.tests import PsutilTestCase from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import retry_on_failure from psutil.tests import sh from psutil.tests import spawn_testproc From 5792d5e049cfbea09fefd19c495f62dd51931f2f Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 19:14:32 +0200 Subject: [PATCH 02/17] fix regex recognition --- psutil/tests/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 1bd7dec63..60aef4674 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -932,9 +932,9 @@ def raises(exc, match=None): def raises(exc, match=None): try: yield - except exc: - if match and not re.search(match, str(exc)): - msg = '"{}" does not match "{}"'.format(match, str(exc)) + except exc as err: + if match and not re.search(match, str(err)): + msg = '"{}" does not match "{}"'.format(match, str(err)) raise AssertionError(msg) else: raise AssertionError("%r not raised" % exc) From 9c395931f32b4c481090fb1c9f05207c4f1cdad6 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 19:26:04 +0200 Subject: [PATCH 03/17] provide ExceptionInfo fake object --- psutil/tests/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 60aef4674..b2bf8b7ca 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -928,14 +928,23 @@ def main(*args, **kw): # noqa ARG004 @staticmethod def raises(exc, match=None): + class ExceptionInfo: + _exc = None + + @property + def value(self): + return self._exc + @contextlib.contextmanager def raises(exc, match=None): + einfo = ExceptionInfo() try: - yield + yield einfo except exc as err: if match and not re.search(match, str(err)): msg = '"{}" does not match "{}"'.format(match, str(err)) raise AssertionError(msg) + einfo._exc = err else: raise AssertionError("%r not raised" % exc) From 94a3908e95a5d47862699007f67723e997f729a2 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 19:26:50 +0200 Subject: [PATCH 04/17] change fun name --- psutil/tests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index b2bf8b7ca..95e9285d1 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -936,7 +936,7 @@ def value(self): return self._exc @contextlib.contextmanager - def raises(exc, match=None): + def context(exc, match=None): einfo = ExceptionInfo() try: yield einfo @@ -948,7 +948,7 @@ def raises(exc, match=None): else: raise AssertionError("%r not raised" % exc) - return raises(exc, match=match) + return context(exc, match=match) class mark: class xdist_group: From a71550d708a7f196ede575fb77ea2ec41707c3b4 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 19:54:15 +0200 Subject: [PATCH 05/17] use mock.patch.object around PSUTIL_DEBUG --- psutil/tests/test_misc.py | 21 ++++++++++++--------- psutil/tests/test_process.py | 5 +++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index aa55c0569..8ddaec28a 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -633,26 +633,29 @@ def test_debug(self): else: from StringIO import StringIO - with redirect_stderr(StringIO()) as f: - debug("hello") - sys.stderr.flush() + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with redirect_stderr(StringIO()) as f: + debug("hello") + sys.stderr.flush() msg = f.getvalue() assert msg.startswith("psutil-debug"), msg assert "hello" in msg assert __file__.replace('.pyc', '.py') in msg # supposed to use repr(exc) - with redirect_stderr(StringIO()) as f: - debug(ValueError("this is an error")) + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with redirect_stderr(StringIO()) as f: + debug(ValueError("this is an error")) msg = f.getvalue() assert "ignoring ValueError" in msg assert "'this is an error'" in msg # supposed to use str(exc), because of extra info about file name - with redirect_stderr(StringIO()) as f: - exc = OSError(2, "no such file") - exc.filename = "/foo" - debug(exc) + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with redirect_stderr(StringIO()) as f: + exc = OSError(2, "no such file") + exc.filename = "/foo" + debug(exc) msg = f.getvalue() assert "no such file" in msg assert "/foo" in msg diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index 71c940300..9e45b8053 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -1450,8 +1450,9 @@ def test_reused_pid(self): # make sure is_running() removed PID from process_iter() # internal cache - with redirect_stderr(StringIO()) as f: - list(psutil.process_iter()) + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with redirect_stderr(StringIO()) as f: + list(psutil.process_iter()) assert ( "refreshing Process instance for reused PID %s" % p.pid in f.getvalue() From bb200a02196af4d7c3b7040512901336b12db700 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 20:00:29 +0200 Subject: [PATCH 06/17] print warning about fake pytest at the end of the tests --- psutil/tests/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 95e9285d1..0a997bd23 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -925,6 +925,11 @@ class fake_pytest: def main(*args, **kw): # noqa ARG004 suite = unittest.TestLoader().discover(HERE) unittest.TextTestRunner(verbosity=2).run(suite) + warnings.warn( + "Fake pytest module was used. Test results may be inaccurate.", + UserWarning, + stacklevel=1, + ) @staticmethod def raises(exc, match=None): From db2bcab24b822fde0ebbb3e08db9bcf8a403d1ac Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 20:10:07 +0200 Subject: [PATCH 07/17] add docstrings --- psutil/tests/__init__.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 0a997bd23..5bd869612 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -920,9 +920,19 @@ def get_testfn(suffix="", dir=None): class fake_pytest: + """A class that mimics some basic pytest APIs. This is meant for + when unit tests are run in production, where pytest may not be + installed. Still, the user may want to test psutil installation + via: + + $ python3 -m psutil.tests + """ @staticmethod def main(*args, **kw): # noqa ARG004 + """Mimics pytest.main(). It has the same effect as running + `python3 -m unittest -v` from the project root directory. + """ suite = unittest.TestLoader().discover(HERE) unittest.TextTestRunner(verbosity=2).run(suite) warnings.warn( @@ -933,6 +943,8 @@ def main(*args, **kw): # noqa ARG004 @staticmethod def raises(exc, match=None): + """Mimics `pytest.raises`.""" + class ExceptionInfo: _exc = None @@ -957,11 +969,13 @@ def context(exc, match=None): class mark: class xdist_group: + """Mimics `@pytest.mark.xdist_group` decorator (no-op).""" + def __init__(self, name=None): pass def __call__(self, cls): - return cls # no-op: just return the class as-is + return cls if pytest is None: From e62a9029e3545c4e11d5104c75a745654ac601e0 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 20:30:53 +0200 Subject: [PATCH 08/17] update doc --- docs/DEVGUIDE.rst | 8 ++++++++ psutil/tests/README.rst | 23 +++++++++-------------- psutil/tests/__init__.py | 3 +-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/DEVGUIDE.rst b/docs/DEVGUIDE.rst index 456714603..563c9b883 100644 --- a/docs/DEVGUIDE.rst +++ b/docs/DEVGUIDE.rst @@ -18,6 +18,14 @@ Once you have a compiler installed run: make install make test +- If you don't have the source code, and just want to test psutil installation. + This will work also if ``pytest`` module is not installed (e.g. production + environments) by using unittest's test runner: + +.. code-block:: bash + + python3 -m psutil.tests + - ``make`` (and the accompanying `Makefile`_) is the designated tool to build, install, run tests and do pretty much anything that involves development. This also includes Windows, meaning that you can run `make command` similarly diff --git a/psutil/tests/README.rst b/psutil/tests/README.rst index 5c7336fb0..8e5b6e7d8 100644 --- a/psutil/tests/README.rst +++ b/psutil/tests/README.rst @@ -1,40 +1,35 @@ Instructions for running tests ============================== -Setup: +There are 2 ways of running tests. If you have the source code: .. code-block:: bash make install-pydeps-test # install pytest + make test -There are two ways of running tests. As a "user", if psutil is already -installed and you just want to test it works on your system: +If you don't have the source code, and just want to test psutil installation. +This will work also if ``pytest`` module is not installed (e.g. production +environments) by using unittest's test runner: .. code-block:: bash python -m psutil.tests -As a "developer", if you have a copy of the source code and you're working on -it: - -.. code-block:: bash - - make test - -You can run tests in parallel with: +To run tests in parallel (faster): .. code-block:: bash make test-parallel -You can run a specific test with: +Run a specific test: .. code-block:: bash make test ARGS=psutil.tests.test_system.TestDiskAPIs -You can run memory leak tests with: +Test C extension memory leaks: .. code-block:: bash - make test-parallel + make test-memleaks diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 5bd869612..3b04ba171 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -922,8 +922,7 @@ def get_testfn(suffix="", dir=None): class fake_pytest: """A class that mimics some basic pytest APIs. This is meant for when unit tests are run in production, where pytest may not be - installed. Still, the user may want to test psutil installation - via: + installed. Still, the user can test psutil installation via: $ python3 -m psutil.tests """ From c6e116d310ac676ea7c7a879396c9e79f79f7a71 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 23:36:42 +0200 Subject: [PATCH 09/17] add test case --- psutil/tests/test_testutils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index 360667696..3f86c1846 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -36,6 +36,7 @@ from psutil.tests import call_until from psutil.tests import chdir from psutil.tests import create_sockets +from psutil.tests import fake_pytest from psutil.tests import filter_proc_net_connections from psutil.tests import get_free_port from psutil.tests import is_namedtuple @@ -444,6 +445,24 @@ def fun_2(): self.execute_w_exc(ZeroDivisionError, fun_2) +class TestFakePytest(PsutilTestCase): + def test_raises(self): + with fake_pytest.raises(ZeroDivisionError) as cm: + 1 / 0 # noqa + assert isinstance(cm.value, ZeroDivisionError) + + with fake_pytest.raises(ValueError, match="foo") as cm: + raise ValueError("foo") + + try: + with fake_pytest.raises(ValueError, match="foo") as cm: + raise ValueError("bar") + except AssertionError as err: + assert str(err) == '"foo" does not match "bar"' + else: + raise self.fail("exception not raised") + + class TestTestingUtils(PsutilTestCase): def test_process_namespace(self): p = psutil.Process() From f88007bc9e496b4ba6855a2a4f6efc52ff93501a Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 5 Oct 2024 23:41:27 +0200 Subject: [PATCH 10/17] add test case --- psutil/tests/__init__.py | 4 ++-- psutil/tests/test_testutils.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 3b04ba171..83548fd22 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -973,8 +973,8 @@ class xdist_group: def __init__(self, name=None): pass - def __call__(self, cls): - return cls + def __call__(self, cls_or_meth): + return cls_or_meth if pytest is None: diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index 3f86c1846..b638b3dcb 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -462,6 +462,20 @@ def test_raises(self): else: raise self.fail("exception not raised") + def test_mark(self): + @fake_pytest.mark.xdist_group(name="serial") + def foo(): + return 1 + + assert foo() == 1 + + @fake_pytest.mark.xdist_group(name="serial") + class Foo: + def bar(self): + return 1 + + assert Foo().bar() == 1 + class TestTestingUtils(PsutilTestCase): def test_process_namespace(self): From e1027a9836f0bb1e104ed38b8365d73fce270abd Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 6 Oct 2024 00:02:06 +0200 Subject: [PATCH 11/17] add test case --- psutil/tests/test_testutils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index b638b3dcb..2a340b467 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -14,6 +14,7 @@ import socket import stat import subprocess +import textwrap import unittest import psutil @@ -27,6 +28,7 @@ from psutil.tests import CI_TESTING from psutil.tests import COVERAGE from psutil.tests import HAS_NET_CONNECTIONS_UNIX +from psutil.tests import HERE from psutil.tests import PYTHON_EXE from psutil.tests import PYTHON_EXE_ENV from psutil.tests import PsutilTestCase @@ -476,6 +478,27 @@ def bar(self): assert Foo().bar() == 1 + def test_main(self): + tmpdir = self.get_testfn(dir=HERE) + os.mkdir(tmpdir) + with open(os.path.join(tmpdir, "__init__.py"), "w"): + pass + with open(os.path.join(tmpdir, "test_file.py"), "w") as f: + f.write(textwrap.dedent("""\ + import unittest, os + + class TestCase(unittest.TestCase): + def test_foo(self): + path = os.path.join("{}", "hello.txt") + with open(path, "w") as f: + pass + """.format(tmpdir)).lstrip()) + with mock.patch.object(psutil.tests, "HERE", tmpdir): + with self.assertWarns(UserWarning) as cm: + fake_pytest.main() + assert "Fake pytest module was used" in str(cm.warning) + assert os.path.isfile(os.path.join(tmpdir, "hello.txt")) + class TestTestingUtils(PsutilTestCase): def test_process_namespace(self): From cf928eac858547c8e0ff01bfc5e79efceaa045db Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 6 Oct 2024 00:20:50 +0200 Subject: [PATCH 12/17] add test case --- psutil/tests/__init__.py | 7 +++++++ psutil/tests/test_testutils.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 83548fd22..891304c3e 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -966,6 +966,13 @@ def context(exc, match=None): return context(exc, match=match) + @staticmethod + def warns(warning, match=None): + """Mimics `pytest.warns`.""" + if match: + return unittest.TestCase().assertWarnsRegex(warning, match) + return unittest.TestCase().assertWarns(warning) + class mark: class xdist_group: """Mimics `@pytest.mark.xdist_group` decorator (no-op).""" diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index 2a340b467..ca9482e0b 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -16,6 +16,7 @@ import subprocess import textwrap import unittest +import warnings import psutil import psutil.tests @@ -494,11 +495,39 @@ def test_foo(self): pass """.format(tmpdir)).lstrip()) with mock.patch.object(psutil.tests, "HERE", tmpdir): - with self.assertWarns(UserWarning) as cm: + with self.assertWarnsRegex( + UserWarning, "Fake pytest module was used" + ): fake_pytest.main() - assert "Fake pytest module was used" in str(cm.warning) assert os.path.isfile(os.path.join(tmpdir, "hello.txt")) + def test_warns(self): + # success + with fake_pytest.warns(UserWarning): + warnings.warn("foo", UserWarning, stacklevel=1) + + # failure + try: + with fake_pytest.warns(UserWarning): + warnings.warn("foo", DeprecationWarning, stacklevel=1) + except AssertionError: + pass + else: + raise self.fail("exception not raised") + + # match success + with fake_pytest.warns(UserWarning, match="foo"): + warnings.warn("foo", UserWarning, stacklevel=1) + + # match failure + try: + with fake_pytest.warns(UserWarning, match="foo"): + warnings.warn("bar", UserWarning, stacklevel=1) + except AssertionError: + pass + else: + raise self.fail("exception not raised") + class TestTestingUtils(PsutilTestCase): def test_process_namespace(self): From 845c28dc20195057c5f3729f0417605db7638399 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 6 Oct 2024 01:12:23 +0200 Subject: [PATCH 13/17] change call_until() signature so that it can be used with lambda --- psutil/tests/__init__.py | 10 ++++------ psutil/tests/test_linux.py | 6 +++--- psutil/tests/test_process.py | 7 ++++--- psutil/tests/test_testutils.py | 4 ++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 891304c3e..04be4f6f4 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -476,7 +476,7 @@ def spawn_zombie(): zpid = int(conn.recv(1024)) _pids_started.add(zpid) zombie = psutil.Process(zpid) - call_until(zombie.status, "ret == psutil.STATUS_ZOMBIE") + call_until(lambda: zombie.status() == psutil.STATUS_ZOMBIE) return (parent, zombie) finally: conn.close() @@ -796,12 +796,10 @@ def wait_for_file(fname, delete=True, empty=False): timeout=GLOBAL_TIMEOUT, interval=0.001, ) -def call_until(fun, expr): - """Keep calling function for timeout secs and exit if eval() - expression is True. - """ +def call_until(fun): + """Keep calling function until it evaluates to True.""" ret = fun() - assert eval(expr) # noqa + assert ret return ret diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index 5543accf3..788fe9914 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -1952,7 +1952,7 @@ def test_open_files_file_gone(self): files = p.open_files() with open(self.get_testfn(), 'w'): # give the kernel some time to see the new file - call_until(p.open_files, "len(ret) != %i" % len(files)) + call_until(lambda: len(p.open_files()) != len(files)) with mock.patch( 'psutil._pslinux.os.readlink', side_effect=OSError(errno.ENOENT, ""), @@ -1976,7 +1976,7 @@ def test_open_files_fd_gone(self): files = p.open_files() with open(self.get_testfn(), 'w'): # give the kernel some time to see the new file - call_until(p.open_files, "len(ret) != %i" % len(files)) + call_until(lambda: len(p.open_files()) != len(files)) patch_point = 'builtins.open' if PY3 else '__builtin__.open' with mock.patch( patch_point, side_effect=IOError(errno.ENOENT, "") @@ -1992,7 +1992,7 @@ def test_open_files_enametoolong(self): files = p.open_files() with open(self.get_testfn(), 'w'): # give the kernel some time to see the new file - call_until(p.open_files, "len(ret) != %i" % len(files)) + call_until(lambda: len(p.open_files()) != len(files)) patch_point = 'psutil._pslinux.os.readlink' with mock.patch( patch_point, side_effect=OSError(errno.ENAMETOOLONG, "") diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index 9e45b8053..b3e6569ee 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -987,7 +987,7 @@ def test_cwd_2(self): ), ] p = self.spawn_psproc(cmd) - call_until(p.cwd, "ret == os.path.dirname(os.getcwd())") + call_until(lambda: p.cwd() == os.path.dirname(os.getcwd())) @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported') def test_cpu_affinity(self): @@ -1074,7 +1074,8 @@ def test_open_files(self): f.write(b'x' * 1024) f.flush() # give the kernel some time to see the new file - files = call_until(p.open_files, "len(ret) != %i" % len(files)) + call_until(lambda: len(p.open_files()) != len(files)) + files = p.open_files() filenames = [os.path.normcase(x.path) for x in files] assert os.path.normcase(testfn) in filenames if LINUX: @@ -1398,7 +1399,7 @@ def assert_raises_nsp(fun, fun_name): p.terminate() p.wait() if WINDOWS: # XXX - call_until(psutil.pids, "%s not in ret" % p.pid) + call_until(lambda: p.pid not in psutil.pids()) self.assertProcessGone(p) ns = process_namespace(p) diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index ca9482e0b..4966bb0c1 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -169,8 +169,8 @@ def test_wait_for_file_no_delete(self): assert os.path.exists(testfn) def test_call_until(self): - ret = call_until(lambda: 1, "ret == 1") - assert ret == 1 + call_until(lambda: 1) + # TODO: test for timeout class TestFSTestUtils(PsutilTestCase): From ed1750fe319c2dcdea7949caf8d29939579d876d Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 6 Oct 2024 01:20:46 +0200 Subject: [PATCH 14/17] try to fix test --- psutil/tests/test_testutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index 4966bb0c1..904e9c0ab 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -499,7 +499,7 @@ def test_foo(self): UserWarning, "Fake pytest module was used" ): fake_pytest.main() - assert os.path.isfile(os.path.join(tmpdir, "hello.txt")) + call_until(lambda: os.path.isfile(os.path.join(tmpdir, "hello.txt"))) def test_warns(self): # success From 838107e89bc38477ac6bdbac0da0d6359fd1c865 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 6 Oct 2024 09:54:45 +0200 Subject: [PATCH 15/17] make test more reliable on win --- psutil/tests/__init__.py | 1 + psutil/tests/test_testutils.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index d3ea5f381..dd31392c0 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -939,6 +939,7 @@ def main(*args, **kw): # noqa ARG004 UserWarning, stacklevel=1, ) + return suite @staticmethod def raises(exc, match=None): diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index c20745e90..7293aa010 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -486,20 +486,18 @@ def test_main(self): pass with open(os.path.join(tmpdir, "test_file.py"), "w") as f: f.write(textwrap.dedent("""\ - import unittest, os + import unittest class TestCase(unittest.TestCase): - def test_foo(self): - path = os.path.join("{}", "hello.txt") - with open(path, "w") as f: - pass - """.format(tmpdir)).lstrip()) + def test_passed(self): + pass + """).lstrip()) with mock.patch.object(psutil.tests, "HERE", tmpdir): with self.assertWarnsRegex( UserWarning, "Fake pytest module was used" ): - fake_pytest.main() - call_until(lambda: os.path.isfile(os.path.join(tmpdir, "hello.txt"))) + suite = fake_pytest.main() + assert suite.countTestCases() == 1 def test_warns(self): # success From b062cbe3191d9a22cbd1e435ec75176b568c66e1 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 6 Oct 2024 10:40:46 +0200 Subject: [PATCH 16/17] update history --- HISTORY.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index d70cee6c9..38432ff4a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -14,6 +14,10 @@ XXXX-XX-XX targets. They can be used to install dependencies meant for running tests and for local development. They can also be installed via ``pip install .[test]`` and ``pip install .[dev]``. +- 2456_: allow to run tests via ``python3 -m psutil.tests`` even if ``pytest`` + module is not installed. This is useful for production environments that + don't have pytest installed, but still want to be able to test psutil + installation. **Bug fixes** From 38641621dc212be6f799599bd83bb11b46b40f56 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 6 Oct 2024 10:41:15 +0200 Subject: [PATCH 17/17] update ver --- HISTORY.rst | 2 +- psutil/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 38432ff4a..c854ef80b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,6 @@ *Bug tracker at https://github.com/giampaolo/psutil/issues* -6.0.1 (IN DEVELOPMENT) +6.1.0 (IN DEVELOPMENT) ====================== XXXX-XX-XX diff --git a/psutil/__init__.py b/psutil/__init__.py index c1e038f3e..763a5f00e 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -214,7 +214,7 @@ AF_LINK = _psplatform.AF_LINK __author__ = "Giampaolo Rodola'" -__version__ = "6.0.1" +__version__ = "6.1.0" version_info = tuple([int(num) for num in __version__.split('.')]) _timer = getattr(time, 'monotonic', time.time)