Repository: giampaolo/psutil
Pull request #2375 (2024-02-09 20:23:53)

chore: build macOS arm64 wheels on macos-14
## Summary

* OS: macOS
* Bug fix: no
* Type: wheels
* Fixes:

## Description

macos-14 runners are running on arm64 processors.
This allows to test macOS arm64 wheels.



MrMino: I believe this duplicates https://github.com/giampaolo/psutil/pull/2224

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8a264498..2c2ac100 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -18,7 +18,7 @@ concurrency:
 jobs:
   # Linux + macOS + Windows Python 3
   py3:
-    name: py3-${{ matrix.os }}-${{ startsWith(matrix.os, 'windows') && matrix.archs || 'all' }}
+    name: py3-${{ matrix.os }}-${{ startsWith(matrix.os, 'ubuntu') && 'all' || matrix.archs }}
     runs-on: ${{ matrix.os }}
     timeout-minutes: 30
     strategy:
@@ -28,7 +28,9 @@ jobs:
         - os: ubuntu-latest
           archs: "x86_64 i686"
         - os: macos-12
-          archs: "x86_64 arm64"
+          archs: "x86_64"
+        - os: macos-14
+          archs: "arm64"
         - os: windows-2019
           archs: "AMD64"
         - os: windows-2019
@@ -40,7 +42,7 @@ jobs:
         python-version: 3.11
 
     - name: Create wheels + run tests
-      uses: pypa/cibuildwheel@v2.16.5
+      uses: pypa/cibuildwheel@v2.17.0
       env:
         CIBW_ARCHS: "${{ matrix.archs }}"
         CIBW_PRERELEASE_PYTHONS: True
diff --git a/CREDITS b/CREDITS
index a3206bb9..3520723d 100644
--- a/CREDITS
+++ b/CREDITS
@@ -824,4 +824,8 @@ I: 2361
 
 N: Shade Gladden
 W: https://github.com/shadeyg56
-I: 2376
\ No newline at end of file
+I: 2376
+
+N: Anthony Ryan
+W: https://github.com/anthonyryan1
+I: 2272
diff --git a/HISTORY.rst b/HISTORY.rst
index ed422337..1d82aefe 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -11,6 +11,7 @@
 
 - 2360_, [macOS]: can't compile on macOS < 10.13.  (patch by Ryan Schmidt)
 - 2254_, [Linux]: offline cpus raise NotImplementedError in cpu_freq() (patch by Shade Gladden)
+- 2272_, Add pickle support to psutil Exceptions
 
 5.9.8
 =====
diff --git a/Makefile b/Makefile
index 1ab66011..a1fdf6fd 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,7 @@
 
 # Configurable.
 PYTHON = python3
+PYTHON_ENV_VARS = PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1
 ARGS =
 TSCRIPT = psutil/tests/runner.py
 
