Repository: giampaolo/psutil
Pull request #2364 (2024-01-29 07:51:32)

Include CoreFoundation/CoreFoundation.h
## Summary

* OS: OS X 10.9.5
* Bug fix: yes
* Type: don't know
* Fixes: #2362

## Description

Include CoreFoundation/CoreFoundation.h

This is correct, since this file does use CoreFoundation types, and it is necessary because on old macOS versions IOKit/ps/IOPowerSources.h, which is already included and which also uses CoreFoundation types, forgot to include CoreFoundation/CoreFoundation.h.

giampaolo: Can you also update HISTORY and CREDITS files?

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 38c164e7..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.2
+      uses: pypa/cibuildwheel@v2.17.0
       env:
         CIBW_ARCHS: "${{ matrix.archs }}"
         CIBW_PRERELEASE_PYTHONS: True
diff --git a/CREDITS b/CREDITS
index ca584713..3520723d 100644
--- a/CREDITS
+++ b/CREDITS
@@ -821,3 +821,11 @@ I: 2222
 N: Ryan Carsten Schmidt
 W: https://github.com/ryandesign
 I: 2361
+
+N: Shade Gladden
+W: https://github.com/shadeyg56
+I: 2376
+
+N: Anthony Ryan
+W: https://github.com/anthonyryan1
+I: 2272
diff --git a/HISTORY.rst b/HISTORY.rst
index 3877d437..1d82aefe 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,9 +3,15 @@
 5.9.9 (IN DEVELOPMENT)
 ======================
 
+**Enhancements**
+
+- 2366_, [Windows]: log debug message when using slower process APIs.
+
 **Bug fixes**
 
 - 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 34279c90..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,27 +87,29 @@ 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 \
 		"import sys, ssl, os, pkgutil, tempfile, atexit; \
 		sys.exit(0) if pkgutil.find_loader('pip') else None; \
-		pyexc = 'from urllib.request import urlopen' if sys.version_info[0] == 3 else 'from urllib2 import urlopen'; \
+		PY3 = sys.version_info[0] == 3; \
+		pyexc = 'from urllib.request import urlopen' if PY3 else 'from urllib2 import urlopen'; \
 		exec(pyexc); \
 		ctx = ssl._create_unverified_context() if hasattr(ssl, '_create_unverified_context') else None; \
+		url = 'https://bootstrap.pypa.io/pip/2.7/get-pip.py' if not PY3 else 'https://bootstrap.pypa.io/get-pip.py'; \
 		kw = dict(context=ctx) if ctx else {}; \
-		req = urlopen('https://bootstrap.pypa.io/get-pip.py', **kw); \
+		req = urlopen(url, **kw); \
 		data = req.read(); \
 		f = tempfile.NamedTemporaryFile(suffix='.py'); \
 		atexit.register(f.close); \
@@ -121,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
@@ -130,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
@@ -208,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
@@ -259,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.
@@ -333,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
@@ -354,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/docs/conf.py b/docs/conf.py
index f2b05483..3ebc6417 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,5 +1,9 @@
 # -*- coding: utf-8 -*-
