diff --git a/.github/workflows/bsd.yml b/.github/workflows/bsd.yml index efae0fc9b..3af8d2371 100644 --- a/.github/workflows/bsd.yml +++ b/.github/workflows/bsd.yml @@ -11,11 +11,11 @@ concurrency: cancel-in-progress: true jobs: freebsd: - runs-on: macos-12 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/freebsd-vm@v0 + uses: vmactions/freebsd-vm@v1 with: usesh: true prepare: | @@ -28,11 +28,11 @@ jobs: make test make test-memleaks openbsd: - runs-on: macos-12 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/openbsd-vm@v0 + uses: vmactions/openbsd-vm@v1 with: usesh: true prepare: | @@ -46,16 +46,16 @@ jobs: make test make test-memleaks netbsd: - runs-on: macos-12 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/netbsd-vm@v0 + uses: vmactions/netbsd-vm@v1 with: usesh: true prepare: | set -e - pkg_add -v pkgin + /usr/sbin/pkg_add -v pkgin pkgin update pkgin -y install python311-* py311-setuptools-* gcc12-* run: | diff --git a/HISTORY.rst b/HISTORY.rst index 30f4338a4..46591705b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,5 +1,11 @@ *Bug tracker at https://github.com/giampaolo/psutil/issues* +5.9.8 (IN DEVELOPMENT) +====================== + +- 2340_, [NetBSD]: if process is terminated, `Process.cwd()`_ will return an + empty string instead of raising `NoSuchProcess`_. + 5.9.7 ===== diff --git a/psutil/__init__.py b/psutil/__init__.py index 0c702fc76..c4686ae2a 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -211,7 +211,7 @@ AF_LINK = _psplatform.AF_LINK __author__ = "Giampaolo Rodola'" -__version__ = "5.9.7" +__version__ = "5.9.8" version_info = tuple([int(num) for num in __version__.split('.')]) _timer = getattr(time, 'monotonic', time.time) diff --git a/psutil/arch/netbsd/proc.c b/psutil/arch/netbsd/proc.c index 1b05d61ad..300f81753 100644 --- a/psutil/arch/netbsd/proc.c +++ b/psutil/arch/netbsd/proc.c @@ -115,13 +115,10 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { ssize_t len = readlink(buf, path, sizeof(path) - 1); free(buf); if (len == -1) { - if (errno == ENOENT) { - psutil_debug("sysctl(KERN_PROC_CWD) -> ENOENT converted to ''"); - return Py_BuildValue("s", ""); - } - else { + if (errno == ENOENT) + NoSuchProcess("sysctl -> ENOENT"); + else PyErr_SetFromErrno(PyExc_OSError); - } return NULL; } path[len] = '\0'; diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index a524bc42b..00899ba97 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -96,6 +96,7 @@ 'unittest', 'skip_on_access_denied', 'skip_on_not_implemented', 'retry_on_failure', 'TestMemoryLeak', 'PsutilTestCase', 'process_namespace', 'system_namespace', 'print_sysinfo', + 'is_win_secure_system_proc', # fs utils 'chdir', 'safe_rmpath', 'create_exe', 'get_testfn', # os @@ -1301,6 +1302,31 @@ def print_sysinfo(): print("=" * 70, file=sys.stderr) # NOQA sys.stdout.flush() + if WINDOWS: + os.system("tasklist") + elif which("ps"): + os.system("ps aux") + print("=" * 70, file=sys.stderr) # NOQA + sys.stdout.flush() + + +def is_win_secure_system_proc(pid): + # see: https://github.com/giampaolo/psutil/issues/2338 + @memoize + def get_procs(): + ret = {} + out = sh("tasklist.exe /NH /FO csv") + for line in out.splitlines()[1:]: + bits = [x.replace('"', "") for x in line.split(",")] + name, pid = bits[0], int(bits[1]) + ret[pid] = name + return ret + + try: + return get_procs()[pid] == "Secure System" + except KeyError: + return False + def _get_eligible_cpu(): p = psutil.Process() diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index f8d771694..1917d7051 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -36,7 +36,6 @@ from psutil._compat import long from psutil._compat import range from psutil._compat import unicode -from psutil.tests import APPVEYOR from psutil.tests import CI_TESTING from psutil.tests import GITHUB_ACTIONS from psutil.tests import HAS_CPU_FREQ @@ -52,6 +51,7 @@ from psutil.tests import create_sockets from psutil.tests import enum from psutil.tests import is_namedtuple +from psutil.tests import is_win_secure_system_proc from psutil.tests import kernel_version from psutil.tests import process_namespace from psutil.tests import serialrun @@ -447,8 +447,8 @@ def test_all(self): meth(value, info) except Exception: s = '\n' + '=' * 70 + '\n' - s += "FAIL: name=test_%s, pid=%s, ret=%s\n" % ( - name, info['pid'], repr(value)) + s += "FAIL: name=test_%s, pid=%s, ret=%s\ninfo=%s\n" % ( + name, info['pid'], repr(value), info) s += '-' * 70 s += "\n%s" % traceback.format_exc() s = "\n".join((" " * 4) + i for i in s.splitlines()) + "\n" @@ -494,11 +494,12 @@ def ppid(self, ret, info): def name(self, ret, info): self.assertIsInstance(ret, (str, unicode)) - if APPVEYOR and not ret and info['status'] == 'stopped': + if WINDOWS and not ret and is_win_secure_system_proc(info['pid']): + # https://github.com/giampaolo/psutil/issues/2338 return # on AIX, "" processes don't have names if not AIX: - assert ret + assert ret, repr(ret) def create_time(self, ret, info): self.assertIsInstance(ret, float) @@ -567,7 +568,8 @@ def ionice(self, ret, info): def num_threads(self, ret, info): self.assertIsInstance(ret, int) - if APPVEYOR and not ret and info['status'] == 'stopped': + if WINDOWS and ret == 0 and is_win_secure_system_proc(info['pid']): + # https://github.com/giampaolo/psutil/issues/2338 return self.assertGreaterEqual(ret, 1) diff --git a/psutil/tests/test_memleaks.py b/psutil/tests/test_memleaks.py index 56b219d6f..707f311ef 100755 --- a/psutil/tests/test_memleaks.py +++ b/psutil/tests/test_memleaks.py @@ -25,6 +25,7 @@ import psutil._common from psutil import LINUX from psutil import MACOS +from psutil import NETBSD from psutil import OPENBSD from psutil import POSIX from psutil import SUNOS @@ -249,6 +250,7 @@ def test_rlimit_set(self): # Windows implementation is based on a single system-wide # function (tested later). @unittest.skipIf(WINDOWS, "worthless on WINDOWS") + @unittest.skipIf(NETBSD, "critically broken on NETBSD (#930)") def test_connections(self): # TODO: UNIX sockets are temporarily implemented by parsing # 'pfiles' cmd output; we don't want that part of the code to @@ -422,6 +424,7 @@ def test_net_io_counters(self): @fewtimes_if_linux() @unittest.skipIf(MACOS and os.getuid() != 0, "need root access") + @unittest.skipIf(NETBSD, "critically broken on NETBSD (#930)") def test_net_connections(self): # always opens and handle on Windows() (once) psutil.net_connections(kind='all') diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index 6f804d260..c5dabab5a 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -765,7 +765,8 @@ def test_name(self): def test_long_name(self): testfn = self.get_testfn(suffix="0123456789" * 2) create_exe(testfn) - p = self.spawn_psproc(testfn) + cmdline = [testfn] + (["0123456789"] * 20) + p = self.spawn_psproc(cmdline) if OPENBSD: # XXX: for some reason the test process may turn into a # zombie (don't know why). Because the name() is long, all @@ -838,9 +839,6 @@ def test_nice(self): init = p.nice() try: if WINDOWS: - # A CI runner may limit our maximum priority, which will break - # this test. Instead, we test in order of increasing priority, - # and match either the expected value or the highest so far. highest_prio = None for prio in [psutil.IDLE_PRIORITY_CLASS, psutil.BELOW_NORMAL_PRIORITY_CLASS, @@ -855,10 +853,16 @@ def test_nice(self): pass else: new_prio = p.nice() - if CI_TESTING: + # The OS may limit our maximum priority, + # even if the function succeeds. For higher + # priorities, we match either the expected + # value or the highest so far. + if prio in (psutil.ABOVE_NORMAL_PRIORITY_CLASS, + psutil.HIGH_PRIORITY_CLASS, + psutil.REALTIME_PRIORITY_CLASS): if new_prio == prio or highest_prio is None: highest_prio = prio - self.assertEqual(new_prio, highest_prio) + self.assertEqual(new_prio, highest_prio) else: self.assertEqual(new_prio, prio) else: diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index ee52ecbf7..fc8dc8a4e 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -212,19 +212,20 @@ def test_users(self): users = psutil.users() self.assertNotEqual(users, []) for user in users: - assert user.name, user - self.assertIsInstance(user.name, str) - self.assertIsInstance(user.terminal, (str, type(None))) - if user.host is not None: - self.assertIsInstance(user.host, (str, type(None))) - user.terminal # noqa - user.host # noqa - assert user.started > 0.0, user - datetime.datetime.fromtimestamp(user.started) - if WINDOWS or OPENBSD: - self.assertIsNone(user.pid) - else: - psutil.Process(user.pid) + with self.subTest(user=user): + assert user.name + self.assertIsInstance(user.name, str) + self.assertIsInstance(user.terminal, (str, type(None))) + if user.host is not None: + self.assertIsInstance(user.host, (str, type(None))) + user.terminal # noqa + user.host # noqa + self.assertGreater(user.started, 0.0) + datetime.datetime.fromtimestamp(user.started) + if WINDOWS or OPENBSD: + self.assertIsNone(user.pid) + else: + psutil.Process(user.pid) def test_test(self): # test for psutil.test() function @@ -434,15 +435,20 @@ def test_per_cpu_times_2(self): if difference >= 0.05: return + @unittest.skipIf(CI_TESTING and OPENBSD, "unreliable on OPENBSD + CI") def test_cpu_times_comparison(self): # Make sure the sum of all per cpu times is almost equal to - # base "one cpu" times. + # base "one cpu" times. On OpenBSD the sum of per-CPUs is + # higher for some reason. base = psutil.cpu_times() per_cpu = psutil.cpu_times(percpu=True) summed_values = base._make([sum(num) for num in zip(*per_cpu)]) for field in base._fields: - self.assertAlmostEqual( - getattr(base, field), getattr(summed_values, field), delta=1) + with self.subTest(field=field, base=base, per_cpu=per_cpu): + self.assertAlmostEqual( + getattr(base, field), + getattr(summed_values, field), + delta=1) def _test_cpu_percent(self, percent, last_ret, new_ret): try: diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py index cf9500a3f..cfc451219 100755 --- a/psutil/tests/test_unicode.py +++ b/psutil/tests/test_unicode.py @@ -84,6 +84,7 @@ from psutil import POSIX from psutil import WINDOWS from psutil._compat import PY3 +from psutil._compat import super from psutil._compat import u from psutil.tests import APPVEYOR from psutil.tests import ASCII_FS @@ -158,10 +159,18 @@ def try_unicode(suffix): class BaseUnicodeTest(PsutilTestCase): funky_suffix = None + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.skip_tests = False + if cls.funky_suffix is not None: + if not try_unicode(cls.funky_suffix): + cls.skip_tests = True + def setUp(self): - if self.funky_suffix is not None: - if not try_unicode(self.funky_suffix): - raise self.skipTest("can't handle unicode str") + super().setUp() + if self.skip_tests: + raise self.skipTest("can't handle unicode str") @serialrun @@ -174,11 +183,13 @@ class TestFSAPIs(BaseUnicodeTest): @classmethod def setUpClass(cls): + super().setUpClass() cls.funky_name = get_testfn(suffix=cls.funky_suffix) create_exe(cls.funky_name) @classmethod def tearDownClass(cls): + super().tearDownClass() safe_rmpath(cls.funky_name) def expect_exact_path_match(self): @@ -192,7 +203,8 @@ def expect_exact_path_match(self): # --- def test_proc_exe(self): - subp = self.spawn_testproc(cmd=[self.funky_name]) + cmd = [self.funky_name, "-c", "time.sleep(10)"] + subp = self.spawn_testproc(cmd) p = psutil.Process(subp.pid) exe = p.exe() self.assertIsInstance(exe, str) @@ -201,20 +213,23 @@ def test_proc_exe(self): os.path.normcase(self.funky_name)) def test_proc_name(self): - subp = self.spawn_testproc(cmd=[self.funky_name]) + cmd = [self.funky_name, "-c", "time.sleep(10)"] + subp = self.spawn_testproc(cmd) name = psutil.Process(subp.pid).name() self.assertIsInstance(name, str) if self.expect_exact_path_match(): self.assertEqual(name, os.path.basename(self.funky_name)) def test_proc_cmdline(self): - subp = self.spawn_testproc(cmd=[self.funky_name]) + cmd = [self.funky_name, "-c", "time.sleep(10)"] + subp = self.spawn_testproc(cmd) p = psutil.Process(subp.pid) cmdline = p.cmdline() for part in cmdline: self.assertIsInstance(part, str) if self.expect_exact_path_match(): - self.assertEqual(cmdline, [self.funky_name]) + self.assertEqual( + cmdline, [self.funky_name, "-c", "time.sleep(10)"]) def test_proc_cwd(self): dname = self.funky_name + "2" diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py index 931a2c001..890a3a415 100755 --- a/scripts/internal/winmake.py +++ b/scripts/internal/winmake.py @@ -442,7 +442,7 @@ def test_by_name(name): sh("%s -m unittest -v %s" % (PYTHON, name)) -def test_failed(): +def test_last_failed(): """Re-run tests which failed on last run.""" build() sh("%s %s --last-failed" % (PYTHON, RUNNER_PY)) @@ -559,7 +559,8 @@ def parse_args(): test_by_name = sp.add_parser('test-by-name', help=" run test by name") sp.add_parser('test-connections', help="run connections tests") sp.add_parser('test-contracts', help="run contracts tests") - sp.add_parser('test-failed', help="re-run tests which failed on last run") + sp.add_parser('test-last-failed', + help="re-run tests which failed on last run") sp.add_parser('test-memleaks', help="run memory leaks tests") sp.add_parser('test-misc', help="run misc tests") sp.add_parser('test-platform', help="run windows only tests")