@@ -47,7 +48,6 @@ BUILD_OPTS = `$(PYTHON) -c \
 # In not in a virtualenv, add --user options for install commands.
 INSTALL_OPTS = `$(PYTHON) -c \
 	"import sys; print('' if hasattr(sys, 'real_prefix') or hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix else '--user')"`
-TEST_PREFIX = PSUTIL_SCRIPTS_DIR=`pwd`/scripts PYTHONWARNINGS=always PSUTIL_DEBUG=1
 
 # if make is invoked with no arg, default to `make help`
 .DEFAULT_GOAL := help
@@ -87,17 +87,17 @@ build:  ## Compile (in parallel) without installing.
 	@# "build_ext -i" copies compiled *.so files in ./psutil directory in order
 	@# to allow "import psutil" when using the interactive interpreter from
 	@# within  this directory.
-	PYTHONWARNINGS=all $(PYTHON) setup.py build_ext -i $(BUILD_OPTS)
-	$(PYTHON) -c "import psutil"  # make sure it actually worked
+	$(PYTHON_ENV_VARS) $(PYTHON) setup.py build_ext -i $(BUILD_OPTS)
+	$(PYTHON_ENV_VARS) $(PYTHON) -c "import psutil"  # make sure it actually worked
 
 install:  ## Install this package as current user in "edit" mode.
 	${MAKE} build
-	PYTHONWARNINGS=all $(PYTHON) setup.py develop $(INSTALL_OPTS)
-	$(PYTHON) -c "import psutil"  # make sure it actually worked
+	$(PYTHON_ENV_VARS) $(PYTHON) setup.py develop $(INSTALL_OPTS)
+	$(PYTHON_ENV_VARS) $(PYTHON) -c "import psutil"  # make sure it actually worked
 
 uninstall:  ## Uninstall this package via pip.
-	cd ..; $(PYTHON) -m pip uninstall -y -v psutil || true
-	$(PYTHON) scripts/internal/purge_installation.py
+	cd ..; $(PYTHON_ENV_VARS) $(PYTHON) -m pip uninstall -y -v psutil || true
+	$(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/purge_installation.py
 
 install-pip:  ## Install pip (no-op if already installed).
 	@$(PYTHON) -c \
@@ -123,8 +123,8 @@ install-pip:  ## Install pip (no-op if already installed).
 setup-dev-env:  ## Install GIT hooks, pip, test deps (also upgrades them).
 	${MAKE} install-git-hooks
 	${MAKE} install-pip
-	$(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade pip
-	$(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade $(PY_DEPS)
+	$(PYTHON_ENV_VARS) $(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade pip
+	$(PYTHON_ENV_VARS) $(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade $(PY_DEPS)
 
 # ===================================================================
 # Tests
@@ -132,65 +132,65 @@ setup-dev-env:  ## Install GIT hooks, pip, test deps (also upgrades them).
 
 test:  ## Run all tests. To run a specific test do "make test ARGS=psutil.tests.test_system.TestDiskAPIs"
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS)
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS)
 
 test-parallel:  ## Run all tests in parallel.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) --parallel
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) --parallel
 
 test-process:  ## Run process-related API tests.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_process.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_process.py
 
 test-process-all:  ## Run tests which iterate over all process PIDs.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_process_all.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_process_all.py
 
 test-system:  ## Run system-related API tests.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_system.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_system.py
 
 test-misc:  ## Run miscellaneous tests.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_misc.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_misc.py
 
 test-testutils:  ## Run test utils tests.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_testutils.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_testutils.py
 
 test-unicode:  ## Test APIs dealing with strings.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_unicode.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_unicode.py
 
 test-contracts:  ## APIs sanity tests.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_contracts.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_contracts.py
 
 test-connections:  ## Test net_connections() and Process.connections().
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_connections.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_connections.py
 
 test-posix:  ## POSIX specific tests.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_posix.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_posix.py
 
 test-platform:  ## Run specific platform tests only.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_`$(PYTHON) -c 'import psutil; print([x.lower() for x in ("LINUX", "BSD", "OSX", "SUNOS", "WINDOWS", "AIX") if getattr(psutil, x)][0])'`.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_`$(PYTHON) -c 'import psutil; print([x.lower() for x in ("LINUX", "BSD", "OSX", "SUNOS", "WINDOWS", "AIX") if getattr(psutil, x)][0])'`.py
 
 test-memleaks:  ## Memory leak tests.
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_memleaks.py
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_memleaks.py
 
 test-last-failed:  ## Re-run tests which failed on last run
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) --last-failed
+	$(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) --last-failed
 
 test-coverage:  ## Run test coverage.
 	${MAKE} build
 	# Note: coverage options are controlled by .coveragerc file
 	rm -rf .coverage htmlcov
-	$(TEST_PREFIX) $(PYTHON) -m coverage run -m unittest -v
+	$(PYTHON_ENV_VARS) $(PYTHON) -m coverage run -m unittest -v
 	$(PYTHON) -m coverage report
 	@echo "writing results to htmlcov/index.html"
 	$(PYTHON) -m coverage html
@@ -210,7 +210,7 @@ _pylint:  ## Python pylint (not mandatory, just run it from time to time)
 	@git ls-files '*.py' | xargs $(PYTHON) -m pylint --rcfile=pyproject.toml --jobs=${NUM_WORKERS}
 
 lint-c:  ## Run C linter.
-	@git ls-files '*.c' '*.h' | xargs $(PYTHON) scripts/internal/clinter.py
+	@git ls-files '*.c' '*.h' | xargs $(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/clinter.py
 
 lint-rst:  ## Run C linter.
 	@git ls-files '*.rst' | xargs rstcheck --config=pyproject.toml
@@ -261,20 +261,20 @@ install-git-hooks:  ## Install GIT pre-commit hook.
 
 sdist:  ## Create tar.gz source distribution.
 	${MAKE} generate-manifest
-	PYTHONWARNINGS=all $(PYTHON) setup.py sdist
+	$(PYTHON_ENV_VARS) $(PYTHON) setup.py sdist
 
 download-wheels-github:  ## Download latest wheels hosted on github.
-	$(PYTHON) scripts/internal/download_wheels_github.py --tokenfile=~/.github.token
+	$(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/download_wheels_github.py --tokenfile=~/.github.token
 	${MAKE} print-dist
 
 download-wheels-appveyor:  ## Download latest wheels hosted on appveyor.
-	$(PYTHON) scripts/internal/download_wheels_appveyor.py
+	$(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/download_wheels_appveyor.py
 	${MAKE} print-dist
 
 check-sdist:  ## Check sanity of source distribution.
-	$(PYTHON) -m virtualenv --clear --no-wheel --quiet build/venv
-	build/venv/bin/python -m pip install -v --isolated --quiet dist/*.tar.gz
-	build/venv/bin/python -c "import os; os.chdir('build/venv'); import psutil"
+	$(PYTHON_ENV_VARS) $(PYTHON) -m virtualenv --clear --no-wheel --quiet build/venv
+	$(PYTHON_ENV_VARS) build/venv/bin/python -m pip install -v --isolated --quiet dist/*.tar.gz
+	$(PYTHON_ENV_VARS) build/venv/bin/python -c "import os; os.chdir('build/venv'); import psutil"
 	$(PYTHON) -m twine check --strict dist/*.tar.gz
 
 check-wheels:  ## Check sanity of wheels.
@@ -335,11 +335,11 @@ print-timeline:  ## Print releases' timeline.
 
 print-access-denied: ## Print AD exceptions
 	${MAKE} build
-	@$(TEST_PREFIX) $(PYTHON) scripts/internal/print_access_denied.py
+	@$(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/print_access_denied.py
 
 print-api-speed:  ## Benchmark all API calls
 	${MAKE} build
-	@$(TEST_PREFIX) $(PYTHON) scripts/internal/print_api_speed.py $(ARGS)
+	@$(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/print_api_speed.py $(ARGS)
 
 print-downloads:  ## Print PYPI download statistics
 	$(PYTHON) scripts/internal/print_downloads.py
@@ -356,11 +356,11 @@ grep-todos:  ## Look for TODOs in the source files.
 
 bench-oneshot:  ## Benchmarks for oneshot() ctx manager (see #799).
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) scripts/internal/bench_oneshot.py
+	$(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/bench_oneshot.py
 
 bench-oneshot-2:  ## Same as above but using perf module (supposed to be more precise)
 	${MAKE} build
-	$(TEST_PREFIX) $(PYTHON) scripts/internal/bench_oneshot_2.py
+	$(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/bench_oneshot_2.py
 
 check-broken-links:  ## Look for broken links in source files.
 	git ls-files | xargs $(PYTHON) -Wa scripts/internal/check_broken_links.py
diff --git a/psutil/_common.py b/psutil/_common.py
index 6989feaf..f33f3e03 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -331,6 +331,9 @@ class NoSuchProcess(Error):
         self.name = name
         self.msg = msg or "process no longer exists"
 
+    def __reduce__(self):
+        return (self.__class__, (self.pid, self.name, self.msg))
+
 
 class ZombieProcess(NoSuchProcess):
     """Exception raised when querying a zombie process. This is
@@ -347,6 +350,9 @@ class ZombieProcess(NoSuchProcess):
         self.ppid = ppid
         self.msg = msg or "PID still exists but it's a zombie"
 
+    def __reduce__(self):
+        return (self.__class__, (self.pid, self.name, self.ppid, self.msg))
+
 
 class AccessDenied(Error):
     """Exception raised when permission to perform an action is denied."""
@@ -359,6 +365,9 @@ class AccessDenied(Error):
         self.name = name
         self.msg = msg or ""
 
+    def __reduce__(self):
+        return (self.__class__, (self.pid, self.name, self.msg))
+
 
 class TimeoutExpired(Error):
     """Raised on Process.wait(timeout) if timeout expires and process
@@ -374,6 +383,9 @@ class TimeoutExpired(Error):
         self.name = name
         self.msg = "timeout after %s seconds" % seconds
 
+    def __reduce__(self):
+        return (self.__class__, (self.seconds, self.pid, self.name))
+
 
 # ===================================================================
 # --- utils
diff --git a/psutil/arch/osx/net.c b/psutil/arch/osx/net.c
index 24ce1b83..d365676c 100644
--- a/psutil/arch/osx/net.c
+++ b/psutil/arch/osx/net.c
@@ -9,11 +9,11 @@
 // https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_osx.c
 
 #include <Python.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <net/route.h>
-#include <sys/sysctl.h>
-#include <sys/socket.h>
 
 #include "../../_psutil_common.h"
 
diff --git a/pyproject.toml b/pyproject.toml
index 0625af1f..94266a7b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,11 +8,11 @@ enable-unstable-feature = ["hug_parens_with_braces_and_square_brackets", "multil
 
 [tool.ruff]
 # https://beta.ruff.rs/docs/settings/
-preview = true
 target-version = "py37"
 line-length = 79
 
 [tool.ruff.lint]
+preview = true
 select = [
     # To get a list of all values: `python3 -m ruff linter`.
     "ALL",


Test patch:
diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py
index 7b502bcb..a714632d 100755
--- a/psutil/tests/test_bsd.py
+++ b/psutil/tests/test_bsd.py
@@ -267,7 +267,7 @@ class FreeBSDSystemTestCase(PsutilTestCase):
         try:
             sysctl_result = int(sysctl(sensor))
         except RuntimeError:
-            self.skipTest("frequencies not supported by kernel")
+            raise unittest.SkipTest("frequencies not supported by kernel")
         self.assertEqual(psutil.cpu_freq().current, sysctl_result)
 
         sensor = "dev.cpu.0.freq_levels"
@@ -500,7 +500,7 @@ class FreeBSDSystemTestCase(PsutilTestCase):
             try:
                 sysctl_result = int(float(sysctl(sensor)[:-1]))
             except RuntimeError:
-                self.skipTest("temperatures not supported by kernel")
+                raise unittest.SkipTest("temperatures not supported by kernel")
             self.assertAlmostEqual(
                 psutil.sensors_temperatures()["coretemp"][cpu].current,
                 sysctl_result,
diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py
index 4736f5f1..b2bfe29d 100755
--- a/psutil/tests/test_contracts.py
+++ b/psutil/tests/test_contracts.py
@@ -239,7 +239,7 @@ class TestSystemAPITypes(PsutilTestCase):
     @unittest.skipIf(not HAS_CPU_FREQ, "not supported")
     def test_cpu_freq(self):
         if psutil.cpu_freq() is None:
-            raise self.skipTest("cpu_freq() returns None")
+            raise unittest.SkipTest("cpu_freq() returns None")
         self.assert_ntuple_of_nums(psutil.cpu_freq(), type_=(float, int, long))
 
     def test_disk_io_counters(self):
diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py
index af27095a..818a6be2 100755
--- a/psutil/tests/test_linux.py
+++ b/psutil/tests/test_linux.py
@@ -278,7 +278,7 @@ class TestSystemVirtualMemoryAgainstFree(PsutilTestCase):
         # https://gitlab.com/procps-ng/procps/commit/
         #     05d751c4f076a2f0118b914c5e51cfbb4762ad8e
         if get_free_version_info() < (3, 3, 12):
-            raise self.skipTest("old free version")
+            raise unittest.SkipTest("old free version")
         cli_value = free_physmem().used
         psutil_value = psutil.virtual_memory().used
         self.assertAlmostEqual(
@@ -342,7 +342,7 @@ class TestSystemVirtualMemoryAgainstVmstat(PsutilTestCase):
         # https://gitlab.com/procps-ng/procps/commit/
         #     05d751c4f076a2f0118b914c5e51cfbb4762ad8e
         if get_free_version_info() < (3, 3, 12):
-            raise self.skipTest("old free version")
+            raise unittest.SkipTest("old free version")
         vmstat_value = vmstat('used memory') * 1024
         psutil_value = psutil.virtual_memory().used
         self.assertAlmostEqual(
diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py
index 700c054f..93204fa0 100755
--- a/psutil/tests/test_misc.py
+++ b/psutil/tests/test_misc.py
@@ -15,11 +15,11 @@ import os
 import pickle
 import socket
 import stat
+import sys
 import unittest
 
 import psutil
 import psutil.tests
-from psutil import LINUX
 from psutil import POSIX
 from psutil import WINDOWS
 from psutil._common import bcat
@@ -34,7 +34,6 @@ from psutil._common import wrap_numbers
 from psutil._compat import PY3
 from psutil._compat import FileNotFoundError
 from psutil._compat import redirect_stderr
-from psutil.tests import APPVEYOR
 from psutil.tests import CI_TESTING
 from psutil.tests import HAS_BATTERY
 from psutil.tests import HAS_MEMORY_MAPS
@@ -47,8 +46,10 @@ from psutil.tests import PYTHON_EXE_ENV
 from psutil.tests import SCRIPTS_DIR
 from psutil.tests import PsutilTestCase
 from psutil.tests import mock
+from psutil.tests import process_namespace
 from psutil.tests import reload_module
 from psutil.tests import sh
+from psutil.tests import system_namespace
 
 
 # ===================================================================
@@ -259,33 +260,88 @@ class TestMisc(PsutilTestCase):
 
     def test_serialization(self):
         def check(ret):
-            if json is not None:
-                json.loads(json.dumps(ret))
+            json.loads(json.dumps(ret))
+
             a = pickle.dumps(ret)
             b = pickle.loads(a)
             self.assertEqual(ret, b)
 
+        # --- process APIs
+
+        proc = psutil.Process()
         check(psutil.Process().as_dict())
-        check(psutil.virtual_memory())
-        check(psutil.swap_memory())
-        check(psutil.cpu_times())
-        check(psutil.cpu_times_percent(interval=0))
-        check(psutil.net_io_counters())
-        if LINUX and not os.path.exists('/proc/diskstats'):
-            pass
-        else:
-            if not APPVEYOR:
-                check(psutil.disk_io_counters())
-        check(psutil.disk_partitions())
-        check(psutil.disk_usage(os.getcwd()))
-        check(psutil.users())
+
+        ns = process_namespace(proc)
+        for fun, name in ns.iter(ns.getters, clear_cache=True):
+            with self.subTest(proc=proc, name=name):
+                try:
+                    ret = fun()
+                except psutil.Error:
+                    pass
+                else:
+                    check(ret)
+
+        # --- system APIs
+
+        ns = system_namespace()
+        for fun, name in ns.iter(ns.getters):
+            if name in {"win_service_iter", "win_service_get"}:
+                continue
+            with self.subTest(name=name):
+                try:
+                    ret = fun()
+                except psutil.AccessDenied:
+                    pass
+                else:
+                    check(ret)
+
+        # --- exception classes
+
+        b = pickle.loads(
+            pickle.dumps(
+                psutil.NoSuchProcess(pid=4567, name='name', msg='msg')
+            )
+        )
+        self.assertIsInstance(b, psutil.NoSuchProcess)
+        self.assertEqual(b.pid, 4567)
+        self.assertEqual(b.name, 'name')
+        self.assertEqual(b.msg, 'msg')
+
+        b = pickle.loads(
+            pickle.dumps(
+                psutil.ZombieProcess(pid=4567, name='name', ppid=42, msg='msg')
+            )
+        )
+        self.assertIsInstance(b, psutil.ZombieProcess)
+        self.assertEqual(b.pid, 4567)
+        self.assertEqual(b.ppid, 42)
+        self.assertEqual(b.name, 'name')
+        self.assertEqual(b.msg, 'msg')
+
+        b = pickle.loads(
+            pickle.dumps(psutil.AccessDenied(pid=123, name='name', msg='msg'))
+        )
+        self.assertIsInstance(b, psutil.AccessDenied)
+        self.assertEqual(b.pid, 123)
+        self.assertEqual(b.name, 'name')
+        self.assertEqual(b.msg, 'msg')
+
+        b = pickle.loads(
+            pickle.dumps(
+                psutil.TimeoutExpired(seconds=33, pid=4567, name='name')
+            )
+        )
+        self.assertIsInstance(b, psutil.TimeoutExpired)
+        self.assertEqual(b.seconds, 33)
+        self.assertEqual(b.pid, 4567)
+        self.assertEqual(b.name, 'name')
 
     # # XXX: https://github.com/pypa/setuptools/pull/2896
     # @unittest.skipIf(APPVEYOR, "temporarily disabled due to setuptools bug")
     # def test_setup_script(self):
     #     setup_py = os.path.join(ROOT_DIR, 'setup.py')
     #     if CI_TESTING and not os.path.exists(setup_py):
-    #         return self.skipTest("can't find setup.py")
+    #         raise unittest.SkipTest("can't find setup.py")
     #     module = import_module_by_path(setup_py)
     #     self.assertRaises(SystemExit, module.setup)
     #     self.assertEqual(module.get_version(), psutil.__version__)
@@ -577,6 +633,7 @@ class TestCommonModule(PsutilTestCase):
 
         with redirect_stderr(StringIO()) as f:
             debug("hello")
+            sys.stderr.flush()
         msg = f.getvalue()
         assert msg.startswith("psutil-debug"), msg
         self.assertIn("hello", msg)
@@ -850,7 +907,7 @@ class TestWrapNumbers(PsutilTestCase):
     @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported')
     def test_cache_clear_public_apis(self):
         if not psutil.disk_io_counters() or not psutil.net_io_counters():
-            return self.skipTest("no disks or NICs available")
+            raise unittest.SkipTest("no disks or NICs available")
         psutil.disk_io_counters()
         psutil.net_io_counters()
         caches = wrap_numbers.cache_info()
@@ -959,7 +1016,7 @@ class TestScripts(PsutilTestCase):
 
     def test_procsmem(self):
         if 'uss' not in psutil.Process().memory_full_info()._fields:
-            raise self.skipTest("not supported")
+            raise unittest.SkipTest("not supported")
         self.assert_stdout('procsmem.py')
 
     def test_killall(self):
@@ -988,13 +1045,13 @@ class TestScripts(PsutilTestCase):
     @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
     def test_temperatures(self):
         if not psutil.sensors_temperatures():
-            self.skipTest("no temperatures")
+            raise unittest.SkipTest("no temperatures")
         self.assert_stdout('temperatures.py')
 
     @unittest.skipIf(not HAS_SENSORS_FANS, "not supported")
     def test_fans(self):
         if not psutil.sensors_fans():
-            self.skipTest("no fans")
+            raise unittest.SkipTest("no fans")
         self.assert_stdout('fans.py')
 
     @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported")
diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py
index 53852cef..5203c270 100755
--- a/psutil/tests/test_posix.py
+++ b/psutil/tests/test_posix.py
@@ -342,7 +342,7 @@ class TestSystemAPIs(PsutilTestCase):
     def test_users(self):
         out = sh("who -u")
         if not out.strip():
-            raise self.skipTest("no users on this system")
+            raise unittest.SkipTest("no users on this system")
         lines = out.split('\n')
         users = [x.split()[0] for x in lines]
         terminals = [x.split()[1] for x in lines]
@@ -358,7 +358,7 @@ class TestSystemAPIs(PsutilTestCase):
     def test_users_started(self):
         out = sh("who -u")
         if not out.strip():
-            raise self.skipTest("no users on this system")
+            raise unittest.SkipTest("no users on this system")
         tstamp = None
         # '2023-04-11 09:31' (Linux)
         started = re.findall(r"\d\d\d\d-\d\d-\d\d \d\d:\d\d", out)
@@ -444,7 +444,7 @@ class TestSystemAPIs(PsutilTestCase):
                 out = sh("df -k %s" % device).strip()
             except RuntimeError as err:
                 if "device busy" in str(err).lower():
-                    raise self.skipTest("df returned EBUSY")
+                    raise unittest.SkipTest("df returned EBUSY")
                 raise
             line = out.split('\n')[1]
             fields = line.split()
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index 35beb41e..bfba9f2d 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -757,7 +757,7 @@ class TestProcess(PsutilTestCase):
             try:
                 self.assertEqual(p.cmdline(), cmdline)
             except psutil.ZombieProcess:
-                raise self.skipTest("OPENBSD: process turned into zombie")
+                raise unittest.SkipTest("OPENBSD: process turned into zombie")
         else:
             self.assertEqual(p.cmdline(), cmdline)
 
@@ -1165,7 +1165,7 @@ class TestProcess(PsutilTestCase):
         # this is the one, now let's make sure there are no duplicates
         pid = sorted(table.items(), key=lambda x: x[1])[-1][0]
         if LINUX and pid == 0:
-            raise self.skipTest("PID 0")
+            raise unittest.SkipTest("PID 0")
         p = psutil.Process(pid)
         try:
             c = p.children(recursive=True)
diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py
index 6656c19b..ce6786a4 100755
--- a/psutil/tests/test_system.py
+++ b/psutil/tests/test_system.py
@@ -355,7 +355,7 @@ class TestCpuAPIs(PsutilTestCase):
         logical = psutil.cpu_count()
         cores = psutil.cpu_count(logical=False)
         if cores is None:
-            raise self.skipTest("cpu_count_cores() is None")
+            raise unittest.SkipTest("cpu_count_cores() is None")
         if WINDOWS and sys.getwindowsversion()[:2] <= (6, 1):  # <= Vista
             self.assertIsNone(cores)
         else:
@@ -579,7 +579,7 @@ class TestCpuAPIs(PsutilTestCase):
 
         ls = psutil.cpu_freq(percpu=True)
         if FREEBSD and not ls:
-            raise self.skipTest("returns empty list on FreeBSD")
+            raise unittest.SkipTest("returns empty list on FreeBSD")
 
         assert ls, ls
         check_ls([psutil.cpu_freq(percpu=False)])
diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py
index d09003d2..cb4bccf7 100755
--- a/psutil/tests/test_unicode.py
+++ b/psutil/tests/test_unicode.py
@@ -175,7 +175,7 @@ class BaseUnicodeTest(PsutilTestCase):
     def setUp(self):
         super().setUp()
         if self.skip_tests:
-            raise self.skipTest("can't handle unicode str")
+            raise unittest.SkipTest("can't handle unicode str")
 
 
 @serialrun
@@ -246,7 +246,7 @@ class TestFSAPIs(BaseUnicodeTest):
         self.assertIsInstance(path, str)
         if BSD and not path:
             # XXX - see https://github.com/giampaolo/psutil/issues/595
-            return self.skipTest("open_files on BSD is broken")
+            raise unittest.SkipTest("open_files on BSD is broken")
         if self.expect_exact_path_match():
             self.assertEqual(
                 os.path.normcase(path), os.path.normcase(self.funky_name)