-#
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
 # psutil documentation build configuration file, created by
 # sphinx-quickstart on Wed Oct 19 21:54:30 2016.
 #
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/_compat.py b/psutil/_compat.py
index 3db56c60..6070c2a0 100644
--- a/psutil/_compat.py
+++ b/psutil/_compat.py
@@ -23,7 +23,7 @@ __all__ = [
     # builtins
     "long", "range", "super", "unicode", "basestring",
     # literals
-    "u", "b",
+    "b",
     # collections module
     "lru_cache",
     # shutil module
@@ -47,9 +47,6 @@ if PY3:
     basestring = str
     range = range
 
-    def u(s):
-        return s
-
     def b(s):
         return s.encode("latin-1")
 
@@ -59,9 +56,6 @@ else:
     unicode = unicode
     basestring = basestring
 
-    def u(s):
-        return unicode(s, "unicode_escape")
-
     def b(s):
         return s
 
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 798dd365..2a59bfe1 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -60,7 +60,6 @@ else:
 
 # fmt: off
 __extra__all__ = [
-    #
     'PROCFS_PATH',
     # io prio constants
     "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE",
@@ -780,6 +779,13 @@ if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or os.path.exists(
                 # https://github.com/giampaolo/psutil/issues/1071
                 curr = bcat(pjoin(path, "cpuinfo_cur_freq"), fallback=None)
                 if curr is None:
+                    online_path = (
+                        "/sys/devices/system/cpu/cpu{}/online".format(i)
+                    )
+                    # if cpu core is offline, set to all zeroes
+                    if cat(online_path, fallback=None) == "0\n":
+                        ret.append(_common.scpufreq(0.0, 0.0, 0.0))
+                        continue
                     msg = "can't find current frequency file"
                     raise NotImplementedError(msg)
             curr = int(curr) / 1000
@@ -1355,7 +1361,7 @@ def disk_partitions(all=False):
         if device in ("/dev/root", "rootfs"):
             device = RootFsDeviceFinder().find() or device
         if not all:
-            if device == '' or fstype not in fstypes:
+            if not device or fstype not in fstypes:
                 continue
         maxfile = maxpath = None  # set later
         ntuple = _common.sdiskpart(
@@ -2101,7 +2107,7 @@ class Process:
                         path
                     ):
                         path = path[:-10]
-                ls.append((
+                item = (
                     decode(addr),
                     decode(perms),
                     path,
@@ -2115,7 +2121,8 @@ class Process:
                     data.get(b'Referenced:', 0),
                     data.get(b'Anonymous:', 0),
                     data.get(b'Swap:', 0),
-                ))
+                )
+                ls.append(item)
             return ls
 
     @wrap_exceptions
diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py
index 2d3a0c9f..8db66f0a 100644
--- a/psutil/_pswindows.py
+++ b/psutil/_pswindows.py
@@ -239,7 +239,6 @@ def virtual_memory():
     """System virtual memory as a namedtuple."""
     mem = cext.virtual_mem()
     totphys, availphys, totsys, availsys = mem
-    #
     total = totphys
     avail = availphys
     free = availphys
@@ -515,7 +514,7 @@ def win_service_get(name):
     return service
 
 
-class WindowsService:
+class WindowsService:  # noqa: PLW1641
     """Represents an installed Windows service."""
 
     def __init__(self, name, display_name):
@@ -865,6 +864,7 @@ class Process:
             if is_permission_err(err):
                 # TODO: the C ext can probably be refactored in order
                 # to get this from cext.proc_info()
+                debug("attempting memory_info() fallback (slower)")
                 info = self._proc_info()
                 return (
                     info[pinfo_map['num_page_faults']],
@@ -992,6 +992,7 @@ class Process:
             return created
         except OSError as err:
             if is_permission_err(err):
+                debug("attempting create_time() fallback (slower)")
                 return self._proc_info()[pinfo_map['create_time']]
             raise
 
@@ -1015,6 +1016,7 @@ class Process:
         except OSError as err:
             if not is_permission_err(err):
                 raise
+            debug("attempting cpu_times() fallback (slower)")
             info = self._proc_info()
             user = info[pinfo_map['user_time']]
             system = info[pinfo_map['kernel_time']]
@@ -1101,6 +1103,7 @@ class Process:
         except OSError as err:
             if not is_permission_err(err):
                 raise
+            debug("attempting io_counters() fallback (slower)")
             info = self._proc_info()
             ret = (
                 info[pinfo_map['io_rcount']],
@@ -1160,6 +1163,7 @@ class Process:
             return cext.proc_num_handles(self.pid)
         except OSError as err:
             if is_permission_err(err):
+                debug("attempting num_handles() fallback (slower)")
                 return self._proc_info()[pinfo_map['num_handles']]
             raise
 
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/psutil/arch/osx/sensors.c b/psutil/arch/osx/sensors.c
index a2faa157..28ee5f0b 100644
--- a/psutil/arch/osx/sensors.c
+++ b/psutil/arch/osx/sensors.c
@@ -12,6 +12,7 @@
 
 
 #include <Python.h>
+#include <CoreFoundation/CoreFoundation.h>
 #include <IOKit/ps/IOPowerSources.h>
 #include <IOKit/ps/IOPSKeys.h>
 
diff --git a/pyproject.toml b/pyproject.toml
index 677722db..94266a7b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,13 +1,18 @@
 [tool.black]
+target-version = ["py37"]
 line-length = 79
 skip-string-normalization = true
+# https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html
 preview = true
-target-version = ["py37"]
+enable-unstable-feature = ["hug_parens_with_braces_and_square_brackets", "multiline_string_handling", "string_processing", "wrap_long_dict_values_in_parens"]
 
 [tool.ruff]
 # https://beta.ruff.rs/docs/settings/
 target-version = "py37"
 line-length = 79
+
+[tool.ruff.lint]
+preview = true
 select = [
     # To get a list of all values: `python3 -m ruff linter`.
     "ALL",
@@ -29,15 +34,22 @@ ignore = [
     "ARG002",  # unused-method-argument
     "B007",  # Loop control variable `x` not used within loop body
     "B904",  # Within an `except` clause, raise exceptions with `raise ... from err` (PYTHON2.7 COMPAT)
+    "B904",  # Use `raise from` to specify exception cause (PYTHON2.7 COMPAT)
     "C4",  # flake8-comprehensions (PYTHON2.7 COMPAT)
     "C90",  # mccabe (function `X` is too complex)
     "COM812",  # Trailing comma missing
     "D",  # pydocstyle
     "DTZ",  # flake8-datetimez
     "ERA001",  # Found commented-out code
+    "F841",  # Local variable `parent` is assigned to but never used
     "FBT",  # flake8-boolean-trap (makes zero sense)
     "FIX",  # Line contains TODO / XXX / ..., consider resolving the issue
     "FLY",  # flynt (PYTHON2.7 COMPAT)
+    "FURB101",  # `open` and `read` should be replaced by `Path(src).read_text()`
+    "FURB113",  # Use `x.extend(('a', 'b', 'c'))` instead of repeatedly calling `x.append()`
+    "FURB118",  # [*] Use `operator.add` instead of defining a lambda
+    "FURB140",  # [*] Use `itertools.starmap` instead of the generator
+    "FURB145",  # [*] Prefer `copy` method over slicing (PYTHON2.7 COMPAT)
     "INP",  # flake8-no-pep420
     "N801",  # Class name `async_chat` should use CapWords convention (ASYNCORE COMPAT)
     "N802",  # Function name X should be lowercase.
@@ -45,13 +57,23 @@ ignore = [
     "N818",  # Exception name `FooBar` should be named with an Error suffix
     "PERF",  # Perflint
     "PGH004",  # Use specific rule codes when using `noqa`
+    "PLC0415",  # `import` should be at the top-level of a file
+    "PLC2701",  # Private name import `x` from external module `y`
+    "PLR0904",  # Too many public methods (x > y)
     "PLR0911",  # Too many return statements (8 > 6)
     "PLR0912",  # Too many branches (x > y)
     "PLR0913",  # Too many arguments in function definition (x > y)
-    "PLR0915",  # Too many statements (92 > 50)
+    "PLR0914",  # Too many local variables (x/y)
+    "PLR0915",  # Too many statements (x > y)
+    "PLR0917",  # Too many positional arguments (x/y)
+    "PLR1702",  # Too many nested blocks (x > y)
+    "PLR1704",  # Redefining argument with the local name `type_`
     "PLR2004",  # Magic value used in comparison, consider replacing X with a constant variable
     "PLR5501",  # Use `elif` instead of `else` then `if`, to reduce indentation
+    "PLR6201",  # Use a `set` literal when testing for membership
+    "PLR6301",  # Method `x` could be a function, class method, or static method
     "PLW0603",  # Using the global statement to update `lineno` is discouraged
+    "PLW1514",  # `open` in text mode without explicit `encoding` argument
     "PLW2901",  # `for` loop variable `x` overwritten by assignment target
     "PT",  # flake8-pytest-style
     "PTH",  # flake8-use-pathlib
@@ -66,18 +88,18 @@ ignore = [
     "SIM117",  # Use a single `with` statement with multiple contexts instead of nested `with` statements
     "SLF",  # flake8-self
     "TD",  # all TODOs, XXXs, etc.
-    "TRY200",  # Use `raise from` to specify exception cause (PYTHON2.7 COMPAT)
     "TRY300",  # Consider moving this statement to an `else` block
     "TRY301",  # Abstract `raise` to an inner function
     "UP009",  # [*] UTF-8 encoding declaration is unnecessary (PYTHON2.7 COMPAT)
     "UP010",  # [*] Unnecessary `__future__` import `print_function` for target Python version (PYTHON2.7 COMPAT)
     "UP024",  # [*] Replace aliased errors with `OSError` (PYTHON2.7 COMPAT)
+    "UP025",  # [*] Remove unicode literals from strings (PYTHON2.7 COMPAT)
     "UP028",  # [*] Replace `yield` over `for` loop with `yield from` (PYTHON2.7 COMPAT)
     "UP031",  # [*] Use format specifiers instead of percent format
     "UP032",  # [*] Use f-string instead of `format` call (PYTHON2.7 COMPAT)
 ]
 
-[tool.ruff.per-file-ignores]
+[tool.ruff.lint.per-file-ignores]
 # T201 == print(), T203 == pprint()
 # EM101 == raw-string-in-exception
 # TRY003 == raise-vanilla-args
@@ -89,7 +111,7 @@ ignore = [
 "scripts/internal/*" = ["T201", "T203"]
 "setup.py" = ["T201", "T203"]
 
-[tool.ruff.isort]
+[tool.ruff.lint.isort]
 # https://beta.ruff.rs/docs/settings/#isort
 force-single-line = true  # one import per line
 lines-after-imports = 2
diff --git a/scripts/disk_usage.py b/scripts/disk_usage.py
index 934b24a1..c75c4f61 100755
--- a/scripts/disk_usage.py
+++ b/scripts/disk_usage.py
@@ -12,6 +12,7 @@ Device               Total     Used     Free  Use %      Type  Mount
 /dev/sda6           345.9G    83.8G   244.5G    24%      ext4  /home
 /dev/sda1           296.0M    43.1M   252.9M    14%      vfat  /boot/efi
 /dev/sda2           600.0M   312.4M   287.6M    52%   fuseblk  /media/Recovery
+
 """
 
 import os
@@ -26,7 +27,7 @@ def main():
     print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type", "Mount"))
     for part in psutil.disk_partitions(all=False):
         if os.name == 'nt':
-            if 'cdrom' in part.opts or part.fstype == '':
+            if 'cdrom' in part.opts or not part.fstype:
                 # skip cd-rom drives with no disk in it; they may raise
                 # ENOENT, pop-up a Windows GUI error for a non-ready
                 # partition or just hang.
diff --git a/scripts/ifconfig.py b/scripts/ifconfig.py
index 4b4246aa..e23472ba 100755
--- a/scripts/ifconfig.py
+++ b/scripts/ifconfig.py
@@ -111,7 +111,7 @@ def main():
                 print("         netmask   : %s" % addr.netmask)
             if addr.ptp:
                 print("      p2p       : %s" % addr.ptp)
-        print("")
+        print()
 
 
 if __name__ == '__main__':
diff --git a/scripts/internal/download_wheels_appveyor.py b/scripts/internal/download_wheels_appveyor.py
index e8b0c54e..0e6490b3 100755
--- a/scripts/internal/download_wheels_appveyor.py
+++ b/scripts/internal/download_wheels_appveyor.py
@@ -65,7 +65,7 @@ def get_file_urls():
             print_color("no artifacts found", 'red')
             sys.exit(1)
         else:
-            for url in sorted(urls, key=lambda x: os.path.basename(x)):
+            for url in sorted(urls, key=os.path.basename):
                 yield url
 
 
diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py
index 9f89f635..68201d7f 100755
--- a/scripts/internal/print_announce.py
+++ b/scripts/internal/print_announce.py
@@ -6,6 +6,7 @@
 
 """Prints release announce based on HISTORY.rst file content.
 See: https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode.
+
 """
 
 import os
diff --git a/scripts/internal/print_api_speed.py b/scripts/internal/print_api_speed.py
index adbaa89a..786644b5 100755
--- a/scripts/internal/print_api_speed.py
+++ b/scripts/internal/print_api_speed.py
@@ -181,7 +181,7 @@ def main():
     print_timings()
 
     # --- process
-    print("")
+    print()
     print_header("PROCESS APIS")
     ignore = [
         'send_signal',
diff --git a/scripts/internal/print_downloads.py b/scripts/internal/print_downloads.py
index 09169984..b453f15b 100755
--- a/scripts/internal/print_downloads.py
+++ b/scripts/internal/print_downloads.py
@@ -141,7 +141,7 @@ def main():
     downs = downloads()
 
     print("# Download stats")
-    print("")
+    print()
     s = "psutil download statistics of the last %s days (last update " % DAYS
     s += "*%s*).\n" % LAST_UPDATE
     s += "Generated via [pypistats.py](%s) script.\n" % GITHUB_SCRIPT_URL
diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py
index 43db68fd..b789aa80 100755
--- a/scripts/internal/winmake.py
+++ b/scripts/internal/winmake.py
@@ -138,7 +138,7 @@ def rm(pattern, directory=False):
         def onerror(fun, path, excinfo):
             exc = excinfo[1]
             if exc.errno != errno.ENOENT:
-                raise
+                raise  # noqa: PLE0704
 
         existed = os.path.isdir(path)
         shutil.rmtree(path, onerror=onerror)
@@ -181,7 +181,7 @@ def safe_rmtree(path):
     def onerror(fun, path, excinfo):
         exc = excinfo[1]
         if exc.errno != errno.ENOENT:
-            raise
+            raise  # noqa: PLE0704
 
     existed = os.path.isdir(path)
     shutil.rmtree(path, onerror=onerror)
diff --git a/scripts/procsmem.py b/scripts/procsmem.py
index 54e89b05..eec5cd51 100755
--- a/scripts/procsmem.py
+++ b/scripts/procsmem.py
@@ -32,6 +32,7 @@ PID     User    Cmdline                            USS     PSS    Swap     RSS
 20513   giampao /opt/sublime_text/sublime_text   65.8M   73.0M      0B   87.9M
 3976    giampao compiz                          115.0M  117.0M      0B  130.9M
 32486   giampao skype                           145.1M  147.5M      0B  149.6M
+
 """
 
 from __future__ import print_function
@@ -89,8 +90,8 @@ def main():
             p.pid,
             p._info["username"][:7] if p._info["username"] else "",
             convert_bytes(p._uss),
-            convert_bytes(p._pss) if p._pss != "" else "",
-            convert_bytes(p._swap) if p._swap != "" else "",
+            convert_bytes(p._pss) if p._pss else "",
+            convert_bytes(p._swap) if p._swap else "",
             convert_bytes(p._rss),
             cmd,
         )
diff --git a/scripts/winservices.py b/scripts/winservices.py
index 8d941d53..216d0a65 100755
--- a/scripts/winservices.py
+++ b/scripts/winservices.py
@@ -52,7 +52,7 @@ def main():
         )
         print(s)
         print("binpath: %s" % info['binpath'])
-        print("")
+        print()
 
 
 if __name__ == '__main__':


Test patch:
diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py
index 83bcfa5a..dd5a6c85 100644
--- a/psutil/tests/__init__.py
+++ b/psutil/tests/__init__.py
@@ -56,7 +56,6 @@ from psutil._compat import FileExistsError
 from psutil._compat import FileNotFoundError
 from psutil._compat import range
 from psutil._compat import super
-from psutil._compat import u
 from psutil._compat import unicode
 from psutil._compat import which
 
@@ -186,7 +185,7 @@ if os.name == 'java':
     TESTFN_PREFIX = '$psutil-%s-' % os.getpid()
 else:
     TESTFN_PREFIX = '@psutil-%s-' % os.getpid()
-UNICODE_SUFFIX = u("-ƒőő")
+UNICODE_SUFFIX = u"-ƒőő"
 # An invalid unicode string.
 if PY3:
     INVALID_UNICODE_SUFFIX = b"f\xc0\x80".decode('utf8', 'surrogateescape')
@@ -737,9 +736,9 @@ class retry:
                     self.sleep()
                     continue
             if PY3:
-                raise exc
+                raise exc  # noqa: PLE0704
             else:
-                raise
+                raise  # noqa: PLE0704
 
         # This way the user of the decorated function can change config
         # parameters.
@@ -1944,7 +1943,7 @@ def is_namedtuple(x):
     """Check if object is an instance of namedtuple."""
     t = type(x)
     b = t.__bases__
-    if len(b) != 1 or b[0] != tuple:
+    if len(b) != 1 or b[0] is not tuple:
         return False
     f = getattr(t, '_fields', None)
     if not isinstance(f, tuple):
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 0aa04f12..818a6be2 100755
--- a/psutil/tests/test_linux.py
+++ b/psutil/tests/test_linux.py
@@ -28,7 +28,6 @@ from psutil import LINUX
 from psutil._compat import PY3
 from psutil._compat import FileNotFoundError
 from psutil._compat import basestring
-from psutil._compat import u
 from psutil.tests import GITHUB_ACTIONS
 from psutil.tests import GLOBAL_TIMEOUT
 from psutil.tests import HAS_BATTERY
@@ -121,7 +120,7 @@ def get_ipv4_broadcast(ifname):
 def get_ipv6_addresses(ifname):
     with open("/proc/net/if_inet6") as f:
         all_fields = []
-        for line in f.readlines():
+        for line in f:
             fields = line.split()
             if fields[-1] == ifname:
                 all_fields.append(fields)
@@ -279,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(
@@ -343,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(
@@ -1218,7 +1217,7 @@ class TestSystemDiskPartitions(PsutilTestCase):
                 raise self.fail("couldn't find any ZFS partition")
         else:
             # No ZFS partitions on this system. Let's fake one.
-            fake_file = io.StringIO(u("nodev\tzfs\n"))
+            fake_file = io.StringIO(u"nodev\tzfs\n")
             with mock.patch(
                 'psutil._common.open', return_value=fake_file, create=True
             ) as m1:
@@ -1342,7 +1341,6 @@ class TestSystemDiskIoCounters(PsutilTestCase):
                 ret = psutil.disk_io_counters(perdisk=False, nowrap=False)
                 self.assertIsNone(ret)
 
-        #
         def is_storage_device(name):
             return name == 'nvme0n1'
 
@@ -1655,7 +1653,7 @@ class TestSensorsBattery(PsutilTestCase):
             if name.endswith(('AC0/online', 'AC/online')):
                 raise IOError(errno.ENOENT, "")
             elif name.endswith("/status"):
-                return io.StringIO(u("charging"))
+                return io.StringIO(u"charging")
             else:
                 return orig_open(name, *args, **kwargs)
 
@@ -1686,7 +1684,7 @@ class TestSensorsBattery(PsutilTestCase):
             if name.endswith(('AC0/online', 'AC/online')):
                 raise IOError(errno.ENOENT, "")
             elif name.endswith("/status"):
-                return io.StringIO(u("discharging"))
+                return io.StringIO(u"discharging")
             else:
                 return orig_open(name, *args, **kwargs)
 
@@ -1760,11 +1758,11 @@ class TestSensorsBatteryEmulated(PsutilTestCase):
     def test_it(self):
         def open_mock(name, *args, **kwargs):
             if name.endswith("/energy_now"):
-                return io.StringIO(u("60000000"))
+                return io.StringIO(u"60000000")
             elif name.endswith("/power_now"):
-                return io.StringIO(u("0"))
+                return io.StringIO(u"0")
             elif name.endswith("/energy_full"):
-                return io.StringIO(u("60000001"))
+                return io.StringIO(u"60000001")
             else:
                 return orig_open(name, *args, **kwargs)
 
@@ -1782,9 +1780,9 @@ class TestSensorsTemperatures(PsutilTestCase):
     def test_emulate_class_hwmon(self):
         def open_mock(name, *args, **kwargs):
             if name.endswith('/name'):
-                return io.StringIO(u("name"))
+                return io.StringIO(u"name")
             elif name.endswith('/temp1_label'):
-                return io.StringIO(u("label"))
+                return io.StringIO(u"label")
             elif name.endswith('/temp1_input'):
                 return io.BytesIO(b"30000")
             elif name.endswith('/temp1_max'):
@@ -1814,9 +1812,9 @@ class TestSensorsTemperatures(PsutilTestCase):
             elif name.endswith('temp'):
                 return io.BytesIO(b"30000")
             elif name.endswith('0_type'):
-                return io.StringIO(u("critical"))
+                return io.StringIO(u"critical")
             elif name.endswith('type'):
-                return io.StringIO(u("name"))
+                return io.StringIO(u"name")
             else:
                 return orig_open(name, *args, **kwargs)
 
@@ -1850,11 +1848,11 @@ class TestSensorsFans(PsutilTestCase):
     def test_emulate_data(self):
         def open_mock(name, *args, **kwargs):
             if name.endswith('/name'):
-                return io.StringIO(u("name"))
+                return io.StringIO(u"name")
             elif name.endswith('/fan1_label'):
-                return io.StringIO(u("label"))
+                return io.StringIO(u"label")
             elif name.endswith('/fan1_input'):
-                return io.StringIO(u("2000"))
+                return io.StringIO(u"2000")
             else:
                 return orig_open(name, *args, **kwargs)
 
@@ -1936,7 +1934,6 @@ class TestProcess(PsutilTestCase):
                         break
             raise RuntimeError("timeout looking for test file")
 
-        #
         testfn = self.get_testfn()
         with open(testfn, "w"):
             self.assertEqual(get_test_file(testfn).mode, "w")
@@ -1944,7 +1941,6 @@ class TestProcess(PsutilTestCase):
             self.assertEqual(get_test_file(testfn).mode, "r")
         with open(testfn, "a"):
             self.assertEqual(get_test_file(testfn).mode, "a")
-        #
         with open(testfn, "r+"):
             self.assertEqual(get_test_file(testfn).mode, "r+")
         with open(testfn, "w+"):
@@ -2036,13 +2032,13 @@ class TestProcess(PsutilTestCase):
     def test_cmdline_mocked(self):
         # see: https://github.com/giampaolo/psutil/issues/639
         p = psutil.Process()
-        fake_file = io.StringIO(u('foo\x00bar\x00'))
+        fake_file = io.StringIO(u'foo\x00bar\x00')
         with mock.patch(
             'psutil._common.open', return_value=fake_file, create=True
         ) as m:
             self.assertEqual(p.cmdline(), ['foo', 'bar'])
             assert m.called
-        fake_file = io.StringIO(u('foo\x00bar\x00\x00'))
+        fake_file = io.StringIO(u'foo\x00bar\x00\x00')
         with mock.patch(
             'psutil._common.open', return_value=fake_file, create=True
         ) as m:
@@ -2052,13 +2048,13 @@ class TestProcess(PsutilTestCase):
     def test_cmdline_spaces_mocked(self):
         # see: https://github.com/giampaolo/psutil/issues/1179
         p = psutil.Process()
-        fake_file = io.StringIO(u('foo bar '))
+        fake_file = io.StringIO(u'foo bar ')
         with mock.patch(
             'psutil._common.open', return_value=fake_file, create=True
         ) as m:
             self.assertEqual(p.cmdline(), ['foo', 'bar'])
             assert m.called
-        fake_file = io.StringIO(u('foo bar  '))
+        fake_file = io.StringIO(u'foo bar  ')
         with mock.patch(
             'psutil._common.open', return_value=fake_file, create=True
         ) as m:
@@ -2069,7 +2065,7 @@ class TestProcess(PsutilTestCase):
         # https://github.com/giampaolo/psutil/issues/
         #    1179#issuecomment-552984549
         p = psutil.Process()
-        fake_file = io.StringIO(u('foo\x20bar\x00'))
+        fake_file = io.StringIO(u'foo\x20bar\x00')
         with mock.patch(
             'psutil._common.open', return_value=fake_file, create=True
         ) as m:
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 451c4920..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)
 
@@ -965,7 +965,7 @@ class TestProcess(PsutilTestCase):
             self.assertEqual(
                 p.cpu_affinity(), list(os.sched_getaffinity(p.pid))
             )
-        #
+
         self.assertRaises(TypeError, p.cpu_affinity, 1)
         p.cpu_affinity(initial)
         # it should work with all iterables, not only lists
@@ -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)
@@ -1344,7 +1344,7 @@ class TestProcess(PsutilTestCase):
 
     @unittest.skipIf(not POSIX, 'POSIX only')
     def test_zombie_process(self):
-        parent, zombie = self.spawn_zombie()
+        _parent, zombie = self.spawn_zombie()
         self.assertProcessZombie(zombie)
 
     @unittest.skipIf(not POSIX, 'POSIX only')
diff --git a/psutil/tests/test_process_all.py b/psutil/tests/test_process_all.py
index ee1a9b58..e59de3fb 100755
--- a/psutil/tests/test_process_all.py
+++ b/psutil/tests/test_process_all.py
@@ -28,6 +28,7 @@ from psutil import OSX
 from psutil import POSIX
 from psutil import WINDOWS
 from psutil._compat import PY3
+from psutil._compat import FileNotFoundError
 from psutil._compat import long
 from psutil._compat import unicode
 from psutil.tests import CI_TESTING
diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py
index 152e378f..d517b740 100755
--- a/psutil/tests/test_system.py
+++ b/psutil/tests/test_system.py
@@ -344,7 +344,7 @@ class TestCpuAPIs(PsutilTestCase):
         self.assertIsNotNone(logical)
         self.assertEqual(logical, len(psutil.cpu_times(percpu=True)))
         self.assertGreaterEqual(logical, 1)
-        #
+
         if os.path.exists("/proc/cpuinfo"):
             with open("/proc/cpuinfo") as fd:
                 cpuinfo_data = fd.read()
@@ -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:
@@ -385,7 +385,7 @@ class TestCpuAPIs(PsutilTestCase):
             self.assertIsInstance(cp_time, float)
             self.assertGreaterEqual(cp_time, 0.0)
             total += cp_time
-        self.assertAlmostEqual(total, sum(times))
+        self.assertAlmostEqual(total, sum(times), places=6)
         str(times)
         # CPU times are always supposed to increase over time
         # or at least remain the same and that's because time
@@ -424,7 +424,7 @@ class TestCpuAPIs(PsutilTestCase):
                 self.assertIsInstance(cp_time, float)
                 self.assertGreaterEqual(cp_time, 0.0)
                 total += cp_time
-            self.assertAlmostEqual(total, sum(times))
+            self.assertAlmostEqual(total, sum(times), places=6)
             str(times)
         self.assertEqual(
             len(psutil.cpu_times(percpu=True)[0]),
@@ -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 f4883bf9..cb4bccf7 100755
--- a/psutil/tests/test_unicode.py
+++ b/psutil/tests/test_unicode.py
@@ -85,7 +85,6 @@ 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
 from psutil.tests import CI_TESTING
@@ -176,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
@@ -190,7 +189,7 @@ class TestFSAPIs(BaseUnicodeTest):
     def expect_exact_path_match(self):
         # Do not expect psutil to correctly handle unicode paths on
         # Python 2 if os.listdir() is not able either.
-        here = '.' if isinstance(self.funky_name, str) else u('.')
+        here = '.' if isinstance(self.funky_name, str) else u'.'
         with warnings.catch_warnings():
             warnings.simplefilter("ignore")
             return self.funky_name in os.listdir(here)
@@ -247,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)