diff --git a/.github/workflows/bsd.yml b/.github/workflows/bsd.yml index 3af8d2371..c405e8228 100644 --- a/.github/workflows/bsd.yml +++ b/.github/workflows/bsd.yml @@ -11,57 +11,37 @@ concurrency: cancel-in-progress: true jobs: freebsd: - runs-on: ubuntu-22.04 + # if: false + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run tests uses: vmactions/freebsd-vm@v1 with: usesh: true - prepare: | - pkg install -y gcc python3 run: | - set -e -x - make install-pip - python3 -m pip install --user setuptools - make install - make test - make test-memleaks + PIP_BREAK_SYSTEM_PACKAGES=1 make install-sysdeps install-pydeps-test install print-sysinfo test test-memleaks + openbsd: - runs-on: ubuntu-22.04 + # if: false + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run tests uses: vmactions/openbsd-vm@v1 with: usesh: true - prepare: | - set -e - pkg_add gcc python3 run: | - set -e - make install-pip - python3 -m pip install --user setuptools - make install - make test - make test-memleaks + PIP_BREAK_SYSTEM_PACKAGES=1 make install-sysdeps install-pydeps-test install print-sysinfo test test-memleaks + netbsd: - runs-on: ubuntu-22.04 + if: false # XXX: disabled + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run tests uses: vmactions/netbsd-vm@v1 with: usesh: true - prepare: | - set -e - /usr/sbin/pkg_add -v pkgin - pkgin update - pkgin -y install python311-* py311-setuptools-* gcc12-* run: | - set -e - make install-pip PYTHON=python3.11 - python3.11 -m pip install --user setuptools - make install PYTHON=python3.11 - make test PYTHON=python3.11 - make test-memleaks PYTHON=python3.11 + PIP_BREAK_SYSTEM_PACKAGES=1 make PYTHON=python3.11 install-sysdeps install-pydeps-test install print-sysinfo test test-memleaks diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c4e9c745e..f609ee651 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,7 @@ # Runs CI tests and generates wheels on the following platforms: -# -# * Linux (py2 and py3) -# * macOS (py2 and py3) -# * Windows (py3, py2 is done by appveyor) +# * Linux +# * macOS +# * Windows # # Useful URLs: # * https://github.com/pypa/cibuildwheel @@ -16,25 +15,23 @@ concurrency: group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.sha || '' }} cancel-in-progress: true jobs: - # Linux + macOS + Windows Python 3 - py3: - name: py3-${{ matrix.os }}-${{ startsWith(matrix.os, 'ubuntu') && 'all' || matrix.archs }} + + # Run tests on Linux, macOS, Windows + tests: + name: "tests, ${{ matrix.os }}, ${{ matrix.arch }}" runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: include: - - os: ubuntu-latest - archs: "x86_64 i686" - - os: macos-12 - archs: "x86_64" - - os: macos-14 - archs: "arm64" - - os: windows-2019 - archs: "AMD64" - - os: windows-2019 - archs: "x86" + - {os: ubuntu-latest, arch: x86_64} + - {os: ubuntu-latest, arch: i686} + - {os: ubuntu-latest, arch: aarch64} + - {os: macos-13, arch: x86_64} + - {os: macos-14, arch: arm64} + - {os: windows-2019, arch: AMD64} + - {os: windows-2019, arch: x86} steps: - uses: actions/checkout@v4 @@ -51,16 +48,23 @@ jobs: with: python-version: 3.11 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + if: matrix.arch == 'aarch64' + - name: Create wheels + run tests - uses: pypa/cibuildwheel@v2.18.0 + uses: pypa/cibuildwheel@v2.22.0 env: - CIBW_ARCHS: "${{ matrix.archs }}" - CIBW_PRERELEASE_PYTHONS: True + CIBW_ARCHS: "${{ matrix.arch }}" + CIBW_ENABLE: "cpython-prerelease" + CIBW_TEST_EXTRAS: test + CIBW_TEST_COMMAND: + make -C {project} PYTHON="env python" PSUTIL_SCRIPTS_DIR="{project}/scripts" install-sysdeps install-pydeps-test install print-sysinfo test test-memleaks - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: wheels-py3-${{ matrix.os }}-${{ startsWith(matrix.os, 'ubuntu') && 'all' || matrix.archs }} + name: wheels-${{ matrix.os }}-${{ matrix.arch }} path: wheelhouse - name: Generate .tar.gz @@ -70,43 +74,19 @@ jobs: python setup.py sdist mv dist/psutil*.tar.gz wheelhouse/ - # Linux + macOS + Python 2 - py2: - name: py2-${{ matrix.os }} - runs-on: ${{ matrix.os }} + # Test python 2.7 fallback installation message produced by setup.py + py2-fallback: + name: py2.7 setup.py check + runs-on: ubuntu-24.04 timeout-minutes: 20 strategy: fail-fast: false - matrix: - os: [ubuntu-latest, macos-12] - env: - CIBW_TEST_COMMAND: - PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 python {project}/psutil/tests/runner.py && - PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 python {project}/psutil/tests/test_memleaks.py - CIBW_TEST_EXTRAS: test - CIBW_BUILD: 'cp27-*' - steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: LizardByte/setup-python-action@master with: - python-version: 3.9 - - - name: Create wheels + run tests - uses: pypa/cibuildwheel@v1.12.0 - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: wheels-py2-${{ matrix.os }} - path: wheelhouse - - - name: Generate .tar.gz - if: matrix.os == 'ubuntu-latest' - run: | - make generate-manifest - python setup.py sdist - mv dist/psutil*.tar.gz wheelhouse/ + python-version: '2.7' + - run: python scripts/internal/test_python2_setup_py.py # Run linters linters: @@ -118,12 +98,12 @@ jobs: python-version: 3.x - name: 'Run linters' run: | - python3 -m pip install ruff==0.4.4 black rstcheck toml-sort sphinx + python3 -m pip install ruff black rstcheck toml-sort sphinx make lint-all - # upload weels as a single artefact + # Produce wheels as artifacts. upload-wheels: - needs: [py2, py3] + needs: [tests] runs-on: ubuntu-latest steps: - uses: actions/upload-artifact/merge@v4 diff --git a/.github/workflows/issues.py b/.github/workflows/issues.py index eddaa7f99..21c720ca5 100755 --- a/.github/workflows/issues.py +++ b/.github/workflows/issues.py @@ -9,7 +9,6 @@ on the situation. """ -from __future__ import print_function import functools import json @@ -42,7 +41,7 @@ "windows", "win32", "WinError", "WindowsError", "win10", "win7", "win ", "mingw", "msys", "studio", "microsoft", "make.bat", "CloseHandle", "GetLastError", "NtQuery", "DLL", "MSVC", "TCHAR", - "WCHAR", ".bat", "OpenProcess", "TerminateProcess", "appveyor", + "WCHAR", ".bat", "OpenProcess", "TerminateProcess", "windows error", "NtWow64", "NTSTATUS", "Visual Studio", ], "macos": [ @@ -62,6 +61,11 @@ "/dev/pts", "posix", ], "pypy": ["pypy"], + "docker": ["docker", "docker-compose"], + "vm": [ + "docker", "docker-compose", "vmware", "lxc", "hyperv", "virtualpc", + "virtualbox", "bhyve", "openvz", "lxc", "xen", "kvm", "qemu", "heroku", + ], # types "enhancement": ["enhancement"], "memleak": ["memory leak", "leaks memory", "memleak", "mem leak"], @@ -84,14 +88,14 @@ ], # tests "tests": [ - " test ", "tests", "travis", "coverage", "cirrus", "appveyor", + " test ", "tests", "travis", "coverage", "cirrus", "continuous integration", "unittest", "pytest", "unit test", ], # critical errors - "priority-high": [ + "critical": [ "WinError", "WindowsError", "RuntimeError", "ZeroDivisionError", - "SystemError", "MemoryError", "core dumped", - "segfault", "segmentation fault", + "SystemError", "MemoryError", "core dump", "segfault", + "segmentation fault", ], } @@ -144,11 +148,6 @@ def has_label(issue, label): return label in assigned -def has_os_label(issue): - labels = set([x.name for x in issue.labels]) - return any(x in labels for x in OS_LABELS) - - def get_repo(): repo = os.environ['GITHUB_REPOSITORY'] token = os.environ['GITHUB_TOKEN'] @@ -160,9 +159,10 @@ def get_repo(): @functools.lru_cache() def _get_event_data(): - ret = json.load(open(os.environ["GITHUB_EVENT_PATH"])) - pp(ret) - return ret + with open(os.environ["GITHUB_EVENT_PATH"]) as f: + ret = json.load(f) + pp(ret) + return ret def is_event_new_issue(): @@ -195,29 +195,29 @@ def get_issue(): def log(msg): if '\n' in msg or "\r\n" in msg: - print(">>>\n%s\n<<<" % msg, flush=True) + print(f">>>\n{msg}\n<<<", flush=True) else: - print(">>> %s <<<" % msg, flush=True) + print(f">>> {msg} <<<", flush=True) def add_label(issue, label): def should_add(issue, label): if has_label(issue, label): - log("already has label %r" % (label)) + log(f"already has label {label!r}") return False for left, right in ILLOGICAL_PAIRS: if label == left and has_label(issue, right): - log("already has label" % (label)) + log(f"already has label f{label}") return False return not has_label(issue, label) if not should_add(issue, label): - log("should not add label %r" % label) + log(f"should not add label {label!r}") return - log("add label %r" % label) + log(f"add label {label!r}") issue.add_to_labels(label) @@ -330,16 +330,16 @@ def on_new_pr(issue): def main(): issue = get_issue() stype = "PR" if is_pr(issue) else "issue" - log("running issue bot for %s %r" % (stype, issue)) + log(f"running issue bot for {stype} {issue!r}") if is_event_new_issue(): - log("created new issue %s" % issue) + log(f"created new issue {issue}") add_labels_from_text(issue, issue.title) if issue.body: add_labels_from_new_body(issue, issue.body) on_new_issue(issue) elif is_event_new_pr(): - log("created new PR %s" % issue) + log(f"created new PR {issue}") add_labels_from_text(issue, issue.title) if issue.body: add_labels_from_new_body(issue, issue.body) diff --git a/CREDITS b/CREDITS index baff0c089..35f4b6b4a 100644 --- a/CREDITS +++ b/CREDITS @@ -831,3 +831,12 @@ I: 2376 N: Anthony Ryan W: https://github.com/anthonyryan1 I: 2272 + +N: Sam Gross +W: https://github.com/colesbury +I: 2401, 2427 + +N: Aleksey Lobanov +C: Russia +E: alex_github@likemath.ru +W: https://github.com/AlekseyLobanov diff --git a/HISTORY.rst b/HISTORY.rst index a53590555..39bb66157 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,8 +1,79 @@ *Bug tracker at https://github.com/giampaolo/psutil/issues* -6.0.0 (IN DEVELOPMENT) +7.0.0 (IN DEVELOPMENT) ====================== +XXXX-XX-XX + +**Enhancements** + +- 2480_: Python 2.7 is no longer supported. Latest version supporting Python + 2.7 is psutil 6.1.X. Install it with: ``pip2 install psutil==6.1.*``. +- 2490_: removed long deprecated ``Process.memory_info_ex()`` method. It was + deprecated in psutil 4.0.0, released 8 years ago. Substitute is + ``Process.memory_full_info()``. + +**Compatibility notes** + +- 2480_: Python 2.7 is no longer supported. +- 2490_: removed long deprecated ``Process.memory_info_ex()`` method. + +6.1.1 +===== + +2024-12-19 + +**Enhancements** + +- 2471_: use Vulture CLI tool to detect dead code. + +**Bug fixes** + +- 2418_, [Linux]: fix race condition in case /proc/PID/stat does not exist, but + /proc/PID does, resulting in FileNotFoundError. +- 2470_, [Linux]: `users()`_ may return "localhost" instead of the actual IP + address of the user logged in. + +6.1.0 +===== + +2024-10-17 + +**Enhancements** + +- 2366_, [Windows]: drastically speedup `process_iter()`_. We now determine + process unique identity by using process "fast" create time method. This + will considerably speedup those apps which use `process_iter()`_ only once, + e.g. to look for a process with a certain name. +- 2446_: use pytest instead of unittest. +- 2448_: add ``make install-sysdeps`` target to install the necessary system + dependencies (python-dev, gcc, etc.) on all supported UNIX flavors. +- 2449_: add ``make install-pydeps-test`` and ``make install-pydeps-dev`` + targets. They can be used to install dependencies meant for running tests and + for local development. They can also be installed via ``pip install .[test]`` + and ``pip install .[dev]``. +- 2456_: allow to run tests via ``python3 -m psutil.tests`` even if ``pytest`` + module is not installed. This is useful for production environments that + don't have pytest installed, but still want to be able to test psutil + installation. + +**Bug fixes** + +- 2427_: psutil (segfault) on import in the free-threaded (no GIL) version of + Python 3.13. (patch by Sam Gross) +- 2455_, [Linux]: ``IndexError`` may occur when reading /proc/pid/stat and + field 40 (blkio_ticks) is missing. +- 2457_, [AIX]: significantly improve the speed of `Process.open_files()`_ for + some edge cases. +- 2460_, [OpenBSD]: `Process.num_fds()`_ and `Process.open_files()`_ may fail + with `NoSuchProcess`_ for PID 0. Instead, we now return "null" values (0 and + [] respectively). + +6.0.0 +====== + +2024-06-18 + **Enhancements** - 2109_: ``maxfile`` and ``maxpath`` fields were removed from the namedtuple @@ -14,22 +85,29 @@ been reused. This makes `process_iter()`_ around 20x times faster. - 2396_: a new ``psutil.process_iter.cache_clear()`` API can be used the clear `process_iter()`_ internal cache. -- 2401_, Support building with free-threaded CPython 3.13. +- 2401_, Support building with free-threaded CPython 3.13. (patch by Sam Gross) - 2407_: `Process.connections()`_ was renamed to `Process.net_connections()`_. The old name is still available, but it's deprecated (triggers a ``DeprecationWarning``) and will be removed in the future. +- 2425_: [Linux]: provide aarch64 wheels. (patch by Matthieu Darbois / Ben Raz) **Bug fixes** -- 2395_, [OpenBSD]: `pid_exists()`_ erroneously return True if the argument is - a thread ID (TID) instead of a PID (process ID). -- 2254_, [Linux]: offline cpus raise NotImplementedError in cpu_freq() (patch by Shade Gladden) +- 2250_, [NetBSD]: `Process.cmdline()`_ sometimes fail with EBUSY. It usually + happens for long cmdlines with lots of arguments. In this case retry getting + the cmdline for up to 50 times, and return an empty list as last resort. +- 2254_, [Linux]: offline cpus raise NotImplementedError in cpu_freq() (patch + by Shade Gladden) - 2272_: Add pickle support to psutil Exceptions. - 2359_, [Windows], [CRITICAL]: `pid_exists()`_ disagrees with `Process`_ on whether a pid exists when ERROR_ACCESS_DENIED. - 2360_, [macOS]: can't compile on macOS < 10.13. (patch by Ryan Schmidt) - 2362_, [macOS]: can't compile on macOS 10.11. (patch by Ryan Schmidt) - 2365_, [macOS]: can't compile on macOS < 10.9. (patch by Ryan Schmidt) +- 2395_, [OpenBSD]: `pid_exists()`_ erroneously return True if the argument is + a thread ID (TID) instead of a PID (process ID). +- 2412_, [macOS]: can't compile on macOS 10.4 PowerPC due to missing `MNT_` + constants. **Porting notes** diff --git a/INSTALL.rst b/INSTALL.rst index e3dab9cb4..86972586c 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -12,10 +12,32 @@ install a C compiler. All you have to do is:: If wheels are not available for your platform or architecture, or you wish to build & install psutil from sources, keep reading. -Linux (build) -------------- +Compile psutil from sources +=========================== + +UNIX +---- + +On all UNIX systems you can use the `install-sysdeps.sh +`__ +script. This will install the system dependencies necessary to compile psutil +from sources. You can invoke this script from the Makefile as:: + + make install-sysdeps + +After system deps are installed, you can compile & install psutil with:: + + make build + make install -Ubuntu / Debian:: +...or this, which will fetch the latest source distribution from `PyPI `__:: + + pip install --no-binary :all: psutil + +Linux +----- + +Debian / Ubuntu:: sudo apt-get install gcc python3-dev pip install --no-binary :all: psutil @@ -30,14 +52,13 @@ Alpine:: sudo apk add gcc python3-dev musl-dev linux-headers pip install --no-binary :all: psutil -Windows (build) ---------------- +Windows +------- -In order to install psutil from sources on Windows you need Visual Studio -(MinGW is not supported). -Here's a couple of guides describing how to do it: `link `__ -and `link `__. -Once VS is installed do:: +In order to build / install psutil from sources on Windows you need to install +`Visua Studio 2017 `__ +or later (see cPython `devguide `__'s instructions). +MinGW is not supported. Once Visual Studio is installed do:: pip install --no-binary :all: psutil @@ -61,7 +82,7 @@ OpenBSD :: - export PKG_PATH=http://ftp.eu.openbsd.org/pub/OpenBSD/`uname -r`/packages/`uname -m`/ + export PKG_PATH=https://cdn.openbsd.org/pub/OpenBSD/`uname -r`/packages/`uname -m`/ pkg_add -v python3 gcc pip install psutil @@ -72,7 +93,7 @@ Assuming Python 3.11 (the most recent at the time of writing): :: - export PKG_PATH="http://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All" + export PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All" pkg_add -v pkgin pkgin install python311-* gcc12-* py311-setuptools-* py311-pip-* python3.11 -m pip install psutil @@ -95,8 +116,7 @@ Troubleshooting Install pip ----------- -Pip is shipped by default with Python 2.7.9+ and 3.4+. -If you don't have pip you can install with wget:: +If you don't have pip you can install it with wget:: wget https://bootstrap.pypa.io/get-pip.py -O - | python3 diff --git a/MANIFEST.in b/MANIFEST.in index bb60aa849..b60794b91 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -24,7 +24,6 @@ include docs/requirements.txt include make.bat include psutil/__init__.py include psutil/_common.py -include psutil/_compat.py include psutil/_psaix.py include psutil/_psbsd.py include psutil/_pslinux.py @@ -153,7 +152,6 @@ include psutil/arch/windows/wmi.h include psutil/tests/README.rst include psutil/tests/__init__.py include psutil/tests/__main__.py -include psutil/tests/runner.py include psutil/tests/test_aix.py include psutil/tests/test_bsd.py include psutil/tests/test_connections.py @@ -165,6 +163,7 @@ include psutil/tests/test_osx.py include psutil/tests/test_posix.py include psutil/tests/test_process.py include psutil/tests/test_process_all.py +include psutil/tests/test_scripts.py include psutil/tests/test_sunos.py include psutil/tests/test_system.py include psutil/tests/test_testutils.py @@ -178,16 +177,16 @@ include scripts/fans.py include scripts/free.py include scripts/ifconfig.py include scripts/internal/README -include scripts/internal/appveyor_run_with_compiler.cmd include scripts/internal/bench_oneshot.py include scripts/internal/bench_oneshot_2.py include scripts/internal/check_broken_links.py include scripts/internal/clinter.py include scripts/internal/convert_readme.py -include scripts/internal/download_wheels_appveyor.py -include scripts/internal/download_wheels_github.py +include scripts/internal/download_wheels.py include scripts/internal/generate_manifest.py include scripts/internal/git_pre_commit.py +include scripts/internal/install-sysdeps.sh +include scripts/internal/install_pip.py include scripts/internal/print_access_denied.py include scripts/internal/print_announce.py include scripts/internal/print_api_speed.py @@ -196,6 +195,7 @@ include scripts/internal/print_downloads.py include scripts/internal/print_hashes.py include scripts/internal/print_timeline.py include scripts/internal/purge_installation.py +include scripts/internal/test_python2_setup_py.py include scripts/internal/winmake.py include scripts/iotop.py include scripts/killall.py diff --git a/Makefile b/Makefile index 0406fb6da..3e74e8c7d 100644 --- a/Makefile +++ b/Makefile @@ -2,56 +2,24 @@ # To use a specific Python version run: "make install PYTHON=python3.3" # You can set the variables below from the command line. -# Configurable. +# Configurable PYTHON = python3 -PYTHON_ENV_VARS = PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 ARGS = -TSCRIPT = psutil/tests/runner.py - -# Internal. -PY3_DEPS = \ - black \ - check-manifest \ - concurrencytest \ - coverage \ - packaging \ - pylint \ - pyperf \ - pypinfo \ - requests \ - rstcheck \ - ruff==0.4.4 \ - setuptools \ - sphinx_rtd_theme \ - teyit \ - toml-sort \ - twine \ - virtualenv \ - wheel -PY2_DEPS = \ - futures \ - ipaddress \ - mock -PY_DEPS = `$(PYTHON) -c \ - "import sys; \ - py3 = sys.version_info[0] == 3; \ - py38 = sys.version_info[:2] >= (3, 8); \ - py3_extra = ' abi3audit' if py38 else ''; \ - print('$(PY3_DEPS)' + py3_extra if py3 else '$(PY2_DEPS)')"` -NUM_WORKERS = `$(PYTHON) -c "import os; print(os.cpu_count() or 1)"` -# "python3 setup.py build" can be parallelized on Python >= 3.6. -BUILD_OPTS = `$(PYTHON) -c \ - "import sys, os; \ - py36 = sys.version_info[:2] >= (3, 6); \ - cpus = os.cpu_count() or 1 if py36 else 1; \ - print('--parallel %s' % cpus if cpus > 1 else '')"` + # In not in a virtualenv, add --user options for install commands. -INSTALL_OPTS = `$(PYTHON) -c \ +SETUP_INSTALL_ARGS = `$(PYTHON) -c \ "import sys; print('' if hasattr(sys, 'real_prefix') or hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix else '--user')"` +PIP_INSTALL_ARGS = --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade +PYTEST_ARGS = -v -s --tb=short +PYTHON_ENV_VARS = PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 + # if make is invoked with no arg, default to `make help` .DEFAULT_GOAL := help +# install git hook +_ := $(shell mkdir -p .git/hooks/ && ln -sf ../../scripts/internal/git_pre_commit.py .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit) + # =================================================================== # Install # =================================================================== @@ -80,6 +48,7 @@ clean: ## Remove all build files. dist/ \ docs/_build/ \ htmlcov/ \ + pytest-cache* \ wheelhouse .PHONY: build @@ -87,44 +56,37 @@ 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. - $(PYTHON_ENV_VARS) $(PYTHON) setup.py build_ext -i $(BUILD_OPTS) + $(PYTHON_ENV_VARS) $(PYTHON) setup.py build_ext -i --parallel 4 $(PYTHON_ENV_VARS) $(PYTHON) -c "import psutil" # make sure it actually worked install: ## Install this package as current user in "edit" mode. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) setup.py develop $(INSTALL_OPTS) - $(PYTHON_ENV_VARS) $(PYTHON) -c "import psutil" # make sure it actually worked + $(PYTHON_ENV_VARS) $(PYTHON) setup.py develop $(SETUP_INSTALL_ARGS) uninstall: ## Uninstall this package via pip. 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; \ - 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(url, **kw); \ - data = req.read(); \ - f = tempfile.NamedTemporaryFile(suffix='.py'); \ - atexit.register(f.close); \ - f.write(data); \ - f.flush(); \ - print('downloaded %s' % f.name); \ - code = os.system('%s %s --user --upgrade' % (sys.executable, f.name)); \ - f.close(); \ - sys.exit(code);" - -setup-dev-env: ## Install GIT hooks, pip, test deps (also upgrades them). + $(PYTHON) scripts/internal/install_pip.py + +install-sysdeps: + ./scripts/internal/install-sysdeps.sh + +install-pydeps-test: ## Install python deps necessary to run unit tests. + ${MAKE} install-pip + $(PYTHON) -m pip install $(PIP_INSTALL_ARGS) pip setuptools + $(PYTHON) -m pip install $(PIP_INSTALL_ARGS) `$(PYTHON) -c "import setup; print(' '.join(setup.TEST_DEPS))"` + +install-pydeps-dev: ## Install python deps meant for local development. ${MAKE} install-git-hooks ${MAKE} install-pip - $(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) + $(PYTHON) -m pip install $(PIP_INSTALL_ARGS) pip setuptools + $(PYTHON) -m pip install $(PIP_INSTALL_ARGS) `$(PYTHON) -c "import setup; print(' '.join(setup.TEST_DEPS + setup.DEV_DEPS))"` + +install-git-hooks: ## Install GIT pre-commit hook. + ln -sf ../../scripts/internal/git_pre_commit.py .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit # =================================================================== # Tests @@ -132,65 +94,69 @@ 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 - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) --ignore=psutil/tests/test_memleaks.py $(ARGS) test-parallel: ## Run all tests in parallel. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) --parallel + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) --ignore=psutil/tests/test_memleaks.py -n auto --dist loadgroup $(ARGS) test-process: ## Run process-related API tests. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_process.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_process.py test-process-all: ## Run tests which iterate over all process PIDs. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_process_all.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_process_all.py test-system: ## Run system-related API tests. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_system.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_system.py test-misc: ## Run miscellaneous tests. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_misc.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_misc.py + +test-scripts: ## Run scripts tests. + ${MAKE} build + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_scripts.py test-testutils: ## Run test utils tests. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_testutils.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_testutils.py test-unicode: ## Test APIs dealing with strings. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_unicode.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_unicode.py test-contracts: ## APIs sanity tests. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_contracts.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_contracts.py test-connections: ## Test psutil.net_connections() and Process.net_connections(). ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_connections.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_connections.py test-posix: ## POSIX specific tests. ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_posix.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_posix.py test-platform: ## Run specific platform tests only. ${MAKE} build - $(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 + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(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 - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_memleaks.py + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_memleaks.py test-last-failed: ## Re-run tests which failed on last run ${MAKE} build - $(PYTHON_ENV_VARS) $(PYTHON) $(TSCRIPT) $(ARGS) --last-failed + $(PYTHON_ENV_VARS) $(PYTHON) -m pytest $(PYTEST_ARGS) --last-failed $(ARGS) test-coverage: ## Run test coverage. ${MAKE} build # Note: coverage options are controlled by .coveragerc file rm -rf .coverage htmlcov - $(PYTHON_ENV_VARS) $(PYTHON) -m coverage run -m unittest -v + $(PYTHON_ENV_VARS) $(PYTHON) -m coverage run -m pytest $(PYTEST_ARGS) --ignore=psutil/tests/test_memleaks.py $(ARGS) $(PYTHON) -m coverage report @echo "writing results to htmlcov/index.html" $(PYTHON) -m coverage html @@ -201,21 +167,18 @@ test-coverage: ## Run test coverage. # =================================================================== ruff: ## Run ruff linter. - @git ls-files '*.py' | xargs $(PYTHON) -m ruff check --no-cache + @git ls-files '*.py' | xargs $(PYTHON) -m ruff check --output-format=concise -black: ## Python files linting (via black) +black: ## Run black formatter. @git ls-files '*.py' | xargs $(PYTHON) -m black --check --safe -_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 -lint-rst: ## Run C linter. +lint-rst: ## Run linter for .rst files. @git ls-files '*.rst' | xargs rstcheck --config=pyproject.toml -lint-toml: ## Linter for pyproject.toml +lint-toml: ## Run linter for pyproject.toml. @git ls-files '*.toml' | xargs toml-sort --check lint-all: ## Run all linters @@ -225,6 +188,14 @@ lint-all: ## Run all linters ${MAKE} lint-rst ${MAKE} lint-toml +# --- not mandatory linters (just run from time to time) + +pylint: ## Python pylint + @git ls-files '*.py' | xargs $(PYTHON) -m pylint --rcfile=pyproject.toml --jobs=0 $(ARGS) + +vulture: ## Find unused code + @git ls-files '*.py' | xargs $(PYTHON) -m vulture $(ARGS) + # =================================================================== # Fixers # =================================================================== @@ -233,10 +204,7 @@ fix-black: @git ls-files '*.py' | xargs $(PYTHON) -m black fix-ruff: - @git ls-files '*.py' | xargs $(PYTHON) -m ruff check --no-cache --fix - -fix-unittests: ## Fix unittest idioms. - @git ls-files '*test_*.py' | xargs $(PYTHON) -m teyit --show-stats + @git ls-files '*.py' | xargs $(PYTHON) -m ruff check --fix --output-format=concise $(ARGS) fix-toml: ## Fix pyproject.toml @git ls-files '*.toml' | xargs toml-sort @@ -244,17 +212,8 @@ fix-toml: ## Fix pyproject.toml fix-all: ## Run all code fixers. ${MAKE} fix-ruff ${MAKE} fix-black - ${MAKE} fix-unittests ${MAKE} fix-toml -# =================================================================== -# GIT -# =================================================================== - -install-git-hooks: ## Install GIT pre-commit hook. - ln -sf ../../scripts/internal/git_pre_commit.py .git/hooks/pre-commit - chmod +x .git/hooks/pre-commit - # =================================================================== # Distribution # =================================================================== @@ -263,13 +222,13 @@ sdist: ## Create tar.gz source distribution. ${MAKE} generate-manifest $(PYTHON_ENV_VARS) $(PYTHON) setup.py sdist -download-wheels-github: ## Download latest wheels hosted on github. - $(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/download_wheels_github.py --tokenfile=~/.github.token +download-wheels: ## Download latest wheels hosted on github. + $(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/download_wheels.py --tokenfile=~/.github.token ${MAKE} print-dist -download-wheels-appveyor: ## Download latest wheels hosted on appveyor. - $(PYTHON_ENV_VARS) $(PYTHON) scripts/internal/download_wheels_appveyor.py - ${MAKE} print-dist +create-wheels: ## Create .whl files + $(PYTHON_ENV_VARS) $(PYTHON) setup.py bdist_wheel + ${MAKE} check-wheels check-sdist: ## Check sanity of source distribution. $(PYTHON_ENV_VARS) $(PYTHON) -m virtualenv --clear --no-wheel --quiet build/venv @@ -286,22 +245,21 @@ pre-release: ## Check if we're ready to produce a new release. ${MAKE} sdist ${MAKE} check-sdist ${MAKE} install - $(PYTHON) -c \ + @$(PYTHON) -c \ "import requests, sys; \ from packaging.version import parse; \ from psutil import __version__; \ res = requests.get('https://pypi.org/pypi/psutil/json', timeout=5); \ versions = sorted(res.json()['releases'], key=parse, reverse=True); \ sys.exit('version %r already exists on PYPI' % __version__) if __version__ in versions else 0" - $(PYTHON) -c \ + @$(PYTHON) -c \ "from psutil import __version__ as ver; \ doc = open('docs/index.rst').read(); \ history = open('HISTORY.rst').read(); \ - assert ver in doc, '%r not in docs/index.rst' % ver; \ - assert ver in history, '%r not in HISTORY.rst' % ver; \ - assert 'XXXX' not in history, 'XXXX in HISTORY.rst';" - ${MAKE} download-wheels-github - ${MAKE} download-wheels-appveyor + assert ver in doc, '%r not found in docs/index.rst' % ver; \ + assert ver in history, '%r not found in HISTORY.rst' % ver; \ + assert 'XXXX' not in history, 'XXXX found in HISTORY.rst';" + ${MAKE} download-wheels ${MAKE} check-wheels ${MAKE} print-hashes ${MAKE} print-dist @@ -347,6 +305,9 @@ print-downloads: ## Print PYPI download statistics print-hashes: ## Prints hashes of files in dist/ directory $(PYTHON) scripts/internal/print_hashes.py dist/ +print-sysinfo: ## Prints system info + $(PYTHON) -c "from psutil.tests import print_sysinfo; print_sysinfo()" + # =================================================================== # Misc # =================================================================== diff --git a/README.rst b/README.rst index 3fc6e601b..16c756c50 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ | |downloads| |stars| |forks| |contributors| |coverage| | |version| |py-versions| |packages| |license| -| |github-actions-wheels| |github-actions-bsd| |appveyor| |doc| |twitter| |tidelift| +| |github-actions-wheels| |github-actions-bsd| |doc| |twitter| |tidelift| .. |downloads| image:: https://img.shields.io/pypi/dm/psutil.svg :target: https://pepy.tech/project/psutil @@ -26,10 +26,6 @@ :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Absd-tests :alt: FreeBSD, NetBSD, OpenBSD -.. |appveyor| image:: https://img.shields.io/appveyor/build/giampaolo/psutil/master.svg?maxAge=3600&label=Windows%20(py2) - :target: https://ci.appveyor.com/project/giampaolo/psutil - :alt: Windows (Appveyor) - .. |coverage| image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master :target: https://coveralls.io/github/giampaolo/psutil?branch=master :alt: Test coverage (coverall.io) @@ -98,8 +94,9 @@ psutil currently supports the following platforms: - **Sun Solaris** - **AIX** -Supported Python versions are **2.7**, **3.6+** and -`PyPy `__. +Supported Python versions are cPython 3.6+ and `PyPy `__. +Latest psutil version supporting Python 2.7 is +`psutil 6.1.1 `__. Funding ======= diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 70a4daec2..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,83 +0,0 @@ -# Build: 3 (bump this up by 1 to force an appveyor run) - -os: Visual Studio 2015 -# avoid 2 builds when pushing on PRs -skip_branch_with_pr: true -# avoid build on new GIT tag -skip_tags: true -matrix: - # stop build on first failure - fast_finish: true -environment: - global: - # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the - # /E:ON and /V:ON options are not enabled in the batch script interpreter - # See: http://stackoverflow.com/a/13751649/163740 - WITH_COMPILER: "cmd /E:ON /V:ON /C .\\scripts\\internal\\appveyor_run_with_compiler.cmd" - PYTHONWARNINGS: always - PYTHONUNBUFFERED: 1 - PSUTIL_DEBUG: 1 - matrix: - # 32 bits - - - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.x" - PYTHON_ARCH: "32" - - # 64 bits - - - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "2.7.x" - PYTHON_ARCH: "64" - - -init: - - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" - -install: - - "%WITH_COMPILER% %PYTHON%/python.exe -m pip --version" - - "%WITH_COMPILER% %PYTHON%/python.exe -m pip install --upgrade --user setuptools pip" - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py setup-dev-env" - - "%WITH_COMPILER% %PYTHON%/python.exe -m pip freeze" - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py install" - -build: off - -test_script: - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py test" - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py test-memleaks" - -after_test: - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py wheel" - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/print_hashes.py dist" - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/print_access_denied.py" - - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/print_api_speed.py" - -artifacts: - - path: dist\* - -cache: - - '%LOCALAPPDATA%\pip\Cache' - -# on_success: -# - might want to upload the content of dist/*.whl to a public wheelhouse - -skip_commits: - message: skip-appveyor - -# run build only if one of the following files is modified on commit -only_commits: - files: - - .ci/appveyor/* - - appveyor.yml - - psutil/__init__.py - - psutil/_common.py - - psutil/_compat.py - - psutil/_psutil_common.* - - psutil/_psutil_windows.* - - psutil/_pswindows.py - - psutil/arch/windows/* - - psutil/tests/* - - scripts/* - - scripts/internal/* - - setup.py diff --git a/docs/DEVGUIDE.rst b/docs/DEVGUIDE.rst index 744ddadf4..563c9b883 100644 --- a/docs/DEVGUIDE.rst +++ b/docs/DEVGUIDE.rst @@ -12,43 +12,56 @@ Once you have a compiler installed run: .. code-block:: bash git clone git@github.com:giampaolo/psutil.git - make setup-dev-env # install useful dev libs (ruff, coverage, ...) + make install-sysdeps # install gcc and python headers + make install-pydeps-test # install python deps necessary to run unit tests make build make install make test -- ``make`` (see `Makefile`_) is the designated tool to run tests, build, install - try new features you're developing, etc. This also includes Windows (see - `make.bat`_ ). - Some useful commands are: +- If you don't have the source code, and just want to test psutil installation. + This will work also if ``pytest`` module is not installed (e.g. production + environments) by using unittest's test runner: .. code-block:: bash - make test-parallel # faster - make test-memleaks - make test-coverage - make lint-all # Run Python and C linter - make fix-all # Fix linting errors + python3 -m psutil.tests + +- ``make`` (and the accompanying `Makefile`_) is the designated tool to build, + install, run tests and do pretty much anything that involves development. + This also includes Windows, meaning that you can run `make command` similarly + as if you were on UNIX (see `make.bat`_ and `winmake.py`_). Some useful + commands are: + +.. code-block:: bash + + make clean # remove build files + make install-pydeps-dev # install dev deps (ruff, black, ...) + make test # run tests + make test-parallel # run tests in parallel (faster) + make test-memleaks # run memory leak tests + make test-coverage # run test coverage + make lint-all # run linters + make fix-all # fix linters errors make uninstall make help - - To run a specific unit test: .. code-block:: bash - make test ARGS=psutil.tests.test_system.TestDiskAPIs + make test ARGS=psutil/tests/test_system.py::TestDiskAPIs::test_disk_usage - If you're working on a new feature and you wish to compile & test it "on the fly" from a test script, this is a quick & dirty way to do it: .. code-block:: bash - make test TSCRIPT=test_script.py # on UNIX + make test ARGS=test_script.py # on UNIX make test test_script.py # on Windows - Do not use ``sudo``. ``make install`` installs psutil as a limited user in - "edit" mode, meaning you can edit psutil code on the fly while you develop. + "edit" / development mode, meaning you can edit psutil code on the fly while + you develop. - If you want to target a specific Python version: @@ -60,10 +73,12 @@ Once you have a compiler installed run: Coding style ------------ -- Oython code strictly follows `PEP-8`_ styling guides and this is enforced by - a commit GIT hook installed via ``make install-git-hooks`` which will reject - commits if code is not PEP-8 complieant. -- C code should follow `PEP-7`_ styling guides. +- Python code strictly follows `PEP-8`_ styling guide. In addition we use + ``black`` and ``ruff`` code formatters / linters. This is enforced by a GIT + commit hook, installed via ``make install-git-hooks``, which will reject the + commit if code is not compliant. +- C code should follow `PEP-7`_ styling guides, but things are more relaxed. +- Linters are enforced also for ``.rst`` and ``.toml`` files. Code organization ----------------- @@ -101,7 +116,7 @@ Make a pull request - Fork psutil (go to https://github.com/giampaolo/psutil and click on "fork") - Git clone the fork locally: ``git clone git@github.com:YOUR-USERNAME/psutil.git`` -- Create a branch:``git checkout -b new-feature`` +- Create a branch: ``git checkout -b new-feature`` - Commit your changes: ``git commit -am 'add some feature'`` - Push the branch: ``git push origin new-feature`` - Create a new PR via the GitHub web interface and sign-off your work (see @@ -118,13 +133,14 @@ Documentation ------------- - doc source code is written in a single file: ``docs/index.rst``. -- doc can be built with ``make setup-dev-env; cd docs; make html``. +- doc can be built with ``make install-pydeps-dev; cd docs; make html``. - public doc is hosted at https://psutil.readthedocs.io. .. _`CREDITS`: https://github.com/giampaolo/psutil/blob/master/CREDITS .. _`CONTRIBUTING.md`: https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md .. _`HISTORY.rst`: https://github.com/giampaolo/psutil/blob/master/HISTORY.rst .. _`make.bat`: https://github.com/giampaolo/psutil/blob/master/make.bat +.. _`winmake.py`: https://github.com/giampaolo/psutil/blob/master/scripts/internal/winmake.py .. _`Makefile`: https://github.com/giampaolo/psutil/blob/master/Makefile .. _`PEP-7`: https://www.python.org/dev/peps/pep-0007/ .. _`PEP-8`: https://www.python.org/dev/peps/pep-0008/ diff --git a/docs/Makefile b/docs/Makefile index 860a2b0e2..be7e058b0 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -229,6 +229,6 @@ dummy: @echo @echo "Build finished. Dummy builder generates no files." -.PHONY: setup-dev-env -setup-dev-env: ## Install GIT hooks, pip, test deps (also upgrades them). +.PHONY: install-pydeps +install-pydeps: ## Install GIT hooks, pip, test deps (also upgrades them). $(PYTHON) -m pip install --user --upgrade --trusted-host files.pythonhosted.org $(DEPS) diff --git a/docs/conf.py b/docs/conf.py index 3ebc64178..604eeccb4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- 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. @@ -86,7 +84,7 @@ def get_version(): # General information about the project. project = PROJECT_NAME -copyright = '2009-%s, %s' % (THIS_YEAR, AUTHOR) +copyright = f"2009-{THIS_YEAR}, {AUTHOR}" author = AUTHOR # The version info for the project you're documenting, acts as replacement for @@ -269,7 +267,7 @@ def get_version(): # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = '%s-doc' % PROJECT_NAME +htmlhelp_basename = f"{PROJECT_NAME}-doc" # -- Options for LaTeX output --------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 201333af4..bde76d0b5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -224,8 +224,8 @@ CPU .. function:: cpu_count(logical=True) - Return the number of logical CPUs in the system (same as `os.cpu_count`_ - in Python 3.4) or ``None`` if undetermined. + Return the number of logical CPUs in the system (same as `os.cpu_count`_) + or ``None`` if undetermined. "logical CPUs" means the number of physical cores multiplied by the number of threads that can run on each core (this is known as Hyper Threading). If *logical* is ``False`` return the number of physical cores only, or @@ -1203,7 +1203,7 @@ Process class >>> import psutil >>> psutil.Process().exe() - '/usr/bin/python2.7' + '/usr/bin/python3' .. method:: cmdline() @@ -1706,13 +1706,6 @@ Process class .. versionchanged:: 4.0.0 multiple fields are returned, not only `rss` and `vms`. - .. method:: memory_info_ex() - - Same as :meth:`memory_info` (deprecated). - - .. warning:: - deprecated in version 4.0.0; use :meth:`memory_info` instead. - .. method:: memory_full_info() This method returns the same information as :meth:`memory_info`, plus, on @@ -2637,6 +2630,18 @@ On Windows: set PSUTIL_DEBUG=1 python.exe script.py psutil-debug [psutil/arch/windows/proc.c:90]> NtWow64ReadVirtualMemory64(pbi64.PebBaseAddress) -> 998 (Unknown error) (ignored) +Python 2.7 +========== + +Latest version spporting Python 2.7 is `psutil 6.1.1 `__. +The 6.1.X serie may receive critical bug-fixes but no new features. It will +be maintained in the dedicated +`python2 `__ branch. +To install it: + +:: + + $ python2 -m pip install psutil==6.1.* Security ======== @@ -2672,6 +2677,18 @@ PyPy3. Timeline ======== +- 2024-12-19: + `6.1.1 `__ - + `what's new `__ - + `diff `__ +- 2024-10-17: + `6.1.0 `__ - + `what's new `__ - + `diff `__ +- 2024-06-18: + `6.0.0 `__ - + `what's new `__ - + `diff `__ - 2024-01-19: `5.9.8 `__ - `what's new `__ - diff --git a/make.bat b/make.bat index 2ac53d9ec..97af61c3a 100644 --- a/make.bat +++ b/make.bat @@ -10,25 +10,17 @@ rem make install rem make test rem rem This script is modeled after my Windows installation which uses: -rem - Visual studio 2008 for Python 2.7 rem - Visual studio 2010 for Python 3.4+ rem ...therefore it might not work on your Windows installation. rem rem To compile for a specific Python version run: rem set PYTHON=C:\Python34\python.exe & make.bat build -rem -rem To use a different test script: -rem set TSCRIPT=foo.py & make.bat test rem ========================================================================== if "%PYTHON%" == "" ( set PYTHON=python ) -if "%TSCRIPT%" == "" ( - set TSCRIPT=psutil\tests\runner.py -) - rem Needed to locate the .pypirc file and upload exes on PyPI. set HOME=%USERPROFILE% set PSUTIL_DEBUG=1 diff --git a/psutil/__init__.py b/psutil/__init__.py index 30f45987e..d5e2550d2 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -1,5 +1,3 @@ -# -*- 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. @@ -17,17 +15,16 @@ - Sun Solaris - AIX -Works with Python versions 2.7 and 3.6+. +Supported Python versions are cPython 3.6+ and PyPy. """ -from __future__ import division - import collections import contextlib import datetime import functools import os import signal +import socket import subprocess import sys import threading @@ -54,16 +51,16 @@ from ._common import CONN_SYN_RECV from ._common import CONN_SYN_SENT from ._common import CONN_TIME_WAIT -from ._common import FREEBSD # NOQA +from ._common import FREEBSD from ._common import LINUX from ._common import MACOS -from ._common import NETBSD # NOQA +from ._common import NETBSD from ._common import NIC_DUPLEX_FULL from ._common import NIC_DUPLEX_HALF from ._common import NIC_DUPLEX_UNKNOWN -from ._common import OPENBSD # NOQA +from ._common import OPENBSD from ._common import OSX # deprecated alias -from ._common import POSIX # NOQA +from ._common import POSIX from ._common import POWER_TIME_UNKNOWN from ._common import POWER_TIME_UNLIMITED from ._common import STATUS_DEAD @@ -85,13 +82,9 @@ from ._common import NoSuchProcess from ._common import TimeoutExpired from ._common import ZombieProcess +from ._common import debug from ._common import memoize_when_activated from ._common import wrap_numbers as _wrap_numbers -from ._compat import PY3 as _PY3 -from ._compat import PermissionError -from ._compat import ProcessLookupError -from ._compat import SubprocessTimeoutExpired as _SubprocessTimeoutExpired -from ._compat import long if LINUX: @@ -100,24 +93,24 @@ PROCFS_PATH = "/proc" from . import _pslinux as _psplatform - from ._pslinux import IOPRIO_CLASS_BE # NOQA - from ._pslinux import IOPRIO_CLASS_IDLE # NOQA - from ._pslinux import IOPRIO_CLASS_NONE # NOQA - from ._pslinux import IOPRIO_CLASS_RT # NOQA + from ._pslinux import IOPRIO_CLASS_BE # noqa: F401 + from ._pslinux import IOPRIO_CLASS_IDLE # noqa: F401 + from ._pslinux import IOPRIO_CLASS_NONE # noqa: F401 + from ._pslinux import IOPRIO_CLASS_RT # noqa: F401 elif WINDOWS: from . import _pswindows as _psplatform - from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS # NOQA - from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS # NOQA - from ._psutil_windows import HIGH_PRIORITY_CLASS # NOQA - from ._psutil_windows import IDLE_PRIORITY_CLASS # NOQA - from ._psutil_windows import NORMAL_PRIORITY_CLASS # NOQA - from ._psutil_windows import REALTIME_PRIORITY_CLASS # NOQA - from ._pswindows import CONN_DELETE_TCB # NOQA - from ._pswindows import IOPRIO_HIGH # NOQA - from ._pswindows import IOPRIO_LOW # NOQA - from ._pswindows import IOPRIO_NORMAL # NOQA - from ._pswindows import IOPRIO_VERYLOW # NOQA + from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS # noqa: F401 + from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS # noqa: F401 + from ._psutil_windows import HIGH_PRIORITY_CLASS # noqa: F401 + from ._psutil_windows import IDLE_PRIORITY_CLASS # noqa: F401 + from ._psutil_windows import NORMAL_PRIORITY_CLASS # noqa: F401 + from ._psutil_windows import REALTIME_PRIORITY_CLASS # noqa: F401 + from ._pswindows import CONN_DELETE_TCB # noqa: F401 + from ._pswindows import IOPRIO_HIGH # noqa: F401 + from ._pswindows import IOPRIO_LOW # noqa: F401 + from ._pswindows import IOPRIO_NORMAL # noqa: F401 + from ._pswindows import IOPRIO_VERYLOW # noqa: F401 elif MACOS: from . import _psosx as _psplatform @@ -127,8 +120,8 @@ elif SUNOS: from . import _pssunos as _psplatform - from ._pssunos import CONN_BOUND # NOQA - from ._pssunos import CONN_IDLE # NOQA + from ._pssunos import CONN_BOUND # noqa: F401 + from ._pssunos import CONN_IDLE # noqa: F401 # This is public writable API which is read from _pslinux.py and # _pssunos.py via sys.modules. @@ -142,7 +135,8 @@ PROCFS_PATH = "/proc" else: # pragma: no cover - raise NotImplementedError('platform %s is not supported' % sys.platform) + msg = f"platform {sys.platform} is not supported" + raise NotImplementedError(msg) # fmt: off @@ -213,7 +207,7 @@ AF_LINK = _psplatform.AF_LINK __author__ = "Giampaolo Rodola'" -__version__ = "6.0.0" +__version__ = "7.0.0" version_info = tuple([int(num) for num in __version__.split('.')]) _timer = getattr(time, 'monotonic', time.time) @@ -230,22 +224,19 @@ if int(__version__.replace('.', '')) != getattr( _psplatform.cext, 'version', None ): - msg = "version conflict: %r C extension " % _psplatform.cext.__file__ + msg = f"version conflict: {_psplatform.cext.__file__!r} C extension " msg += "module was built for another version of psutil" if hasattr(_psplatform.cext, 'version'): - msg += " (%s instead of %s)" % ( - '.'.join([x for x in str(_psplatform.cext.version)]), - __version__, - ) + v = ".".join([x for x in str(_psplatform.cext.version)]) + msg += f" ({v} instead of {__version__})" else: - msg += " (different than %s)" % __version__ - msg += "; you may try to 'pip uninstall psutil', manually remove %s" % ( - getattr( - _psplatform.cext, - "__file__", - "the existing psutil install directory", - ) + msg += f" (different than {__version__})" + what = getattr( + _psplatform.cext, + "__file__", + "the existing psutil install directory", ) + msg += f"; you may try to 'pip uninstall psutil', manually remove {what}" msg += " or clean the virtual env somehow, then reinstall" raise ImportError(msg) @@ -281,12 +272,20 @@ def _pprint_secs(secs): return datetime.datetime.fromtimestamp(secs).strftime(fmt) +def _check_conn_kind(kind): + """Check net_connections()'s `kind` parameter.""" + kinds = tuple(_common.conn_tmap) + if kind not in kinds: + msg = f"invalid kind argument {kind!r}; valid ones are: {kinds}" + raise ValueError(msg) + + # ===================================================================== # --- Process class # ===================================================================== -class Process(object): # noqa: UP004 +class Process: """Represents an OS process with the given PID. If PID is omitted current process PID (os.getpid()) is used. Raise NoSuchProcess if PID does not exist. @@ -321,17 +320,14 @@ def _init(self, pid, _ignore_nsp=False): if pid is None: pid = os.getpid() else: - if not _PY3 and not isinstance(pid, (int, long)): - msg = "pid must be an integer (got %r)" % pid - raise TypeError(msg) if pid < 0: - msg = "pid must be a positive integer (got %s)" % pid + msg = f"pid must be a positive integer (got {pid})" raise ValueError(msg) try: _psplatform.cext.check_pid_range(pid) - except OverflowError: - msg = "process PID out of range (got %s)" % pid - raise NoSuchProcess(pid, msg=msg) + except OverflowError as err: + msg = "process PID out of range" + raise NoSuchProcess(pid, msg=msg) from err self._pid = pid self._name = None @@ -349,13 +345,13 @@ def _init(self, pid, _ignore_nsp=False): self._last_sys_cpu_times = None self._last_proc_cpu_times = None self._exitcode = _SENTINEL - # cache creation time for later use in is_running() method + self._ident = (self.pid, None) try: - self.create_time() + self._ident = self._get_ident() except AccessDenied: - # We should never get here as AFAIK we're able to get - # process creation time on all platforms even as a - # limited user. + # This should happen on Windows only, since we use the fast + # create time method. AFAIK, on all other platforms we are + # able to get create time for all PIDs. pass except ZombieProcess: # Zombies can still be queried by this class (although @@ -364,14 +360,34 @@ def _init(self, pid, _ignore_nsp=False): except NoSuchProcess: if not _ignore_nsp: msg = "process PID not found" - raise NoSuchProcess(pid, msg=msg) - else: - self._gone = True - # This pair is supposed to identify a Process instance - # univocally over time (the PID alone is not enough as - # it might refer to a process whose PID has been reused). - # This will be used later in __eq__() and is_running(). - self._ident = (self.pid, self._create_time) + raise NoSuchProcess(pid, msg=msg) from None + self._gone = True + + def _get_ident(self): + """Return a (pid, uid) tuple which is supposed to identify a + Process instance univocally over time. The PID alone is not + enough, as it can be assigned to a new process after this one + terminates, so we add process creation time to the mix. We need + this in order to prevent killing the wrong process later on. + This is also known as PID reuse or PID recycling problem. + + The reliability of this strategy mostly depends on + create_time() precision, which is 0.01 secs on Linux. The + assumption is that, after a process terminates, the kernel + won't reuse the same PID after such a short period of time + (0.01 secs). Technically this is inherently racy, but + practically it should be good enough. + """ + if WINDOWS: + # Use create_time() fast method in order to speedup + # `process_iter()`. This means we'll get AccessDenied for + # most ADMIN processes, but that's fine since it means + # we'll also get AccessDenied on kill(). + # https://github.com/giampaolo/psutil/issues/2366#issuecomment-2381646555 + self._create_time = self._proc.create_time(fast_only=True) + return (self.pid, self._create_time) + else: + return (self.pid, self.create_time()) def __str__(self): info = collections.OrderedDict() @@ -392,15 +408,15 @@ def __str__(self): except AccessDenied: pass - if self._exitcode not in (_SENTINEL, None): + if self._exitcode not in {_SENTINEL, None}: info["exitcode"] = self._exitcode if self._create_time is not None: info['started'] = _pprint_secs(self._create_time) - return "%s.%s(%s)" % ( + return "{}.{}({})".format( self.__class__.__module__, self.__class__.__name__, - ", ".join(["%s=%r" % (k, v) for k, v in info.items()]), + ", ".join([f"{k}={v!r}" for k, v in info.items()]), ) __repr__ = __str__ @@ -416,10 +432,10 @@ def __eq__(self, other): # (so it has a ctime), then it turned into a zombie. It's # important to do this because is_running() depends on # __eq__. - pid1, ctime1 = self._ident - pid2, ctime2 = other._ident + pid1, ident1 = self._ident + pid2, ident2 = other._ident if pid1 == pid2: - if ctime1 and not ctime2: + if ident1 and not ident2: try: return self.status() == STATUS_ZOMBIE except Error: @@ -534,12 +550,12 @@ def as_dict(self, attrs=None, ad_value=None): valid_names = _as_dict_attrnames if attrs is not None: if not isinstance(attrs, (list, tuple, set, frozenset)): - msg = "invalid attrs type %s" % type(attrs) + msg = f"invalid attrs type {type(attrs)}" raise TypeError(msg) attrs = set(attrs) invalid_names = attrs - valid_names if invalid_names: - msg = "invalid attr name%s %s" % ( + msg = "invalid attr name{} {}".format( "s" if len(invalid_names) > 1 else "", ", ".join(map(repr, invalid_names)), ) @@ -613,8 +629,7 @@ def is_running(self): # time) and that is verified in __eq__. self._pid_reused = self != Process(self.pid) if self._pid_reused: - # remove this PID from `process_iter()` internal cache - _pmap.pop(self.pid, None) + _pids_reused.add(self.pid) raise NoSuchProcess(self.pid) return True except ZombieProcess: @@ -1028,7 +1043,7 @@ def cpu_percent(self, interval=None): """ blocking = interval is not None and interval > 0.0 if interval is not None and interval < 0: - msg = "interval is not positive (got %r)" % interval + msg = f"interval is not positive (got {interval!r})" raise ValueError(msg) num_cpus = cpu_count() or 1 @@ -1106,10 +1121,6 @@ def memory_info(self): """ return self._proc.memory_info() - @_common.deprecated_method(replacement="memory_info") - def memory_info_ex(self): - return self.memory_info() - def memory_full_info(self): """This method returns the same information as memory_info(), plus, on some platform (Linux, macOS, Windows), also provides @@ -1138,9 +1149,9 @@ def memory_percent(self, memtype="rss"): """ valid_types = list(_psplatform.pfullmem._fields) if memtype not in valid_types: - msg = "invalid memtype %r; valid types are %r" % ( - memtype, - tuple(valid_types), + msg = ( + f"invalid memtype {memtype!r}; valid types are" + f" {tuple(valid_types)!r}" ) raise ValueError(msg) fun = ( @@ -1157,7 +1168,7 @@ def memory_percent(self, memtype="rss"): # we should never get here msg = ( "can't calculate process memory percent because total physical" - " system memory is not positive (%r)" % (total_phymem) + f" system memory is not positive ({total_phymem!r})" ) raise ValueError(msg) return (value / float(total_phymem)) * 100 @@ -1186,7 +1197,7 @@ def memory_maps(self, grouped=True): except KeyError: d[path] = nums nt = _psplatform.pmmap_grouped - return [nt(path, *d[path]) for path in d] # NOQA + return [nt(path, *d[path]) for path in d] else: nt = _psplatform.pmmap_ext return [nt(*x) for x in it] @@ -1220,6 +1231,7 @@ def net_connections(self, kind='inet'): | all | the sum of all the possible families and protocols | +------------+----------------------------------------------------+ """ + _check_conn_kind(kind) return self._proc.net_connections(kind) @_common.deprecated_method(replacement="net_connections") @@ -1233,7 +1245,9 @@ def connections(self, kind="inet"): def _send_signal(self, sig): assert not self.pid < 0, self.pid self._raise_if_pid_reused() - if self.pid == 0: + + pid, ppid, name = self.pid, self._ppid, self._name + if pid == 0: # see "man 2 kill" msg = ( "preventing sending signal to process with PID 0 as it " @@ -1242,17 +1256,16 @@ def _send_signal(self, sig): ) raise ValueError(msg) try: - os.kill(self.pid, sig) - except ProcessLookupError: - if OPENBSD and pid_exists(self.pid): + os.kill(pid, sig) + except ProcessLookupError as err: + if OPENBSD and pid_exists(pid): # We do this because os.kill() lies in case of # zombie processes. - raise ZombieProcess(self.pid, self._name, self._ppid) - else: - self._gone = True - raise NoSuchProcess(self.pid, self._name) - except PermissionError: - raise AccessDenied(self.pid, self._name) + raise ZombieProcess(pid, name, ppid) from err + self._gone = True + raise NoSuchProcess(pid, name) from err + except PermissionError as err: + raise AccessDenied(pid, name) from err def send_signal(self, sig): """Send a signal *sig* to process pre-emptively checking @@ -1337,11 +1350,12 @@ def wait(self, timeout=None): # The valid attr names which can be processed by Process.as_dict(). # fmt: off -_as_dict_attrnames = set( - [x for x in dir(Process) if not x.startswith('_') and x not in +_as_dict_attrnames = { + x for x in dir(Process) if not x.startswith("_") and x not in {'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', 'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit', - 'memory_info_ex', 'connections', 'oneshot'}]) + 'connections', 'oneshot'} +} # fmt: on @@ -1418,16 +1432,13 @@ def __getattribute__(self, name): try: return object.__getattribute__(self.__subproc, name) except AttributeError: - msg = "%s instance has no attribute '%s'" % ( - self.__class__.__name__, - name, - ) - raise AttributeError(msg) + msg = f"{self.__class__!r} has no attribute {name!r}" + raise AttributeError(msg) from None def wait(self, timeout=None): if self.__subproc.returncode is not None: return self.__subproc.returncode - ret = super(Popen, self).wait(timeout) # noqa + ret = super().wait(timeout) self.__subproc.returncode = ret return ret @@ -1464,6 +1475,7 @@ def pid_exists(pid): _pmap = {} +_pids_reused = set() def process_iter(attrs=None, ad_value=None): @@ -1501,6 +1513,10 @@ def remove(pid): gone_pids = b - a for pid in gone_pids: remove(pid) + while _pids_reused: + pid = _pids_reused.pop() + debug(f"refreshing Process instance for reused PID {pid}") + remove(pid) try: ls = sorted(list(pmap.items()) + list(dict.fromkeys(new_pids).items())) for pid, proc in ls: @@ -1516,7 +1532,7 @@ def remove(pid): _pmap = pmap -process_iter.cache_clear = lambda: _pmap.clear() # noqa +process_iter.cache_clear = lambda: _pmap.clear() # noqa: PLW0108 process_iter.cache_clear.__doc__ = "Clear process_iter() internal cache." @@ -1560,9 +1576,7 @@ def wait_procs(procs, timeout=None, callback=None): def check_gone(proc, timeout): try: returncode = proc.wait(timeout=timeout) - except TimeoutExpired: - pass - except _SubprocessTimeoutExpired: + except (TimeoutExpired, subprocess.TimeoutExpired): pass else: if returncode is not None or not proc.is_running(): @@ -1573,12 +1587,12 @@ def check_gone(proc, timeout): callback(proc) if timeout is not None and not timeout >= 0: - msg = "timeout must be a positive integer, got %s" % timeout + msg = f"timeout must be a positive integer, got {timeout}" raise ValueError(msg) gone = set() alive = set(procs) if callback is not None and not callable(callback): - msg = "callback %r is not a callable" % callback + msg = f"callback {callback!r} is not a callable" raise TypeError(msg) if timeout is not None: deadline = _timer() + timeout @@ -1601,7 +1615,7 @@ def check_gone(proc, timeout): check_gone(proc, timeout) else: check_gone(proc, max_timeout) - alive = alive - gone # noqa PLR6104 + alive = alive - gone # noqa: PLR6104 if alive: # Last attempt over processes survived so far. @@ -1620,7 +1634,7 @@ def check_gone(proc, timeout): def cpu_count(logical=True): """Return the number of logical CPUs in the system (same as - os.cpu_count() in Python 3.4). + os.cpu_count()). If *logical* is False return the number of physical cores only (e.g. hyper thread CPUs are excluded). @@ -1778,7 +1792,7 @@ def cpu_percent(interval=None, percpu=False): tid = threading.current_thread().ident blocking = interval is not None and interval > 0.0 if interval is not None and interval < 0: - msg = "interval is not positive (got %r)" % interval + msg = f"interval is not positive (got {interval})" raise ValueError(msg) def calculate(t1, t2): @@ -1838,7 +1852,7 @@ def cpu_times_percent(interval=None, percpu=False): tid = threading.current_thread().ident blocking = interval is not None and interval > 0.0 if interval is not None and interval < 0: - msg = "interval is not positive (got %r)" % interval + msg = f"interval is not positive (got {interval!r})" raise ValueError(msg) def calculate(t1, t2): @@ -2176,6 +2190,7 @@ def net_connections(kind='inet'): On macOS this function requires root privileges. """ + _check_conn_kind(kind) return _psplatform.net_connections(kind) @@ -2197,34 +2212,29 @@ def net_if_addrs(): Note: you can have more than one address of the same family associated with each interface. """ - has_enums = _PY3 - if has_enums: - import socket rawlist = _psplatform.net_if_addrs() rawlist.sort(key=lambda x: x[1]) # sort by family ret = collections.defaultdict(list) for name, fam, addr, mask, broadcast, ptp in rawlist: - if has_enums: - try: - fam = socket.AddressFamily(fam) - except ValueError: - if WINDOWS and fam == -1: - fam = _psplatform.AF_LINK - elif ( - hasattr(_psplatform, "AF_LINK") - and fam == _psplatform.AF_LINK - ): - # Linux defines AF_LINK as an alias for AF_PACKET. - # We re-set the family here so that repr(family) - # will show AF_LINK rather than AF_PACKET - fam = _psplatform.AF_LINK + try: + fam = socket.AddressFamily(fam) + except ValueError: + if WINDOWS and fam == -1: + fam = _psplatform.AF_LINK + elif ( + hasattr(_psplatform, "AF_LINK") and fam == _psplatform.AF_LINK + ): + # Linux defines AF_LINK as an alias for AF_PACKET. + # We re-set the family here so that repr(family) + # will show AF_LINK rather than AF_PACKET + fam = _psplatform.AF_LINK if fam == _psplatform.AF_LINK: # The underlying C function may return an incomplete MAC # address in which case we fill it with null bytes, see: # https://github.com/giampaolo/psutil/issues/786 separator = ":" if POSIX else "-" while addr.count(separator) < 5: - addr += "%s00" % separator + addr += f"{separator}00" ret[name].append(_common.snicaddr(fam, addr, mask, broadcast, ptp)) return dict(ret) @@ -2378,83 +2388,4 @@ def _set_debug(value): _psplatform.cext.set_debug(bool(value)) -def test(): # pragma: no cover - from ._common import bytes2human - from ._compat import get_terminal_size - - today_day = datetime.date.today() - # fmt: off - templ = "%-10s %5s %5s %7s %7s %5s %6s %6s %6s %s" - attrs = ['pid', 'memory_percent', 'name', 'cmdline', 'cpu_times', - 'create_time', 'memory_info', 'status', 'nice', 'username'] - print(templ % ("USER", "PID", "%MEM", "VSZ", "RSS", "NICE", # NOQA - "STATUS", "START", "TIME", "CMDLINE")) - # fmt: on - for p in process_iter(attrs, ad_value=None): - if p.info['create_time']: - ctime = datetime.datetime.fromtimestamp(p.info['create_time']) - if ctime.date() == today_day: - ctime = ctime.strftime("%H:%M") - else: - ctime = ctime.strftime("%b%d") - else: - ctime = '' - if p.info['cpu_times']: - cputime = time.strftime( - "%M:%S", time.localtime(sum(p.info['cpu_times'])) - ) - else: - cputime = '' - - user = p.info['username'] or '' - if not user and POSIX: - try: - user = p.uids()[0] - except Error: - pass - if user and WINDOWS and '\\' in user: - user = user.split('\\')[1] - user = user[:9] - vms = ( - bytes2human(p.info['memory_info'].vms) - if p.info['memory_info'] is not None - else '' - ) - rss = ( - bytes2human(p.info['memory_info'].rss) - if p.info['memory_info'] is not None - else '' - ) - memp = ( - round(p.info['memory_percent'], 1) - if p.info['memory_percent'] is not None - else '' - ) - nice = int(p.info['nice']) if p.info['nice'] else '' - if p.info['cmdline']: - cmdline = ' '.join(p.info['cmdline']) - else: - cmdline = p.info['name'] - status = p.info['status'][:5] if p.info['status'] else '' - - line = templ % ( - user[:10], - p.info['pid'], - memp, - vms, - rss, - nice, - status, - ctime, - cputime, - cmdline, - ) - print(line[: get_terminal_size()[0]]) # NOQA - - -del memoize_when_activated, division -if sys.version_info[0] < 3: - del num, x # noqa - -if __name__ == "__main__": - test() +del memoize_when_activated diff --git a/psutil/_common.py b/psutil/_common.py index c1ff18d1f..0c385fa6a 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -7,12 +7,9 @@ # Note: this module is imported by setup.py so it should not import # psutil or third-party modules. -from __future__ import division -from __future__ import print_function import collections -import contextlib -import errno +import enum import functools import os import socket @@ -36,14 +33,6 @@ AF_UNIX = None -# can't take it from _common.py as this script is imported by setup.py -PY3 = sys.version_info[0] >= 3 -if PY3: - import enum -else: - enum = None - - PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG')) _DEFAULT = object() @@ -57,7 +46,7 @@ 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT', # net constants - 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', + 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', # noqa: F822 # process status constants 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED', 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED', @@ -134,42 +123,29 @@ CONN_CLOSING = "CLOSING" CONN_NONE = "NONE" + # net_if_stats() -if enum is None: +class NicDuplex(enum.IntEnum): NIC_DUPLEX_FULL = 2 NIC_DUPLEX_HALF = 1 NIC_DUPLEX_UNKNOWN = 0 -else: - class NicDuplex(enum.IntEnum): - NIC_DUPLEX_FULL = 2 - NIC_DUPLEX_HALF = 1 - NIC_DUPLEX_UNKNOWN = 0 - globals().update(NicDuplex.__members__) +globals().update(NicDuplex.__members__) + # sensors_battery() -if enum is None: +class BatteryTime(enum.IntEnum): POWER_TIME_UNKNOWN = -1 POWER_TIME_UNLIMITED = -2 -else: - class BatteryTime(enum.IntEnum): - POWER_TIME_UNKNOWN = -1 - POWER_TIME_UNLIMITED = -2 - globals().update(BatteryTime.__members__) +globals().update(BatteryTime.__members__) # --- others ENCODING = sys.getfilesystemencoding() -if not PY3: - ENCODING_ERRS = "replace" -else: - try: - ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6 - except AttributeError: - ENCODING_ERRS = "surrogateescape" if POSIX else "replace" +ENCODING_ERRS = sys.getfilesystemencodeerrors() # =================================================================== @@ -273,7 +249,7 @@ class BatteryTime(enum.IntEnum): "udp6": ([AF_INET6], [SOCK_DGRAM]), }) -if AF_UNIX is not None: +if AF_UNIX is not None and not SUNOS: conn_tmap.update({"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM])}) @@ -293,9 +269,7 @@ def _infodict(self, attrs): info = collections.OrderedDict() for name in attrs: value = getattr(self, name, None) - if value: # noqa - info[name] = value - elif name == "pid" and value == 0: + if value or (name == "pid" and value == 0): info[name] = value return info @@ -303,8 +277,8 @@ def __str__(self): # invoked on `raise Error` info = self._infodict(("pid", "ppid", "name")) if info: - details = "(%s)" % ", ".join( - ["%s=%r" % (k, v) for k, v in info.items()] + details = "({})".format( + ", ".join([f"{k}={v!r}" for k, v in info.items()]) ) else: details = None @@ -313,8 +287,8 @@ def __str__(self): def __repr__(self): # invoked on `repr(Error)` info = self._infodict(("pid", "ppid", "name", "seconds", "msg")) - details = ", ".join(["%s=%r" % (k, v) for k, v in info.items()]) - return "psutil.%s(%s)" % (self.__class__.__name__, details) + details = ", ".join([f"{k}={v!r}" for k, v in info.items()]) + return f"psutil.{self.__class__.__name__}({details})" class NoSuchProcess(Error): @@ -380,7 +354,7 @@ def __init__(self, seconds, pid=None, name=None): self.seconds = seconds self.pid = pid self.name = name - self.msg = "timeout after %s seconds" % seconds + self.msg = f"timeout after {seconds} seconds" def __reduce__(self): return (self.__class__, (self.seconds, self.pid, self.name)) @@ -391,26 +365,6 @@ def __reduce__(self): # =================================================================== -# This should be in _compat.py rather than here, but does not work well -# with setup.py importing this module via a sys.path trick. -if PY3: - if isinstance(__builtins__, dict): # cpython - exec_ = __builtins__["exec"] - else: # pypy - exec_ = getattr(__builtins__, "exec") # noqa - - exec_("""def raise_from(value, from_value): - try: - raise value from from_value - finally: - value = None - """) -else: - - def raise_from(value, from_value): - raise value - - def usage_percent(used, total, round_=None): """Calculate percentage usage of 'used' against 'total'.""" try: @@ -456,7 +410,7 @@ def wrapper(*args, **kwargs): try: ret = cache[key] = fun(*args, **kwargs) except Exception as err: # noqa: BLE001 - raise raise_from(err, None) + raise err from None return ret def cache_clear(): @@ -505,14 +459,14 @@ def wrapper(self): try: return fun(self) except Exception as err: # noqa: BLE001 - raise raise_from(err, None) + raise err from None except KeyError: # case 3: we entered oneshot() ctx but there's no cache # for this entry yet try: ret = fun(self) except Exception as err: # noqa: BLE001 - raise raise_from(err, None) + raise err from None try: self._cache[fun] = ret except AttributeError: @@ -546,9 +500,9 @@ def isfile_strict(path): """ try: st = os.stat(path) - except OSError as err: - if err.errno in (errno.EPERM, errno.EACCES): - raise + except PermissionError: + raise + except OSError: return False else: return stat.S_ISREG(st.st_mode) @@ -561,9 +515,9 @@ def path_exists_strict(path): """ try: os.stat(path) - except OSError as err: - if err.errno in (errno.EPERM, errno.EACCES): - raise + except PermissionError: + raise + except OSError: return False else: return True @@ -575,11 +529,10 @@ def supports_ipv6(): if not socket.has_ipv6 or AF_INET6 is None: return False try: - sock = socket.socket(AF_INET6, socket.SOCK_STREAM) - with contextlib.closing(sock): + with socket.socket(AF_INET6, socket.SOCK_STREAM) as sock: sock.bind(("::1", 0)) return True - except socket.error: + except OSError: return False @@ -615,36 +568,30 @@ def sockfam_to_enum(num): """Convert a numeric socket family value to an IntEnum member. If it's not a known member, return the numeric value itself. """ - if enum is None: + try: + return socket.AddressFamily(num) + except ValueError: return num - else: # pragma: no cover - try: - return socket.AddressFamily(num) - except ValueError: - return num def socktype_to_enum(num): """Convert a numeric socket type value to an IntEnum member. If it's not a known member, return the numeric value itself. """ - if enum is None: + try: + return socket.SocketKind(num) + except ValueError: return num - else: # pragma: no cover - try: - return socket.SocketKind(num) - except ValueError: - return num def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None): """Convert a raw connection tuple to a proper ntuple.""" - if fam in (socket.AF_INET, AF_INET6): + if fam in {socket.AF_INET, AF_INET6}: if laddr: laddr = addr(*laddr) if raddr: raddr = addr(*raddr) - if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6): + if type_ == socket.SOCK_STREAM and fam in {AF_INET, AF_INET6}: status = status_map.get(status, CONN_NONE) else: status = CONN_NONE # ignore whatever C returned to us @@ -662,9 +609,9 @@ def deprecated_method(replacement): """ def outer(fun): - msg = "%s() is deprecated and will be removed; use %s() instead" % ( - fun.__name__, - replacement, + msg = ( + f"{fun.__name__}() is deprecated and will be removed; use" + f" {replacement}() instead" ) if fun.__doc__ is None: fun.__doc__ = msg @@ -789,8 +736,6 @@ def wrap_numbers(input_dict, name): # is 8K. We use a bigger buffer (32K) in order to have more consistent # results when reading /proc pseudo files on Linux, see: # https://github.com/giampaolo/psutil/issues/2050 -# On Python 2 this also speeds up the reading of big files: -# (namely /proc/{pid}/smaps and /proc/net/*): # https://github.com/giampaolo/psutil/issues/708 FILE_READ_BUFFER_SIZE = 32 * 1024 @@ -800,17 +745,13 @@ def open_binary(fname): def open_text(fname): - """On Python 3 opens a file in text mode by using fs encoding and - a proper en/decoding errors handler. - On Python 2 this is just an alias for open(name, 'rt'). + """Open a file in text mode by using the proper FS encoding and + en/decoding error handlers. """ - if not PY3: - return open(fname, buffering=FILE_READ_BUFFER_SIZE) - # See: # https://github.com/giampaolo/psutil/issues/675 # https://github.com/giampaolo/psutil/pull/733 - fobj = open( + fobj = open( # noqa: SIM115 fname, buffering=FILE_READ_BUFFER_SIZE, encoding=ENCODING, @@ -842,7 +783,7 @@ def cat(fname, fallback=_DEFAULT, _open=open_text): try: with _open(fname) as f: return f.read() - except (IOError, OSError): + except OSError: return fallback @@ -875,15 +816,8 @@ def get_procfs_path(): return sys.modules['psutil'].PROCFS_PATH -if PY3: - - def decode(s): - return s.decode(encoding=ENCODING, errors=ENCODING_ERRS) - -else: - - def decode(s): - return s +def decode(s): + return s.decode(encoding=ENCODING, errors=ENCODING_ERRS) # ===================================================================== @@ -927,13 +861,12 @@ def hilite(s, color=None, bold=False): # pragma: no cover try: color = colors[color] except KeyError: - raise ValueError( - "invalid color %r; choose between %s" % (list(colors.keys())) - ) + msg = f"invalid color {color!r}; choose amongst {list(colors.keys())}" + raise ValueError(msg) from None attr.append(color) if bold: attr.append('1') - return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s) + return f"\x1b[{';'.join(attr)}m{s}\x1b[0m" def print_color( @@ -941,9 +874,9 @@ def print_color( ): # pragma: no cover """Print a colorized version of string.""" if not term_supports_colors(): - print(s, file=file) # NOQA + print(s, file=file) elif POSIX: - print(hilite(s, color, bold), file=file) # NOQA + print(hilite(s, color, bold), file=file) else: import ctypes @@ -958,10 +891,11 @@ def print_color( try: color = colors[color] except KeyError: - raise ValueError( - "invalid color %r; choose between %r" - % (color, list(colors.keys())) + msg = ( + f"invalid color {color!r}; choose between" + f" {list(colors.keys())!r}" ) + raise ValueError(msg) from None if bold and color <= 7: color += 8 @@ -970,7 +904,7 @@ def print_color( handle = GetStdHandle(handle_id) SetConsoleTextAttribute(handle, color) try: - print(s, file=file) # NOQA + print(s, file=file) finally: SetConsoleTextAttribute(handle, DEFAULT_COLOR) @@ -980,15 +914,15 @@ def debug(msg): if PSUTIL_DEBUG: import inspect - fname, lineno, _, lines, index = inspect.getframeinfo( + fname, lineno, _, _lines, _index = inspect.getframeinfo( inspect.currentframe().f_back ) if isinstance(msg, Exception): - if isinstance(msg, (OSError, IOError, EnvironmentError)): + if isinstance(msg, OSError): # ...because str(exc) may contain info about the file name - msg = "ignoring %s" % msg + msg = f"ignoring {msg}" else: - msg = "ignoring %r" % msg - print( # noqa - "psutil-debug [%s:%s]> %s" % (fname, lineno, msg), file=sys.stderr + msg = f"ignoring {msg!r}" + print( # noqa: T201 + f"psutil-debug [{fname}:{lineno}]> {msg}", file=sys.stderr ) diff --git a/psutil/_compat.py b/psutil/_compat.py deleted file mode 100644 index 6070c2a04..000000000 --- a/psutil/_compat.py +++ /dev/null @@ -1,477 +0,0 @@ -# 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. - -"""Module which provides compatibility with older Python versions. -This is more future-compatible rather than the opposite (prefer latest -Python 3 way of doing things). -""" - -import collections -import contextlib -import errno -import functools -import os -import sys -import types - - -# fmt: off -__all__ = [ - # constants - "PY3", - # builtins - "long", "range", "super", "unicode", "basestring", - # literals - "b", - # collections module - "lru_cache", - # shutil module - "which", "get_terminal_size", - # contextlib module - "redirect_stderr", - # python 3 exceptions - "FileNotFoundError", "PermissionError", "ProcessLookupError", - "InterruptedError", "ChildProcessError", "FileExistsError", -] -# fmt: on - - -PY3 = sys.version_info[0] >= 3 -_SENTINEL = object() - -if PY3: - long = int - xrange = range - unicode = str - basestring = str - range = range - - def b(s): - return s.encode("latin-1") - -else: - long = long - range = xrange - unicode = unicode - basestring = basestring - - def b(s): - return s - - -# --- builtins - - -# Python 3 super(). -# Taken from "future" package. -# Credit: Ryan Kelly -if PY3: - super = super -else: - _builtin_super = super - - def super(type_=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1): - """Like Python 3 builtin super(). If called without any arguments - it attempts to infer them at runtime. - """ - if type_ is _SENTINEL: - f = sys._getframe(framedepth) - try: - # Get the function's first positional argument. - type_or_obj = f.f_locals[f.f_code.co_varnames[0]] - except (IndexError, KeyError): - msg = 'super() used in a function with no args' - raise RuntimeError(msg) - try: - # Get the MRO so we can crawl it. - mro = type_or_obj.__mro__ - except (AttributeError, RuntimeError): - try: - mro = type_or_obj.__class__.__mro__ - except AttributeError: - msg = 'super() used in a non-newstyle class' - raise RuntimeError(msg) - for type_ in mro: - # Find the class that owns the currently-executing method. - for meth in type_.__dict__.values(): - # Drill down through any wrappers to the underlying func. - # This handles e.g. classmethod() and staticmethod(). - try: - while not isinstance(meth, types.FunctionType): - if isinstance(meth, property): - # Calling __get__ on the property will invoke - # user code which might throw exceptions or - # have side effects - meth = meth.fget - else: - try: - meth = meth.__func__ - except AttributeError: - meth = meth.__get__(type_or_obj, type_) - except (AttributeError, TypeError): - continue - if meth.func_code is f.f_code: - break # found - else: - # Not found. Move onto the next class in MRO. - continue - break # found - else: - msg = 'super() called outside a method' - raise RuntimeError(msg) - - # Dispatch to builtin super(). - if type_or_obj is not _SENTINEL: - return _builtin_super(type_, type_or_obj) - return _builtin_super(type_) - - -# --- exceptions - - -if PY3: - FileNotFoundError = FileNotFoundError # NOQA - PermissionError = PermissionError # NOQA - ProcessLookupError = ProcessLookupError # NOQA - InterruptedError = InterruptedError # NOQA - ChildProcessError = ChildProcessError # NOQA - FileExistsError = FileExistsError # NOQA -else: - # https://github.com/PythonCharmers/python-future/blob/exceptions/ - # src/future/types/exceptions/pep3151.py - import platform - - def _instance_checking_exception(base_exception=Exception): - def wrapped(instance_checker): - class TemporaryClass(base_exception): - def __init__(self, *args, **kwargs): - if len(args) == 1 and isinstance(args[0], TemporaryClass): - unwrap_me = args[0] - for attr in dir(unwrap_me): - if not attr.startswith('__'): - setattr(self, attr, getattr(unwrap_me, attr)) - else: - super(TemporaryClass, self).__init__( # noqa - *args, **kwargs - ) - - class __metaclass__(type): - def __instancecheck__(cls, inst): - return instance_checker(inst) - - def __subclasscheck__(cls, classinfo): - value = sys.exc_info()[1] - return isinstance(value, cls) - - TemporaryClass.__name__ = instance_checker.__name__ - TemporaryClass.__doc__ = instance_checker.__doc__ - return TemporaryClass - - return wrapped - - @_instance_checking_exception(EnvironmentError) - def FileNotFoundError(inst): - return getattr(inst, 'errno', _SENTINEL) == errno.ENOENT - - @_instance_checking_exception(EnvironmentError) - def ProcessLookupError(inst): - return getattr(inst, 'errno', _SENTINEL) == errno.ESRCH - - @_instance_checking_exception(EnvironmentError) - def PermissionError(inst): - return getattr(inst, 'errno', _SENTINEL) in (errno.EACCES, errno.EPERM) - - @_instance_checking_exception(EnvironmentError) - def InterruptedError(inst): - return getattr(inst, 'errno', _SENTINEL) == errno.EINTR - - @_instance_checking_exception(EnvironmentError) - def ChildProcessError(inst): - return getattr(inst, 'errno', _SENTINEL) == errno.ECHILD - - @_instance_checking_exception(EnvironmentError) - def FileExistsError(inst): - return getattr(inst, 'errno', _SENTINEL) == errno.EEXIST - - if platform.python_implementation() != "CPython": - try: - raise OSError(errno.EEXIST, "perm") - except FileExistsError: - pass - except OSError: - msg = ( - "broken or incompatible Python implementation, see: " - "https://github.com/giampaolo/psutil/issues/1659" - ) - raise RuntimeError(msg) - - -# --- stdlib additions - - -# py 3.2 functools.lru_cache -# Taken from: http://code.activestate.com/recipes/578078 -# Credit: Raymond Hettinger -try: - from functools import lru_cache -except ImportError: - try: - from threading import RLock - except ImportError: - from dummy_threading import RLock - - _CacheInfo = collections.namedtuple( - "CacheInfo", ["hits", "misses", "maxsize", "currsize"] - ) - - class _HashedSeq(list): - __slots__ = ('hashvalue',) - - def __init__(self, tup, hash=hash): - self[:] = tup - self.hashvalue = hash(tup) - - def __hash__(self): - return self.hashvalue - - def _make_key( - args, - kwds, - typed, - kwd_mark=(_SENTINEL,), - fasttypes=set((int, str, frozenset, type(None))), # noqa - sorted=sorted, - tuple=tuple, - type=type, - len=len, - ): - key = args - if kwds: - sorted_items = sorted(kwds.items()) - key += kwd_mark - for item in sorted_items: - key += item - if typed: - key += tuple(type(v) for v in args) - if kwds: - key += tuple(type(v) for k, v in sorted_items) - elif len(key) == 1 and type(key[0]) in fasttypes: - return key[0] - return _HashedSeq(key) - - def lru_cache(maxsize=100, typed=False): - """Least-recently-used cache decorator, see: - http://docs.python.org/3/library/functools.html#functools.lru_cache. - """ - - def decorating_function(user_function): - cache = {} - stats = [0, 0] - HITS, MISSES = 0, 1 - make_key = _make_key - cache_get = cache.get - _len = len - lock = RLock() - root = [] - root[:] = [root, root, None, None] - nonlocal_root = [root] - PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 - if maxsize == 0: - - def wrapper(*args, **kwds): - result = user_function(*args, **kwds) - stats[MISSES] += 1 - return result - - elif maxsize is None: - - def wrapper(*args, **kwds): - key = make_key(args, kwds, typed) - result = cache_get(key, root) - if result is not root: - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - stats[MISSES] += 1 - return result - - else: - - def wrapper(*args, **kwds): - if kwds or typed: - key = make_key(args, kwds, typed) - else: - key = args - lock.acquire() - try: - link = cache_get(key) - if link is not None: - (root,) = nonlocal_root - link_prev, link_next, key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - stats[HITS] += 1 - return result - finally: - lock.release() - result = user_function(*args, **kwds) - lock.acquire() - try: - (root,) = nonlocal_root - if key in cache: - pass - elif _len(cache) >= maxsize: - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - root = nonlocal_root[0] = oldroot[NEXT] - oldkey = root[KEY] - root[KEY] = root[RESULT] = None - del cache[oldkey] - cache[key] = oldroot - else: - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - stats[MISSES] += 1 - finally: - lock.release() - return result - - def cache_info(): - """Report cache statistics.""" - lock.acquire() - try: - return _CacheInfo( - stats[HITS], stats[MISSES], maxsize, len(cache) - ) - finally: - lock.release() - - def cache_clear(): - """Clear the cache and cache statistics.""" - lock.acquire() - try: - cache.clear() - root = nonlocal_root[0] - root[:] = [root, root, None, None] - stats[:] = [0, 0] - finally: - lock.release() - - wrapper.__wrapped__ = user_function - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return functools.update_wrapper(wrapper, user_function) - - return decorating_function - - -# python 3.3 -try: - from shutil import which -except ImportError: - - def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - """ - - def _access_check(fn, mode): - return ( - os.path.exists(fn) - and os.access(fn, mode) - and not os.path.isdir(fn) - ) - - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - if path is None: - path = os.environ.get("PATH", os.defpath) - if not path: - return None - path = path.split(os.pathsep) - - if sys.platform == "win32": - if os.curdir not in path: - path.insert(0, os.curdir) - - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext for ext in pathext] - else: - files = [cmd] - - seen = set() - for dir in path: - normdir = os.path.normcase(dir) - if normdir not in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None - - -# python 3.3 -try: - from shutil import get_terminal_size -except ImportError: - - def get_terminal_size(fallback=(80, 24)): - try: - import fcntl - import struct - import termios - except ImportError: - return fallback - else: - try: - # This should work on Linux. - res = struct.unpack( - 'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234') - ) - return (res[1], res[0]) - except Exception: # noqa: BLE001 - return fallback - - -# python 3.3 -try: - from subprocess import TimeoutExpired as SubprocessTimeoutExpired -except ImportError: - - class SubprocessTimeoutExpired(Exception): - pass - - -# python 3.5 -try: - from contextlib import redirect_stderr -except ImportError: - - @contextlib.contextmanager - def redirect_stderr(new_target): - original = sys.stderr - try: - sys.stderr = new_target - yield new_target - finally: - sys.stderr = original diff --git a/psutil/_psaix.py b/psutil/_psaix.py index 65ce3374f..ba2725fdc 100644 --- a/psutil/_psaix.py +++ b/psutil/_psaix.py @@ -28,10 +28,6 @@ from ._common import get_procfs_path from ._common import memoize_when_activated from ._common import usage_percent -from ._compat import PY3 -from ._compat import FileNotFoundError -from ._compat import PermissionError -from ._compat import ProcessLookupError __extra__all__ = ["PROCFS_PATH"] @@ -105,7 +101,7 @@ def virtual_memory(): - total, avail, free, pinned, inuse = cext.virtual_mem() + total, avail, free, _pinned, inuse = cext.virtual_mem() percent = usage_percent((total - avail), total, round_=1) return svmem(total, avail, percent, inuse, free) @@ -148,12 +144,10 @@ def cpu_count_cores(): cmd = ["lsdev", "-Cc", "processor"] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() - if PY3: - stdout, stderr = ( - x.decode(sys.stdout.encoding) for x in (stdout, stderr) - ) + stdout, stderr = (x.decode(sys.stdout.encoding) for x in (stdout, stderr)) if p.returncode != 0: - raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + msg = f"{cmd!r} command error\n{stderr}" + raise RuntimeError(msg) processors = stdout.strip().splitlines() return len(processors) or None @@ -211,12 +205,6 @@ def net_connections(kind, _pid=-1): """Return socket connections. If pid == -1 return system-wide connections (as opposed to connections opened by one process only). """ - cmap = _common.conn_tmap - if kind not in cmap: - raise ValueError( - "invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in cmap])) - ) families, types = _common.conn_tmap[kind] rawlist = cext.net_connections(_pid) ret = [] @@ -243,7 +231,7 @@ def net_connections(kind, _pid=-1): def net_if_stats(): """Get NIC stats (isup, duplex, speed, mtu).""" duplex_map = {"Full": NIC_DUPLEX_FULL, "Half": NIC_DUPLEX_HALF} - names = set([x[0] for x in net_if_addrs()]) + names = {x[0] for x in net_if_addrs()} ret = {} for name in names: mtu = cext_posix.net_if_mtu(name) @@ -260,10 +248,9 @@ def net_if_stats(): stderr=subprocess.PIPE, ) stdout, stderr = p.communicate() - if PY3: - stdout, stderr = ( - x.decode(sys.stdout.encoding) for x in (stdout, stderr) - ) + stdout, stderr = ( + x.decode(sys.stdout.encoding) for x in (stdout, stderr) + ) if p.returncode == 0: re_result = re.search( r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout @@ -330,18 +317,18 @@ def wrap_exceptions(fun): @functools.wraps(fun) def wrapper(self, *args, **kwargs): + pid, ppid, name = self.pid, self._ppid, self._name try: return fun(self, *args, **kwargs) - except (FileNotFoundError, ProcessLookupError): + except (FileNotFoundError, ProcessLookupError) as err: # ENOENT (no such file or directory) gets raised on open(). # ESRCH (no such process) can get raised on read() if # process is gone in meantime. - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - else: - raise ZombieProcess(self.pid, self._name, self._ppid) - except PermissionError: - raise AccessDenied(self.pid, self._name) + if not pid_exists(pid): + raise NoSuchProcess(pid, name) from err + raise ZombieProcess(pid, name, ppid) from err + except PermissionError as err: + raise AccessDenied(pid, name) from err return wrapper @@ -349,7 +336,7 @@ def wrapper(self, *args, **kwargs): class Process: """Wrapper class around underlying C implementation.""" - __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] + __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"] def __init__(self, pid): self.pid = pid @@ -444,7 +431,7 @@ def threads(self): # is no longer there. if not retlist: # will raise NSP if process is gone - os.stat('%s/%s' % (self._procfs_path, self.pid)) + os.stat(f"{self._procfs_path}/{self.pid}") return retlist @wrap_exceptions @@ -457,7 +444,7 @@ def net_connections(self, kind='inet'): # is no longer there. if not ret: # will raise NSP if process is gone - os.stat('%s/%s' % (self._procfs_path, self.pid)) + os.stat(f"{self._procfs_path}/{self.pid}") return ret @wrap_exceptions @@ -503,10 +490,10 @@ def terminal(self): def cwd(self): procfs_path = self._procfs_path try: - result = os.readlink("%s/%s/cwd" % (procfs_path, self.pid)) + result = os.readlink(f"{procfs_path}/{self.pid}/cwd") return result.rstrip('/') except FileNotFoundError: - os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD + os.stat(f"{procfs_path}/{self.pid}") # raise NSP or AD return "" @wrap_exceptions @@ -533,13 +520,12 @@ def open_files(self): stderr=subprocess.PIPE, ) stdout, stderr = p.communicate() - if PY3: - stdout, stderr = ( - x.decode(sys.stdout.encoding) for x in (stdout, stderr) - ) + stdout, stderr = ( + x.decode(sys.stdout.encoding) for x in (stdout, stderr) + ) if "no such process" in stderr.lower(): raise NoSuchProcess(self.pid, self._name) - procfiles = re.findall(r"(\d+): S_IFREG.*\s*.*name:(.*)\n", stdout) + procfiles = re.findall(r"(\d+): S_IFREG.*name:(.*)\n", stdout) retlist = [] for fd, path in procfiles: path = path.strip() @@ -554,7 +540,7 @@ def open_files(self): def num_fds(self): if self.pid == 0: # no /proc/0/fd return 0 - return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) + return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd")) @wrap_exceptions def num_ctx_switches(self): @@ -570,10 +556,10 @@ def wait(self, timeout=None): def io_counters(self): try: rc, wc, rb, wb = cext.proc_io_counters(self.pid) - except OSError: + except OSError as err: # if process is terminated, proc_io_counters returns OSError # instead of NSP if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) + raise NoSuchProcess(self.pid, self._name) from err raise return _common.pio(rc, wc, rb, wb) diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index b11b81c35..13bd926fa 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -10,7 +10,7 @@ import os from collections import defaultdict from collections import namedtuple -from xml.etree import ElementTree +from xml.etree import ElementTree # noqa: ICN001 from . import _common from . import _psposix @@ -28,10 +28,6 @@ from ._common import memoize from ._common import memoize_when_activated from ._common import usage_percent -from ._compat import FileNotFoundError -from ._compat import PermissionError -from ._compat import ProcessLookupError -from ._compat import which __extra__all__ = [] @@ -196,8 +192,8 @@ def virtual_memory(): # #2233), so zabbix seems to be wrong. Htop calculates it # differently, and the used value seem more realistic, so let's # match htop. - # https://github.com/htop-dev/htop/blob/e7f447b/netbsd/NetBSDProcessList.c#L162 # noqa - # https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L135 # noqa + # https://github.com/htop-dev/htop/blob/e7f447b/netbsd/NetBSDProcessList.c#L162 + # https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L135 used = active + wired avail = total - used else: @@ -206,7 +202,7 @@ def virtual_memory(): # * https://people.freebsd.org/~rse/dist/freebsd-memory # * https://www.cyberciti.biz/files/scripts/freebsd-memory.pl.txt # matches zabbix: - # * https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/freebsd/memory.c#L143 # noqa + # * https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/freebsd/memory.c#L143 avail = inactive + cached + free used = active + wired + cached @@ -321,7 +317,7 @@ def cpu_stats(): if FREEBSD: # Note: the C ext is returning some metrics we are not exposing: # traps. - ctxsw, intrs, soft_intrs, syscalls, traps = cext.cpu_stats() + ctxsw, intrs, soft_intrs, syscalls, _traps = cext.cpu_stats() elif NETBSD: # XXX # Note about intrs: the C extension returns 0. intrs @@ -332,7 +328,7 @@ def cpu_stats(): # # Note: the C ext is returning some metrics we are not exposing: # traps, faults and forks. - ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = ( + ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = ( cext.cpu_stats() ) with open('/proc/stat', 'rb') as f: @@ -342,7 +338,7 @@ def cpu_stats(): elif OPENBSD: # Note: the C ext is returning some metrics we are not exposing: # traps, faults and forks. - ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = ( + ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = ( cext.cpu_stats() ) return _common.scpustats(ctxsw, intrs, soft_intrs, syscalls) @@ -439,14 +435,8 @@ def net_if_stats(): def net_connections(kind): """System-wide network connections.""" - if kind not in _common.conn_tmap: - raise ValueError( - "invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap])) - ) families, types = conn_tmap[kind] ret = set() - if OPENBSD: rawlist = cext.net_connections(-1, families, types) elif NETBSD: @@ -495,7 +485,7 @@ def sensors_temperatures(): current, high = cext.sensors_cpu_temperature(cpu) if high <= 0: high = None - name = "Core %s" % cpu + name = f"Core {cpu}" ret["coretemp"].append( _common.shwtemp(name, current, high, high) ) @@ -600,21 +590,18 @@ def wrap_exceptions(fun): @functools.wraps(fun) def wrapper(self, *args, **kwargs): + pid, ppid, name = self.pid, self._ppid, self._name try: return fun(self, *args, **kwargs) - except ProcessLookupError: - if is_zombie(self.pid): - raise ZombieProcess(self.pid, self._name, self._ppid) - else: - raise NoSuchProcess(self.pid, self._name) - except PermissionError: - raise AccessDenied(self.pid, self._name) - except OSError: - if self.pid == 0: - if 0 in pids(): - raise AccessDenied(self.pid, self._name) - else: - raise + except ProcessLookupError as err: + if is_zombie(pid): + raise ZombieProcess(pid, name, ppid) from err + raise NoSuchProcess(pid, name) from err + except PermissionError as err: + raise AccessDenied(pid, name) from err + except OSError as err: + if pid == 0 and 0 in pids(): + raise AccessDenied(pid, name) from err raise return wrapper @@ -623,24 +610,25 @@ def wrapper(self, *args, **kwargs): @contextlib.contextmanager def wrap_exceptions_procfs(inst): """Same as above, for routines relying on reading /proc fs.""" + pid, name, ppid = inst.pid, inst._name, inst._ppid try: yield - except (ProcessLookupError, FileNotFoundError): + except (ProcessLookupError, FileNotFoundError) as err: # ENOENT (no such file or directory) gets raised on open(). # ESRCH (no such process) can get raised on read() if # process is gone in meantime. if is_zombie(inst.pid): - raise ZombieProcess(inst.pid, inst._name, inst._ppid) + raise ZombieProcess(pid, name, ppid) from err else: - raise NoSuchProcess(inst.pid, inst._name) - except PermissionError: - raise AccessDenied(inst.pid, inst._name) + raise NoSuchProcess(pid, name) from err + except PermissionError as err: + raise AccessDenied(pid, name) from err class Process: """Wrapper class around underlying C implementation.""" - __slots__ = ["pid", "_name", "_ppid", "_cache"] + __slots__ = ["_cache", "_name", "_ppid", "pid"] def __init__(self, pid): self.pid = pid @@ -683,16 +671,18 @@ def exe(self): # /proc/0 dir exists but /proc/0/exe doesn't return "" with wrap_exceptions_procfs(self): - return os.readlink("/proc/%s/exe" % self.pid) + return os.readlink(f"/proc/{self.pid}/exe") else: # OpenBSD: exe cannot be determined; references: # https://chromium.googlesource.com/chromium/src/base/+/ # master/base_paths_posix.cc # We try our best guess by using which against the first # cmdline arg (may return None). + import shutil + cmdline = self.cmdline() if cmdline: - return which(cmdline[0]) or "" + return shutil.which(cmdline[0]) or "" else: return "" @@ -709,15 +699,15 @@ def cmdline(self): return cext.proc_cmdline(self.pid) except OSError as err: if err.errno == errno.EINVAL: + pid, name, ppid = self.pid, self._name, self._ppid if is_zombie(self.pid): - raise ZombieProcess(self.pid, self._name, self._ppid) - elif not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name, self._ppid) - else: - # XXX: this happens with unicode tests. It means the C - # routine is unable to decode invalid unicode chars. - debug("ignoring %r and returning an empty list" % err) - return [] + raise ZombieProcess(pid, name, ppid) from err + if not pid_exists(self.pid): + raise NoSuchProcess(pid, name, ppid) from err + # XXX: this happens with unicode tests. It means the C + # routine is unable to decode invalid unicode chars. + debug(f"ignoring {err!r} and returning an empty list") + return [] else: raise else: @@ -822,11 +812,6 @@ def threads(self): @wrap_exceptions def net_connections(self, kind='inet'): - if kind not in conn_tmap: - raise ValueError( - "invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap])) - ) families, types = conn_tmap[kind] ret = [] @@ -945,12 +930,11 @@ def cpu_affinity_set(self, cpus): # Pre-emptively check if CPUs are valid because the C # function has a weird behavior in case of invalid CPUs, # see: https://github.com/giampaolo/psutil/issues/586 - allcpus = tuple(range(len(per_cpu_times()))) + allcpus = set(range(len(per_cpu_times()))) for cpu in cpus: if cpu not in allcpus: - raise ValueError( - "invalid CPU #%i (choose between %s)" % (cpu, allcpus) - ) + msg = f"invalid CPU {cpu!r} (choose between {allcpus})" + raise ValueError(msg) try: cext.proc_cpu_affinity_set(self.pid, cpus) except OSError as err: @@ -958,13 +942,14 @@ def cpu_affinity_set(self, cpus): # <> - if err.errno in (errno.EINVAL, errno.EDEADLK): + if err.errno in {errno.EINVAL, errno.EDEADLK}: for cpu in cpus: if cpu not in allcpus: - raise ValueError( - "invalid CPU #%i (choose between %s)" - % (cpu, allcpus) + msg = ( + f"invalid CPU {cpu!r} (choose between" + f" {allcpus})" ) + raise ValueError(msg) from err raise @wrap_exceptions @@ -977,9 +962,10 @@ def rlimit(self, resource, limits=None): return cext.proc_getrlimit(self.pid, resource) else: if len(limits) != 2: - raise ValueError( - "second argument must be a (soft, hard) tuple, got %s" - % repr(limits) + msg = ( + "second argument must be a (soft, hard) tuple, got" + f" {limits!r}" ) + raise ValueError(msg) soft, hard = limits return cext.proc_setrlimit(self.pid, resource, soft, hard) diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 0ee134799..165125fdf 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -4,15 +4,16 @@ """Linux platform implementation.""" -from __future__ import division import base64 import collections +import enum import errno import functools import glob import os import re +import resource import socket import struct import sys @@ -24,6 +25,7 @@ from . import _psposix from . import _psutil_linux as cext from . import _psutil_posix as cext_posix +from ._common import ENCODING from ._common import NIC_DUPLEX_FULL from ._common import NIC_DUPLEX_HALF from ._common import NIC_DUPLEX_UNKNOWN @@ -44,18 +46,6 @@ from ._common import path_exists_strict from ._common import supports_ipv6 from ._common import usage_percent -from ._compat import PY3 -from ._compat import FileNotFoundError -from ._compat import PermissionError -from ._compat import ProcessLookupError -from ._compat import b -from ._compat import basestring - - -if PY3: - import enum -else: - enum = None # fmt: off @@ -69,6 +59,11 @@ "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ] + +if hasattr(resource, "prlimit"): + __extra__all__.extend( + [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()] + ) # fmt: on @@ -78,8 +73,8 @@ POWER_SUPPLY_PATH = "/sys/class/power_supply" -HAS_PROC_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) -HAS_PROC_SMAPS_ROLLUP = os.path.exists('/proc/%s/smaps_rollup' % os.getpid()) +HAS_PROC_SMAPS = os.path.exists(f"/proc/{os.getpid()}/smaps") +HAS_PROC_SMAPS_ROLLUP = os.path.exists(f"/proc/{os.getpid()}/smaps_rollup") HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") @@ -102,29 +97,21 @@ # * https://lkml.org/lkml/2015/8/17/234 DISK_SECTOR_SIZE = 512 -if enum is None: - AF_LINK = socket.AF_PACKET -else: - AddressFamily = enum.IntEnum( - 'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)} - ) - AF_LINK = AddressFamily.AF_LINK +AddressFamily = enum.IntEnum( + 'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)} +) +AF_LINK = AddressFamily.AF_LINK + # ioprio_* constants http://linux.die.net/man/2/ioprio_get -if enum is None: +class IOPriority(enum.IntEnum): IOPRIO_CLASS_NONE = 0 IOPRIO_CLASS_RT = 1 IOPRIO_CLASS_BE = 2 IOPRIO_CLASS_IDLE = 3 -else: - class IOPriority(enum.IntEnum): - IOPRIO_CLASS_NONE = 0 - IOPRIO_CLASS_RT = 1 - IOPRIO_CLASS_BE = 2 - IOPRIO_CLASS_IDLE = 3 - globals().update(IOPriority.__members__) +globals().update(IOPriority.__members__) # See: # https://github.com/torvalds/linux/blame/master/fs/proc/array.c @@ -211,7 +198,7 @@ class IOPriority(enum.IntEnum): def readlink(path): """Wrapper around os.readlink().""" - assert isinstance(path, basestring), path + assert isinstance(path, str), path path = os.readlink(path) # readlink() might return paths containing null bytes ('\x00') # resulting in "TypeError: must be encoded string without NULL @@ -255,9 +242,9 @@ def is_storage_device(name): name = name.replace('/', '!') including_virtual = True if including_virtual: - path = "/sys/block/%s" % name + path = f"/sys/block/{name}" else: - path = "/sys/block/%s/device" % name + path = f"/sys/block/{name}/device" return os.access(path, os.F_OK) @@ -270,7 +257,7 @@ def set_scputimes_ntuple(procfs_path): Used by cpu_times() function. """ global scputimes - with open_binary('%s/stat' % procfs_path) as f: + with open_binary(f"{procfs_path}/stat") as f: values = f.readline().split()[1:] fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] vlen = len(values) @@ -290,62 +277,10 @@ def set_scputimes_ntuple(procfs_path): set_scputimes_ntuple("/proc") except Exception as err: # noqa: BLE001 # Don't want to crash at import time. - debug("ignoring exception on import: %r" % err) + debug(f"ignoring exception on import: {err!r}") scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0) -# ===================================================================== -# --- prlimit -# ===================================================================== - -# Backport of resource.prlimit() for Python 2. Originally this was done -# in C, but CentOS-6 which we use to create manylinux wheels is too old -# and does not support prlimit() syscall. As such the resulting wheel -# would not include prlimit(), even when installed on newer systems. -# This is the only part of psutil using ctypes. - -prlimit = None -try: - from resource import prlimit # python >= 3.4 -except ImportError: - import ctypes - - libc = ctypes.CDLL(None, use_errno=True) - - if hasattr(libc, "prlimit"): - - def prlimit(pid, resource_, limits=None): - class StructRlimit(ctypes.Structure): - _fields_ = [ - ('rlim_cur', ctypes.c_longlong), - ('rlim_max', ctypes.c_longlong), - ] - - current = StructRlimit() - if limits is None: - # get - ret = libc.prlimit(pid, resource_, None, ctypes.byref(current)) - else: - # set - new = StructRlimit() - new.rlim_cur = limits[0] - new.rlim_max = limits[1] - ret = libc.prlimit( - pid, resource_, ctypes.byref(new), ctypes.byref(current) - ) - - if ret != 0: - errno_ = ctypes.get_errno() - raise OSError(errno_, os.strerror(errno_)) - return (current.rlim_cur, current.rlim_max) - - -if prlimit is not None: - __extra__all__.extend( - [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()] - ) - - # ===================================================================== # --- system memory # ===================================================================== @@ -389,14 +324,13 @@ def calculate_avail_vmem(mems): slab_reclaimable = mems[b'SReclaimable:'] except KeyError as err: debug( - "%s is missing from /proc/meminfo; using an approximation for " - "calculating available memory" - % err.args[0] + f"{err.args[0]} is missing from /proc/meminfo; using an" + " approximation for calculating available memory" ) return fallback try: - f = open_binary('%s/zoneinfo' % get_procfs_path()) - except IOError: + f = open_binary(f"{get_procfs_path()}/zoneinfo") + except OSError: return fallback # kernel 2.6.13 watermark_low = 0 @@ -425,7 +359,7 @@ def virtual_memory(): """ missing_fields = [] mems = {} - with open_binary('%s/meminfo' % get_procfs_path()) as f: + with open_binary(f"{get_procfs_path()}/meminfo") as f: for line in f: fields = line.split() mems[fields[0]] = int(fields[1]) * 1024 @@ -527,7 +461,7 @@ def virtual_memory(): # Warn about missing metrics which are set to 0. if missing_fields: - msg = "%s memory stats couldn't be determined and %s set to 0" % ( + msg = "{} memory stats couldn't be determined and {} set to 0".format( ", ".join(missing_fields), "was" if len(missing_fields) == 1 else "were", ) @@ -551,7 +485,7 @@ def virtual_memory(): def swap_memory(): """Return swap memory metrics.""" mems = {} - with open_binary('%s/meminfo' % get_procfs_path()) as f: + with open_binary(f"{get_procfs_path()}/meminfo") as f: for line in f: fields = line.split() mems[fields[0]] = int(fields[1]) * 1024 @@ -571,12 +505,12 @@ def swap_memory(): percent = usage_percent(used, total, round_=1) # get pgin/pgouts try: - f = open_binary("%s/vmstat" % get_procfs_path()) - except IOError as err: + f = open_binary(f"{get_procfs_path()}/vmstat") + except OSError as err: # see https://github.com/giampaolo/psutil/issues/722 msg = ( "'sin' and 'sout' swap memory stats couldn't " - + "be determined and were set to 0 (%s)" % str(err) + f"be determined and were set to 0 ({err})" ) warnings.warn(msg, RuntimeWarning, stacklevel=2) sin = sout = 0 @@ -617,7 +551,7 @@ def cpu_times(): """ procfs_path = get_procfs_path() set_scputimes_ntuple(procfs_path) - with open_binary('%s/stat' % procfs_path) as f: + with open_binary(f"{procfs_path}/stat") as f: values = f.readline().split() fields = values[1 : len(scputimes._fields) + 1] fields = [float(x) / CLOCK_TICKS for x in fields] @@ -631,7 +565,7 @@ def per_cpu_times(): procfs_path = get_procfs_path() set_scputimes_ntuple(procfs_path) cpus = [] - with open_binary('%s/stat' % procfs_path) as f: + with open_binary(f"{procfs_path}/stat") as f: # get rid of the first line which refers to system wide CPU stats f.readline() for line in f: @@ -651,7 +585,7 @@ def cpu_count_logical(): except ValueError: # as a second fallback we try to parse /proc/cpuinfo num = 0 - with open_binary('%s/cpuinfo' % get_procfs_path()) as f: + with open_binary(f"{get_procfs_path()}/cpuinfo") as f: for line in f: if line.lower().startswith(b'processor'): num += 1 @@ -661,7 +595,7 @@ def cpu_count_logical(): # try to parse /proc/stat as a last resort if num == 0: search = re.compile(r'cpu\d') - with open_text('%s/stat' % get_procfs_path()) as f: + with open_text(f"{get_procfs_path()}/stat") as f: for line in f: line = line.split(' ')[0] if search.match(line): @@ -694,7 +628,7 @@ def cpu_count_cores(): # Method #2 mapping = {} current_info = {} - with open_binary('%s/cpuinfo' % get_procfs_path()) as f: + with open_binary(f"{get_procfs_path()}/cpuinfo") as f: for line in f: line = line.strip().lower() if not line: @@ -706,11 +640,10 @@ def cpu_count_cores(): except KeyError: pass current_info = {} - else: + elif line.startswith((b'physical id', b'cpu cores')): # ongoing section - if line.startswith((b'physical id', b'cpu cores')): - key, value = line.split(b'\t:', 1) - current_info[key] = int(value) + key, value = line.split(b'\t:', 1) + current_info[key] = int(value) result = sum(mapping.values()) return result or None # mimic os.cpu_count() @@ -718,7 +651,7 @@ def cpu_count_cores(): def cpu_stats(): """Return various CPU stats as a named tuple.""" - with open_binary('%s/stat' % get_procfs_path()) as f: + with open_binary(f"{get_procfs_path()}/stat") as f: ctx_switches = None interrupts = None soft_interrupts = None @@ -744,7 +677,7 @@ def cpu_stats(): def _cpu_get_cpuinfo_freq(): """Return current CPU frequency from cpuinfo if available.""" ret = [] - with open_binary('%s/cpuinfo' % get_procfs_path()) as f: + with open_binary(f"{get_procfs_path()}/cpuinfo") as f: for line in f: if line.lower().startswith(b'cpu mhz'): ret.append(float(line.split(b':', 1)[1])) @@ -779,9 +712,7 @@ def cpu_freq(): # 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) - ) + online_path = f"/sys/devices/system/cpu/cpu{i}/online" # 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)) @@ -852,12 +783,12 @@ def __init__(self): def get_proc_inodes(self, pid): inodes = defaultdict(list) - for fd in os.listdir("%s/%s/fd" % (self._procfs_path, pid)): + for fd in os.listdir(f"{self._procfs_path}/{pid}/fd"): try: - inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd)) + inode = readlink(f"{self._procfs_path}/{pid}/fd/{fd}") except (FileNotFoundError, ProcessLookupError): # ENOENT == file which is gone in the meantime; - # os.stat('/proc/%s' % self.pid) will be done later + # os.stat(f"/proc/{self.pid}") will be done later # to force NSP (if it's the case) continue except OSError as err: @@ -915,8 +846,7 @@ def decode_address(addr, family): # no end-points connected if not port: return () - if PY3: - ip = ip.encode('ascii') + ip = ip.encode('ascii') if family == socket.AF_INET: # see: https://github.com/giampaolo/psutil/issues/201 if LITTLE_ENDIAN: @@ -940,9 +870,8 @@ def decode_address(addr, family): except ValueError: # see: https://github.com/giampaolo/psutil/issues/623 if not supports_ipv6(): - raise _Ipv6UnsupportedError - else: - raise + raise _Ipv6UnsupportedError from None + raise return _common.addr(ip, port) @staticmethod @@ -959,10 +888,11 @@ def process_inet(file, family, type_, inodes, filter_pid=None): line.split()[:10] ) except ValueError: - raise RuntimeError( - "error while parsing %s; malformed line %s %r" - % (file, lineno, line) + msg = ( + f"error while parsing {file}; malformed line" + f" {lineno} {line!r}" ) + raise RuntimeError(msg) from None if inode in inodes: # # We assume inet sockets are unique, so we error # # out if there are multiple references to the @@ -1000,11 +930,11 @@ def process_unix(file, family, inodes, filter_pid=None): if ' ' not in line: # see: https://github.com/giampaolo/psutil/issues/766 continue - raise RuntimeError( - "error while parsing %s; malformed line %r" - % (file, line) + msg = ( + f"error while parsing {file}; malformed line {line!r}" ) - if inode in inodes: # noqa + raise RuntimeError(msg) # noqa: B904 + if inode in inodes: # noqa: SIM108, SIM401 # With UNIX sockets we can have a single inode # referencing many file descriptors. pairs = inodes[inode] @@ -1024,11 +954,6 @@ def process_unix(file, family, inodes, filter_pid=None): yield (fd, family, type_, path, raddr, status, pid) def retrieve(self, kind, pid=None): - if kind not in self.tmap: - raise ValueError( - "invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in self.tmap])) - ) self._procfs_path = get_procfs_path() if pid is not None: inodes = self.get_proc_inodes(pid) @@ -1039,8 +964,8 @@ def retrieve(self, kind, pid=None): inodes = self.get_all_inodes() ret = set() for proto_name, family, type_ in self.tmap[kind]: - path = "%s/net/%s" % (self._procfs_path, proto_name) - if family in (socket.AF_INET, socket.AF_INET6): + path = f"{self._procfs_path}/net/{proto_name}" + if family in {socket.AF_INET, socket.AF_INET6}: ls = self.process_inet( path, family, type_, inodes, filter_pid=pid ) @@ -1071,7 +996,7 @@ def net_io_counters(): """Return network I/O statistics for every network interface installed on the system as a dict of raw tuples. """ - with open_text("%s/net/dev" % get_procfs_path()) as f: + with open_text(f"{get_procfs_path()}/net/dev") as f: lines = f.readlines() retdict = {} for line in lines[2:]: @@ -1080,25 +1005,25 @@ def net_io_counters(): name = line[:colon].strip() fields = line[colon + 1 :].strip().split() - # in ( + # in bytes_recv, packets_recv, errin, dropin, - fifoin, # unused - framein, # unused - compressedin, # unused - multicastin, # unused + _fifoin, # unused + _framein, # unused + _compressedin, # unused + _multicastin, # unused # out bytes_sent, packets_sent, errout, dropout, - fifoout, # unused - collisionsout, # unused - carrierout, # unused - compressedout, + _fifoout, # unused + _collisionsout, # unused + _carrierout, # unused + _compressedout, # unused ) = map(int, fields) retdict[name] = ( @@ -1132,8 +1057,7 @@ def net_if_stats(): # https://github.com/giampaolo/psutil/issues/1279 if err.errno != errno.ENODEV: raise - else: - debug(err) + debug(err) else: output_flags = ','.join(flags) isup = 'running' in flags @@ -1173,7 +1097,7 @@ def read_procfs(): # See: # https://www.kernel.org/doc/Documentation/iostats.txt # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats - with open_text("%s/diskstats" % get_procfs_path()) as f: + with open_text(f"{get_procfs_path()}/diskstats") as f: lines = f.readlines() for line in lines: fields = line.split() @@ -1196,7 +1120,8 @@ def read_procfs(): reads, rbytes, writes, wbytes = map(int, fields[3:]) rtime = wtime = reads_merged = writes_merged = busy_time = 0 else: - raise ValueError("not sure how to interpret line %r" % line) + msg = f"not sure how to interpret line {line!r}" + raise ValueError(msg) yield (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, writes_merged, busy_time) # fmt: on @@ -1216,16 +1141,16 @@ def read_sysfs(): wtime, reads_merged, writes_merged, busy_time) # fmt: on - if os.path.exists('%s/diskstats' % get_procfs_path()): + if os.path.exists(f"{get_procfs_path()}/diskstats"): gen = read_procfs() elif os.path.exists('/sys/block'): gen = read_sysfs() else: - raise NotImplementedError( - "%s/diskstats nor /sys/block filesystem are available on this " - "system" - % get_procfs_path() + msg = ( + f"{get_procfs_path()}/diskstats nor /sys/block are available on" + " this system" ) + raise NotImplementedError(msg) retdict = {} for entry in gen: @@ -1271,7 +1196,7 @@ def __init__(self): self.minor = os.minor(dev) def ask_proc_partitions(self): - with open_text("%s/partitions" % get_procfs_path()) as f: + with open_text(f"{get_procfs_path()}/partitions") as f: for line in f.readlines()[2:]: fields = line.split() if len(fields) < 4: # just for extra safety @@ -1281,19 +1206,19 @@ def ask_proc_partitions(self): name = fields[3] if major == self.major and minor == self.minor: if name: # just for extra safety - return "/dev/%s" % name + return f"/dev/{name}" def ask_sys_dev_block(self): - path = "/sys/dev/block/%s:%s/uevent" % (self.major, self.minor) + path = f"/sys/dev/block/{self.major}:{self.minor}/uevent" with open_text(path) as f: for line in f: if line.startswith("DEVNAME="): name = line.strip().rpartition("DEVNAME=")[2] if name: # just for extra safety - return "/dev/%s" % name + return f"/dev/{name}" def ask_sys_class_block(self): - needle = "%s:%s" % (self.major, self.minor) + needle = f"{self.major}:{self.minor}" files = glob.iglob("/sys/class/block/*/dev") for file in files: try: @@ -1305,24 +1230,24 @@ def ask_sys_class_block(self): data = f.read().strip() if data == needle: name = os.path.basename(os.path.dirname(file)) - return "/dev/%s" % name + return f"/dev/{name}" def find(self): path = None if path is None: try: path = self.ask_proc_partitions() - except (IOError, OSError) as err: + except OSError as err: debug(err) if path is None: try: path = self.ask_sys_dev_block() - except (IOError, OSError) as err: + except OSError as err: debug(err) if path is None: try: path = self.ask_sys_class_block() - except (IOError, OSError) as err: + except OSError as err: debug(err) # We use exists() because the "/dev/*" part of the path is hard # coded, so we want to be sure. @@ -1335,7 +1260,7 @@ def disk_partitions(all=False): fstypes = set() procfs_path = get_procfs_path() if not all: - with open_text("%s/filesystems" % procfs_path) as f: + with open_text(f"{procfs_path}/filesystems") as f: for line in f: line = line.strip() if not line.startswith("nodev"): @@ -1350,7 +1275,7 @@ def disk_partitions(all=False): if procfs_path == "/proc" and os.path.isfile('/etc/mtab'): mounts_path = os.path.realpath("/etc/mtab") else: - mounts_path = os.path.realpath("%s/self/mounts" % procfs_path) + mounts_path = os.path.realpath(f"{procfs_path}/self/mounts") retlist = [] partitions = cext.disk_partitions(mounts_path) @@ -1358,7 +1283,7 @@ def disk_partitions(all=False): device, mountpoint, fstype, opts = partition if device == 'none': device = '' - if device in ("/dev/root", "rootfs"): + if device in {"/dev/root", "rootfs"}: device = RootFsDeviceFinder().find() or device if not all: if not device or fstype not in fstypes: @@ -1393,7 +1318,7 @@ def sensors_temperatures(): # https://github.com/giampaolo/psutil/issues/971 # https://github.com/nicolargo/glances/issues/1060 basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*')) - basenames = sorted(set([x.split('_')[0] for x in basenames])) + basenames = sorted({x.split('_')[0] for x in basenames}) # Only add the coretemp hwmon entries if they're not already in # /sys/class/hwmon/ @@ -1402,7 +1327,7 @@ def sensors_temperatures(): basenames2 = glob.glob( '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*' ) - repl = re.compile('/sys/devices/platform/coretemp.*/hwmon/') + repl = re.compile(r"/sys/devices/platform/coretemp.*/hwmon/") for name in basenames2: altname = repl.sub('/sys/class/hwmon/', name) if altname not in basenames: @@ -1414,7 +1339,7 @@ def sensors_temperatures(): current = float(bcat(path)) / 1000.0 path = os.path.join(os.path.dirname(base), 'name') unit_name = cat(path).strip() - except (IOError, OSError, ValueError): + except (OSError, ValueError): # A lot of things can go wrong here, so let's just skip the # whole entry. Sure thing is Linux's /sys/class/hwmon really # is a stinky broken mess. @@ -1453,15 +1378,15 @@ def sensors_temperatures(): current = float(bcat(path)) / 1000.0 path = os.path.join(base, 'type') unit_name = cat(path).strip() - except (IOError, OSError, ValueError) as err: + except (OSError, ValueError) as err: debug(err) continue trip_paths = glob.glob(base + '/trip_point*') - trip_points = set([ + trip_points = { '_'.join(os.path.basename(p).split('_')[0:3]) for p in trip_paths - ]) + } critical = None high = None for trip_point in trip_points: @@ -1509,11 +1434,11 @@ def sensors_fans(): # https://github.com/giampaolo/psutil/issues/971 basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*') - basenames = sorted(set([x.split('_')[0] for x in basenames])) + basenames = sorted({x.split("_")[0] for x in basenames}) for base in basenames: try: current = int(bcat(base + '_input')) - except (IOError, OSError) as err: + except OSError as err: debug(err) continue unit_name = cat(os.path.join(os.path.dirname(base), 'name')).strip() @@ -1555,7 +1480,7 @@ def multi_bcat(*paths): # Get the first available battery. Usually this is "BAT0", except # some rare exceptions: # https://github.com/giampaolo/psutil/issues/1238 - root = os.path.join(POWER_SUPPLY_PATH, sorted(bats)[0]) + root = os.path.join(POWER_SUPPLY_PATH, min(bats)) # Base metrics. energy_now = multi_bcat(root + "/energy_now", root + "/charge_now") @@ -1589,7 +1514,7 @@ def multi_bcat(*paths): status = cat(root + "/status", fallback="").strip().lower() if status == "discharging": power_plugged = False - elif status in ("charging", "full"): + elif status in {"charging", "full"}: power_plugged = True # Seconds left. @@ -1632,14 +1557,15 @@ def users(): def boot_time(): """Return the system boot time expressed in seconds since the epoch.""" global BOOT_TIME - path = '%s/stat' % get_procfs_path() + path = f"{get_procfs_path()}/stat" with open_binary(path) as f: for line in f: if line.startswith(b'btime'): ret = float(line.strip().split()[1]) BOOT_TIME = ret return ret - raise RuntimeError("line 'btime' not found in %s" % path) + msg = f"line 'btime' not found in {path}" + raise RuntimeError(msg) # ===================================================================== @@ -1649,7 +1575,8 @@ def boot_time(): def pids(): """Returns a list of PIDs currently running on the system.""" - return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()] + path = get_procfs_path().encode(ENCODING) + return [int(x) for x in os.listdir(path) if x.isdigit()] def pid_exists(pid): @@ -1671,7 +1598,7 @@ def pid_exists(pid): # Note: already checked that this is faster than using a # regular expr. Also (a lot) faster than doing # 'return pid in pids()' - path = "%s/%s/status" % (get_procfs_path(), pid) + path = f"{get_procfs_path()}/{pid}/status" with open_binary(path) as f: for line in f: if line.startswith(b"Tgid:"): @@ -1679,8 +1606,9 @@ def pid_exists(pid): # If tgid and pid are the same then we're # dealing with a process PID. return tgid == pid - raise ValueError("'Tgid' line not found in %s" % path) - except (EnvironmentError, ValueError): + msg = f"'Tgid' line not found in {path}" + raise ValueError(msg) + except (OSError, ValueError): return pid in pids() @@ -1692,7 +1620,7 @@ def ppid_map(): procfs_path = get_procfs_path() for pid in pids(): try: - with open_binary("%s/%s/stat" % (procfs_path, pid)) as f: + with open_binary(f"{procfs_path}/{pid}/stat") as f: data = f.read() except (FileNotFoundError, ProcessLookupError): # Note: we should be able to access /stat for all processes @@ -1707,23 +1635,27 @@ def ppid_map(): def wrap_exceptions(fun): - """Decorator which translates bare OSError and IOError exceptions + """Decorator which translates bare OSError and OSError exceptions into NoSuchProcess and AccessDenied. """ @functools.wraps(fun) def wrapper(self, *args, **kwargs): + pid, name = self.pid, self._name try: return fun(self, *args, **kwargs) - except PermissionError: - raise AccessDenied(self.pid, self._name) - except ProcessLookupError: + except PermissionError as err: + raise AccessDenied(pid, name) from err + except ProcessLookupError as err: self._raise_if_zombie() - raise NoSuchProcess(self.pid, self._name) - except FileNotFoundError: + raise NoSuchProcess(pid, name) from err + except FileNotFoundError as err: self._raise_if_zombie() - if not os.path.exists("%s/%s" % (self._procfs_path, self.pid)): - raise NoSuchProcess(self.pid, self._name) + # /proc/PID directory may still exist, but the files within + # it may not, indicating the process is gone, see: + # https://github.com/giampaolo/psutil/issues/2418 + if not os.path.exists(f"{self._procfs_path}/{pid}/stat"): + raise NoSuchProcess(pid, name) from err raise return wrapper @@ -1732,7 +1664,7 @@ def wrapper(self, *args, **kwargs): class Process: """Linux process implementation.""" - __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] + __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"] def __init__(self, pid): self.pid = pid @@ -1748,8 +1680,8 @@ def _is_zombie(self): # it's empty. Instead of returning a "null" value we'll raise an # exception. try: - data = bcat("%s/%s/stat" % (self._procfs_path, self.pid)) - except (IOError, OSError): + data = bcat(f"{self._procfs_path}/{self.pid}/stat") + except OSError: return False else: rpar = data.rfind(b')') @@ -1764,7 +1696,7 @@ def _raise_if_not_alive(self): """Raise NSP if the process disappeared on us.""" # For those C function who do not raise NSP, possibly returning # incorrect or incomplete result. - os.stat('%s/%s' % (self._procfs_path, self.pid)) + os.stat(f"{self._procfs_path}/{self.pid}") @wrap_exceptions @memoize_when_activated @@ -1777,7 +1709,7 @@ def _parse_stat_file(self): The return value is cached in case oneshot() ctx manager is in use. """ - data = bcat("%s/%s/stat" % (self._procfs_path, self.pid)) + data = bcat(f"{self._procfs_path}/{self.pid}/stat") # Process name is between parentheses. It can contain spaces and # other parentheses. This is taken into account by looking for # the first occurrence of "(" and the last occurrence of ")". @@ -1796,7 +1728,12 @@ def _parse_stat_file(self): ret['children_stime'] = fields[14] ret['create_time'] = fields[19] ret['cpu_num'] = fields[36] - ret['blkio_ticks'] = fields[39] # aka 'delayacct_blkio_ticks' + try: + ret['blkio_ticks'] = fields[39] # aka 'delayacct_blkio_ticks' + except IndexError: + # https://github.com/giampaolo/psutil/issues/2455 + debug("can't get blkio_ticks, set iowait to 0") + ret['blkio_ticks'] = 0 return ret @@ -1807,13 +1744,13 @@ def _read_status_file(self): The return value is cached in case oneshot() ctx manager is in use. """ - with open_binary("%s/%s/status" % (self._procfs_path, self.pid)) as f: + with open_binary(f"{self._procfs_path}/{self.pid}/status") as f: return f.read() @wrap_exceptions @memoize_when_activated def _read_smaps_file(self): - with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid)) as f: + with open_binary(f"{self._procfs_path}/{self.pid}/smaps") as f: return f.read().strip() def oneshot_enter(self): @@ -1828,28 +1765,25 @@ def oneshot_exit(self): @wrap_exceptions def name(self): - name = self._parse_stat_file()['name'] - if PY3: - name = decode(name) # XXX - gets changed later and probably needs refactoring - return name + return decode(self._parse_stat_file()['name']) @wrap_exceptions def exe(self): try: - return readlink("%s/%s/exe" % (self._procfs_path, self.pid)) + return readlink(f"{self._procfs_path}/{self.pid}/exe") except (FileNotFoundError, ProcessLookupError): self._raise_if_zombie() # no such file error; might be raised also if the # path actually exists for system processes with # low pids (about 0-20) - if os.path.lexists("%s/%s" % (self._procfs_path, self.pid)): + if os.path.lexists(f"{self._procfs_path}/{self.pid}"): return "" raise @wrap_exceptions def cmdline(self): - with open_text("%s/%s/cmdline" % (self._procfs_path, self.pid)) as f: + with open_text(f"{self._procfs_path}/{self.pid}/cmdline") as f: data = f.read() if not data: # may happen in case of zombie process @@ -1875,7 +1809,7 @@ def cmdline(self): @wrap_exceptions def environ(self): - with open_text("%s/%s/environ" % (self._procfs_path, self.pid)) as f: + with open_text(f"{self._procfs_path}/{self.pid}/environ") as f: data = f.read() return parse_environ_block(data) @@ -1889,11 +1823,11 @@ def terminal(self): return None # May not be available on old kernels. - if os.path.exists('/proc/%s/io' % os.getpid()): + if os.path.exists(f"/proc/{os.getpid()}/io"): @wrap_exceptions def io_counters(self): - fname = "%s/%s/io" % (self._procfs_path, self.pid) + fname = f"{self._procfs_path}/{self.pid}/io" fields = {} with open_binary(fname) as f: for line in f: @@ -1908,7 +1842,8 @@ def io_counters(self): else: fields[name] = int(value) if not fields: - raise RuntimeError("%s file was empty" % fname) + msg = f"{fname} file was empty" + raise RuntimeError(msg) try: return pio( fields[b'syscr'], # read syscalls @@ -1919,10 +1854,11 @@ def io_counters(self): fields[b'wchar'], # write chars ) except KeyError as err: - raise ValueError( - "%r field was not found in %s; found fields are %r" - % (err.args[0], fname, fields) + msg = ( + f"{err.args[0]!r} field was not found in {fname}; found" + f" fields are {fields!r}" ) + raise ValueError(msg) from None @wrap_exceptions def cpu_times(self): @@ -1967,7 +1903,7 @@ def memory_info(self): # | data | data + stack | drs | DATA | # | dirty | dirty pages (unused in Linux 2.6) | dt | | # ============================================================ - with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f: + with open_binary(f"{self._procfs_path}/{self.pid}/statm") as f: vms, rss, shared, text, lib, data, dirty = ( int(x) * PAGESIZE for x in f.readline().split()[:7] ) @@ -1986,7 +1922,7 @@ def _parse_smaps_rollup(self): # compared to /proc/pid/smaps_rollup. uss = pss = swap = 0 with open_binary( - "{}/{}/smaps_rollup".format(self._procfs_path, self.pid) + f"{self._procfs_path}/{self.pid}/smaps_rollup" ) as f: for line in f: if line.startswith(b"Private_"): @@ -2011,8 +1947,6 @@ def _parse_smaps( # Note: using 3 regexes is faster than reading the file # line by line. - # XXX: on Python 3 the 2 regexes are 30% slower than on - # Python 2 though. Figure out why. # # You might be tempted to calculate USS by subtracting # the "shared" value from the "resident" value in @@ -2072,10 +2006,11 @@ def get_blocks(lines, current_block): # see issue #369 continue else: - raise ValueError( - "don't know how to interpret line %r" - % line + msg = ( + "don't know how to interpret line" + f" {line!r}" ) + raise ValueError(msg) from None yield (current_block.pop(), data) data = self._read_smaps_file() @@ -2091,14 +2026,13 @@ def get_blocks(lines, current_block): for header, data in get_blocks(lines, current_block): hfields = header.split(None, 5) try: - addr, perms, offset, dev, inode, path = hfields + addr, perms, _offset, _dev, _inode, path = hfields except ValueError: - addr, perms, offset, dev, inode, path = hfields + [''] + addr, perms, _offset, _dev, _inode, path = hfields + [''] if not path: path = '[anon]' else: - if PY3: - path = decode(path) + path = decode(path) path = path.strip() if path.endswith(' (deleted)') and not path_exists_strict( path @@ -2124,7 +2058,7 @@ def get_blocks(lines, current_block): @wrap_exceptions def cwd(self): - return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) + return readlink(f"{self._procfs_path}/{self.pid}/cwd") @wrap_exceptions def num_ctx_switches( @@ -2133,34 +2067,29 @@ def num_ctx_switches( data = self._read_status_file() ctxsw = _ctxsw_re.findall(data) if not ctxsw: - raise NotImplementedError( - "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" - "lines were not found in %s/%s/status; the kernel is " - "probably older than 2.6.23" % (self._procfs_path, self.pid) + msg = ( + "'voluntary_ctxt_switches' and" + " 'nonvoluntary_ctxt_switches'lines were not found in" + f" {self._procfs_path}/{self.pid}/status; the kernel is" + " probably older than 2.6.23" ) - else: - return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1])) + raise NotImplementedError(msg) + return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1])) @wrap_exceptions def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')): - # Note: on Python 3 using a re is faster than iterating over file - # line by line. On Python 2 is the exact opposite, and iterating - # over a file on Python 3 is slower than on Python 2. + # Using a re is faster than iterating over file line by line. data = self._read_status_file() return int(_num_threads_re.findall(data)[0]) @wrap_exceptions def threads(self): - thread_ids = os.listdir("%s/%s/task" % (self._procfs_path, self.pid)) + thread_ids = os.listdir(f"{self._procfs_path}/{self.pid}/task") thread_ids.sort() retlist = [] hit_enoent = False for thread_id in thread_ids: - fname = "%s/%s/task/%s/stat" % ( - self._procfs_path, - self.pid, - thread_id, - ) + fname = f"{self._procfs_path}/{self.pid}/task/{thread_id}/stat" try: with open_binary(fname) as f: st = f.read().strip() @@ -2182,7 +2111,7 @@ def threads(self): @wrap_exceptions def nice_get(self): - # with open_text('%s/%s/stat' % (self._procfs_path, self.pid)) as f: + # with open_text(f"{self._procfs_path}/{self.pid}/stat") as f: # data = f.read() # return int(data.split()[18]) @@ -2221,15 +2150,17 @@ def cpu_affinity_set(self, cpus): all_cpus = tuple(range(len(per_cpu_times()))) for cpu in cpus: if cpu not in all_cpus: - raise ValueError( - "invalid CPU number %r; choose between %s" - % (cpu, eligible_cpus) + msg = ( + f"invalid CPU {cpu!r}; choose between" + f" {eligible_cpus!r}" ) + raise ValueError(msg) from None if cpu not in eligible_cpus: - raise ValueError( - "CPU number %r is not eligible; choose " - "between %s" % (cpu, eligible_cpus) + msg = ( + f"CPU number {cpu} is not eligible; choose" + f" between {eligible_cpus}" ) + raise ValueError(msg) from err raise # only starting from kernel 2.6.13 @@ -2238,22 +2169,25 @@ def cpu_affinity_set(self, cpus): @wrap_exceptions def ionice_get(self): ioclass, value = cext.proc_ioprio_get(self.pid) - if enum is not None: - ioclass = IOPriority(ioclass) + ioclass = IOPriority(ioclass) return _common.pionice(ioclass, value) @wrap_exceptions def ionice_set(self, ioclass, value): if value is None: value = 0 - if value and ioclass in (IOPRIO_CLASS_IDLE, IOPRIO_CLASS_NONE): - raise ValueError("%r ioclass accepts no value" % ioclass) + if value and ioclass in { + IOPriority.IOPRIO_CLASS_IDLE, + IOPriority.IOPRIO_CLASS_NONE, + }: + msg = f"{ioclass!r} ioclass accepts no value" + raise ValueError(msg) if value < 0 or value > 7: msg = "value not in 0-7 range" raise ValueError(msg) return cext.proc_ioprio_set(self.pid, ioclass, value) - if prlimit is not None: + if hasattr(resource, "prlimit"): @wrap_exceptions def rlimit(self, resource_, limits=None): @@ -2266,16 +2200,16 @@ def rlimit(self, resource_, limits=None): try: if limits is None: # get - return prlimit(self.pid, resource_) + return resource.prlimit(self.pid, resource_) else: # set if len(limits) != 2: msg = ( "second argument must be a (soft, hard) " - + "tuple, got %s" % repr(limits) + f"tuple, got {limits!r}" ) raise ValueError(msg) - prlimit(self.pid, resource_, limits) + resource.prlimit(self.pid, resource_, limits) except OSError as err: if err.errno == errno.ENOSYS: # I saw this happening on Travis: @@ -2286,18 +2220,17 @@ def rlimit(self, resource_, limits=None): @wrap_exceptions def status(self): letter = self._parse_stat_file()['status'] - if PY3: - letter = letter.decode() + letter = letter.decode() # XXX is '?' legit? (we're not supposed to return it anyway) return PROC_STATUSES.get(letter, '?') @wrap_exceptions def open_files(self): retlist = [] - files = os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)) + files = os.listdir(f"{self._procfs_path}/{self.pid}/fd") hit_enoent = False for fd in files: - file = "%s/%s/fd/%s" % (self._procfs_path, self.pid, fd) + file = f"{self._procfs_path}/{self.pid}/fd/{fd}" try: path = readlink(file) except (FileNotFoundError, ProcessLookupError): @@ -2320,11 +2253,7 @@ def open_files(self): # absolute path though. if path.startswith('/') and isfile_strict(path): # Get file position and flags. - file = "%s/%s/fdinfo/%s" % ( - self._procfs_path, - self.pid, - fd, - ) + file = f"{self._procfs_path}/{self.pid}/fdinfo/{fd}" try: with open_binary(file) as f: pos = int(f.readline().split()[1]) @@ -2351,7 +2280,7 @@ def net_connections(self, kind='inet'): @wrap_exceptions def num_fds(self): - return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) + return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd")) @wrap_exceptions def ppid(self): diff --git a/psutil/_psosx.py b/psutil/_psosx.py index 106709467..fa2c8b81d 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -22,8 +22,6 @@ from ._common import memoize_when_activated from ._common import parse_environ_block from ._common import usage_percent -from ._compat import PermissionError -from ._compat import ProcessLookupError __extra__all__ = [] @@ -165,7 +163,7 @@ def cpu_count_cores(): def cpu_stats(): - ctx_switches, interrupts, soft_interrupts, syscalls, traps = ( + ctx_switches, interrupts, soft_interrupts, syscalls, _traps = ( cext.cpu_stats() ) return _common.scpustats( @@ -345,15 +343,15 @@ def wrap_exceptions(fun): @functools.wraps(fun) def wrapper(self, *args, **kwargs): + pid, ppid, name = self.pid, self._ppid, self._name try: return fun(self, *args, **kwargs) - except ProcessLookupError: - if is_zombie(self.pid): - raise ZombieProcess(self.pid, self._name, self._ppid) - else: - raise NoSuchProcess(self.pid, self._name) - except PermissionError: - raise AccessDenied(self.pid, self._name) + except ProcessLookupError as err: + if is_zombie(pid): + raise ZombieProcess(pid, name, ppid) from err + raise NoSuchProcess(pid, name) from err + except PermissionError as err: + raise AccessDenied(pid, name) from err return wrapper @@ -361,7 +359,7 @@ def wrapper(self, *args, **kwargs): class Process: """Wrapper class around underlying C implementation.""" - __slots__ = ["pid", "_name", "_ppid", "_cache"] + __slots__ = ["_cache", "_name", "_ppid", "pid"] def __init__(self, pid): self.pid = pid @@ -502,11 +500,6 @@ def open_files(self): @wrap_exceptions def net_connections(self, kind='inet'): - if kind not in conn_tmap: - raise ValueError( - "invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap])) - ) families, types = conn_tmap[kind] rawlist = cext.proc_net_connections(self.pid, families, types) ret = [] diff --git a/psutil/_psposix.py b/psutil/_psposix.py index 42bdfa7ef..88703fdbd 100644 --- a/psutil/_psposix.py +++ b/psutil/_psposix.py @@ -4,10 +4,10 @@ """Routines common to all posix systems.""" +import enum import glob import os import signal -import sys import time from ._common import MACOS @@ -15,25 +15,12 @@ from ._common import memoize from ._common import sdiskusage from ._common import usage_percent -from ._compat import PY3 -from ._compat import ChildProcessError -from ._compat import FileNotFoundError -from ._compat import InterruptedError -from ._compat import PermissionError -from ._compat import ProcessLookupError -from ._compat import unicode if MACOS: from . import _psutil_osx -if PY3: - import enum -else: - enum = None - - __all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map'] @@ -59,23 +46,16 @@ def pid_exists(pid): return True -# Python 3.5 signals enum (contributed by me ^^): -# https://bugs.python.org/issue21076 -if enum is not None and hasattr(signal, "Signals"): - Negsignal = enum.IntEnum( - 'Negsignal', dict([(x.name, -x.value) for x in signal.Signals]) - ) - - def negsig_to_enum(num): - """Convert a negative signal value to an enum.""" - try: - return Negsignal(num) - except ValueError: - return num +Negsignal = enum.IntEnum( + 'Negsignal', {x.name: -x.value for x in signal.Signals} +) -else: # pragma: no cover - def negsig_to_enum(num): +def negsig_to_enum(num): + """Convert a negative signal value to an enum.""" + try: + return Negsignal(num) + except ValueError: return num @@ -139,7 +119,7 @@ def sleep(interval): # can't determine its exit status code. while _pid_exists(pid): interval = sleep(interval) - return + return None else: if retpid == 0: # WNOHANG flag was used and PID is still running. @@ -171,7 +151,8 @@ def sleep(interval): # continue else: # Should never happen. - raise ValueError("unknown process exit status %r" % status) + msg = f"unknown process exit status {status!r}" + raise ValueError(msg) def disk_usage(path): @@ -181,24 +162,7 @@ def disk_usage(path): total and used disk space whereas "free" and "percent" represent the "free" and "used percent" user disk space. """ - if PY3: - st = os.statvfs(path) - else: # pragma: no cover - # os.statvfs() does not support unicode on Python 2: - # - https://github.com/giampaolo/psutil/issues/416 - # - http://bugs.python.org/issue18695 - try: - st = os.statvfs(path) - except UnicodeEncodeError: - if isinstance(path, unicode): - try: - path = path.encode(sys.getfilesystemencoding()) - except UnicodeEncodeError: - pass - st = os.statvfs(path) - else: - raise - + st = os.statvfs(path) # Total space which is only available to root (unless changed # at system level). total = st.f_blocks * st.f_frsize diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py index 6112728b1..fdc9e7e00 100644 --- a/psutil/_pssunos.py +++ b/psutil/_pssunos.py @@ -18,6 +18,7 @@ from . import _psutil_posix as cext_posix from . import _psutil_sunos as cext from ._common import AF_INET6 +from ._common import ENCODING from ._common import AccessDenied from ._common import NoSuchProcess from ._common import ZombieProcess @@ -28,11 +29,6 @@ from ._common import sockfam_to_enum from ._common import socktype_to_enum from ._common import usage_percent -from ._compat import PY3 -from ._compat import FileNotFoundError -from ._compat import PermissionError -from ._compat import ProcessLookupError -from ._compat import b __extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"] @@ -147,17 +143,17 @@ def swap_memory(): p = subprocess.Popen( [ '/usr/bin/env', - 'PATH=/usr/sbin:/sbin:%s' % os.environ['PATH'], + f"PATH=/usr/sbin:/sbin:{os.environ['PATH']}", 'swap', '-l', ], stdout=subprocess.PIPE, ) stdout, _ = p.communicate() - if PY3: - stdout = stdout.decode(sys.stdout.encoding) + stdout = stdout.decode(sys.stdout.encoding) if p.returncode != 0: - raise RuntimeError("'swap -l' failed (retcode=%s)" % p.returncode) + msg = f"'swap -l' failed (retcode={p.returncode})" + raise RuntimeError(msg) lines = stdout.strip().split('\n')[1:] if not lines: @@ -209,7 +205,7 @@ def cpu_count_cores(): def cpu_stats(): """Return various CPU stats as a named tuple.""" - ctx_switches, interrupts, syscalls, traps = cext.cpu_stats() + ctx_switches, interrupts, syscalls, _traps = cext.cpu_stats() soft_interrupts = 0 return _common.scpustats( ctx_switches, interrupts, soft_interrupts, syscalls @@ -244,7 +240,7 @@ def disk_partitions(all=False): continue except OSError as err: # https://github.com/giampaolo/psutil/issues/1674 - debug("skipping %r: %s" % (mountpoint, err)) + debug(f"skipping {mountpoint!r}: {err}") continue ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) retlist.append(ntuple) @@ -265,14 +261,6 @@ def net_connections(kind, _pid=-1): connections (as opposed to connections opened by one process only). Only INET sockets are returned (UNIX are not). """ - cmap = _common.conn_tmap.copy() - if _pid == -1: - cmap.pop('unix', 0) - if kind not in cmap: - raise ValueError( - "invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in cmap])) - ) families, types = _common.conn_tmap[kind] rawlist = cext.net_connections(_pid) ret = set() @@ -283,7 +271,7 @@ def net_connections(kind, _pid=-1): if type_ not in types: continue # TODO: refactor and use _common.conn_to_ntuple. - if fam in (AF_INET, AF_INET6): + if fam in {AF_INET, AF_INET6}: if laddr: laddr = _common.addr(*laddr) if raddr: @@ -346,7 +334,8 @@ def users(): def pids(): """Returns a list of PIDs currently running on the system.""" - return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()] + path = get_procfs_path().encode(ENCODING) + return [int(x) for x in os.listdir(path) if x.isdigit()] def pid_exists(pid): @@ -361,24 +350,23 @@ def wrap_exceptions(fun): @functools.wraps(fun) def wrapper(self, *args, **kwargs): + pid, ppid, name = self.pid, self._ppid, self._name try: return fun(self, *args, **kwargs) - except (FileNotFoundError, ProcessLookupError): + except (FileNotFoundError, ProcessLookupError) as err: # ENOENT (no such file or directory) gets raised on open(). # ESRCH (no such process) can get raised on read() if # process is gone in meantime. - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - else: - raise ZombieProcess(self.pid, self._name, self._ppid) - except PermissionError: - raise AccessDenied(self.pid, self._name) - except OSError: - if self.pid == 0: + if not pid_exists(pid): + raise NoSuchProcess(pid, name) from err + raise ZombieProcess(pid, name, ppid) from err + except PermissionError as err: + raise AccessDenied(pid, name) from err + except OSError as err: + if pid == 0: if 0 in pids(): - raise AccessDenied(self.pid, self._name) - else: - raise + raise AccessDenied(pid, name) from err + raise raise return wrapper @@ -387,7 +375,7 @@ def wrapper(self, *args, **kwargs): class Process: """Wrapper class around underlying C implementation.""" - __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] + __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"] def __init__(self, pid): self.pid = pid @@ -399,7 +387,7 @@ def _assert_alive(self): """Raise NSP if the process disappeared on us.""" # For those C function who do not raise NSP, possibly returning # incorrect or incomplete result. - os.stat('%s/%s' % (self._procfs_path, self.pid)) + os.stat(f"{self._procfs_path}/{self.pid}") def oneshot_enter(self): self._proc_name_and_args.cache_activate(self) @@ -420,7 +408,7 @@ def _proc_name_and_args(self): @memoize_when_activated def _proc_basic_info(self): if self.pid == 0 and not os.path.exists( - '%s/%s/psinfo' % (self._procfs_path, self.pid) + f"{self._procfs_path}/{self.pid}/psinfo" ): raise AccessDenied(self.pid) ret = cext.proc_basic_info(self.pid, self._procfs_path) @@ -440,9 +428,7 @@ def name(self): @wrap_exceptions def exe(self): try: - return os.readlink( - "%s/%s/path/a.out" % (self._procfs_path, self.pid) - ) + return os.readlink(f"{self._procfs_path}/{self.pid}/path/a.out") except OSError: pass # continue and guess the exe name from the cmdline # Will be guessed later from cmdline but we want to explicitly @@ -476,7 +462,7 @@ def nice_get(self): @wrap_exceptions def nice_set(self, value): - if self.pid in (2, 3): + if self.pid in {2, 3}: # Special case PIDs: internally setpriority(3) return ESRCH # (no such process), no matter what. # The process actually exists though, as it has a name, @@ -539,9 +525,7 @@ def terminal(self): if tty != cext.PRNODEV: for x in (0, 1, 2, 255): try: - return os.readlink( - '%s/%d/path/%d' % (procfs_path, self.pid, x) - ) + return os.readlink(f"{procfs_path}/{self.pid}/path/{x}") except FileNotFoundError: hit_enoent = True continue @@ -556,9 +540,9 @@ def cwd(self): # Reference: http://goo.gl/55XgO procfs_path = self._procfs_path try: - return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid)) + return os.readlink(f"{procfs_path}/{self.pid}/path/cwd") except FileNotFoundError: - os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD + os.stat(f"{procfs_path}/{self.pid}") # raise NSP or AD return "" @wrap_exceptions @@ -580,7 +564,7 @@ def status(self): def threads(self): procfs_path = self._procfs_path ret = [] - tids = os.listdir('%s/%d/lwp' % (procfs_path, self.pid)) + tids = os.listdir(f"{procfs_path}/{self.pid}/lwp") hit_enoent = False for tid in tids: tid = int(tid) @@ -588,7 +572,7 @@ def threads(self): utime, stime = cext.query_process_thread( self.pid, tid, procfs_path ) - except EnvironmentError as err: + except OSError as err: if err.errno == errno.EOVERFLOW and not IS_64_BIT: # We may get here if we attempt to query a 64bit process # with a 32bit python. @@ -615,8 +599,8 @@ def open_files(self): retlist = [] hit_enoent = False procfs_path = self._procfs_path - pathdir = '%s/%d/path' % (procfs_path, self.pid) - for fd in os.listdir('%s/%d/fd' % (procfs_path, self.pid)): + pathdir = f"{procfs_path}/{self.pid}/path" + for fd in os.listdir(f"{procfs_path}/{self.pid}/fd"): path = os.path.join(pathdir, fd) if os.path.islink(path): try: @@ -640,16 +624,16 @@ def _get_unix_sockets(self, pid): cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, stderr = p.communicate() - if PY3: - stdout, stderr = ( - x.decode(sys.stdout.encoding) for x in (stdout, stderr) - ) + stdout, stderr = ( + x.decode(sys.stdout.encoding) for x in (stdout, stderr) + ) if p.returncode != 0: if 'permission denied' in stderr.lower(): raise AccessDenied(self.pid, self._name) if 'no such process' in stderr.lower(): raise NoSuchProcess(self.pid, self._name) - raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + msg = f"{cmd!r} command error\n{stderr}" + raise RuntimeError(msg) lines = stdout.split('\n')[2:] for i, line in enumerate(lines): @@ -675,10 +659,10 @@ def net_connections(self, kind='inet'): # is no longer there. if not ret: # will raise NSP if process is gone - os.stat('%s/%s' % (self._procfs_path, self.pid)) + os.stat(f"{self._procfs_path}/{self.pid}") # UNIX sockets - if kind in ('all', 'unix'): + if kind in {'all', 'unix'}: ret.extend([ _common.pconn(*conn) for conn in self._get_unix_sockets(self.pid) @@ -691,9 +675,8 @@ def net_connections(self, kind='inet'): @wrap_exceptions def memory_maps(self): def toaddr(start, end): - return '%s-%s' % ( - hex(start)[2:].strip('L'), - hex(end)[2:].strip('L'), + return "{}-{}".format( + hex(start)[2:].strip('L'), hex(end)[2:].strip('L') ) procfs_path = self._procfs_path @@ -718,9 +701,7 @@ def toaddr(start, end): addr = toaddr(addr, addrsize) if not name.startswith('['): try: - name = os.readlink( - '%s/%s/path/%s' % (procfs_path, self.pid, name) - ) + name = os.readlink(f"{procfs_path}/{self.pid}/path/{name}") except OSError as err: if err.errno == errno.ENOENT: # sometimes the link may not be resolved by @@ -729,7 +710,7 @@ def toaddr(start, end): # unresolved link path. # This seems an inconsistency with /proc similar # to: http://goo.gl/55XgO - name = '%s/%s/path/%s' % (procfs_path, self.pid, name) + name = f"{procfs_path}/{self.pid}/path/{name}" hit_enoent = True else: raise @@ -740,7 +721,7 @@ def toaddr(start, end): @wrap_exceptions def num_fds(self): - return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) + return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd")) @wrap_exceptions def num_ctx_switches(self): diff --git a/psutil/_psutil_aix.c b/psutil/_psutil_aix.c index ce89a7bd7..958a2c0c2 100644 --- a/psutil/_psutil_aix.c +++ b/psutil/_psutil_aix.c @@ -58,6 +58,7 @@ #define TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec) +#define INITERROR return NULL /* * Read a file content and fills a C structure with it. @@ -1030,18 +1031,12 @@ struct module_state { PyObject *error; }; -#if PY_MAJOR_VERSION >= 3 #define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) -#else -#define GETSTATE(m) (&_state) -#endif #ifdef __cplusplus extern "C" { #endif -#if PY_MAJOR_VERSION >= 3 - static int psutil_aix_traverse(PyObject *m, visitproc visit, void *arg) { Py_VISIT(GETSTATE(m)->error); @@ -1066,20 +1061,15 @@ static struct PyModuleDef moduledef = { NULL }; -#define INITERROR return NULL -PyMODINIT_FUNC PyInit__psutil_aix(void) - -#else -#define INITERROR return - -void init_psutil_aix(void) -#endif -{ -#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC +PyInit__psutil_aix(void) { PyObject *module = PyModule_Create(&moduledef); -#else - PyObject *module = Py_InitModule("_psutil_aix", PsutilMethods); + if (module == NULL) + INITERROR; + +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); #endif PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); @@ -1106,9 +1096,8 @@ void init_psutil_aix(void) if (module == NULL) INITERROR; -#if PY_MAJOR_VERSION >= 3 + return module; -#endif } #ifdef __cplusplus diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c index 6517d5800..503e96cac 100644 --- a/psutil/_psutil_bsd.c +++ b/psutil/_psutil_bsd.c @@ -52,6 +52,9 @@ #endif +#define INITERR return NULL + + /* * define the psutil C module methods and initialize the module. */ @@ -112,37 +115,30 @@ static PyMethodDef mod_methods[] = { {NULL, NULL, 0, NULL} }; -#if PY_MAJOR_VERSION >= 3 - #define INITERR return NULL - - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_psutil_bsd", - NULL, - -1, - mod_methods, - NULL, - NULL, - NULL, - NULL - }; - - PyObject *PyInit__psutil_bsd(void) -#else /* PY_MAJOR_VERSION */ - #define INITERR return - - void init_psutil_bsd(void) -#endif /* PY_MAJOR_VERSION */ -{ + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_bsd", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyObject +*PyInit__psutil_bsd(void) { PyObject *v; -#if PY_MAJOR_VERSION >= 3 PyObject *mod = PyModule_Create(&moduledef); -#else - PyObject *mod = Py_InitModule("_psutil_bsd", mod_methods); -#endif if (mod == NULL) INITERR; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); +#endif + if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) INITERR; // process status constants @@ -206,7 +202,5 @@ static PyMethodDef mod_methods[] = { if (mod == NULL) INITERR; -#if PY_MAJOR_VERSION >= 3 return mod; -#endif } diff --git a/psutil/_psutil_common.c b/psutil/_psutil_common.c index 2f2edca26..81132fd3e 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -68,15 +68,6 @@ PyErr_SetExcFromWindowsErrWithFilenameObject(PyObject *type, return PyErr_SetFromWindowsErrWithFilename(ierr, NULL); } #endif // !defined(PyErr_SetExcFromWindowsErrWithFilenameObject) - - -// PyPy 2.7 -#if !defined(PyErr_SetFromWindowsErr) -PyObject * -PyErr_SetFromWindowsErr(int winerr) { - return PyErr_SetFromWindowsErrWithFilename(winerr, ""); -} -#endif // !defined(PyErr_SetFromWindowsErr) #endif // defined(PSUTIL_WINDOWS) && defined(PYPY_VERSION) @@ -89,7 +80,7 @@ PyErr_SetFromWindowsErr(int winerr) { * message. */ PyObject * -PyErr_SetFromOSErrnoWithSyscall(const char *syscall) { +psutil_PyErr_SetFromOSErrnoWithSyscall(const char *syscall) { char fullmsg[1024]; #ifdef PSUTIL_WINDOWS diff --git a/psutil/_psutil_common.h b/psutil/_psutil_common.h index 20c03ebd7..024452630 100644 --- a/psutil/_psutil_common.h +++ b/psutil/_psutil_common.h @@ -23,14 +23,6 @@ static const int PSUTIL_CONN_NONE = 128; // --- Backward compatibility with missing Python.h APIs // ==================================================================== -#if PY_MAJOR_VERSION < 3 - // On Python 2 we just return a plain byte string, which is never - // supposed to raise decoding errors, see: - // https://github.com/giampaolo/psutil/issues/1040 - #define PyUnicode_DecodeFSDefault PyString_FromString - #define PyUnicode_DecodeFSDefaultAndSize PyString_FromStringAndSize -#endif - #if defined(PSUTIL_WINDOWS) && defined(PYPY_VERSION) #if !defined(PyErr_SetFromWindowsErrWithFilename) PyObject *PyErr_SetFromWindowsErrWithFilename(int ierr, @@ -45,7 +37,6 @@ static const int PSUTIL_CONN_NONE = 128; // --- _Py_PARSE_PID // SIZEOF_INT|LONG is missing on Linux + PyPy (only?). -// SIZEOF_PID_T is missing on Windows + Python2. // In this case we guess it from setup.py. It's not 100% bullet proof, // If wrong we'll probably get compiler warnings. // FWIW on all UNIX platforms I've seen pid_t is defined as an int. @@ -60,8 +51,8 @@ static const int PSUTIL_CONN_NONE = 128; #define SIZEOF_PID_T PSUTIL_SIZEOF_PID_T // set as a macro in setup.py #endif -// _Py_PARSE_PID is Python 3 only, but since it's private make sure it's -// always present. +// _Py_PARSE_PID was added in Python 3, but since it's private we make +// sure it's always present. #ifndef _Py_PARSE_PID #if SIZEOF_PID_T == SIZEOF_INT #define _Py_PARSE_PID "i" @@ -75,14 +66,10 @@ static const int PSUTIL_CONN_NONE = 128; #endif #endif -// Python 2 or PyPy on Windows +// PyPy on Windows #ifndef PyLong_FromPid #if ((SIZEOF_PID_T == SIZEOF_INT) || (SIZEOF_PID_T == SIZEOF_LONG)) - #if PY_MAJOR_VERSION >= 3 - #define PyLong_FromPid PyLong_FromLong - #else - #define PyLong_FromPid PyInt_FromLong - #endif + #define PyLong_FromPid PyLong_FromLong #elif defined(SIZEOF_LONG_LONG) && SIZEOF_PID_T == SIZEOF_LONG_LONG #define PyLong_FromPid PyLong_FromLongLong #else @@ -97,7 +84,7 @@ static const int PSUTIL_CONN_NONE = 128; PyObject* AccessDenied(const char *msg); PyObject* NoSuchProcess(const char *msg); -PyObject* PyErr_SetFromOSErrnoWithSyscall(const char *syscall); +PyObject* psutil_PyErr_SetFromOSErrnoWithSyscall(const char *syscall); // ==================================================================== // --- Global utils diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c index 292e1c552..fcd886bfb 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -19,6 +19,13 @@ #include "arch/linux/proc.h" #include "arch/linux/users.h" +// May happen on old RedHat versions, see: +// https://github.com/giampaolo/psutil/issues/607 +#ifndef DUPLEX_UNKNOWN + #define DUPLEX_UNKNOWN 0xff +#endif + +#define INITERR return NULL static PyMethodDef mod_methods[] = { // --- per-process functions @@ -41,43 +48,31 @@ static PyMethodDef mod_methods[] = { {"set_debug", psutil_set_debug, METH_VARARGS}, {NULL, NULL, 0, NULL} }; -// May happen on old RedHat versions, see: -// https://github.com/giampaolo/psutil/issues/607 -#ifndef DUPLEX_UNKNOWN - #define DUPLEX_UNKNOWN 0xff -#endif -#if PY_MAJOR_VERSION >= 3 - #define INITERR return NULL - - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_psutil_linux", - NULL, - -1, - mod_methods, - NULL, - NULL, - NULL, - NULL - }; +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_linux", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL +}; - PyObject *PyInit__psutil_linux(void) -#else /* PY_MAJOR_VERSION */ - #define INITERR return - void init_psutil_linux(void) -#endif /* PY_MAJOR_VERSION */ -{ -#if PY_MAJOR_VERSION >= 3 +PyObject * +PyInit__psutil_linux(void) { PyObject *mod = PyModule_Create(&moduledef); -#else - PyObject *mod = Py_InitModule("_psutil_linux", mod_methods); -#endif if (mod == NULL) INITERR; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); +#endif + if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) INITERR; if (PyModule_AddIntConstant(mod, "DUPLEX_HALF", DUPLEX_HALF)) INITERR; if (PyModule_AddIntConstant(mod, "DUPLEX_FULL", DUPLEX_FULL)) INITERR; @@ -87,7 +82,5 @@ static PyMethodDef mod_methods[] = { if (mod == NULL) INITERR; -#if PY_MAJOR_VERSION >= 3 return mod; -#endif } diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c index 4aa11d170..d9a486fe5 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -20,6 +20,8 @@ #include "arch/osx/sys.h" +#define INITERR return NULL + static PyMethodDef mod_methods[] = { // --- per-process functions {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS}, @@ -61,36 +63,29 @@ static PyMethodDef mod_methods[] = { }; -#if PY_MAJOR_VERSION >= 3 - #define INITERR return NULL - - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_psutil_osx", - NULL, - -1, - mod_methods, - NULL, - NULL, - NULL, - NULL - }; - - PyObject *PyInit__psutil_osx(void) -#else /* PY_MAJOR_VERSION */ - #define INITERR return - - void init_psutil_osx(void) -#endif /* PY_MAJOR_VERSION */ -{ -#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_osx", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL +}; + + +PyObject * +PyInit__psutil_osx(void) { PyObject *mod = PyModule_Create(&moduledef); -#else - PyObject *mod = Py_InitModule("_psutil_osx", mod_methods); -#endif if (mod == NULL) INITERR; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); +#endif + if (psutil_setup() != 0) INITERR; @@ -136,7 +131,5 @@ static PyMethodDef mod_methods[] = { if (mod == NULL) INITERR; -#if PY_MAJOR_VERSION >= 3 return mod; -#endif } diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index 24628afc7..d4d16e7ff 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -52,6 +52,9 @@ #include "_psutil_common.h" +#define INITERR return NULL + + // ==================================================================== // --- Utils // ==================================================================== @@ -148,7 +151,7 @@ psutil_pid_exists(pid_t pid) { void psutil_raise_for_pid(long pid, char *syscall) { if (errno != 0) - PyErr_SetFromOSErrnoWithSyscall(syscall); + psutil_PyErr_SetFromOSErrnoWithSyscall(syscall); else if (psutil_pid_exists(pid) == 0) NoSuchProcess(syscall); else @@ -434,11 +437,7 @@ append_flag(PyObject *py_retlist, const char * flag_name) { PyObject *py_str = NULL; -#if PY_MAJOR_VERSION >= 3 py_str = PyUnicode_FromString(flag_name); -#else - py_str = PyString_FromString(flag_name); -#endif if (! py_str) return 0; if (PyList_Append(py_retlist, py_str)) { @@ -470,14 +469,14 @@ psutil_net_if_flags(PyObject *self, PyObject *args) { sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { - PyErr_SetFromOSErrnoWithSyscall("socket(SOCK_DGRAM)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("socket(SOCK_DGRAM)"); goto error; } PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); ret = ioctl(sock, SIOCGIFFLAGS, &ifr); if (ret == -1) { - PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCGIFFLAGS)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCGIFFLAGS)"); goto error; } @@ -883,36 +882,28 @@ static PyMethodDef mod_methods[] = { }; -#if PY_MAJOR_VERSION >= 3 - #define INITERR return NULL - - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_psutil_posix", - NULL, - -1, - mod_methods, - NULL, - NULL, - NULL, - NULL - }; - - PyObject *PyInit__psutil_posix(void) -#else /* PY_MAJOR_VERSION */ - #define INITERR return +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_posix", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL +}; - void init_psutil_posix(void) -#endif /* PY_MAJOR_VERSION */ -{ -#if PY_MAJOR_VERSION >= 3 +PyObject * +PyInit__psutil_posix(void) { PyObject *mod = PyModule_Create(&moduledef); -#else - PyObject *mod = Py_InitModule("_psutil_posix", mod_methods); -#endif if (mod == NULL) INITERR; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); +#endif + #if defined(PSUTIL_BSD) || \ defined(PSUTIL_OSX) || \ defined(PSUTIL_SUNOS) || \ @@ -1018,9 +1009,7 @@ static PyMethodDef mod_methods[] = { if (mod == NULL) INITERR; -#if PY_MAJOR_VERSION >= 3 return mod; -#endif } #ifdef __cplusplus diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c index 54f353c10..dde9f7015 100644 --- a/psutil/_psutil_sunos.c +++ b/psutil/_psutil_sunos.c @@ -59,6 +59,8 @@ #include "arch/solaris/environ.h" #define PSUTIL_TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec) +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#define INITERROR return NULL /* @@ -1671,13 +1673,6 @@ struct module_state { PyObject *error; }; -#if PY_MAJOR_VERSION >= 3 -#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) -#else -#define GETSTATE(m) (&_state) -#endif - -#if PY_MAJOR_VERSION >= 3 static int psutil_sunos_traverse(PyObject *m, visitproc visit, void *arg) { @@ -1703,24 +1698,17 @@ static struct PyModuleDef moduledef = { NULL }; -#define INITERROR return NULL - -PyMODINIT_FUNC PyInit__psutil_sunos(void) -#else -#define INITERROR return - -void init_psutil_sunos(void) -#endif -{ -#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC +PyInit__psutil_sunos(void) { PyObject *module = PyModule_Create(&moduledef); -#else - PyObject *module = Py_InitModule("_psutil_sunos", PsutilMethods); -#endif if (module == NULL) INITERROR; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); +#endif + if (psutil_setup() != 0) INITERROR; @@ -1762,7 +1750,6 @@ void init_psutil_sunos(void) if (module == NULL) INITERROR; -#if PY_MAJOR_VERSION >= 3 + return module; -#endif } diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index bb6e12ff8..0af18f3e2 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -34,6 +34,10 @@ #include "arch/windows/wmi.h" +#define INITERROR return NULL +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) + + // ------------------------ Python init --------------------------- static PyMethodDef @@ -116,21 +120,15 @@ struct module_state { PyObject *error; }; -#if PY_MAJOR_VERSION >= 3 -#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) -#else -#define GETSTATE(m) (&_state) -static struct module_state _state; -#endif -#if PY_MAJOR_VERSION >= 3 - -static int psutil_windows_traverse(PyObject *m, visitproc visit, void *arg) { +static int +psutil_windows_traverse(PyObject *m, visitproc visit, void *arg) { Py_VISIT(GETSTATE(m)->error); return 0; } -static int psutil_windows_clear(PyObject *m) { +static int +psutil_windows_clear(PyObject *m) { Py_CLEAR(GETSTATE(m)->error); return 0; } @@ -147,24 +145,19 @@ static struct PyModuleDef moduledef = { NULL }; -#define INITERROR return NULL -PyMODINIT_FUNC PyInit__psutil_windows(void) - -#else -#define INITERROR return -void init_psutil_windows(void) -#endif -{ +PyMODINIT_FUNC +PyInit__psutil_windows(void) { struct module_state *st = NULL; -#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); -#else - PyObject *module = Py_InitModule("_psutil_windows", PsutilMethods); -#endif if (module == NULL) INITERROR; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); +#endif + if (psutil_setup() != 0) INITERROR; if (psutil_set_se_debug() != 0) @@ -279,7 +272,5 @@ void init_psutil_windows(void) PyModule_AddIntConstant( module, "WINDOWS_10", PSUTIL_WINDOWS_10); -#if PY_MAJOR_VERSION >= 3 return module; -#endif } diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index babb8e82e..69820ba41 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -5,7 +5,7 @@ """Windows platform implementation.""" import contextlib -import errno +import enum import functools import os import signal @@ -15,7 +15,6 @@ from . import _common from ._common import ENCODING -from ._common import ENCODING_ERRS from ._common import AccessDenied from ._common import NoSuchProcess from ._common import TimeoutExpired @@ -27,11 +26,6 @@ from ._common import memoize_when_activated from ._common import parse_environ_block from ._common import usage_percent -from ._compat import PY3 -from ._compat import long -from ._compat import lru_cache -from ._compat import range -from ._compat import unicode from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS from ._psutil_windows import HIGH_PRIORITY_CLASS @@ -54,14 +48,10 @@ msg = "this Windows version is too old (< Windows Vista); " msg += "psutil 3.4.2 is the latest version which supports Windows " msg += "2000, XP and 2003 server" - raise RuntimeError(msg) + raise RuntimeError(msg) from err else: raise -if PY3: - import enum -else: - enum = None # process priority constants, import from __init__.py: # http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx @@ -88,11 +78,8 @@ ERROR_PARTIAL_COPY = 299 PYPY = '__pypy__' in sys.builtin_module_names -if enum is None: - AF_LINK = -1 -else: - AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) - AF_LINK = AddressFamily.AF_LINK +AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) +AF_LINK = AddressFamily.AF_LINK TCP_STATUSES = { cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED, @@ -110,32 +97,27 @@ cext.PSUTIL_CONN_NONE: _common.CONN_NONE, } -if enum is not None: - class Priority(enum.IntEnum): - ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS - BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS - HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS - IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS - NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS - REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS +class Priority(enum.IntEnum): + ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS + BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS + HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS + IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS + NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS + REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS - globals().update(Priority.__members__) -if enum is None: +globals().update(Priority.__members__) + + +class IOPriority(enum.IntEnum): IOPRIO_VERYLOW = 0 IOPRIO_LOW = 1 IOPRIO_NORMAL = 2 IOPRIO_HIGH = 3 -else: - class IOPriority(enum.IntEnum): - IOPRIO_VERYLOW = 0 - IOPRIO_LOW = 1 - IOPRIO_NORMAL = 2 - IOPRIO_HIGH = 3 - globals().update(IOPriority.__members__) +globals().update(IOPriority.__members__) pinfo_map = dict( num_handles=0, @@ -199,7 +181,7 @@ class IOPriority(enum.IntEnum): # ===================================================================== -@lru_cache(maxsize=512) +@functools.lru_cache(maxsize=512) def convert_dos_path(s): r"""Convert paths using native DOS format like: "\Device\HarddiskVolume1\Windows\systemew\file.txt" @@ -212,19 +194,6 @@ def convert_dos_path(s): return os.path.join(driveletter, remainder) -def py2_strencode(s): - """Encode a unicode string to a byte string by using the default fs - encoding + "replace" error handler. - """ - if PY3: - return s - else: - if isinstance(s, str): - return s - else: - return s.encode(ENCODING, ENCODING_ERRS) - - @memoize def getpagesize(): return cext.getpagesize() @@ -238,7 +207,7 @@ def getpagesize(): def virtual_memory(): """System virtual memory as a namedtuple.""" mem = cext.virtual_mem() - totphys, availphys, totsys, availsys = mem + totphys, availphys, _totsys, _availsys = mem total = totphys avail = availphys free = availphys @@ -283,7 +252,7 @@ def swap_memory(): def disk_usage(path): """Return disk usage associated with path.""" - if PY3 and isinstance(path, bytes): + if isinstance(path, bytes): # XXX: do we want to use "strict"? Probably yes, in order # to fail immediately. After all we are accepting input here... path = path.decode(ENCODING, errors="strict") @@ -337,7 +306,7 @@ def cpu_count_cores(): def cpu_stats(): """Return CPU statistics.""" - ctx_switches, interrupts, dpcs, syscalls = cext.cpu_stats() + ctx_switches, interrupts, _dpcs, syscalls = cext.cpu_stats() soft_interrupts = 0 return _common.scpustats( ctx_switches, interrupts, soft_interrupts, syscalls @@ -380,11 +349,6 @@ def net_connections(kind, _pid=-1): """Return socket connections. If pid == -1 return system-wide connections (as opposed to connections opened by one process only). """ - if kind not in conn_tmap: - raise ValueError( - "invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap])) - ) families, types = conn_tmap[kind] rawlist = cext.net_connections(_pid, families, types) ret = set() @@ -409,9 +373,6 @@ def net_if_stats(): ret = {} rawdict = cext.net_if_stats() for name, items in rawdict.items(): - if not PY3: - assert isinstance(name, unicode), type(name) - name = py2_strencode(name) isup, duplex, speed, mtu = items if hasattr(_common, 'NicDuplex'): duplex = _common.NicDuplex(duplex) @@ -423,18 +384,12 @@ def net_io_counters(): """Return network I/O statistics for every network interface installed on the system as a dict of raw tuples. """ - ret = cext.net_io_counters() - return dict([(py2_strencode(k), v) for k, v in ret.items()]) + return cext.net_io_counters() def net_if_addrs(): """Return the addresses associated to each NIC.""" - ret = [] - for items in cext.net_if_addrs(): - items = list(items) - items[0] = py2_strencode(items[0]) - ret.append(items) - return ret + return cext.net_if_addrs() # ===================================================================== @@ -490,7 +445,6 @@ def users(): rawlist = cext.users() for item in rawlist: user, hostname, tstamp = item - user = py2_strencode(user) nt = _common.suser(user, None, hostname, tstamp, None) retlist.append(nt) return retlist @@ -504,7 +458,7 @@ def users(): def win_service_iter(): """Yields a list of WindowsService instances.""" for name, display_name in cext.winservice_enumerate(): - yield WindowsService(py2_strencode(name), py2_strencode(display_name)) + yield WindowsService(name, display_name) def win_service_get(name): @@ -522,14 +476,11 @@ def __init__(self, name, display_name): self._display_name = display_name def __str__(self): - details = "(name=%r, display_name=%r)" % ( - self._name, - self._display_name, - ) - return "%s%s" % (self.__class__.__name__, details) + details = f"(name={self._name!r}, display_name={self._display_name!r})" + return f"{self.__class__.__name__}{details}" def __repr__(self): - return "<%s at %s>" % (self.__str__(), id(self)) + return f"<{self.__str__()} at {id(self)}>" def __eq__(self, other): # Test for equality with another WindosService object based @@ -548,10 +499,10 @@ def _query_config(self): ) # XXX - update _self.display_name? return dict( - display_name=py2_strencode(display_name), - binpath=py2_strencode(binpath), - username=py2_strencode(username), - start_type=py2_strencode(start_type), + display_name=display_name, + binpath=binpath, + username=username, + start_type=start_type, ) def _query_status(self): @@ -569,18 +520,18 @@ def _wrap_exceptions(self): try: yield except OSError as err: + name = self._name if is_permission_err(err): msg = ( - "service %r is not querable (not enough privileges)" - % self._name + f"service {name!r} is not querable (not enough privileges)" ) - raise AccessDenied(pid=None, name=self._name, msg=msg) - elif err.winerror in ( + raise AccessDenied(pid=None, name=name, msg=msg) from err + elif err.winerror in { cext.ERROR_INVALID_NAME, cext.ERROR_SERVICE_DOES_NOT_EXIST, - ): - msg = "service %r does not exist" % self._name - raise NoSuchProcess(pid=None, name=self._name, msg=msg) + }: + msg = f"service {name!r} does not exist" + raise NoSuchProcess(pid=None, name=name, msg=msg) from err else: raise @@ -629,7 +580,7 @@ def status(self): def description(self): """Service long description.""" - return py2_strencode(cext.winservice_query_descr(self.name())) + return cext.winservice_query_descr(self.name()) # utils @@ -697,15 +648,10 @@ def as_dict(self): def is_permission_err(exc): """Return True if this is a permission error.""" assert isinstance(exc, OSError), exc - if exc.errno in (errno.EPERM, errno.EACCES): - return True - # On Python 2 OSError doesn't always have 'winerror'. Sometimes - # it does, in which case the original exception was WindowsError - # (which is a subclass of OSError). - return getattr(exc, "winerror", -1) in ( + return isinstance(exc, PermissionError) or exc.winerror in { cext.ERROR_ACCESS_DENIED, cext.ERROR_PRIVILEGE_NOT_HELD, - ) + } def convert_oserror(exc, pid=None, name=None): @@ -713,7 +659,7 @@ def convert_oserror(exc, pid=None, name=None): assert isinstance(exc, OSError), exc if is_permission_err(exc): return AccessDenied(pid=pid, name=name) - if exc.errno == errno.ESRCH: + if isinstance(exc, ProcessLookupError): return NoSuchProcess(pid=pid, name=name) raise exc @@ -726,7 +672,7 @@ def wrapper(self, *args, **kwargs): try: return fun(self, *args, **kwargs) except OSError as err: - raise convert_oserror(err, pid=self.pid, name=self._name) + raise convert_oserror(err, pid=self.pid, name=self._name) from err return wrapper @@ -743,7 +689,7 @@ def wrapper(self, *args, **kwargs): for _ in range(times): # retries for roughly 1 second try: return fun(self, *args, **kwargs) - except WindowsError as _: + except OSError as _: err = _ if err.winerror == ERROR_PARTIAL_COPY: time.sleep(delay) @@ -751,8 +697,8 @@ def wrapper(self, *args, **kwargs): continue raise msg = ( - "{} retried {} times, converted to AccessDenied as it's still" - "returning {}".format(fun, times, err) + f"{fun} retried {times} times, converted to AccessDenied as it's " + f"still returning {err}" ) raise AccessDenied(pid=self.pid, name=self._name, msg=msg) @@ -762,7 +708,7 @@ def wrapper(self, *args, **kwargs): class Process: """Wrapper class around underlying C implementation.""" - __slots__ = ["pid", "_name", "_ppid", "_cache"] + __slots__ = ["_cache", "_name", "_ppid", "pid"] def __init__(self, pid): self.pid = pid @@ -806,17 +752,15 @@ def exe(self): if PYPY: try: exe = cext.proc_exe(self.pid) - except WindowsError as err: + except OSError as err: # 24 = ERROR_TOO_MANY_OPEN_FILES. Not sure why this happens # (perhaps PyPy's JIT delaying garbage collection of files?). if err.errno == 24: - debug("%r translated into AccessDenied" % err) - raise AccessDenied(self.pid, self._name) + debug(f"{err!r} translated into AccessDenied") + raise AccessDenied(self.pid, self._name) from err raise else: exe = cext.proc_exe(self.pid) - if not PY3: - exe = py2_strencode(exe) if exe.startswith('\\'): return convert_dos_path(exe) return exe # May be "Registry", "MemCompression", ... @@ -828,32 +772,26 @@ def cmdline(self): # PEB method detects cmdline changes but requires more # privileges: https://github.com/giampaolo/psutil/pull/1398 try: - ret = cext.proc_cmdline(self.pid, use_peb=True) + return cext.proc_cmdline(self.pid, use_peb=True) except OSError as err: if is_permission_err(err): - ret = cext.proc_cmdline(self.pid, use_peb=False) + return cext.proc_cmdline(self.pid, use_peb=False) else: raise else: - ret = cext.proc_cmdline(self.pid, use_peb=True) - if PY3: - return ret - else: - return [py2_strencode(s) for s in ret] + return cext.proc_cmdline(self.pid, use_peb=True) @wrap_exceptions @retry_error_partial_copy def environ(self): - ustr = cext.proc_environ(self.pid) - if ustr and not PY3: - assert isinstance(ustr, unicode), type(ustr) - return parse_environ_block(py2_strencode(ustr)) + s = cext.proc_environ(self.pid) + return parse_environ_block(s) def ppid(self): try: return ppid_map()[self.pid] except KeyError: - raise NoSuchProcess(self.pid, self._name) + raise NoSuchProcess(self.pid, self._name) from None def _get_raw_meminfo(self): try: @@ -901,12 +839,10 @@ def memory_maps(self): except OSError as err: # XXX - can't use wrap_exceptions decorator as we're # returning a generator; probably needs refactoring. - raise convert_oserror(err, self.pid, self._name) + raise convert_oserror(err, self.pid, self._name) from err else: for addr, perm, path, rss in raw: path = convert_dos_path(path) - if not PY3: - path = py2_strencode(path) addr = hex(addr) yield (addr, perm, path, rss) @@ -918,11 +854,7 @@ def kill(self): def send_signal(self, sig): if sig == signal.SIGTERM: cext.proc_kill(self.pid) - # py >= 2.7 - elif sig in ( - getattr(signal, "CTRL_C_EVENT", object()), - getattr(signal, "CTRL_BREAK_EVENT", object()), - ): + elif sig in {signal.CTRL_C_EVENT, signal.CTRL_BREAK_EVENT}: os.kill(self.pid, sig) else: msg = ( @@ -947,9 +879,9 @@ def wait(self, timeout=None): # May also be None if OpenProcess() failed with # ERROR_INVALID_PARAMETER, meaning PID is already gone. exit_code = cext.proc_wait(self.pid, cext_timeout) - except cext.TimeoutExpired: + except cext.TimeoutExpired as err: # WaitForSingleObject() returned WAIT_TIMEOUT. Just raise. - raise TimeoutExpired(timeout, self.pid, self._name) + raise TimeoutExpired(timeout, self.pid, self._name) from err except cext.TimeoutAbandoned: # WaitForSingleObject() returned WAIT_ABANDONED, see: # https://github.com/giampaolo/psutil/issues/1224 @@ -976,20 +908,22 @@ def wait(self, timeout=None): @wrap_exceptions def username(self): - if self.pid in (0, 4): + if self.pid in {0, 4}: return 'NT AUTHORITY\\SYSTEM' domain, user = cext.proc_username(self.pid) - return py2_strencode(domain) + '\\' + py2_strencode(user) + return f"{domain}\\{user}" @wrap_exceptions - def create_time(self): + def create_time(self, fast_only=False): # Note: proc_times() not put under oneshot() 'cause create_time() # is already cached by the main Process class. try: - user, system, created = cext.proc_times(self.pid) + _user, _system, created = cext.proc_times(self.pid) return created except OSError as err: if is_permission_err(err): + if fast_only: + raise debug("attempting create_time() fallback (slower)") return self._proc_info()[pinfo_map['create_time']] raise @@ -1010,7 +944,7 @@ def threads(self): @wrap_exceptions def cpu_times(self): try: - user, system, created = cext.proc_times(self.pid) + user, system, _created = cext.proc_times(self.pid) except OSError as err: if not is_permission_err(err): raise @@ -1032,16 +966,16 @@ def resume(self): @wrap_exceptions @retry_error_partial_copy def cwd(self): - if self.pid in (0, 4): + if self.pid in {0, 4}: raise AccessDenied(self.pid, self._name) # return a normalized pathname since the native C function appends # "\\" at the and of the path path = cext.proc_cwd(self.pid) - return py2_strencode(os.path.normpath(path)) + return os.path.normpath(path) @wrap_exceptions def open_files(self): - if self.pid in (0, 4): + if self.pid in {0, 4}: return [] ret = set() # Filenames come in in native format like: @@ -1049,12 +983,10 @@ def open_files(self): # Convert the first part in the corresponding drive letter # (e.g. "C:\") by using Windows's QueryDosDevice() raw_file_names = cext.proc_open_files(self.pid) - for _file in raw_file_names: - _file = convert_dos_path(_file) - if isfile_strict(_file): - if not PY3: - _file = py2_strencode(_file) - ntuple = _common.popenfile(_file, -1) + for file in raw_file_names: + file = convert_dos_path(file) + if isfile_strict(file): + ntuple = _common.popenfile(file, -1) ret.add(ntuple) return list(ret) @@ -1065,8 +997,7 @@ def net_connections(self, kind='inet'): @wrap_exceptions def nice_get(self): value = cext.proc_priority_get(self.pid) - if enum is not None: - value = Priority(value) + value = Priority(value) return value @wrap_exceptions @@ -1076,8 +1007,7 @@ def nice_set(self, value): @wrap_exceptions def ionice_get(self): ret = cext.proc_io_priority_get(self.pid) - if enum is not None: - ret = IOPriority(ret) + ret = IOPriority(ret) return ret @wrap_exceptions @@ -1085,13 +1015,14 @@ def ionice_set(self, ioclass, value): if value: msg = "value argument not accepted on Windows" raise TypeError(msg) - if ioclass not in ( - IOPRIO_VERYLOW, - IOPRIO_LOW, - IOPRIO_NORMAL, - IOPRIO_HIGH, - ): - raise ValueError("%s is not a valid priority" % ioclass) + if ioclass not in { + IOPriority.IOPRIO_VERYLOW, + IOPriority.IOPRIO_LOW, + IOPriority.IOPRIO_NORMAL, + IOPriority.IOPRIO_HIGH, + }: + msg = f"{ioclass} is not a valid priority" + raise ValueError(msg) cext.proc_io_priority_set(self.pid, ioclass) @wrap_exceptions @@ -1133,7 +1064,8 @@ def from_bitmask(x): def cpu_affinity_set(self, value): def to_bitmask(ls): if not ls: - raise ValueError("invalid argument %r" % ls) + msg = f"invalid argument {ls!r}" + raise ValueError(msg) out = 0 for b in ls: out |= 2**b @@ -1145,12 +1077,11 @@ def to_bitmask(ls): allcpus = list(range(len(per_cpu_times()))) for cpu in value: if cpu not in allcpus: - if not isinstance(cpu, (int, long)): - raise TypeError( - "invalid CPU %r; an integer is required" % cpu - ) - else: - raise ValueError("invalid CPU %r" % cpu) + if not isinstance(cpu, int): + msg = f"invalid CPU {cpu!r}; an integer is required" + raise TypeError(msg) + msg = f"invalid CPU {cpu!r}" + raise ValueError(msg) bitmask = to_bitmask(value) cext.proc_cpu_affinity_set(self.pid, bitmask) diff --git a/psutil/arch/bsd/proc.c b/psutil/arch/bsd/proc.c index e64cf80dc..959b28ee0 100644 --- a/psutil/arch/bsd/proc.c +++ b/psutil/arch/bsd/proc.c @@ -380,7 +380,7 @@ psutil_proc_environ(PyObject *self, PyObject *args) { #endif default: sprintf(errbuf, "kvm_getenvv(pid=%ld)", pid); - PyErr_SetFromOSErrnoWithSyscall(errbuf); + psutil_PyErr_SetFromOSErrnoWithSyscall(errbuf); break; } goto error; @@ -442,8 +442,17 @@ psutil_proc_open_files(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { -#if !defined(PSUTIL_OPENBSD) +#if defined(PSUTIL_OPENBSD) + if ((pid == 0) && (errno == ESRCH)) { + psutil_debug( + "open_files() returned ESRCH for PID 0; forcing `return []`" + ); + PyErr_Clear(); + return py_retlist; + } +#else psutil_raise_for_pid(pid, "kinfo_getfile()"); #endif goto error; diff --git a/psutil/arch/freebsd/cpu.c b/psutil/arch/freebsd/cpu.c index a15d96efc..29a5ec575 100644 --- a/psutil/arch/freebsd/cpu.c +++ b/psutil/arch/freebsd/cpu.c @@ -26,7 +26,7 @@ For reference, here's the git history with original(ish) implementations: PyObject * psutil_per_cpu_times(PyObject *self, PyObject *args) { - static int maxcpus; + int maxcpus; int mib[2]; int ncpu; size_t len; @@ -42,7 +42,7 @@ psutil_per_cpu_times(PyObject *self, PyObject *args) { size = sizeof(maxcpus); if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { Py_DECREF(py_retlist); - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('kern.smp.maxcpus')"); } long cpu_time[maxcpus][CPUSTATES]; @@ -52,14 +52,16 @@ psutil_per_cpu_times(PyObject *self, PyObject *args) { mib[1] = HW_NCPU; len = sizeof(ncpu); if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(HW_NCPU)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(HW_NCPU)"); goto error; } // per-cpu info size = sizeof(cpu_time); if (sysctlbyname("kern.cp_times", &cpu_time, &size, NULL, 0) == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctlbyname('kern.smp.maxcpus')"); + psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('kern.smp.maxcpus')" + ); goto error; } @@ -126,23 +128,23 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { size_t size = sizeof(v_soft); if (sysctlbyname("vm.stats.sys.v_soft", &v_soft, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('vm.stats.sys.v_soft')"); } if (sysctlbyname("vm.stats.sys.v_intr", &v_intr, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('vm.stats.sys.v_intr')"); } if (sysctlbyname("vm.stats.sys.v_syscall", &v_syscall, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('vm.stats.sys.v_syscall')"); } if (sysctlbyname("vm.stats.sys.v_trap", &v_trap, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('vm.stats.sys.v_trap')"); } if (sysctlbyname("vm.stats.sys.v_swtch", &v_swtch, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('vm.stats.sys.v_swtch')"); } diff --git a/psutil/arch/freebsd/mem.c b/psutil/arch/freebsd/mem.c index 3326f63a1..0482ef72d 100644 --- a/psutil/arch/freebsd/mem.c +++ b/psutil/arch/freebsd/mem.c @@ -39,36 +39,44 @@ psutil_virtual_mem(PyObject *self, PyObject *args) { size_t buffers_size = sizeof(buffers); if (sysctlbyname("hw.physmem", &total, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall("sysctlbyname('hw.physmem')"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('hw.physmem')" + ); } if (sysctlbyname("vm.stats.vm.v_active_count", &active, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('vm.stats.vm.v_active_count')"); } if (sysctlbyname("vm.stats.vm.v_inactive_count", &inactive, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( + return psutil_PyErr_SetFromOSErrnoWithSyscall( "sysctlbyname('vm.stats.vm.v_inactive_count')"); } if (sysctlbyname("vm.stats.vm.v_wire_count", &wired, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('vm.stats.vm.v_wire_count')"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_wire_count')" + ); } // https://github.com/giampaolo/psutil/issues/997 if (sysctlbyname("vm.stats.vm.v_cache_count", &cached, &size, NULL, 0)) { cached = 0; } if (sysctlbyname("vm.stats.vm.v_free_count", &free, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('vm.stats.vm.v_free_count')"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_free_count')" + ); } if (sysctlbyname("vfs.bufspace", &buffers, &buffers_size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall("sysctlbyname('vfs.bufspace')"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vfs.bufspace')" + ); } size = sizeof(vm); if (sysctl(mib, 2, &vm, &size, NULL, 0) != 0) { - return PyErr_SetFromOSErrnoWithSyscall("sysctl(CTL_VM | VM_METER)"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctl(CTL_VM | VM_METER)" + ); } return Py_BuildValue("KKKKKKKK", @@ -109,20 +117,24 @@ psutil_swap_mem(PyObject *self, PyObject *args) { kvm_close(kd); if (sysctlbyname("vm.stats.vm.v_swapin", &swapin, &size, NULL, 0) == -1) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('vm.stats.vm.v_swapin)'"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_swapin)'" + ); } if (sysctlbyname("vm.stats.vm.v_swapout", &swapout, &size, NULL, 0) == -1){ - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('vm.stats.vm.v_swapout)'"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_swapout)'" + ); } if (sysctlbyname("vm.stats.vm.v_vnodein", &nodein, &size, NULL, 0) == -1) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('vm.stats.vm.v_vnodein)'"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_vnodein)'" + ); } if (sysctlbyname("vm.stats.vm.v_vnodeout", &nodeout, &size, NULL, 0) == -1) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('vm.stats.vm.v_vnodeout)'"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_vnodeout)'" + ); } return Py_BuildValue( @@ -135,4 +147,3 @@ psutil_swap_mem(PyObject *self, PyObject *args) { nodein + nodeout // swap out ); } - diff --git a/psutil/arch/freebsd/proc.c b/psutil/arch/freebsd/proc.c index 6528ece45..5c6fab971 100644 --- a/psutil/arch/freebsd/proc.c +++ b/psutil/arch/freebsd/proc.c @@ -45,7 +45,7 @@ psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc) { size = sizeof(struct kinfo_proc); if (sysctl((int *)mib, 4, proc, &size, NULL, 0) == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_PID)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_PID)"); return -1; } @@ -92,7 +92,7 @@ psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount) { // Call sysctl with a NULL buffer in order to get buffer length. err = sysctl(name, 3, NULL, &length, NULL, 0); if (err == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl (null buffer)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl (null buffer)"); return 1; } @@ -120,7 +120,7 @@ psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount) { } } - PyErr_SetFromOSErrnoWithSyscall("sysctl()"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl()"); return 1; } else { @@ -177,7 +177,7 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { size = argmax; if (sysctl(mib, 4, procargs, &size, NULL, 0) == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ARGS)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ARGS)"); goto error; } @@ -238,8 +238,9 @@ psutil_proc_exe(PyObject *self, PyObject *args) { return PyUnicode_DecodeFSDefault(""); } else { - return \ - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_PATHNAME)"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctl(KERN_PROC_PATHNAME)" + ); } } if (size == 0 || strlen(pathname) == 0) { @@ -300,7 +301,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { size = 0; error = sysctl(mib, 4, NULL, &size, NULL, 0); if (error == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_INC_THREAD)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_INC_THREAD)"); goto error; } if (size == 0) { @@ -316,7 +317,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { error = sysctl(mib, 4, kip, &size, NULL, 0); if (error == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_INC_THREAD)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_INC_THREAD)"); goto error; } if (size == 0) { @@ -610,11 +611,7 @@ psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { CPU_ZERO(&cpu_set); for (i = 0; i < seq_len; i++) { PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i); -#if PY_MAJOR_VERSION >= 3 long value = PyLong_AsLong(item); -#else - long value = PyInt_AsLong(item); -#endif if (value == -1 || PyErr_Occurred()) goto error; CPU_SET(value, &cpu_set); diff --git a/psutil/arch/linux/net.c b/psutil/arch/linux/net.c index d193e9408..6d2785ee6 100644 --- a/psutil/arch/linux/net.c +++ b/psutil/arch/linux/net.c @@ -71,7 +71,7 @@ psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) - return PyErr_SetFromOSErrnoWithSyscall("socket()"); + return psutil_PyErr_SetFromOSErrnoWithSyscall("socket()"); PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); // duplex and speed @@ -102,7 +102,7 @@ psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { speed = 0; } else { - PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)"); goto error; } } diff --git a/psutil/arch/linux/proc.c b/psutil/arch/linux/proc.c index b58a3ce2a..f8230ee75 100644 --- a/psutil/arch/linux/proc.c +++ b/psutil/arch/linux/proc.c @@ -118,11 +118,7 @@ psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { cpucount_s = CPU_COUNT_S(setsize, mask); for (cpu = 0, count = cpucount_s; count; cpu++) { if (CPU_ISSET_S(cpu, setsize, mask)) { -#if PY_MAJOR_VERSION >= 3 PyObject *cpu_num = PyLong_FromLong(cpu); -#else - PyObject *cpu_num = PyInt_FromLong(cpu); -#endif if (cpu_num == NULL) goto error; if (PyList_Append(py_list, cpu_num)) { @@ -159,11 +155,7 @@ psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { if (!PySequence_Check(py_cpu_set)) { return PyErr_Format( PyExc_TypeError, -#if PY_MAJOR_VERSION >= 3 "sequence argument expected, got %R", Py_TYPE(py_cpu_set) -#else - "sequence argument expected, got %s", Py_TYPE(py_cpu_set)->tp_name -#endif ); } @@ -177,11 +169,7 @@ psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { if (!item) { return NULL; } -#if PY_MAJOR_VERSION >= 3 long value = PyLong_AsLong(item); -#else - long value = PyInt_AsLong(item); -#endif Py_XDECREF(item); if ((value == -1) || PyErr_Occurred()) { if (!PyErr_Occurred()) diff --git a/psutil/arch/linux/users.c b/psutil/arch/linux/users.c index 546e29ee9..663d0f2a4 100644 --- a/psutil/arch/linux/users.c +++ b/psutil/arch/linux/users.c @@ -33,7 +33,7 @@ psutil_users(PyObject *self, PyObject *args) { py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); if (! py_tty) goto error; - if (strcmp(ut->ut_host, ":0") || strcmp(ut->ut_host, ":0.0")) + if (strcmp(ut->ut_host, ":0") == 0 || strcmp(ut->ut_host, ":0.0") == 0) py_hostname = PyUnicode_DecodeFSDefault("localhost"); else py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); diff --git a/psutil/arch/netbsd/proc.c b/psutil/arch/netbsd/proc.c index c645f301e..7613b5459 100644 --- a/psutil/arch/netbsd/proc.c +++ b/psutil/arch/netbsd/proc.c @@ -332,6 +332,8 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { pid_t pid; int mib[4]; int st; + int attempt; + int max_attempts = 50; size_t len = 0; size_t pos = 0; char *procargs = NULL; @@ -350,7 +352,9 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { st = sysctl(mib, __arraycount(mib), NULL, &len, NULL, 0); if (st == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ARGV) get size"); + psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctl(KERN_PROC_ARGV) get size" + ); goto error; } @@ -359,10 +363,32 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { PyErr_NoMemory(); goto error; } - st = sysctl(mib, __arraycount(mib), procargs, &len, NULL, 0); - if (st == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ARGV)"); - goto error; + + while (1) { + st = sysctl(mib, __arraycount(mib), procargs, &len, NULL, 0); + if (st == -1) { + if (errno == EBUSY) { + // Usually happens with TestProcess.test_long_cmdline. See: + // https://github.com/giampaolo/psutil/issues/2250 + attempt += 1; + if (attempt < max_attempts) { + psutil_debug("proc %zu cmdline(): retry on EBUSY", pid); + continue; + } + else { + psutil_debug( + "proc %zu cmdline(): return [] due to EBUSY", pid + ); + free(procargs); + return py_retlist; + } + } + else { + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ARGV)"); + goto error; + } + } + break; } if (len > 0) { diff --git a/psutil/arch/openbsd/proc.c b/psutil/arch/openbsd/proc.c index 96b85bc50..bf4015be4 100644 --- a/psutil/arch/openbsd/proc.c +++ b/psutil/arch/openbsd/proc.c @@ -41,7 +41,7 @@ psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc) { ret = sysctl((int*)mib, 6, proc, &size, NULL, 0); if (ret == -1) { - PyErr_SetFromErrno(PyExc_OSError); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(kinfo_proc)"); return -1; } // sysctl stores 0 in the size if we can't find the process information. @@ -69,7 +69,7 @@ kinfo_getfile(pid_t pid, int* cnt) { /* get the size of what would be returned */ if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { - PyErr_SetFromErrno(PyExc_OSError); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(kinfo_file) (1/2)"); return NULL; } if ((kf = malloc(len)) == NULL) { @@ -79,7 +79,7 @@ kinfo_getfile(pid_t pid, int* cnt) { mib[5] = (int)(len / sizeof(struct kinfo_file)); if (sysctl(mib, 6, kf, &len, NULL, 0) < 0) { free(kf); - PyErr_SetFromErrno(PyExc_OSError); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(kinfo_file) (2/2)"); return NULL; } @@ -147,7 +147,7 @@ PyObject * psutil_proc_cmdline(PyObject *self, PyObject *args) { pid_t pid; int mib[4]; - static char **argv; + char **argv = NULL; char **p; size_t argv_size = 128; PyObject *py_retlist = PyList_New(0); @@ -189,9 +189,12 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { Py_DECREF(py_arg); } + free(argv); return py_retlist; error: + if (argv != NULL) + free(argv); Py_XDECREF(py_arg); Py_DECREF(py_retlist); return NULL; @@ -285,8 +288,19 @@ psutil_proc_num_fds(PyObject *self, PyObject *args) { return NULL; freep = kinfo_getfile(pid, &cnt); - if (freep == NULL) + + if (freep == NULL) { +#if defined(PSUTIL_OPENBSD) + if ((pid == 0) && (errno == ESRCH)) { + psutil_debug( + "num_fds() returned ESRCH for PID 0; forcing `return 0`" + ); + PyErr_Clear(); + return Py_BuildValue("i", 0); + } +#endif return NULL; + } free(freep); return Py_BuildValue("i", cnt); diff --git a/psutil/arch/openbsd/socks.c b/psutil/arch/openbsd/socks.c index 69daa447b..05849aa1d 100644 --- a/psutil/arch/openbsd/socks.c +++ b/psutil/arch/openbsd/socks.c @@ -67,7 +67,7 @@ psutil_net_connections(PyObject *self, PyObject *args) { ikf = kvm_getfiles(kd, KERN_FILE_BYPID, -1, sizeof(*ikf), &cnt); if (! ikf) { - PyErr_SetFromOSErrnoWithSyscall("kvm_getfiles"); + psutil_PyErr_SetFromOSErrnoWithSyscall("kvm_getfiles"); goto error; } diff --git a/psutil/arch/osx/cpu.c b/psutil/arch/osx/cpu.c index 4196083ec..d2f8b1855 100644 --- a/psutil/arch/osx/cpu.c +++ b/psutil/arch/osx/cpu.c @@ -243,7 +243,7 @@ psutil_cpu_freq(PyObject *self, PyObject *args) { mib[1] = HW_CPU_FREQ; if (sysctl(mib, 2, &curr, &len, NULL, 0) < 0) - return PyErr_SetFromOSErrnoWithSyscall("sysctl(HW_CPU_FREQ)"); + return psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(HW_CPU_FREQ)"); if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0)) psutil_debug("sysctl('hw.cpufrequency_min') failed (set to 0)"); diff --git a/psutil/arch/osx/disk.c b/psutil/arch/osx/disk.c index 961fc42a4..e1a8f5a49 100644 --- a/psutil/arch/osx/disk.c +++ b/psutil/arch/osx/disk.c @@ -86,8 +86,6 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strlcat(opts, ",async", sizeof(opts)); if (flags & MNT_EXPORTED) strlcat(opts, ",exported", sizeof(opts)); - if (flags & MNT_QUARANTINE) - strlcat(opts, ",quarantine", sizeof(opts)); if (flags & MNT_LOCAL) strlcat(opts, ",local", sizeof(opts)); if (flags & MNT_QUOTA) @@ -108,10 +106,6 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strlcat(opts, ",nouserxattr", sizeof(opts)); if (flags & MNT_DEFWRITE) strlcat(opts, ",defwrite", sizeof(opts)); - if (flags & MNT_MULTILABEL) - strlcat(opts, ",multilabel", sizeof(opts)); - if (flags & MNT_NOATIME) - strlcat(opts, ",noatime", sizeof(opts)); if (flags & MNT_UPDATE) strlcat(opts, ",update", sizeof(opts)); if (flags & MNT_RELOAD) @@ -120,7 +114,19 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strlcat(opts, ",force", sizeof(opts)); if (flags & MNT_CMDFLAGS) strlcat(opts, ",cmdflags", sizeof(opts)); - + // requires macOS >= 10.5 +#ifdef MNT_QUARANTINE + if (flags & MNT_QUARANTINE) + strlcat(opts, ",quarantine", sizeof(opts)); +#endif +#ifdef MNT_MULTILABEL + if (flags & MNT_MULTILABEL) + strlcat(opts, ",multilabel", sizeof(opts)); +#endif +#ifdef MNT_NOATIME + if (flags & MNT_NOATIME) + strlcat(opts, ",noatime", sizeof(opts)); +#endif py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname); if (! py_dev) goto error; @@ -162,7 +168,6 @@ psutil_disk_usage_used(PyObject *self, PyObject *args) { PyObject *py_mount_point_bytes = NULL; char* mount_point; -#if PY_MAJOR_VERSION >= 3 if (!PyArg_ParseTuple(args, "O&O", PyUnicode_FSConverter, &py_mount_point_bytes, &py_default_value)) { return NULL; } @@ -171,11 +176,6 @@ psutil_disk_usage_used(PyObject *self, PyObject *args) { Py_XDECREF(py_mount_point_bytes); return NULL; } -#else - if (!PyArg_ParseTuple(args, "sO", &mount_point, &py_default_value)) { - return NULL; - } -#endif #ifdef ATTR_VOL_SPACEUSED /* Call getattrlist(ATTR_VOL_SPACEUSED) to get used space info. */ diff --git a/psutil/arch/osx/proc.c b/psutil/arch/osx/proc.c index 2cdb9911c..136311ece 100644 --- a/psutil/arch/osx/proc.c +++ b/psutil/arch/osx/proc.c @@ -77,7 +77,7 @@ psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { while (lim-- > 0) { size = 0; if (sysctl((int *)mib, 3, NULL, &size, NULL, 0) == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); return 1; } size2 = size + (size >> 3); // add some @@ -100,7 +100,7 @@ psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { err = errno; free(ptr); if (err != ENOMEM) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); return 1; } } @@ -132,7 +132,7 @@ psutil_sysctl_argmax() { if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0) return argmax; - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_ARGMAX)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_ARGMAX)"); return 0; } @@ -164,7 +164,7 @@ psutil_sysctl_procargs(pid_t pid, char *procargs, size_t *argmax) { AccessDenied("sysctl(KERN_PROCARGS2) -> EIO"); return 1; } - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROCARGS2)"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROCARGS2)"); return 1; } return 0; @@ -186,7 +186,7 @@ psutil_get_kinfo_proc(pid_t pid, struct kinfo_proc *kp) { // now read the data from sysctl if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) { // raise an exception and throw errno as the error - PyErr_SetFromOSErrnoWithSyscall("sysctl"); + psutil_PyErr_SetFromOSErrnoWithSyscall("sysctl"); return -1; } @@ -605,8 +605,9 @@ psutil_proc_memory_uss(PyObject *self, PyObject *args) { len = sizeof(cpu_type); if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('sysctl.proc_cputype')"); + return psutil_PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('sysctl.proc_cputype')" + ); } // Roughly based on libtop_update_vm_regions in @@ -977,7 +978,7 @@ psutil_proc_net_connections(PyObject *self, PyObject *args) { // check for inet_ntop failures if (errno != 0) { - PyErr_SetFromOSErrnoWithSyscall("inet_ntop()"); + psutil_PyErr_SetFromOSErrnoWithSyscall("inet_ntop()"); goto error; } diff --git a/psutil/arch/windows/disk.c b/psutil/arch/windows/disk.c index 004174561..552929f38 100644 --- a/psutil/arch/windows/disk.c +++ b/psutil/arch/windows/disk.c @@ -44,33 +44,6 @@ PyObject * psutil_disk_usage(PyObject *self, PyObject *args) { BOOL retval; ULARGE_INTEGER _, total, free; - -#if PY_MAJOR_VERSION <= 2 - char *path; - - if (PyArg_ParseTuple(args, "u", &path)) { - Py_BEGIN_ALLOW_THREADS - retval = GetDiskFreeSpaceExW((LPCWSTR)path, &_, &total, &free); - Py_END_ALLOW_THREADS - goto return_; - } - - // on Python 2 we also want to accept plain strings other - // than Unicode - PyErr_Clear(); // drop the argument parsing error - if (PyArg_ParseTuple(args, "s", &path)) { - Py_BEGIN_ALLOW_THREADS - retval = GetDiskFreeSpaceEx(path, &_, &total, &free); - Py_END_ALLOW_THREADS - goto return_; - } - - return NULL; - -return_: - if (retval == 0) - return PyErr_SetFromWindowsErrWithFilename(0, path); -#else PyObject *py_path; wchar_t *path; @@ -91,7 +64,7 @@ psutil_disk_usage(PyObject *self, PyObject *args) { if (retval == 0) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, py_path); -#endif + return Py_BuildValue("(LL)", total.QuadPart, free.QuadPart); } diff --git a/psutil/arch/windows/net.c b/psutil/arch/windows/net.c index 8d8f7d1c0..9a5634b06 100644 --- a/psutil/arch/windows/net.c +++ b/psutil/arch/windows/net.c @@ -255,21 +255,14 @@ psutil_net_if_addrs(PyObject *self, PyObject *args) { continue; } -#if PY_MAJOR_VERSION >= 3 py_address = PyUnicode_FromString(buff_addr); -#else - py_address = PyString_FromString(buff_addr); -#endif if (py_address == NULL) goto error; if (netmaskIntRet != NULL) { -#if PY_MAJOR_VERSION >= 3 py_netmask = PyUnicode_FromString(buff_netmask); -#else - py_netmask = PyString_FromString(buff_netmask); -#endif - } else { + } + else { Py_INCREF(Py_None); py_netmask = Py_None; } diff --git a/psutil/arch/windows/proc.c b/psutil/arch/windows/proc.c index af3df267a..41fa9dda6 100644 --- a/psutil/arch/windows/proc.c +++ b/psutil/arch/windows/proc.c @@ -116,7 +116,7 @@ psutil_proc_kill(PyObject *self, PyObject *args) { // https://github.com/giampaolo/psutil/issues/1099 // http://bugs.python.org/issue14252 if (GetLastError() != ERROR_ACCESS_DENIED) { - PyErr_SetFromOSErrnoWithSyscall("TerminateProcess"); + psutil_PyErr_SetFromOSErrnoWithSyscall("TerminateProcess"); return NULL; } } @@ -151,7 +151,7 @@ psutil_proc_wait(PyObject *self, PyObject *args) { Py_RETURN_NONE; } else { - PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); return NULL; } } @@ -163,7 +163,7 @@ psutil_proc_wait(PyObject *self, PyObject *args) { // handle return code if (retVal == WAIT_FAILED) { - PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); + psutil_PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); CloseHandle(hProcess); return NULL; } @@ -185,18 +185,14 @@ psutil_proc_wait(PyObject *self, PyObject *args) { // process is gone so we can get its process exit code. The PID // may still stick around though but we'll handle that from Python. if (GetExitCodeProcess(hProcess, &ExitCode) == 0) { - PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); + psutil_PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); CloseHandle(hProcess); return NULL; } CloseHandle(hProcess); -#if PY_MAJOR_VERSION >= 3 return PyLong_FromLong((long) ExitCode); -#else - return PyInt_FromLong((long) ExitCode); -#endif } @@ -598,7 +594,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadSnap == INVALID_HANDLE_VALUE) { - PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot"); + psutil_PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot"); goto error; } @@ -606,7 +602,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { te32.dwSize = sizeof(THREADENTRY32); if (! Thread32First(hThreadSnap, &te32)) { - PyErr_SetFromOSErrnoWithSyscall("Thread32First"); + psutil_PyErr_SetFromOSErrnoWithSyscall("Thread32First"); goto error; } @@ -626,7 +622,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { rc = GetThreadTimes(hThread, &ftDummy, &ftDummy, &ftKernel, &ftUser); if (rc == 0) { - PyErr_SetFromOSErrnoWithSyscall("GetThreadTimes"); + psutil_PyErr_SetFromOSErrnoWithSyscall("GetThreadTimes"); goto error; } @@ -702,7 +698,7 @@ _psutil_user_token_from_pid(DWORD pid) { return NULL; if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { - PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); goto error; } @@ -721,7 +717,7 @@ _psutil_user_token_from_pid(DWORD pid) { continue; } else { - PyErr_SetFromOSErrnoWithSyscall("GetTokenInformation"); + psutil_PyErr_SetFromOSErrnoWithSyscall("GetTokenInformation"); goto error; } } @@ -797,7 +793,7 @@ psutil_proc_username(PyObject *self, PyObject *args) { goto error; } else { - PyErr_SetFromOSErrnoWithSyscall("LookupAccountSidW"); + psutil_PyErr_SetFromOSErrnoWithSyscall("LookupAccountSidW"); goto error; } } diff --git a/psutil/arch/windows/proc_handles.c b/psutil/arch/windows/proc_handles.c index 30e7cd2d8..01ef6a425 100644 --- a/psutil/arch/windows/proc_handles.c +++ b/psutil/arch/windows/proc_handles.c @@ -156,7 +156,7 @@ psutil_threaded_get_filename(HANDLE hFile) { hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)psutil_get_filename, &hFile, 0, NULL); if (hThread == NULL) { - PyErr_SetFromOSErrnoWithSyscall("CreateThread"); + psutil_PyErr_SetFromOSErrnoWithSyscall("CreateThread"); return 1; } @@ -168,7 +168,7 @@ psutil_threaded_get_filename(HANDLE hFile) { psutil_debug( "get handle name thread timed out after %i ms", THREAD_TIMEOUT); if (TerminateThread(hThread, 0) == 0) { - PyErr_SetFromOSErrnoWithSyscall("TerminateThread"); + psutil_PyErr_SetFromOSErrnoWithSyscall("TerminateThread"); CloseHandle(hThread); return 1; } @@ -179,26 +179,28 @@ psutil_threaded_get_filename(HANDLE hFile) { if (dwWait == WAIT_FAILED) { psutil_debug("WaitForSingleObject -> WAIT_FAILED"); if (TerminateThread(hThread, 0) == 0) { - PyErr_SetFromOSErrnoWithSyscall( - "WaitForSingleObject -> WAIT_FAILED -> TerminateThread"); + psutil_PyErr_SetFromOSErrnoWithSyscall( + "WaitForSingleObject -> WAIT_FAILED -> TerminateThread" + ); CloseHandle(hThread); return 1; } - PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); + psutil_PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); CloseHandle(hThread); return 1; } if (GetExitCodeThread(hThread, &threadRetValue) == 0) { if (TerminateThread(hThread, 0) == 0) { - PyErr_SetFromOSErrnoWithSyscall( - "GetExitCodeThread (failed) -> TerminateThread"); + psutil_PyErr_SetFromOSErrnoWithSyscall( + "GetExitCodeThread (failed) -> TerminateThread" + ); CloseHandle(hThread); return 1; } CloseHandle(hThread); - PyErr_SetFromOSErrnoWithSyscall("GetExitCodeThread"); + psutil_PyErr_SetFromOSErrnoWithSyscall("GetExitCodeThread"); return 1; } CloseHandle(hThread); diff --git a/psutil/arch/windows/proc_info.c b/psutil/arch/windows/proc_info.c index 5d16b8133..9e0caf344 100644 --- a/psutil/arch/windows/proc_info.c +++ b/psutil/arch/windows/proc_info.c @@ -41,7 +41,7 @@ psutil_get_process_region_size(HANDLE hProcess, LPCVOID src, SIZE_T *psize) { MEMORY_BASIC_INFORMATION info; if (!VirtualQueryEx(hProcess, src, &info, sizeof(info))) { - PyErr_SetFromOSErrnoWithSyscall("VirtualQueryEx"); + psutil_PyErr_SetFromOSErrnoWithSyscall("VirtualQueryEx"); return -1; } @@ -67,7 +67,7 @@ psutil_convert_winerr(ULONG err, char* syscall) { AccessDenied(fullmsg); } else { - PyErr_SetFromOSErrnoWithSyscall(syscall); + psutil_PyErr_SetFromOSErrnoWithSyscall(syscall); } } @@ -226,7 +226,7 @@ psutil_get_process_data(DWORD pid, // 32 bit case. Check if the target is also 32 bit. if (!IsWow64Process(GetCurrentProcess(), &weAreWow64) || !IsWow64Process(hProcess, &theyAreWow64)) { - PyErr_SetFromOSErrnoWithSyscall("IsWow64Process"); + psutil_PyErr_SetFromOSErrnoWithSyscall("IsWow64Process"); goto error; } @@ -594,7 +594,7 @@ psutil_proc_cmdline(PyObject *self, PyObject *args, PyObject *kwdict) { // attempt to parse the command line using Win32 API szArglist = CommandLineToArgvW(data, &nArgs); if (szArglist == NULL) { - PyErr_SetFromOSErrnoWithSyscall("CommandLineToArgvW"); + psutil_PyErr_SetFromOSErrnoWithSyscall("CommandLineToArgvW"); goto error; } diff --git a/psutil/arch/windows/proc_utils.c b/psutil/arch/windows/proc_utils.c index 77b6dbf1e..1ebb76c44 100644 --- a/psutil/arch/windows/proc_utils.c +++ b/psutil/arch/windows/proc_utils.c @@ -103,7 +103,7 @@ psutil_check_phandle(HANDLE hProcess, DWORD pid, int check_exit_code) { } return NULL; } - PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); return NULL; } @@ -129,7 +129,7 @@ psutil_check_phandle(HANDLE hProcess, DWORD pid, int check_exit_code) { SetLastError(0); return hProcess; } - PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); + psutil_PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); CloseHandle(hProcess); return NULL; } @@ -151,7 +151,7 @@ psutil_handle_from_pid(DWORD pid, DWORD access) { hProcess = OpenProcess(access, FALSE, pid); if ((hProcess == NULL) && (GetLastError() == ERROR_ACCESS_DENIED)) { - PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); return NULL; } diff --git a/psutil/arch/windows/security.c b/psutil/arch/windows/security.c index 7e400a254..07d239984 100644 --- a/psutil/arch/windows/security.c +++ b/psutil/arch/windows/security.c @@ -21,7 +21,7 @@ psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) { DWORD cbPrevious = sizeof(TOKEN_PRIVILEGES); if (! LookupPrivilegeValue(NULL, Privilege, &luid)) { - PyErr_SetFromOSErrnoWithSyscall("LookupPrivilegeValue"); + psutil_PyErr_SetFromOSErrnoWithSyscall("LookupPrivilegeValue"); return 1; } @@ -38,7 +38,7 @@ psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) { &tpPrevious, &cbPrevious)) { - PyErr_SetFromOSErrnoWithSyscall("AdjustTokenPrivileges"); + psutil_PyErr_SetFromOSErrnoWithSyscall("AdjustTokenPrivileges"); return 1; } @@ -60,7 +60,7 @@ psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) { NULL, NULL)) { - PyErr_SetFromOSErrnoWithSyscall("AdjustTokenPrivileges"); + psutil_PyErr_SetFromOSErrnoWithSyscall("AdjustTokenPrivileges"); return 1; } @@ -79,18 +79,18 @@ psutil_get_thisproc_token() { if (GetLastError() == ERROR_NO_TOKEN) { if (! ImpersonateSelf(SecurityImpersonation)) { - PyErr_SetFromOSErrnoWithSyscall("ImpersonateSelf"); + psutil_PyErr_SetFromOSErrnoWithSyscall("ImpersonateSelf"); return NULL; } if (! OpenProcessToken( me, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { - PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); return NULL; } } else { - PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); return NULL; } } diff --git a/psutil/arch/windows/services.c b/psutil/arch/windows/services.c index fa3e646e5..4931e9d66 100644 --- a/psutil/arch/windows/services.c +++ b/psutil/arch/windows/services.c @@ -24,12 +24,12 @@ psutil_get_service_handler(char *service_name, DWORD scm_access, DWORD access) sc = OpenSCManager(NULL, NULL, scm_access); if (sc == NULL) { - PyErr_SetFromOSErrnoWithSyscall("OpenSCManager"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenSCManager"); return NULL; } hService = OpenService(sc, service_name, access); if (hService == NULL) { - PyErr_SetFromOSErrnoWithSyscall("OpenService"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenService"); CloseServiceHandle(sc); return NULL; } @@ -113,7 +113,7 @@ psutil_winservice_enumerate(PyObject *self, PyObject *args) { sc = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE); if (sc == NULL) { - PyErr_SetFromOSErrnoWithSyscall("OpenSCManager"); + psutil_PyErr_SetFromOSErrnoWithSyscall("OpenSCManager"); return NULL; } @@ -211,13 +211,13 @@ psutil_winservice_query_config(PyObject *self, PyObject *args) { bytesNeeded = 0; QueryServiceConfigW(hService, NULL, 0, &bytesNeeded); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfigW"); + psutil_PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfigW"); goto error; } qsc = (QUERY_SERVICE_CONFIGW *)malloc(bytesNeeded); ok = QueryServiceConfigW(hService, qsc, bytesNeeded, &bytesNeeded); if (ok == 0) { - PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfigW"); + psutil_PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfigW"); goto error; } @@ -303,7 +303,7 @@ psutil_winservice_query_status(PyObject *self, PyObject *args) { return Py_BuildValue("s", ""); } if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - PyErr_SetFromOSErrnoWithSyscall("QueryServiceStatusEx"); + psutil_PyErr_SetFromOSErrnoWithSyscall("QueryServiceStatusEx"); goto error; } ssp = (SERVICE_STATUS_PROCESS *)HeapAlloc( @@ -317,7 +317,7 @@ psutil_winservice_query_status(PyObject *self, PyObject *args) { ok = QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)ssp, bytesNeeded, &bytesNeeded); if (ok == 0) { - PyErr_SetFromOSErrnoWithSyscall("QueryServiceStatusEx"); + psutil_PyErr_SetFromOSErrnoWithSyscall("QueryServiceStatusEx"); goto error; } @@ -375,7 +375,7 @@ psutil_winservice_query_descr(PyObject *self, PyObject *args) { return Py_BuildValue("s", ""); } if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfig2W"); + psutil_PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfig2W"); goto error; } @@ -383,7 +383,7 @@ psutil_winservice_query_descr(PyObject *self, PyObject *args) { ok = QueryServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)scd, bytesNeeded, &bytesNeeded); if (ok == 0) { - PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfig2W"); + psutil_PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfig2W"); goto error; } @@ -429,7 +429,7 @@ psutil_winservice_start(PyObject *self, PyObject *args) { } ok = StartService(hService, 0, NULL); if (ok == 0) { - PyErr_SetFromOSErrnoWithSyscall("StartService"); + psutil_PyErr_SetFromOSErrnoWithSyscall("StartService"); goto error; } @@ -466,7 +466,7 @@ psutil_winservice_stop(PyObject *self, PyObject *args) { ok = ControlService(hService, SERVICE_CONTROL_STOP, &ssp); Py_END_ALLOW_THREADS if (ok == 0) { - PyErr_SetFromOSErrnoWithSyscall("ControlService"); + psutil_PyErr_SetFromOSErrnoWithSyscall("ControlService"); goto error; } diff --git a/psutil/arch/windows/sys.c b/psutil/arch/windows/sys.c index 3e12e71b7..ada684f6f 100644 --- a/psutil/arch/windows/sys.c +++ b/psutil/arch/windows/sys.c @@ -70,7 +70,7 @@ psutil_users(PyObject *self, PyObject *args) { // On Windows Nano server, the Wtsapi32 API can be present, but return WinError 120. return py_retlist; } - PyErr_SetFromOSErrnoWithSyscall("WTSEnumerateSessionsW"); + psutil_PyErr_SetFromOSErrnoWithSyscall("WTSEnumerateSessionsW"); goto error; } @@ -93,7 +93,9 @@ psutil_users(PyObject *self, PyObject *args) { bytes = 0; if (WTSQuerySessionInformationW(hServer, sessionId, WTSUserName, &buffer_user, &bytes) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); + psutil_PyErr_SetFromOSErrnoWithSyscall( + "WTSQuerySessionInformationW" + ); goto error; } if (bytes <= 2) @@ -103,7 +105,9 @@ psutil_users(PyObject *self, PyObject *args) { bytes = 0; if (WTSQuerySessionInformationW(hServer, sessionId, WTSClientAddress, &buffer_addr, &bytes) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); + psutil_PyErr_SetFromOSErrnoWithSyscall( + "WTSQuerySessionInformationW" + ); goto error; } @@ -130,7 +134,9 @@ psutil_users(PyObject *self, PyObject *args) { bytes = 0; if (WTSQuerySessionInformationW(hServer, sessionId, WTSSessionInfo, &buffer_info, &bytes) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); + psutil_PyErr_SetFromOSErrnoWithSyscall( + "WTSQuerySessionInformationW" + ); goto error; } wts_info = (PWTSINFOW)buffer_info; diff --git a/psutil/arch/windows/wmi.c b/psutil/arch/windows/wmi.c index fc7a66529..2cf7e8a59 100644 --- a/psutil/arch/windows/wmi.c +++ b/psutil/arch/windows/wmi.c @@ -80,7 +80,7 @@ psutil_init_loadavg_counter(PyObject *self, PyObject *args) { event = CreateEventW(NULL, FALSE, FALSE, L"LoadUpdateEvent"); if (event == NULL) { - PyErr_SetFromOSErrnoWithSyscall("CreateEventW"); + psutil_PyErr_SetFromOSErrnoWithSyscall("CreateEventW"); return NULL; } @@ -100,7 +100,7 @@ psutil_init_loadavg_counter(PyObject *self, PyObject *args) { WT_EXECUTEDEFAULT); if (ret == 0) { - PyErr_SetFromOSErrnoWithSyscall("RegisterWaitForSingleObject"); + psutil_PyErr_SetFromOSErrnoWithSyscall("RegisterWaitForSingleObject"); return NULL; } diff --git a/psutil/tests/README.rst b/psutil/tests/README.rst index 9dca11867..8e5b6e7d8 100644 --- a/psutil/tests/README.rst +++ b/psutil/tests/README.rst @@ -1,14 +1,35 @@ Instructions for running tests ============================== -* There are two ways of running tests. As a "user", if psutil is already - installed and you just want to test it works:: +There are 2 ways of running tests. If you have the source code: + +.. code-block:: bash + + make install-pydeps-test # install pytest + make test + +If you don't have the source code, and just want to test psutil installation. +This will work also if ``pytest`` module is not installed (e.g. production +environments) by using unittest's test runner: + +.. code-block:: bash python -m psutil.tests - As a "developer", if you have a copy of the source code and you wish to hack - on psutil:: +To run tests in parallel (faster): + +.. code-block:: bash + + make test-parallel + +Run a specific test: + +.. code-block:: bash + + make test ARGS=psutil.tests.test_system.TestDiskAPIs + +Test C extension memory leaks: + +.. code-block:: bash - make setup-dev-env # install missing third-party deps - make test # serial run - make test-parallel # parallel run + make test-memleaks diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index b18b74239..e37b84297 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -1,20 +1,19 @@ -# -*- 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. """Test utilities.""" -from __future__ import print_function import atexit import contextlib import ctypes +import enum import errno import functools import gc -import inspect +import importlib +import ipaddress import os import platform import random @@ -37,6 +36,12 @@ from socket import AF_INET6 from socket import SOCK_STREAM + +try: + import pytest +except ImportError: + pytest = None + import psutil from psutil import AIX from psutil import LINUX @@ -51,26 +56,7 @@ from psutil._common import memoize from psutil._common import print_color from psutil._common import supports_ipv6 -from psutil._compat import PY3 -from psutil._compat import FileExistsError -from psutil._compat import FileNotFoundError -from psutil._compat import range -from psutil._compat import super -from psutil._compat import unicode -from psutil._compat import which - - -try: - from unittest import mock # py3 -except ImportError: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - import mock # NOQA - requires "pip install mock" -if PY3: - import enum -else: - enum = None if POSIX: from psutil._psposix import wait_pid @@ -79,7 +65,7 @@ # fmt: off __all__ = [ # constants - 'APPVEYOR', 'DEVNULL', 'GLOBAL_TIMEOUT', 'TOLERANCE_SYS_MEM', 'NO_RETRIES', + 'DEVNULL', 'GLOBAL_TIMEOUT', 'TOLERANCE_SYS_MEM', 'NO_RETRIES', 'PYPY', 'PYTHON_EXE', 'PYTHON_EXE_ENV', 'ROOT_DIR', 'SCRIPTS_DIR', 'TESTFN_PREFIX', 'UNICODE_SUFFIX', 'INVALID_UNICODE_SUFFIX', 'CI_TESTING', 'VALID_PROC_STATUSES', 'TOLERANCE_DISK_USAGE', 'IS_64BIT', @@ -87,7 +73,7 @@ "HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT", "HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS", "HAS_SENSORS_TEMPERATURES", "HAS_NET_CONNECTIONS_UNIX", "MACOS_11PLUS", - "MACOS_12PLUS", "COVERAGE", + "MACOS_12PLUS", "COVERAGE", 'AARCH64', "QEMU_USER", "PYTEST_PARALLEL", # subprocesses 'pyrun', 'terminate', 'reap_children', 'spawn_testproc', 'spawn_zombie', 'spawn_children_pair', @@ -97,7 +83,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', + 'is_win_secure_system_proc', 'fake_pytest', # fs utils 'chdir', 'safe_rmpath', 'create_py_exe', 'create_c_exe', 'get_testfn', # os @@ -124,12 +110,18 @@ PYPY = '__pypy__' in sys.builtin_module_names # whether we're running this test suite on a Continuous Integration service -APPVEYOR = 'APPVEYOR' in os.environ GITHUB_ACTIONS = 'GITHUB_ACTIONS' in os.environ or 'CIBUILDWHEEL' in os.environ -CI_TESTING = APPVEYOR or GITHUB_ACTIONS +CI_TESTING = GITHUB_ACTIONS COVERAGE = 'COVERAGE_RUN' in os.environ +PYTEST_PARALLEL = "PYTEST_XDIST_WORKER" in os.environ # `make test-parallel` +if LINUX and GITHUB_ACTIONS: + with open('/proc/1/cmdline') as f: + QEMU_USER = "/bin/qemu-" in f.read() +else: + QEMU_USER = False # are we a 64 bit process? IS_64BIT = sys.maxsize > 2**32 +AARCH64 = platform.machine() == "aarch64" @memoize @@ -182,16 +174,13 @@ def macos_version(): # Disambiguate TESTFN for parallel testing. if os.name == 'java': # Jython disallows @ in module names - TESTFN_PREFIX = '$psutil-%s-' % os.getpid() + TESTFN_PREFIX = f"$psutil-{os.getpid()}-" else: - TESTFN_PREFIX = '@psutil-%s-' % os.getpid() -UNICODE_SUFFIX = u"-ƒőő" + TESTFN_PREFIX = f"@psutil-{os.getpid()}-" +UNICODE_SUFFIX = "-ƒőő" # An invalid unicode string. -if PY3: - INVALID_UNICODE_SUFFIX = b"f\xc0\x80".decode('utf8', 'surrogateescape') -else: - INVALID_UNICODE_SUFFIX = "f\xc0\x80" -ASCII_FS = sys.getfilesystemencoding().lower() in ('ascii', 'us-ascii') +INVALID_UNICODE_SUFFIX = b"f\xc0\x80".decode('utf8', 'surrogateescape') +ASCII_FS = sys.getfilesystemencoding().lower() in {"ascii", "us-ascii"} # --- paths @@ -259,7 +248,9 @@ def attempt(exe): exe = ( attempt(sys.executable) or attempt(os.path.realpath(sys.executable)) - or attempt(which("python%s.%s" % sys.version_info[:2])) + or attempt( + shutil.which("python{}.{}".format(*sys.version_info[:2])) + ) or attempt(psutil.Process().exe()) ) if not exe: @@ -272,7 +263,7 @@ def attempt(exe): PYTHON_EXE, PYTHON_EXE_ENV = _get_py_exe() -DEVNULL = open(os.devnull, 'r+') +DEVNULL = open(os.devnull, 'r+') # noqa: SIM115 atexit.register(DEVNULL.close) VALID_PROC_STATUSES = [ @@ -300,7 +291,7 @@ def __init__(self): def __repr__(self): name = self.__class__.__name__ - return '<%s running=%s at %#x>' % (name, self._running, id(self)) + return f"<{name} running={self._running} at {id(self):#x}>" def __enter__(self): self.start() @@ -375,8 +366,8 @@ def spawn_testproc(cmd=None, **kwds): safe_rmpath(testfn) pyline = ( "import time;" - + "open(r'%s', 'w').close();" % testfn - + "[time.sleep(0.1) for x in range(100)];" # 10 secs + f"open(r'{testfn}', 'w').close();" + "[time.sleep(0.1) for x in range(100)];" # 10 secs ) cmd = [PYTHON_EXE, "-c", pyline] sproc = subprocess.Popen(cmd, **kwds) @@ -402,16 +393,16 @@ def spawn_children_pair(): tfile = None testfn = get_testfn(dir=os.getcwd()) try: - s = textwrap.dedent("""\ + s = textwrap.dedent(f"""\ import subprocess, os, sys, time s = "import os, time;" - s += "f = open('%s', 'w');" + s += "f = open('{os.path.basename(testfn)}', 'w');" s += "f.write(str(os.getpid()));" s += "f.close();" - s += "time.sleep(60);" - p = subprocess.Popen([r'%s', '-c', s]) + s += "[time.sleep(0.1) for x in range(100 * 6)];" + p = subprocess.Popen([r'{PYTHON_EXE}', '-c', s]) p.wait() - """ % (os.path.basename(testfn), PYTHON_EXE)) + """) # On Windows if we create a subprocess with CREATE_NO_WINDOW flag # set (which is the default) a "conhost.exe" extra process will be # spawned as a child. We don't want that. @@ -437,22 +428,18 @@ def spawn_zombie(): """ assert psutil.POSIX unix_file = get_testfn() - src = textwrap.dedent("""\ + src = textwrap.dedent(f"""\ import os, sys, time, socket, contextlib child_pid = os.fork() if child_pid > 0: time.sleep(3000) else: # this is the zombie process - s = socket.socket(socket.AF_UNIX) - with contextlib.closing(s): - s.connect('%s') - if sys.version_info < (3, ): - pid = str(os.getpid()) - else: - pid = bytes(str(os.getpid()), 'ascii') + with socket.socket(socket.AF_UNIX) as s: + s.connect('{unix_file}') + pid = bytes(str(os.getpid()), 'ascii') s.sendall(pid) - """ % unix_file) + """) tfile = None sock = bind_unix_socket(unix_file) try: @@ -464,7 +451,7 @@ def spawn_zombie(): zpid = int(conn.recv(1024)) _pids_started.add(zpid) zombie = psutil.Process(zpid) - call_until(zombie.status, "ret == psutil.STATUS_ZOMBIE") + call_until(lambda: zombie.status() == psutil.STATUS_ZOMBIE) return (parent, zombie) finally: conn.close() @@ -510,12 +497,9 @@ def sh(cmd, **kwds): cmd = shlex.split(cmd) p = subprocess.Popen(cmd, **kwds) _subprocesses_started.add(p) - if PY3: - stdout, stderr = p.communicate(timeout=GLOBAL_TIMEOUT) - else: - stdout, stderr = p.communicate() + stdout, stderr = p.communicate(timeout=GLOBAL_TIMEOUT) if p.returncode != 0: - raise RuntimeError(stderr) + raise RuntimeError(stdout + stderr) if stderr: warn(stderr) if stdout.endswith('\n'): @@ -535,10 +519,7 @@ def terminate(proc_or_pid, sig=signal.SIGTERM, wait_timeout=GLOBAL_TIMEOUT): """ def wait(proc, timeout): - if isinstance(proc, subprocess.Popen) and not PY3: - proc.wait() - else: - proc.wait(timeout) + proc.wait(timeout) if WINDOWS and isinstance(proc, subprocess.Popen): # Otherwise PID may still hang around. try: @@ -559,11 +540,12 @@ def sendsig(proc, sig): def term_subprocess_proc(proc, timeout): try: sendsig(proc, sig) + except ProcessLookupError: + pass except OSError as err: if WINDOWS and err.winerror == 6: # "invalid handle" pass - elif err.errno != errno.ESRCH: - raise + raise return wait(proc, timeout) def term_psutil_proc(proc, timeout): @@ -601,7 +583,7 @@ def flush_popen(proc): elif isinstance(p, subprocess.Popen): return term_subprocess_proc(p, wait_timeout) else: - raise TypeError("wrong type %r" % p) + raise TypeError(f"wrong type {p!r}") finally: if isinstance(p, (subprocess.Popen, psutil.Popen)): flush_popen(p) @@ -637,7 +619,7 @@ def reap_children(recursive=False): terminate(p, wait_timeout=None) _, alive = psutil.wait_procs(children, timeout=GLOBAL_TIMEOUT) for p in alive: - warn("couldn't terminate process %r; attempting kill()" % p) + warn(f"couldn't terminate process {p!r}; attempting kill()") terminate(p, sig=signal.SIGKILL) @@ -658,7 +640,7 @@ def kernel_version(): else: break if not s: - raise ValueError("can't parse %r" % uname) + raise ValueError(f"can't parse {uname!r}") minor = 0 micro = 0 nums = s.split('.') @@ -674,11 +656,7 @@ def get_winver(): if not WINDOWS: raise NotImplementedError("not WINDOWS") wv = sys.getwindowsversion() - if hasattr(wv, 'service_pack_major'): # python >= 2.7 - sp = wv.service_pack_major or 0 - else: - r = re.search(r"\s\d$", wv[4]) - sp = int(r.group(0)) if r else 0 + sp = wv.service_pack_major or 0 return (wv[0], wv[1], sp) @@ -729,16 +707,14 @@ def wrapper(*args, **kwargs): for _ in self: try: return fun(*args, **kwargs) - except self.exception as _: # NOQA + except self.exception as _: exc = _ if self.logfun is not None: self.logfun(exc) self.sleep() continue - if PY3: - raise exc # noqa: PLE0704 - else: - raise # noqa: PLE0704 + + raise exc # This way the user of the decorated function can change config # parameters. @@ -784,12 +760,10 @@ def wait_for_file(fname, delete=True, empty=False): timeout=GLOBAL_TIMEOUT, interval=0.001, ) -def call_until(fun, expr): - """Keep calling function for timeout secs and exit if eval() - expression is True. - """ +def call_until(fun): + """Keep calling function until it evaluates to True.""" ret = fun() - assert eval(expr) # noqa + assert ret return ret @@ -812,9 +786,9 @@ def retry_fun(fun): return fun() except FileNotFoundError: pass - except WindowsError as _: + except OSError as _: err = _ - warn("ignoring %s" % (str(err))) + warn(f"ignoring {err}") time.sleep(0.01) raise err @@ -865,8 +839,8 @@ def create_py_exe(path): def create_c_exe(path, c_code=None): """Create a compiled C executable in the given location.""" assert not os.path.exists(path), path - if not which("gcc"): - raise unittest.SkipTest("gcc is not installed") + if not shutil.which("gcc"): + raise pytest.skip("gcc is not installed") if c_code is None: c_code = textwrap.dedent(""" #include @@ -907,44 +881,91 @@ def get_testfn(suffix="", dir=None): # =================================================================== -class TestCase(unittest.TestCase): +class fake_pytest: + """A class that mimics some basic pytest APIs. This is meant for + when unit tests are run in production, where pytest may not be + installed. Still, the user can test psutil installation via: - # Print a full path representation of the single unit tests - # being run. - def __str__(self): - fqmod = self.__class__.__module__ - if not fqmod.startswith('psutil.'): - fqmod = 'psutil.tests.' + fqmod - return "%s.%s.%s" % ( - fqmod, - self.__class__.__name__, - self._testMethodName, + $ python3 -m psutil.tests + """ + + @staticmethod + def main(*args, **kw): # noqa: ARG004 + """Mimics pytest.main(). It has the same effect as running + `python3 -m unittest -v` from the project root directory. + """ + suite = unittest.TestLoader().discover(HERE) + unittest.TextTestRunner(verbosity=2).run(suite) + warnings.warn( + "Fake pytest module was used. Test results may be inaccurate.", + UserWarning, + stacklevel=1, ) + return suite - # assertRaisesRegexp renamed to assertRaisesRegex in 3.3; - # add support for the new name. - if not hasattr(unittest.TestCase, 'assertRaisesRegex'): - assertRaisesRegex = unittest.TestCase.assertRaisesRegexp # noqa + @staticmethod + def raises(exc, match=None): + """Mimics `pytest.raises`.""" - # ...otherwise multiprocessing.Pool complains - if not PY3: + class ExceptionInfo: + _exc = None - def runTest(self): - pass + @property + def value(self): + return self._exc @contextlib.contextmanager - def subTest(self, *args, **kw): - # fake it for python 2.7 - yield + def context(exc, match=None): + einfo = ExceptionInfo() + try: + yield einfo + except exc as err: + if match and not re.search(match, str(err)): + msg = f'"{match}" does not match "{err}"' + raise AssertionError(msg) + einfo._exc = err + else: + raise AssertionError(f"{exc!r} not raised") + + return context(exc, match=match) + + @staticmethod + def warns(warning, match=None): + """Mimics `pytest.warns`.""" + if match: + return unittest.TestCase().assertWarnsRegex(warning, match) + return unittest.TestCase().assertWarns(warning) + @staticmethod + def skip(reason=""): + """Mimics `unittest.SkipTest`.""" + raise unittest.SkipTest(reason) + + class mark: -# monkey patch default unittest.TestCase -unittest.TestCase = TestCase + @staticmethod + def skipif(condition, reason=""): + """Mimics `@pytest.mark.skipif` decorator.""" + return unittest.skipIf(condition, reason) + class xdist_group: + """Mimics `@pytest.mark.xdist_group` decorator (no-op).""" + + def __init__(self, name=None): + pass -class PsutilTestCase(TestCase): + def __call__(self, cls_or_meth): + return cls_or_meth + + +if pytest is None: + pytest = fake_pytest + + +class PsutilTestCase(unittest.TestCase): """Test class providing auto-cleanup wrappers on top of process - test utilities. + test utilities. All test classes should derive from this one, even + if we use pytest. """ def get_testfn(self, suffix="", dir=None): @@ -976,29 +997,29 @@ def pyrun(self, *args, **kwds): return sproc def _check_proc_exc(self, proc, exc): - self.assertIsInstance(exc, psutil.Error) - self.assertEqual(exc.pid, proc.pid) - self.assertEqual(exc.name, proc._name) + assert isinstance(exc, psutil.Error) + assert exc.pid == proc.pid + assert exc.name == proc._name if exc.name: - self.assertNotEqual(exc.name, "") + assert exc.name if isinstance(exc, psutil.ZombieProcess): - self.assertEqual(exc.ppid, proc._ppid) + assert exc.ppid == proc._ppid if exc.ppid is not None: - self.assertGreaterEqual(exc.ppid, 0) + assert exc.ppid >= 0 str(exc) repr(exc) def assertPidGone(self, pid): - with self.assertRaises(psutil.NoSuchProcess) as cm: + with pytest.raises(psutil.NoSuchProcess) as cm: try: psutil.Process(pid) except psutil.ZombieProcess: raise AssertionError("wasn't supposed to raise ZombieProcess") - self.assertEqual(cm.exception.pid, pid) - self.assertEqual(cm.exception.name, None) + assert cm.value.pid == pid + assert cm.value.name is None assert not psutil.pid_exists(pid), pid - self.assertNotIn(pid, psutil.pids()) - self.assertNotIn(pid, [x.pid for x in psutil.process_iter()]) + assert pid not in psutil.pids() + assert pid not in [x.pid for x in psutil.process_iter()] def assertProcessGone(self, proc): self.assertPidGone(proc.pid) @@ -1012,9 +1033,9 @@ def assertProcessGone(self, proc): except psutil.NoSuchProcess as exc: self._check_proc_exc(proc, exc) else: - msg = "Process.%s() didn't raise NSP and returned %r" % ( - name, - ret, + msg = ( + f"Process.{name}() didn't raise NSP and returned" + f" {ret!r}" ) raise AssertionError(msg) proc.wait(timeout=0) # assert not raise TimeoutExpired @@ -1024,21 +1045,21 @@ def assertProcessZombie(self, proc): clone = psutil.Process(proc.pid) # Cloned zombie on Open/NetBSD has null creation time, see: # https://github.com/giampaolo/psutil/issues/2287 - self.assertEqual(proc, clone) + assert proc == clone if not (OPENBSD or NETBSD): - self.assertEqual(hash(proc), hash(clone)) + assert hash(proc) == hash(clone) # Its status always be querable. - self.assertEqual(proc.status(), psutil.STATUS_ZOMBIE) + assert proc.status() == psutil.STATUS_ZOMBIE # It should be considered 'running'. assert proc.is_running() assert psutil.pid_exists(proc.pid) # as_dict() shouldn't crash. proc.as_dict() # It should show up in pids() and process_iter(). - self.assertIn(proc.pid, psutil.pids()) - self.assertIn(proc.pid, [x.pid for x in psutil.process_iter()]) + assert proc.pid in psutil.pids() + assert proc.pid in [x.pid for x in psutil.process_iter()] psutil._pmap = {} - self.assertIn(proc.pid, [x.pid for x in psutil.process_iter()]) + assert proc.pid in [x.pid for x in psutil.process_iter()] # Call all methods. ns = process_namespace(proc) for fun, name in ns.iter(ns.all, clear_cache=True): @@ -1049,15 +1070,15 @@ def assertProcessZombie(self, proc): self._check_proc_exc(proc, exc) if LINUX: # https://github.com/giampaolo/psutil/pull/2288 - with self.assertRaises(psutil.ZombieProcess) as cm: + with pytest.raises(psutil.ZombieProcess) as cm: proc.cmdline() - self._check_proc_exc(proc, cm.exception) - with self.assertRaises(psutil.ZombieProcess) as cm: + self._check_proc_exc(proc, cm.value) + with pytest.raises(psutil.ZombieProcess) as cm: proc.exe() - self._check_proc_exc(proc, cm.exception) - with self.assertRaises(psutil.ZombieProcess) as cm: + self._check_proc_exc(proc, cm.value) + with pytest.raises(psutil.ZombieProcess) as cm: proc.memory_maps() - self._check_proc_exc(proc, cm.exception) + self._check_proc_exc(proc, cm.value) # Zombie cannot be signaled or terminated. proc.suspend() proc.resume() @@ -1065,10 +1086,10 @@ def assertProcessZombie(self, proc): proc.kill() assert proc.is_running() assert psutil.pid_exists(proc.pid) - self.assertIn(proc.pid, psutil.pids()) - self.assertIn(proc.pid, [x.pid for x in psutil.process_iter()]) + assert proc.pid in psutil.pids() + assert proc.pid in [x.pid for x in psutil.process_iter()] psutil._pmap = {} - self.assertIn(proc.pid, [x.pid for x in psutil.process_iter()]) + assert proc.pid in [x.pid for x in psutil.process_iter()] # Its parent should 'see' it (edit: not true on BSD and MACOS). # descendants = [x.pid for x in psutil.Process().children( @@ -1085,7 +1106,7 @@ def assertProcessZombie(self, proc): # self.assertEqual(proc.ppid(), os.getpid()) -@unittest.skipIf(PYPY, "unreliable on PYPY") +@pytest.mark.skipif(PYPY, reason="unreliable on PYPY") class TestMemoryLeak(PsutilTestCase): """Test framework class for detecting function memory leaks, typically functions implemented in C which forgot to free() memory @@ -1160,15 +1181,16 @@ def _check_fds(self, fun): after = self._get_num_fds() diff = after - before if diff < 0: - raise self.fail( - "negative diff %r (gc probably collected a " - "resource from a previous test)" % diff + msg = ( + f"negative diff {diff!r} (gc probably collected a" + " resource from a previous test)" ) + raise self.fail(msg) if diff > 0: type_ = "fd" if POSIX else "handle" if diff > 1: type_ += "s" - msg = "%s unclosed %s after calling %r" % (diff, type_, fun) + msg = f"{diff} unclosed {type_} after calling {fun!r}" raise self.fail(msg) def _call_ntimes(self, fun, times): @@ -1182,7 +1204,7 @@ def _call_ntimes(self, fun, times): del x, ret gc.collect(generation=1) mem2 = self._get_mem() - self.assertEqual(gc.garbage, []) + assert gc.garbage == [] diff = mem2 - mem1 # can also be negative return diff @@ -1192,7 +1214,7 @@ def _check_mem(self, fun, times, retries, tolerance): increase = times for idx in range(1, retries + 1): mem = self._call_ntimes(fun, times) - msg = "Run #%s: extra-mem=%s, per-call=%s, calls=%s" % ( + msg = "Run #{}: extra-mem={}, per-call={}, calls={}".format( idx, bytes2human(mem), bytes2human(mem / times), @@ -1206,7 +1228,7 @@ def _check_mem(self, fun, times, retries, tolerance): return else: if idx == 1: - print() # NOQA + print() # noqa: T201 self._log(msg) times += increase prev_mem = mem @@ -1269,16 +1291,16 @@ def print_sysinfo(): info = collections.OrderedDict() # OS - if psutil.LINUX and which('lsb_release'): + if psutil.LINUX and shutil.which("lsb_release"): info['OS'] = sh('lsb_release -d -s') elif psutil.OSX: - info['OS'] = 'Darwin %s' % platform.mac_ver()[0] + info['OS'] = f"Darwin {platform.mac_ver()[0]}" elif psutil.WINDOWS: info['OS'] = "Windows " + ' '.join(map(str, platform.win32_ver())) if hasattr(platform, 'win32_edition'): info['OS'] += ", " + platform.win32_edition() else: - info['OS'] = "%s %s" % (platform.system(), platform.version()) + info['OS'] = f"{platform.system()} {platform.version()}" info['arch'] = ', '.join( list(platform.architecture()) + [platform.machine()] ) @@ -1293,11 +1315,11 @@ def print_sysinfo(): ]) info['pip'] = getattr(pip, '__version__', 'not installed') if wheel is not None: - info['pip'] += " (wheel=%s)" % wheel.__version__ + info['pip'] += f" (wheel={wheel.__version__})" # UNIX if psutil.POSIX: - if which('gcc'): + if shutil.which("gcc"): out = sh(['gcc', '--version']) info['gcc'] = str(out).split('\n')[0] else: @@ -1309,7 +1331,7 @@ def print_sysinfo(): # system info['fs-encoding'] = sys.getfilesystemencoding() lang = locale.getlocale() - info['lang'] = '%s, %s' % (lang[0], lang[1]) + info['lang'] = f"{lang[0]}, {lang[1]}" info['boot-time'] = datetime.datetime.fromtimestamp( psutil.boot_time() ).strftime("%Y-%m-%d %H:%M:%S") @@ -1323,17 +1345,17 @@ def print_sysinfo(): # metrics info['cpus'] = psutil.cpu_count() - info['loadavg'] = "%.1f%%, %.1f%%, %.1f%%" % ( - tuple([x / psutil.cpu_count() * 100 for x in psutil.getloadavg()]) + info['loadavg'] = "{:.1f}%, {:.1f}%, {:.1f}%".format( + *tuple([x / psutil.cpu_count() * 100 for x in psutil.getloadavg()]) ) mem = psutil.virtual_memory() - info['memory'] = "%s%%, used=%s, total=%s" % ( + info['memory'] = "{}%%, used={}, total={}".format( int(mem.percent), bytes2human(mem.used), bytes2human(mem.total), ) swap = psutil.swap_memory() - info['swap'] = "%s%%, used=%s, total=%s" % ( + info['swap'] = "{}%%, used={}, total={}".format( int(swap.percent), bytes2human(swap.used), bytes2human(swap.total), @@ -1343,17 +1365,18 @@ def print_sysinfo(): pinfo.pop('memory_maps', None) info['proc'] = pprint.pformat(pinfo) - print("=" * 70, file=sys.stderr) # NOQA + print("=" * 70, file=sys.stderr) # noqa: T201 for k, v in info.items(): - print("%-17s %s" % (k + ':', v), file=sys.stderr) # NOQA - print("=" * 70, file=sys.stderr) # NOQA + print("{:<17} {}".format(k + ":", v), file=sys.stderr) # noqa: T201 + print("=" * 70, file=sys.stderr) # noqa: T201 sys.stdout.flush() - if WINDOWS: - os.system("tasklist") - elif which("ps"): - os.system("ps aux") - print("=" * 70, file=sys.stderr) # NOQA + # if WINDOWS: + # os.system("tasklist") + # elif shutil.which("ps"): + # os.system("ps aux") + # print("=" * 70, file=sys.stderr) + sys.stdout.flush() @@ -1401,7 +1424,6 @@ class process_namespace: ('children', (), {'recursive': True}), ('connections', (), {}), # deprecated ('is_running', (), {}), - ('memory_info_ex', (), {}), # deprecated ('oneshot', (), {}), ('parent', (), {}), ('parents', (), {}), @@ -1506,20 +1528,20 @@ def test_class_coverage(cls, test_class, ls): for fun_name, _, _ in ls: meth_name = 'test_' + fun_name if not hasattr(test_class, meth_name): - msg = "%r class should define a '%s' method" % ( - test_class.__class__.__name__, - meth_name, + msg = ( + f"{test_class.__class__.__name__!r} class should define a" + f" {meth_name!r} method" ) raise AttributeError(msg) @classmethod def test(cls): - this = set([x[0] for x in cls.all]) - ignored = set([x[0] for x in cls.ignored]) - klass = set([x for x in dir(psutil.Process) if x[0] != '_']) + this = {x[0] for x in cls.all} + ignored = {x[0] for x in cls.ignored} + klass = {x for x in dir(psutil.Process) if x[0] != '_'} leftout = (this | ignored) ^ klass if leftout: - raise ValueError("uncovered Process class names: %r" % leftout) + raise ValueError(f"uncovered Process class names: {leftout!r}") class system_namespace: @@ -1592,23 +1614,13 @@ def iter(ls): test_class_coverage = process_namespace.test_class_coverage -def serialrun(klass): - """A decorator to mark a TestCase class. When running parallel tests, - class' unit tests will be run serially (1 process). - """ - # assert issubclass(klass, unittest.TestCase), klass - assert inspect.isclass(klass), klass - klass._serialrun = True - return klass - - def retry_on_failure(retries=NO_RETRIES): """Decorator which runs a test function and retries N times before actually failing. """ def logfun(exc): - print("%r, retrying" % exc, file=sys.stderr) # NOQA + print(f"{exc!r}, retrying", file=sys.stderr) # noqa: T201 return retry( exception=AssertionError, timeout=None, retries=retries, logfun=logfun @@ -1627,7 +1639,7 @@ def wrapper(*args, **kwargs): if only_if is not None: if not only_if: raise - raise unittest.SkipTest("raises AccessDenied") + raise pytest.skip("raises AccessDenied") return wrapper @@ -1647,10 +1659,10 @@ def wrapper(*args, **kwargs): if not only_if: raise msg = ( - "%r was skipped because it raised NotImplementedError" - % fun.__name__ + f"{fun.__name__!r} was skipped because it raised" + " NotImplementedError" ) - raise unittest.SkipTest(msg) + raise pytest.skip(msg) return wrapper @@ -1665,18 +1677,18 @@ def wrapper(*args, **kwargs): # XXX: no longer used def get_free_port(host='127.0.0.1'): """Return an unused TCP port. Subject to race conditions.""" - with contextlib.closing(socket.socket()) as sock: + with socket.socket() as sock: sock.bind((host, 0)) return sock.getsockname()[1] def bind_socket(family=AF_INET, type=SOCK_STREAM, addr=None): """Binds a generic socket.""" - if addr is None and family in (AF_INET, AF_INET6): + if addr is None and family in {AF_INET, AF_INET6}: addr = ("", 0) sock = socket.socket(family, type) try: - if os.name not in ('nt', 'cygwin'): + if os.name not in {'nt', 'cygwin'}: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(addr) if type == socket.SOCK_STREAM: @@ -1706,7 +1718,7 @@ def tcp_socketpair(family, addr=("", 0)): """Build a pair of TCP sockets connected to each other. Return a (server, client) tuple. """ - with contextlib.closing(socket.socket(family, SOCK_STREAM)) as ll: + with socket.socket(family, SOCK_STREAM) as ll: ll.bind(addr) ll.listen(5) addr = ll.getsockname() @@ -1779,27 +1791,20 @@ def check_net_address(addr, family): """Check a net address validity. Supported families are IPv4, IPv6 and MAC addresses. """ - import ipaddress # python >= 3.3 / requires "pip install ipaddress" - - if enum and PY3 and not PYPY: - assert isinstance(family, enum.IntEnum), family + assert isinstance(family, enum.IntEnum), family if family == socket.AF_INET: octs = [int(x) for x in addr.split('.')] assert len(octs) == 4, addr for num in octs: assert 0 <= num <= 255, addr - if not PY3: - addr = unicode(addr) ipaddress.IPv4Address(addr) elif family == socket.AF_INET6: assert isinstance(addr, str), addr - if not PY3: - addr = unicode(addr) ipaddress.IPv6Address(addr) elif family == psutil.AF_LINK: assert re.match(r'([a-fA-F0-9]{2}[:|\-]?){6}', addr) is not None, addr else: - raise ValueError("unknown family %r" % family) + raise ValueError(f"unknown family {family!r}") def check_connection_ntuple(conn): @@ -1807,7 +1812,7 @@ def check_connection_ntuple(conn): def check_ntuple(conn): has_pid = len(conn) == 7 - assert len(conn) in (6, 7), len(conn) + assert len(conn) in {6, 7}, len(conn) assert conn[0] == conn.fd, conn.fd assert conn[1] == conn.family, conn.family assert conn[2] == conn.type, conn.type @@ -1818,21 +1823,17 @@ def check_ntuple(conn): assert conn[6] == conn.pid, conn.pid def check_family(conn): - assert conn.family in (AF_INET, AF_INET6, AF_UNIX), conn.family - if enum is not None: - assert isinstance(conn.family, enum.IntEnum), conn - else: - assert isinstance(conn.family, int), conn + assert conn.family in {AF_INET, AF_INET6, AF_UNIX}, conn.family + assert isinstance(conn.family, enum.IntEnum), conn if conn.family == AF_INET: # actually try to bind the local socket; ignore IPv6 # sockets as their address might be represented as # an IPv4-mapped-address (e.g. "::127.0.0.1") # and that's rejected by bind() - s = socket.socket(conn.family, conn.type) - with contextlib.closing(s): + with socket.socket(conn.family, conn.type) as s: try: s.bind((conn.laddr[0], 0)) - except socket.error as err: + except OSError as err: if err.errno != errno.EADDRNOTAVAIL: raise elif conn.family == AF_UNIX: @@ -1841,22 +1842,19 @@ def check_family(conn): def check_type(conn): # SOCK_SEQPACKET may happen in case of AF_UNIX socks SOCK_SEQPACKET = getattr(socket, "SOCK_SEQPACKET", object()) - assert conn.type in ( + assert conn.type in { socket.SOCK_STREAM, socket.SOCK_DGRAM, SOCK_SEQPACKET, - ), conn.type - if enum is not None: - assert isinstance(conn.type, enum.IntEnum), conn - else: - assert isinstance(conn.type, int), conn + }, conn.type + assert isinstance(conn.type, enum.IntEnum), conn if conn.type == socket.SOCK_DGRAM: assert conn.status == psutil.CONN_NONE, conn.status def check_addrs(conn): # check IP address and port sanity for addr in (conn.laddr, conn.raddr): - if conn.family in (AF_INET, AF_INET6): + if conn.family in {AF_INET, AF_INET6}: assert isinstance(addr, tuple), type(addr) if not addr: continue @@ -1872,7 +1870,7 @@ def check_status(conn): getattr(psutil, x) for x in dir(psutil) if x.startswith('CONN_') ] assert conn.status in valids, conn.status - if conn.family in (AF_INET, AF_INET6) and conn.type == SOCK_STREAM: + if conn.family in {AF_INET, AF_INET6} and conn.type == SOCK_STREAM: assert conn.status != psutil.CONN_NONE, conn.status else: assert conn.status == psutil.CONN_NONE, conn.status @@ -1892,45 +1890,27 @@ def filter_proc_net_connections(cons): for conn in cons: if POSIX and conn.family == socket.AF_UNIX: if MACOS and "/syslog" in conn.raddr: - debug("skipping %s" % str(conn)) + debug(f"skipping {conn}") continue new.append(conn) return new # =================================================================== -# --- compatibility +# --- import utils # =================================================================== def reload_module(module): - """Backport of importlib.reload of Python 3.3+.""" - try: - import importlib - - if not hasattr(importlib, 'reload'): # python <=3.3 - raise ImportError - except ImportError: - import imp - - return imp.reload(module) - else: - return importlib.reload(module) + return importlib.reload(module) def import_module_by_path(path): name = os.path.splitext(os.path.basename(path))[0] - if sys.version_info[0] < 3: - import imp - - return imp.load_source(name, path) - else: - import importlib.util - - spec = importlib.util.spec_from_file_location(name, path) - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - return mod + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod # =================================================================== diff --git a/psutil/tests/__main__.py b/psutil/tests/__main__.py index 2460abdb3..ce6fc24c7 100644 --- a/psutil/tests/__main__.py +++ b/psutil/tests/__main__.py @@ -6,7 +6,7 @@ $ python -m psutil.tests. """ -from .runner import main +from psutil.tests import pytest -main() +pytest.main(["-v", "-s", "--tb=short"]) diff --git a/psutil/tests/runner.py b/psutil/tests/runner.py deleted file mode 100755 index a054e4817..000000000 --- a/psutil/tests/runner.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/env python3 - -# 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. - -"""Unit test runner, providing new features on top of unittest module: -- colourized output -- parallel run (UNIX only) -- print failures/tracebacks on CTRL+C -- re-run failed tests only (make test-failed). - -Invocation examples: -- make test -- make test-failed - -Parallel: -- make test-parallel -- make test-process ARGS=--parallel -""" - -from __future__ import print_function - -import atexit -import optparse -import os -import sys -import textwrap -import time -import unittest - - -try: - import ctypes -except ImportError: - ctypes = None - -try: - import concurrencytest # pip install concurrencytest -except ImportError: - concurrencytest = None - -import psutil -from psutil._common import hilite -from psutil._common import print_color -from psutil._common import term_supports_colors -from psutil._compat import super -from psutil.tests import CI_TESTING -from psutil.tests import import_module_by_path -from psutil.tests import print_sysinfo -from psutil.tests import reap_children -from psutil.tests import safe_rmpath - - -VERBOSITY = 2 -FAILED_TESTS_FNAME = '.failed-tests.txt' -NWORKERS = psutil.cpu_count() or 1 -USE_COLORS = not CI_TESTING and term_supports_colors() - -HERE = os.path.abspath(os.path.dirname(__file__)) -loadTestsFromTestCase = ( # noqa: N816 - unittest.defaultTestLoader.loadTestsFromTestCase -) - - -def cprint(msg, color, bold=False, file=None): - if file is None: - file = sys.stderr if color == 'red' else sys.stdout - if USE_COLORS: - print_color(msg, color, bold=bold, file=file) - else: - print(msg, file=file) - - -class TestLoader: - - testdir = HERE - skip_files = ['test_memleaks.py'] - if "WHEELHOUSE_UPLOADER_USERNAME" in os.environ: - skip_files.extend(['test_osx.py', 'test_linux.py', 'test_posix.py']) - - def _get_testmods(self): - return [ - os.path.join(self.testdir, x) - for x in os.listdir(self.testdir) - if x.startswith('test_') - and x.endswith('.py') - and x not in self.skip_files - ] - - def _iter_testmod_classes(self): - """Iterate over all test files in this directory and return - all TestCase classes in them. - """ - for path in self._get_testmods(): - mod = import_module_by_path(path) - for name in dir(mod): - obj = getattr(mod, name) - if isinstance(obj, type) and issubclass( - obj, unittest.TestCase - ): - yield obj - - def all(self): - suite = unittest.TestSuite() - for obj in self._iter_testmod_classes(): - test = loadTestsFromTestCase(obj) - suite.addTest(test) - return suite - - def last_failed(self): - # ...from previously failed test run - suite = unittest.TestSuite() - if not os.path.isfile(FAILED_TESTS_FNAME): - return suite - with open(FAILED_TESTS_FNAME) as f: - names = f.read().split() - for n in names: - test = unittest.defaultTestLoader.loadTestsFromName(n) - suite.addTest(test) - return suite - - def from_name(self, name): - if name.endswith('.py'): - name = os.path.splitext(os.path.basename(name))[0] - return unittest.defaultTestLoader.loadTestsFromName(name) - - -class ColouredResult(unittest.TextTestResult): - def addSuccess(self, test): - unittest.TestResult.addSuccess(self, test) - cprint("OK", "green") - - def addError(self, test, err): - unittest.TestResult.addError(self, test, err) - cprint("ERROR", "red", bold=True) - - def addFailure(self, test, err): - unittest.TestResult.addFailure(self, test, err) - cprint("FAIL", "red") - - def addSkip(self, test, reason): - unittest.TestResult.addSkip(self, test, reason) - cprint("skipped: %s" % reason.strip(), "brown") - - def printErrorList(self, flavour, errors): - flavour = hilite(flavour, "red", bold=flavour == 'ERROR') - super().printErrorList(flavour, errors) - - -class ColouredTextRunner(unittest.TextTestRunner): - """A coloured text runner which also prints failed tests on - KeyboardInterrupt and save failed tests in a file so that they can - be re-run. - """ - - resultclass = ColouredResult if USE_COLORS else unittest.TextTestResult - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.failed_tnames = set() - - def _makeResult(self): - # Store result instance so that it can be accessed on - # KeyboardInterrupt. - self.result = super()._makeResult() - return self.result - - def _write_last_failed(self): - if self.failed_tnames: - with open(FAILED_TESTS_FNAME, "w") as f: - for tname in self.failed_tnames: - f.write(tname + '\n') - - def _save_result(self, result): - if not result.wasSuccessful(): - for t in result.errors + result.failures: - tname = t[0].id() - self.failed_tnames.add(tname) - - def _run(self, suite): - try: - result = super().run(suite) - except (KeyboardInterrupt, SystemExit): - result = self.runner.result - result.printErrors() - raise sys.exit(1) - else: - self._save_result(result) - return result - - def _exit(self, success): - if success: - cprint("SUCCESS", "green", bold=True) - safe_rmpath(FAILED_TESTS_FNAME) - sys.exit(0) - else: - cprint("FAILED", "red", bold=True) - self._write_last_failed() - sys.exit(1) - - def run(self, suite): - result = self._run(suite) - self._exit(result.wasSuccessful()) - - -class ParallelRunner(ColouredTextRunner): - @staticmethod - def _parallelize(suite): - def fdopen(fd, mode, *kwds): - stream = orig_fdopen(fd, mode) - atexit.register(stream.close) - return stream - - # Monkey patch concurrencytest lib bug (fdopen() stream not closed). - # https://github.com/cgoldberg/concurrencytest/issues/11 - orig_fdopen = os.fdopen - concurrencytest.os.fdopen = fdopen - forker = concurrencytest.fork_for_tests(NWORKERS) - return concurrencytest.ConcurrentTestSuite(suite, forker) - - @staticmethod - def _split_suite(suite): - serial = unittest.TestSuite() - parallel = unittest.TestSuite() - for test in suite: - if test.countTestCases() == 0: - continue - if isinstance(test, unittest.TestSuite): - test_class = test._tests[0].__class__ - elif isinstance(test, unittest.TestCase): - test_class = test - else: - raise TypeError("can't recognize type %r" % test) - - if getattr(test_class, '_serialrun', False): - serial.addTest(test) - else: - parallel.addTest(test) - return (serial, parallel) - - def run(self, suite): - ser_suite, par_suite = self._split_suite(suite) - par_suite = self._parallelize(par_suite) - - # run parallel - cprint( - "starting parallel tests using %s workers" % NWORKERS, - "green", - bold=True, - ) - t = time.time() - par = self._run(par_suite) - par_elapsed = time.time() - t - - # At this point we should have N zombies (the workers), which - # will disappear with wait(). - orphans = psutil.Process().children() - gone, alive = psutil.wait_procs(orphans, timeout=1) - if alive: - cprint("alive processes %s" % alive, "red") - reap_children() - - # run serial - t = time.time() - ser = self._run(ser_suite) - ser_elapsed = time.time() - t - - # print - if not par.wasSuccessful() and ser_suite.countTestCases() > 0: - par.printErrors() # print them again at the bottom - par_fails, par_errs, par_skips = map( - len, (par.failures, par.errors, par.skipped) - ) - ser_fails, ser_errs, ser_skips = map( - len, (ser.failures, ser.errors, ser.skipped) - ) - print( - textwrap.dedent( - """ - +----------+----------+----------+----------+----------+----------+ - | | total | failures | errors | skipped | time | - +----------+----------+----------+----------+----------+----------+ - | parallel | %3s | %3s | %3s | %3s | %.2fs | - +----------+----------+----------+----------+----------+----------+ - | serial | %3s | %3s | %3s | %3s | %.2fs | - +----------+----------+----------+----------+----------+----------+ - """ - % ( - par.testsRun, - par_fails, - par_errs, - par_skips, - par_elapsed, - ser.testsRun, - ser_fails, - ser_errs, - ser_skips, - ser_elapsed, - ) - ) - ) - print( - "Ran %s tests in %.3fs using %s workers" - % ( - par.testsRun + ser.testsRun, - par_elapsed + ser_elapsed, - NWORKERS, - ) - ) - ok = par.wasSuccessful() and ser.wasSuccessful() - self._exit(ok) - - -def get_runner(parallel=False): - def warn(msg): - cprint(msg + " Running serial tests instead.", "red") - - if parallel: - if psutil.WINDOWS: - warn("Can't run parallel tests on Windows.") - elif concurrencytest is None: - warn("concurrencytest module is not installed.") - elif NWORKERS == 1: - warn("Only 1 CPU available.") - else: - return ParallelRunner(verbosity=VERBOSITY) - return ColouredTextRunner(verbosity=VERBOSITY) - - -# Used by test_*,py modules. -def run_from_name(name): - if CI_TESTING: - print_sysinfo() - suite = TestLoader().from_name(name) - runner = get_runner() - runner.run(suite) - - -def setup(): - psutil._set_debug(True) - - -def main(): - setup() - usage = "python3 -m psutil.tests [opts] [test-name]" - parser = optparse.OptionParser(usage=usage, description="run unit tests") - parser.add_option( - "--last-failed", - action="store_true", - default=False, - help="only run last failed tests", - ) - parser.add_option( - "--parallel", - action="store_true", - default=False, - help="run tests in parallel", - ) - opts, args = parser.parse_args() - - if not opts.last_failed: - safe_rmpath(FAILED_TESTS_FNAME) - - # loader - loader = TestLoader() - if args: - if len(args) > 1: - parser.print_usage() - return sys.exit(1) - else: - suite = loader.from_name(args[0]) - elif opts.last_failed: - suite = loader.last_failed() - else: - suite = loader.all() - - if CI_TESTING: - print_sysinfo() - runner = get_runner(opts.parallel) - runner.run(suite) - - -if __name__ == '__main__': - main() diff --git a/psutil/tests/test_aix.py b/psutil/tests/test_aix.py index e7e0c8aa5..10934c12d 100755 --- a/psutil/tests/test_aix.py +++ b/psutil/tests/test_aix.py @@ -9,26 +9,32 @@ """AIX specific tests.""" import re -import unittest import psutil from psutil import AIX from psutil.tests import PsutilTestCase +from psutil.tests import pytest from psutil.tests import sh -@unittest.skipIf(not AIX, "AIX only") +@pytest.mark.skipif(not AIX, reason="AIX only") class AIXSpecificTestCase(PsutilTestCase): def test_virtual_memory(self): out = sh('/usr/bin/svmon -O unit=KB') re_pattern = r"memory\s*" - for field in ("size inuse free pin virtual available mmode").split(): - re_pattern += r"(?P<%s>\S+)\s+" % (field,) + for field in [ + "size", + "inuse", + "free", + "pin", + "virtual", + "available", + "mmode", + ]: + re_pattern += rf"(?P<{field}>\S+)\s+" matchobj = re.search(re_pattern, out) - self.assertIsNotNone( - matchobj, "svmon command returned unexpected output" - ) + assert matchobj is not None KB = 1024 total = int(matchobj.group("size")) * KB @@ -42,16 +48,10 @@ def test_virtual_memory(self): # we're seeing differences of ~1.2 MB. 2 MB is still a good tolerance # when compared to GBs. TOLERANCE_SYS_MEM = 2 * KB * KB # 2 MB - self.assertEqual(psutil_result.total, total) - self.assertAlmostEqual( - psutil_result.used, used, delta=TOLERANCE_SYS_MEM - ) - self.assertAlmostEqual( - psutil_result.available, available, delta=TOLERANCE_SYS_MEM - ) - self.assertAlmostEqual( - psutil_result.free, free, delta=TOLERANCE_SYS_MEM - ) + assert psutil_result.total == total + assert abs(psutil_result.used - used) < TOLERANCE_SYS_MEM + assert abs(psutil_result.available - available) < TOLERANCE_SYS_MEM + assert abs(psutil_result.free - free) < TOLERANCE_SYS_MEM def test_swap_memory(self): out = sh('/usr/sbin/lsps -a') @@ -67,71 +67,76 @@ def test_swap_memory(self): out, ) - self.assertIsNotNone( - matchobj, "lsps command returned unexpected output" - ) + assert matchobj is not None total_mb = int(matchobj.group("size")) MB = 1024**2 psutil_result = psutil.swap_memory() # we divide our result by MB instead of multiplying the lsps value by # MB because lsps may round down, so we round down too - self.assertEqual(int(psutil_result.total / MB), total_mb) + assert int(psutil_result.total / MB) == total_mb def test_cpu_stats(self): out = sh('/usr/bin/mpstat -a') re_pattern = r"ALL\s*" - for field in ( - "min maj mpcs mpcr dev soft dec ph cs ics bound rq " - "push S3pull S3grd S0rd S1rd S2rd S3rd S4rd S5rd " - "sysc" - ).split(): - re_pattern += r"(?P<%s>\S+)\s+" % (field,) + for field in [ + "min", + "maj", + "mpcs", + "mpcr", + "dev", + "soft", + "dec", + "ph", + "cs", + "ics", + "bound", + "rq", + "push", + "S3pull", + "S3grd", + "S0rd", + "S1rd", + "S2rd", + "S3rd", + "S4rd", + "S5rd", + "sysc", + ]: + re_pattern += rf"(?P<{field}>\S+)\s+" matchobj = re.search(re_pattern, out) - self.assertIsNotNone( - matchobj, "mpstat command returned unexpected output" - ) + assert matchobj is not None # numbers are usually in the millions so 1000 is ok for tolerance CPU_STATS_TOLERANCE = 1000 psutil_result = psutil.cpu_stats() - self.assertAlmostEqual( - psutil_result.ctx_switches, - int(matchobj.group("cs")), - delta=CPU_STATS_TOLERANCE, + assert ( + abs(psutil_result.ctx_switches - int(matchobj.group("cs"))) + < CPU_STATS_TOLERANCE ) - self.assertAlmostEqual( - psutil_result.syscalls, - int(matchobj.group("sysc")), - delta=CPU_STATS_TOLERANCE, + assert ( + abs(psutil_result.syscalls - int(matchobj.group("sysc"))) + < CPU_STATS_TOLERANCE ) - self.assertAlmostEqual( - psutil_result.interrupts, - int(matchobj.group("dev")), - delta=CPU_STATS_TOLERANCE, + assert ( + abs(psutil_result.interrupts - int(matchobj.group("dev"))) + < CPU_STATS_TOLERANCE ) - self.assertAlmostEqual( - psutil_result.soft_interrupts, - int(matchobj.group("soft")), - delta=CPU_STATS_TOLERANCE, + assert ( + abs(psutil_result.soft_interrupts - int(matchobj.group("soft"))) + < CPU_STATS_TOLERANCE ) def test_cpu_count_logical(self): out = sh('/usr/bin/mpstat -a') mpstat_lcpu = int(re.search(r"lcpu=(\d+)", out).group(1)) psutil_lcpu = psutil.cpu_count(logical=True) - self.assertEqual(mpstat_lcpu, psutil_lcpu) + assert mpstat_lcpu == psutil_lcpu def test_net_if_addrs_names(self): out = sh('/etc/ifconfig -l') ifconfig_names = set(out.split()) psutil_names = set(psutil.net_if_addrs().keys()) - self.assertSetEqual(ifconfig_names, psutil_names) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert ifconfig_names == psutil_names diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py index a714632dc..2786c3485 100755 --- a/psutil/tests/test_bsd.py +++ b/psutil/tests/test_bsd.py @@ -9,12 +9,11 @@ """Tests specific to all BSD platforms.""" - import datetime import os import re +import shutil import time -import unittest import psutil from psutil import BSD @@ -24,11 +23,11 @@ from psutil.tests import HAS_BATTERY from psutil.tests import TOLERANCE_SYS_MEM from psutil.tests import PsutilTestCase +from psutil.tests import pytest from psutil.tests import retry_on_failure from psutil.tests import sh from psutil.tests import spawn_testproc from psutil.tests import terminate -from psutil.tests import which if BSD: @@ -36,7 +35,7 @@ PAGESIZE = getpagesize() # muse requires root privileges - MUSE_AVAILABLE = os.getuid() == 0 and which('muse') + MUSE_AVAILABLE = os.getuid() == 0 and shutil.which("muse") else: PAGESIZE = None MUSE_AVAILABLE = False @@ -73,7 +72,7 @@ def muse(field): # ===================================================================== -@unittest.skipIf(not BSD, "BSD only") +@pytest.mark.skipif(not BSD, reason="BSD only") class BSDTestCase(PsutilTestCase): """Generic tests common to all BSD variants.""" @@ -85,21 +84,21 @@ def setUpClass(cls): def tearDownClass(cls): terminate(cls.pid) - @unittest.skipIf(NETBSD, "-o lstart doesn't work on NETBSD") + @pytest.mark.skipif(NETBSD, reason="-o lstart doesn't work on NETBSD") def test_process_create_time(self): - output = sh("ps -o lstart -p %s" % self.pid) + output = sh(f"ps -o lstart -p {self.pid}") start_ps = output.replace('STARTED', '').strip() start_psutil = psutil.Process(self.pid).create_time() start_psutil = time.strftime( "%a %b %e %H:%M:%S %Y", time.localtime(start_psutil) ) - self.assertEqual(start_ps, start_psutil) + assert start_ps == start_psutil def test_disks(self): # test psutil.disk_usage() and psutil.disk_partitions() # against "df -a" def df(path): - out = sh('df -k "%s"' % path).strip() + out = sh(f'df -k "{path}"').strip() lines = out.split('\n') lines.pop(0) line = lines.pop(0) @@ -114,38 +113,44 @@ def df(path): for part in psutil.disk_partitions(all=False): usage = psutil.disk_usage(part.mountpoint) dev, total, used, free = df(part.mountpoint) - self.assertEqual(part.device, dev) - self.assertEqual(usage.total, total) + assert part.device == dev + assert usage.total == total # 10 MB tolerance if abs(usage.free - free) > 10 * 1024 * 1024: - raise self.fail("psutil=%s, df=%s" % (usage.free, free)) + raise self.fail(f"psutil={usage.free}, df={free}") if abs(usage.used - used) > 10 * 1024 * 1024: - raise self.fail("psutil=%s, df=%s" % (usage.used, used)) + raise self.fail(f"psutil={usage.used}, df={used}") - @unittest.skipIf(not which('sysctl'), "sysctl cmd not available") + @pytest.mark.skipif( + not shutil.which("sysctl"), reason="sysctl cmd not available" + ) def test_cpu_count_logical(self): syst = sysctl("hw.ncpu") - self.assertEqual(psutil.cpu_count(logical=True), syst) - - @unittest.skipIf(not which('sysctl'), "sysctl cmd not available") - @unittest.skipIf(NETBSD, "skipped on NETBSD") # we check /proc/meminfo + assert psutil.cpu_count(logical=True) == syst + + @pytest.mark.skipif( + not shutil.which("sysctl"), reason="sysctl cmd not available" + ) + @pytest.mark.skipif( + NETBSD, reason="skipped on NETBSD" # we check /proc/meminfo + ) def test_virtual_memory_total(self): num = sysctl('hw.physmem') - self.assertEqual(num, psutil.virtual_memory().total) + assert num == psutil.virtual_memory().total - @unittest.skipIf(not which('ifconfig'), "ifconfig cmd not available") + @pytest.mark.skipif( + not shutil.which("ifconfig"), reason="ifconfig cmd not available" + ) def test_net_if_stats(self): for name, stats in psutil.net_if_stats().items(): try: - out = sh("ifconfig %s" % name) + out = sh(f"ifconfig {name}") except RuntimeError: pass else: - self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) + assert stats.isup == ('RUNNING' in out) if "mtu" in out: - self.assertEqual( - stats.mtu, int(re.findall(r'mtu (\d+)', out)[0]) - ) + assert stats.mtu == int(re.findall(r'mtu (\d+)', out)[0]) # ===================================================================== @@ -153,7 +158,7 @@ def test_net_if_stats(self): # ===================================================================== -@unittest.skipIf(not FREEBSD, "FREEBSD only") +@pytest.mark.skipif(not FREEBSD, reason="FREEBSD only") class FreeBSDPsutilTestCase(PsutilTestCase): @classmethod def setUpClass(cls): @@ -165,61 +170,58 @@ def tearDownClass(cls): @retry_on_failure() def test_memory_maps(self): - out = sh('procstat -v %s' % self.pid) + out = sh(f"procstat -v {self.pid}") maps = psutil.Process(self.pid).memory_maps(grouped=False) lines = out.split('\n')[1:] while lines: line = lines.pop() fields = line.split() - _, start, stop, perms, res = fields[:5] + _, start, stop, _perms, res = fields[:5] map = maps.pop() - self.assertEqual("%s-%s" % (start, stop), map.addr) - self.assertEqual(int(res), map.rss) + assert f"{start}-{stop}" == map.addr + assert int(res) == map.rss if not map.path.startswith('['): - self.assertEqual(fields[10], map.path) + assert fields[10] == map.path def test_exe(self): - out = sh('procstat -b %s' % self.pid) - self.assertEqual( - psutil.Process(self.pid).exe(), out.split('\n')[1].split()[-1] - ) + out = sh(f"procstat -b {self.pid}") + assert psutil.Process(self.pid).exe() == out.split('\n')[1].split()[-1] def test_cmdline(self): - out = sh('procstat -c %s' % self.pid) - self.assertEqual( - ' '.join(psutil.Process(self.pid).cmdline()), - ' '.join(out.split('\n')[1].split()[2:]), + out = sh(f"procstat -c {self.pid}") + assert ' '.join(psutil.Process(self.pid).cmdline()) == ' '.join( + out.split('\n')[1].split()[2:] ) def test_uids_gids(self): - out = sh('procstat -s %s' % self.pid) + out = sh(f"procstat -s {self.pid}") euid, ruid, suid, egid, rgid, sgid = out.split('\n')[1].split()[2:8] p = psutil.Process(self.pid) uids = p.uids() gids = p.gids() - self.assertEqual(uids.real, int(ruid)) - self.assertEqual(uids.effective, int(euid)) - self.assertEqual(uids.saved, int(suid)) - self.assertEqual(gids.real, int(rgid)) - self.assertEqual(gids.effective, int(egid)) - self.assertEqual(gids.saved, int(sgid)) + assert uids.real == int(ruid) + assert uids.effective == int(euid) + assert uids.saved == int(suid) + assert gids.real == int(rgid) + assert gids.effective == int(egid) + assert gids.saved == int(sgid) @retry_on_failure() def test_ctx_switches(self): tested = [] - out = sh('procstat -r %s' % self.pid) + out = sh(f"procstat -r {self.pid}") p = psutil.Process(self.pid) for line in out.split('\n'): line = line.lower().strip() if ' voluntary context' in line: pstat_value = int(line.split()[-1]) psutil_value = p.num_ctx_switches().voluntary - self.assertEqual(pstat_value, psutil_value) + assert pstat_value == psutil_value tested.append(None) elif ' involuntary context' in line: pstat_value = int(line.split()[-1]) psutil_value = p.num_ctx_switches().involuntary - self.assertEqual(pstat_value, psutil_value) + assert pstat_value == psutil_value tested.append(None) if len(tested) != 2: raise RuntimeError("couldn't find lines match in procstat out") @@ -227,25 +229,25 @@ def test_ctx_switches(self): @retry_on_failure() def test_cpu_times(self): tested = [] - out = sh('procstat -r %s' % self.pid) + out = sh(f"procstat -r {self.pid}") p = psutil.Process(self.pid) for line in out.split('\n'): line = line.lower().strip() if 'user time' in line: pstat_value = float('0.' + line.split()[-1].split('.')[-1]) psutil_value = p.cpu_times().user - self.assertEqual(pstat_value, psutil_value) + assert pstat_value == psutil_value tested.append(None) elif 'system time' in line: pstat_value = float('0.' + line.split()[-1].split('.')[-1]) psutil_value = p.cpu_times().system - self.assertEqual(pstat_value, psutil_value) + assert pstat_value == psutil_value tested.append(None) if len(tested) != 2: raise RuntimeError("couldn't find lines match in procstat out") -@unittest.skipIf(not FREEBSD, "FREEBSD only") +@pytest.mark.skipif(not FREEBSD, reason="FREEBSD only") class FreeBSDSystemTestCase(PsutilTestCase): @staticmethod def parse_swapinfo(): @@ -254,7 +256,7 @@ def parse_swapinfo(): parts = re.split(r'\s+', output) if not parts: - raise ValueError("Can't parse swapinfo: %s" % output) + raise ValueError(f"Can't parse swapinfo: {output}") # the size is in 1k units, so multiply by 1024 total, used, free = (int(p) * 1024 for p in parts[1:4]) @@ -267,8 +269,8 @@ def test_cpu_frequency_against_sysctl(self): try: sysctl_result = int(sysctl(sensor)) except RuntimeError: - raise unittest.SkipTest("frequencies not supported by kernel") - self.assertEqual(psutil.cpu_freq().current, sysctl_result) + raise pytest.skip("frequencies not supported by kernel") + assert psutil.cpu_freq().current == sysctl_result sensor = "dev.cpu.0.freq_levels" sysctl_result = sysctl(sensor) @@ -277,136 +279,114 @@ def test_cpu_frequency_against_sysctl(self): # Ordered highest available to lowest available. max_freq = int(sysctl_result.split()[0].split("/")[0]) min_freq = int(sysctl_result.split()[-1].split("/")[0]) - self.assertEqual(psutil.cpu_freq().max, max_freq) - self.assertEqual(psutil.cpu_freq().min, min_freq) + assert psutil.cpu_freq().max == max_freq + assert psutil.cpu_freq().min == min_freq # --- virtual_memory(); tests against sysctl @retry_on_failure() def test_vmem_active(self): syst = sysctl("vm.stats.vm.v_active_count") * PAGESIZE - self.assertAlmostEqual( - psutil.virtual_memory().active, syst, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().active - syst) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_inactive(self): syst = sysctl("vm.stats.vm.v_inactive_count") * PAGESIZE - self.assertAlmostEqual( - psutil.virtual_memory().inactive, syst, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().inactive - syst) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_wired(self): syst = sysctl("vm.stats.vm.v_wire_count") * PAGESIZE - self.assertAlmostEqual( - psutil.virtual_memory().wired, syst, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().wired - syst) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_cached(self): syst = sysctl("vm.stats.vm.v_cache_count") * PAGESIZE - self.assertAlmostEqual( - psutil.virtual_memory().cached, syst, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().cached - syst) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_free(self): syst = sysctl("vm.stats.vm.v_free_count") * PAGESIZE - self.assertAlmostEqual( - psutil.virtual_memory().free, syst, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().free - syst) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_buffers(self): syst = sysctl("vfs.bufspace") - self.assertAlmostEqual( - psutil.virtual_memory().buffers, syst, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().buffers - syst) < TOLERANCE_SYS_MEM # --- virtual_memory(); tests against muse - @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") + @pytest.mark.skipif(not MUSE_AVAILABLE, reason="muse not installed") def test_muse_vmem_total(self): num = muse('Total') - self.assertEqual(psutil.virtual_memory().total, num) + assert psutil.virtual_memory().total == num - @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") + @pytest.mark.skipif(not MUSE_AVAILABLE, reason="muse not installed") @retry_on_failure() def test_muse_vmem_active(self): num = muse('Active') - self.assertAlmostEqual( - psutil.virtual_memory().active, num, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().active - num) < TOLERANCE_SYS_MEM - @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") + @pytest.mark.skipif(not MUSE_AVAILABLE, reason="muse not installed") @retry_on_failure() def test_muse_vmem_inactive(self): num = muse('Inactive') - self.assertAlmostEqual( - psutil.virtual_memory().inactive, num, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().inactive - num) < TOLERANCE_SYS_MEM - @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") + @pytest.mark.skipif(not MUSE_AVAILABLE, reason="muse not installed") @retry_on_failure() def test_muse_vmem_wired(self): num = muse('Wired') - self.assertAlmostEqual( - psutil.virtual_memory().wired, num, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().wired - num) < TOLERANCE_SYS_MEM - @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") + @pytest.mark.skipif(not MUSE_AVAILABLE, reason="muse not installed") @retry_on_failure() def test_muse_vmem_cached(self): num = muse('Cache') - self.assertAlmostEqual( - psutil.virtual_memory().cached, num, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().cached - num) < TOLERANCE_SYS_MEM - @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") + @pytest.mark.skipif(not MUSE_AVAILABLE, reason="muse not installed") @retry_on_failure() def test_muse_vmem_free(self): num = muse('Free') - self.assertAlmostEqual( - psutil.virtual_memory().free, num, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().free - num) < TOLERANCE_SYS_MEM - @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") + @pytest.mark.skipif(not MUSE_AVAILABLE, reason="muse not installed") @retry_on_failure() def test_muse_vmem_buffers(self): num = muse('Buffer') - self.assertAlmostEqual( - psutil.virtual_memory().buffers, num, delta=TOLERANCE_SYS_MEM - ) + assert abs(psutil.virtual_memory().buffers - num) < TOLERANCE_SYS_MEM def test_cpu_stats_ctx_switches(self): - self.assertAlmostEqual( - psutil.cpu_stats().ctx_switches, - sysctl('vm.stats.sys.v_swtch'), - delta=1000, + assert ( + abs( + psutil.cpu_stats().ctx_switches + - sysctl('vm.stats.sys.v_swtch') + ) + < 1000 ) def test_cpu_stats_interrupts(self): - self.assertAlmostEqual( - psutil.cpu_stats().interrupts, - sysctl('vm.stats.sys.v_intr'), - delta=1000, + assert ( + abs(psutil.cpu_stats().interrupts - sysctl('vm.stats.sys.v_intr')) + < 1000 ) def test_cpu_stats_soft_interrupts(self): - self.assertAlmostEqual( - psutil.cpu_stats().soft_interrupts, - sysctl('vm.stats.sys.v_soft'), - delta=1000, + assert ( + abs( + psutil.cpu_stats().soft_interrupts + - sysctl('vm.stats.sys.v_soft') + ) + < 1000 ) @retry_on_failure() def test_cpu_stats_syscalls(self): # pretty high tolerance but it looks like it's OK. - self.assertAlmostEqual( - psutil.cpu_stats().syscalls, - sysctl('vm.stats.sys.v_syscall'), - delta=200000, + assert ( + abs(psutil.cpu_stats().syscalls - sysctl('vm.stats.sys.v_syscall')) + < 200000 ) # def test_cpu_stats_traps(self): @@ -416,22 +396,16 @@ def test_cpu_stats_syscalls(self): # --- swap memory def test_swapmem_free(self): - total, used, free = self.parse_swapinfo() - self.assertAlmostEqual( - psutil.swap_memory().free, free, delta=TOLERANCE_SYS_MEM - ) + _total, _used, free = self.parse_swapinfo() + assert abs(psutil.swap_memory().free - free) < TOLERANCE_SYS_MEM def test_swapmem_used(self): - total, used, free = self.parse_swapinfo() - self.assertAlmostEqual( - psutil.swap_memory().used, used, delta=TOLERANCE_SYS_MEM - ) + _total, used, _free = self.parse_swapinfo() + assert abs(psutil.swap_memory().used - used) < TOLERANCE_SYS_MEM def test_swapmem_total(self): - total, used, free = self.parse_swapinfo() - self.assertAlmostEqual( - psutil.swap_memory().total, total, delta=TOLERANCE_SYS_MEM - ) + total, _used, _free = self.parse_swapinfo() + assert abs(psutil.swap_memory().total - total) < TOLERANCE_SYS_MEM # --- others @@ -440,78 +414,77 @@ def test_boot_time(self): s = s[s.find(" sec = ") + 7 :] s = s[: s.find(',')] btime = int(s) - self.assertEqual(btime, psutil.boot_time()) + assert btime == psutil.boot_time() # --- sensors_battery - @unittest.skipIf(not HAS_BATTERY, "no battery") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") def test_sensors_battery(self): def secs2hours(secs): - m, s = divmod(secs, 60) + m, _s = divmod(secs, 60) h, m = divmod(m, 60) - return "%d:%02d" % (h, m) + return f"{int(h)}:{int(m):02}" out = sh("acpiconf -i 0") - fields = dict( - [(x.split('\t')[0], x.split('\t')[-1]) for x in out.split("\n")] - ) + fields = {x.split('\t')[0]: x.split('\t')[-1] for x in out.split("\n")} metrics = psutil.sensors_battery() percent = int(fields['Remaining capacity:'].replace('%', '')) remaining_time = fields['Remaining time:'] - self.assertEqual(metrics.percent, percent) + assert metrics.percent == percent if remaining_time == 'unknown': - self.assertEqual(metrics.secsleft, psutil.POWER_TIME_UNLIMITED) + assert metrics.secsleft == psutil.POWER_TIME_UNLIMITED else: - self.assertEqual(secs2hours(metrics.secsleft), remaining_time) + assert secs2hours(metrics.secsleft) == remaining_time - @unittest.skipIf(not HAS_BATTERY, "no battery") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") def test_sensors_battery_against_sysctl(self): - self.assertEqual( - psutil.sensors_battery().percent, sysctl("hw.acpi.battery.life") + assert psutil.sensors_battery().percent == sysctl( + "hw.acpi.battery.life" ) - self.assertEqual( - psutil.sensors_battery().power_plugged, - sysctl("hw.acpi.acline") == 1, + assert psutil.sensors_battery().power_plugged == ( + sysctl("hw.acpi.acline") == 1 ) secsleft = psutil.sensors_battery().secsleft if secsleft < 0: - self.assertEqual(sysctl("hw.acpi.battery.time"), -1) + assert sysctl("hw.acpi.battery.time") == -1 else: - self.assertEqual(secsleft, sysctl("hw.acpi.battery.time") * 60) + assert secsleft == sysctl("hw.acpi.battery.time") * 60 - @unittest.skipIf(HAS_BATTERY, "has battery") + @pytest.mark.skipif(HAS_BATTERY, reason="has battery") def test_sensors_battery_no_battery(self): # If no battery is present one of these calls is supposed # to fail, see: # https://github.com/giampaolo/psutil/issues/1074 - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): sysctl("hw.acpi.battery.life") sysctl("hw.acpi.battery.time") sysctl("hw.acpi.acline") - self.assertIsNone(psutil.sensors_battery()) + assert psutil.sensors_battery() is None # --- sensors_temperatures def test_sensors_temperatures_against_sysctl(self): num_cpus = psutil.cpu_count(True) for cpu in range(num_cpus): - sensor = "dev.cpu.%s.temperature" % cpu + sensor = f"dev.cpu.{cpu}.temperature" # sysctl returns a string in the format 46.0C try: sysctl_result = int(float(sysctl(sensor)[:-1])) except RuntimeError: - raise unittest.SkipTest("temperatures not supported by kernel") - self.assertAlmostEqual( - psutil.sensors_temperatures()["coretemp"][cpu].current, - sysctl_result, - delta=10, + raise pytest.skip("temperatures not supported by kernel") + assert ( + abs( + psutil.sensors_temperatures()["coretemp"][cpu].current + - sysctl_result + ) + < 10 ) - sensor = "dev.cpu.%s.coretemp.tjmax" % cpu + sensor = f"dev.cpu.{cpu}.coretemp.tjmax" sysctl_result = int(float(sysctl(sensor)[:-1])) - self.assertEqual( - psutil.sensors_temperatures()["coretemp"][cpu].high, - sysctl_result, + assert ( + psutil.sensors_temperatures()["coretemp"][cpu].high + == sysctl_result ) @@ -520,13 +493,13 @@ def test_sensors_temperatures_against_sysctl(self): # ===================================================================== -@unittest.skipIf(not OPENBSD, "OPENBSD only") +@pytest.mark.skipif(not OPENBSD, reason="OPENBSD only") class OpenBSDTestCase(PsutilTestCase): def test_boot_time(self): s = sysctl('kern.boottime') sys_bt = datetime.datetime.strptime(s, "%a %b %d %H:%M:%S %Y") psutil_bt = datetime.datetime.fromtimestamp(psutil.boot_time()) - self.assertEqual(sys_bt, psutil_bt) + assert sys_bt == psutil_bt # ===================================================================== @@ -534,7 +507,7 @@ def test_boot_time(self): # ===================================================================== -@unittest.skipIf(not NETBSD, "NETBSD only") +@pytest.mark.skipif(not NETBSD, reason="NETBSD only") class NetBSDTestCase(PsutilTestCase): @staticmethod def parse_meminfo(look_for): @@ -542,62 +515,60 @@ def parse_meminfo(look_for): for line in f: if line.startswith(look_for): return int(line.split()[1]) * 1024 - raise ValueError("can't find %s" % look_for) + raise ValueError(f"can't find {look_for}") # --- virtual mem def test_vmem_total(self): - self.assertEqual( - psutil.virtual_memory().total, self.parse_meminfo("MemTotal:") - ) + assert psutil.virtual_memory().total == self.parse_meminfo("MemTotal:") def test_vmem_free(self): - self.assertAlmostEqual( - psutil.virtual_memory().free, - self.parse_meminfo("MemFree:"), - delta=TOLERANCE_SYS_MEM, + assert ( + abs(psutil.virtual_memory().free - self.parse_meminfo("MemFree:")) + < TOLERANCE_SYS_MEM ) def test_vmem_buffers(self): - self.assertAlmostEqual( - psutil.virtual_memory().buffers, - self.parse_meminfo("Buffers:"), - delta=TOLERANCE_SYS_MEM, + assert ( + abs( + psutil.virtual_memory().buffers + - self.parse_meminfo("Buffers:") + ) + < TOLERANCE_SYS_MEM ) def test_vmem_shared(self): - self.assertAlmostEqual( - psutil.virtual_memory().shared, - self.parse_meminfo("MemShared:"), - delta=TOLERANCE_SYS_MEM, + assert ( + abs( + psutil.virtual_memory().shared + - self.parse_meminfo("MemShared:") + ) + < TOLERANCE_SYS_MEM ) def test_vmem_cached(self): - self.assertAlmostEqual( - psutil.virtual_memory().cached, - self.parse_meminfo("Cached:"), - delta=TOLERANCE_SYS_MEM, + assert ( + abs(psutil.virtual_memory().cached - self.parse_meminfo("Cached:")) + < TOLERANCE_SYS_MEM ) # --- swap mem def test_swapmem_total(self): - self.assertAlmostEqual( - psutil.swap_memory().total, - self.parse_meminfo("SwapTotal:"), - delta=TOLERANCE_SYS_MEM, + assert ( + abs(psutil.swap_memory().total - self.parse_meminfo("SwapTotal:")) + < TOLERANCE_SYS_MEM ) def test_swapmem_free(self): - self.assertAlmostEqual( - psutil.swap_memory().free, - self.parse_meminfo("SwapFree:"), - delta=TOLERANCE_SYS_MEM, + assert ( + abs(psutil.swap_memory().free - self.parse_meminfo("SwapFree:")) + < TOLERANCE_SYS_MEM ) def test_swapmem_used(self): smem = psutil.swap_memory() - self.assertEqual(smem.used, smem.total - smem.free) + assert smem.used == smem.total - smem.free # --- others @@ -609,9 +580,7 @@ def test_cpu_stats_interrupts(self): break else: raise ValueError("couldn't find line") - self.assertAlmostEqual( - psutil.cpu_stats().interrupts, interrupts, delta=1000 - ) + assert abs(psutil.cpu_stats().interrupts - interrupts) < 1000 def test_cpu_stats_ctx_switches(self): with open('/proc/stat', 'rb') as f: @@ -621,12 +590,4 @@ def test_cpu_stats_ctx_switches(self): break else: raise ValueError("couldn't find line") - self.assertAlmostEqual( - psutil.cpu_stats().ctx_switches, ctx_switches, delta=1000 - ) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert abs(psutil.cpu_stats().ctx_switches - ctx_switches) < 1000 diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py index de3ae59df..5ddeb855f 100755 --- a/psutil/tests/test_connections.py +++ b/psutil/tests/test_connections.py @@ -9,7 +9,6 @@ import os import socket import textwrap -import unittest from contextlib import closing from socket import AF_INET from socket import AF_INET6 @@ -26,7 +25,6 @@ from psutil import SUNOS from psutil import WINDOWS from psutil._common import supports_ipv6 -from psutil._compat import PY3 from psutil.tests import AF_UNIX from psutil.tests import HAS_NET_CONNECTIONS_UNIX from psutil.tests import SKIP_SYSCONS @@ -36,9 +34,9 @@ from psutil.tests import check_connection_ntuple from psutil.tests import create_sockets from psutil.tests import filter_proc_net_connections +from psutil.tests import pytest from psutil.tests import reap_children from psutil.tests import retry_on_failure -from psutil.tests import serialrun from psutil.tests import skip_on_access_denied from psutil.tests import tcp_socketpair from psutil.tests import unix_socketpair @@ -50,19 +48,19 @@ def this_proc_net_connections(kind): cons = psutil.Process().net_connections(kind=kind) - if kind in ("all", "unix"): + if kind in {"all", "unix"}: return filter_proc_net_connections(cons) return cons -@serialrun +@pytest.mark.xdist_group(name="serial") class ConnectionTestCase(PsutilTestCase): def setUp(self): - self.assertEqual(this_proc_net_connections(kind='all'), []) + assert this_proc_net_connections(kind='all') == [] def tearDown(self): # Make sure we closed all resources. - self.assertEqual(this_proc_net_connections(kind='all'), []) + assert this_proc_net_connections(kind='all') == [] def compare_procsys_connections(self, pid, proc_cons, kind='all'): """Given a process PID and its list of connections compare @@ -82,11 +80,11 @@ def compare_procsys_connections(self, pid, proc_cons, kind='all'): sys_cons = [c[:-1] for c in sys_cons if c.pid == pid] sys_cons.sort() proc_cons.sort() - self.assertEqual(proc_cons, sys_cons) + assert proc_cons == sys_cons class TestBasicOperations(ConnectionTestCase): - @unittest.skipIf(SKIP_SYSCONS, "requires root") + @pytest.mark.skipif(SKIP_SYSCONS, reason="requires root") def test_system(self): with create_sockets(): for conn in psutil.net_connections(kind='all'): @@ -98,25 +96,27 @@ def test_process(self): check_connection_ntuple(conn) def test_invalid_kind(self): - self.assertRaises(ValueError, this_proc_net_connections, kind='???') - self.assertRaises(ValueError, psutil.net_connections, kind='???') + with pytest.raises(ValueError): + this_proc_net_connections(kind='???') + with pytest.raises(ValueError): + psutil.net_connections(kind='???') -@serialrun +@pytest.mark.xdist_group(name="serial") class TestUnconnectedSockets(ConnectionTestCase): """Tests sockets which are open but not connected to anything.""" def get_conn_from_sock(self, sock): cons = this_proc_net_connections(kind='all') - smap = dict([(c.fd, c) for c in cons]) + smap = {c.fd: c for c in cons} if NETBSD or FREEBSD: # NetBSD opens a UNIX socket to /var/log/run # so there may be more connections. return smap[sock.fileno()] else: - self.assertEqual(len(cons), 1) + assert len(cons) == 1 if cons[0].fd != -1: - self.assertEqual(smap[sock.fileno()].fd, sock.fileno()) + assert smap[sock.fileno()].fd == sock.fileno() return cons[0] def check_socket(self, sock): @@ -129,21 +129,19 @@ def check_socket(self, sock): # fd, family, type if conn.fd != -1: - self.assertEqual(conn.fd, sock.fileno()) - self.assertEqual(conn.family, sock.family) + assert conn.fd == sock.fileno() + assert conn.family == sock.family # see: http://bugs.python.org/issue30204 - self.assertEqual( - conn.type, sock.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE) - ) + assert conn.type == sock.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE) # local address laddr = sock.getsockname() - if not laddr and PY3 and isinstance(laddr, bytes): + if not laddr and isinstance(laddr, bytes): # See: http://bugs.python.org/issue30205 laddr = laddr.decode() if sock.family == AF_INET6: laddr = laddr[:2] - self.assertEqual(conn.laddr, laddr) + assert conn.laddr == laddr # XXX Solaris can't retrieve system-wide UNIX sockets if sock.family == AF_UNIX and HAS_NET_CONNECTIONS_UNIX: @@ -155,50 +153,50 @@ def test_tcp_v4(self): addr = ("127.0.0.1", 0) with closing(bind_socket(AF_INET, SOCK_STREAM, addr=addr)) as sock: conn = self.check_socket(sock) - self.assertEqual(conn.raddr, ()) - self.assertEqual(conn.status, psutil.CONN_LISTEN) + assert conn.raddr == () + assert conn.status == psutil.CONN_LISTEN - @unittest.skipIf(not supports_ipv6(), "IPv6 not supported") + @pytest.mark.skipif(not supports_ipv6(), reason="IPv6 not supported") def test_tcp_v6(self): addr = ("::1", 0) with closing(bind_socket(AF_INET6, SOCK_STREAM, addr=addr)) as sock: conn = self.check_socket(sock) - self.assertEqual(conn.raddr, ()) - self.assertEqual(conn.status, psutil.CONN_LISTEN) + assert conn.raddr == () + assert conn.status == psutil.CONN_LISTEN def test_udp_v4(self): addr = ("127.0.0.1", 0) with closing(bind_socket(AF_INET, SOCK_DGRAM, addr=addr)) as sock: conn = self.check_socket(sock) - self.assertEqual(conn.raddr, ()) - self.assertEqual(conn.status, psutil.CONN_NONE) + assert conn.raddr == () + assert conn.status == psutil.CONN_NONE - @unittest.skipIf(not supports_ipv6(), "IPv6 not supported") + @pytest.mark.skipif(not supports_ipv6(), reason="IPv6 not supported") def test_udp_v6(self): addr = ("::1", 0) with closing(bind_socket(AF_INET6, SOCK_DGRAM, addr=addr)) as sock: conn = self.check_socket(sock) - self.assertEqual(conn.raddr, ()) - self.assertEqual(conn.status, psutil.CONN_NONE) + assert conn.raddr == () + assert conn.status == psutil.CONN_NONE - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_unix_tcp(self): testfn = self.get_testfn() with closing(bind_unix_socket(testfn, type=SOCK_STREAM)) as sock: conn = self.check_socket(sock) - self.assertEqual(conn.raddr, "") - self.assertEqual(conn.status, psutil.CONN_NONE) + assert conn.raddr == "" + assert conn.status == psutil.CONN_NONE - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_unix_udp(self): testfn = self.get_testfn() with closing(bind_unix_socket(testfn, type=SOCK_STREAM)) as sock: conn = self.check_socket(sock) - self.assertEqual(conn.raddr, "") - self.assertEqual(conn.status, psutil.CONN_NONE) + assert conn.raddr == "" + assert conn.status == psutil.CONN_NONE -@serialrun +@pytest.mark.xdist_group(name="serial") class TestConnectedSocket(ConnectionTestCase): """Test socket pairs which are actually connected to each other. @@ -206,16 +204,16 @@ class TestConnectedSocket(ConnectionTestCase): # On SunOS, even after we close() it, the server socket stays around # in TIME_WAIT state. - @unittest.skipIf(SUNOS, "unreliable on SUONS") + @pytest.mark.skipif(SUNOS, reason="unreliable on SUONS") def test_tcp(self): addr = ("127.0.0.1", 0) - self.assertEqual(this_proc_net_connections(kind='tcp4'), []) + assert this_proc_net_connections(kind='tcp4') == [] server, client = tcp_socketpair(AF_INET, addr=addr) try: cons = this_proc_net_connections(kind='tcp4') - self.assertEqual(len(cons), 2) - self.assertEqual(cons[0].status, psutil.CONN_ESTABLISHED) - self.assertEqual(cons[1].status, psutil.CONN_ESTABLISHED) + assert len(cons) == 2 + assert cons[0].status == psutil.CONN_ESTABLISHED + assert cons[1].status == psutil.CONN_ESTABLISHED # May not be fast enough to change state so it stays # commenteed. # client.close() @@ -226,7 +224,7 @@ def test_tcp(self): server.close() client.close() - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_unix(self): testfn = self.get_testfn() server, client = unix_socketpair(testfn) @@ -238,17 +236,17 @@ def test_unix(self): # On NetBSD creating a UNIX socket will cause # a UNIX connection to /var/run/log. cons = [c for c in cons if c.raddr != '/var/run/log'] - self.assertEqual(len(cons), 2, msg=cons) + assert len(cons) == 2 if LINUX or FREEBSD or SUNOS or OPENBSD: # remote path is never set - self.assertEqual(cons[0].raddr, "") - self.assertEqual(cons[1].raddr, "") + assert cons[0].raddr == "" + assert cons[1].raddr == "" # one local address should though - self.assertEqual(testfn, cons[0].laddr or cons[1].laddr) + assert testfn == (cons[0].laddr or cons[1].laddr) else: # On other systems either the laddr or raddr # of both peers are set. - self.assertEqual(cons[0].laddr or cons[1].laddr, testfn) + assert (cons[0].laddr or cons[1].laddr) == testfn finally: server.close() client.close() @@ -258,12 +256,12 @@ class TestFilters(ConnectionTestCase): def test_filters(self): def check(kind, families, types): for conn in this_proc_net_connections(kind=kind): - self.assertIn(conn.family, families) - self.assertIn(conn.type, types) + assert conn.family in families + assert conn.type in types if not SKIP_SYSCONS: for conn in psutil.net_connections(kind=kind): - self.assertIn(conn.family, families) - self.assertIn(conn.type, types) + assert conn.family in families + assert conn.type in types with create_sockets(): check( @@ -304,17 +302,17 @@ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): "udp6", ) check_connection_ntuple(conn) - self.assertEqual(conn.family, family) - self.assertEqual(conn.type, type) - self.assertEqual(conn.laddr, laddr) - self.assertEqual(conn.raddr, raddr) - self.assertEqual(conn.status, status) + assert conn.family == family + assert conn.type == type + assert conn.laddr == laddr + assert conn.raddr == raddr + assert conn.status == status for kind in all_kinds: cons = proc.net_connections(kind=kind) if kind in kinds: - self.assertNotEqual(cons, []) + assert cons != [] else: - self.assertEqual(cons, []) + assert cons == [] # compare against system-wide connections # XXX Solaris can't retrieve system-wide UNIX # sockets. @@ -328,7 +326,7 @@ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): s.listen(5) with open('{testfn}', 'w') as f: f.write(str(s.getsockname()[:2])) - time.sleep(60) + [time.sleep(0.1) for x in range(100)] """) udp_template = textwrap.dedent(""" @@ -337,7 +335,7 @@ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): s.bind(('{addr}', 0)) with open('{testfn}', 'w') as f: f.write(str(s.getsockname()[:2])) - time.sleep(60) + [time.sleep(0.1) for x in range(100)] """) # must be relative on Windows @@ -358,14 +356,14 @@ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): # launch various subprocess instantiating a socket of various # families and types to enrich psutil results tcp4_proc = self.pyrun(tcp4_template) - tcp4_addr = eval(wait_for_file(testfile, delete=True)) # noqa + tcp4_addr = eval(wait_for_file(testfile, delete=True)) udp4_proc = self.pyrun(udp4_template) - udp4_addr = eval(wait_for_file(testfile, delete=True)) # noqa + udp4_addr = eval(wait_for_file(testfile, delete=True)) if supports_ipv6(): tcp6_proc = self.pyrun(tcp6_template) - tcp6_addr = eval(wait_for_file(testfile, delete=True)) # noqa + tcp6_addr = eval(wait_for_file(testfile, delete=True)) udp6_proc = self.pyrun(udp6_template) - udp6_addr = eval(wait_for_file(testfile, delete=True)) # noqa + udp6_addr = eval(wait_for_file(testfile, delete=True)) else: tcp6_proc = None udp6_proc = None @@ -374,7 +372,7 @@ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): for p in psutil.Process().children(): cons = p.net_connections() - self.assertEqual(len(cons), 1) + assert len(cons) == 1 for conn in cons: # TCP v4 if p.pid == tcp4_proc.pid: @@ -429,71 +427,71 @@ def test_count(self): with create_sockets(): # tcp cons = this_proc_net_connections(kind='tcp') - self.assertEqual(len(cons), 2 if supports_ipv6() else 1) + assert len(cons) == (2 if supports_ipv6() else 1) for conn in cons: - self.assertIn(conn.family, (AF_INET, AF_INET6)) - self.assertEqual(conn.type, SOCK_STREAM) + assert conn.family in {AF_INET, AF_INET6} + assert conn.type == SOCK_STREAM # tcp4 cons = this_proc_net_connections(kind='tcp4') - self.assertEqual(len(cons), 1) - self.assertEqual(cons[0].family, AF_INET) - self.assertEqual(cons[0].type, SOCK_STREAM) + assert len(cons) == 1 + assert cons[0].family == AF_INET + assert cons[0].type == SOCK_STREAM # tcp6 if supports_ipv6(): cons = this_proc_net_connections(kind='tcp6') - self.assertEqual(len(cons), 1) - self.assertEqual(cons[0].family, AF_INET6) - self.assertEqual(cons[0].type, SOCK_STREAM) + assert len(cons) == 1 + assert cons[0].family == AF_INET6 + assert cons[0].type == SOCK_STREAM # udp cons = this_proc_net_connections(kind='udp') - self.assertEqual(len(cons), 2 if supports_ipv6() else 1) + assert len(cons) == (2 if supports_ipv6() else 1) for conn in cons: - self.assertIn(conn.family, (AF_INET, AF_INET6)) - self.assertEqual(conn.type, SOCK_DGRAM) + assert conn.family in {AF_INET, AF_INET6} + assert conn.type == SOCK_DGRAM # udp4 cons = this_proc_net_connections(kind='udp4') - self.assertEqual(len(cons), 1) - self.assertEqual(cons[0].family, AF_INET) - self.assertEqual(cons[0].type, SOCK_DGRAM) + assert len(cons) == 1 + assert cons[0].family == AF_INET + assert cons[0].type == SOCK_DGRAM # udp6 if supports_ipv6(): cons = this_proc_net_connections(kind='udp6') - self.assertEqual(len(cons), 1) - self.assertEqual(cons[0].family, AF_INET6) - self.assertEqual(cons[0].type, SOCK_DGRAM) + assert len(cons) == 1 + assert cons[0].family == AF_INET6 + assert cons[0].type == SOCK_DGRAM # inet cons = this_proc_net_connections(kind='inet') - self.assertEqual(len(cons), 4 if supports_ipv6() else 2) + assert len(cons) == (4 if supports_ipv6() else 2) for conn in cons: - self.assertIn(conn.family, (AF_INET, AF_INET6)) - self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM)) + assert conn.family in {AF_INET, AF_INET6} + assert conn.type in {SOCK_STREAM, SOCK_DGRAM} # inet6 if supports_ipv6(): cons = this_proc_net_connections(kind='inet6') - self.assertEqual(len(cons), 2) + assert len(cons) == 2 for conn in cons: - self.assertEqual(conn.family, AF_INET6) - self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM)) + assert conn.family == AF_INET6 + assert conn.type in {SOCK_STREAM, SOCK_DGRAM} # Skipped on BSD becayse by default the Python process # creates a UNIX socket to '/var/run/log'. if HAS_NET_CONNECTIONS_UNIX and not (FREEBSD or NETBSD): cons = this_proc_net_connections(kind='unix') - self.assertEqual(len(cons), 3) + assert len(cons) == 3 for conn in cons: - self.assertEqual(conn.family, AF_UNIX) - self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM)) + assert conn.family == AF_UNIX + assert conn.type in {SOCK_STREAM, SOCK_DGRAM} -@unittest.skipIf(SKIP_SYSCONS, "requires root") +@pytest.mark.skipif(SKIP_SYSCONS, reason="requires root") class TestSystemWideConnections(ConnectionTestCase): """Tests for net_connections().""" def test_it(self): def check(cons, families, types_): for conn in cons: - self.assertIn(conn.family, families, msg=conn) + assert conn.family in families if conn.family != AF_UNIX: - self.assertIn(conn.type, types_, msg=conn) + assert conn.type in types_ check_connection_ntuple(conn) with create_sockets(): @@ -505,7 +503,7 @@ def check(cons, families, types_): continue families, types_ = groups cons = psutil.net_connections(kind) - self.assertEqual(len(cons), len(set(cons))) + assert len(cons) == len(set(cons)) check(cons, families, types_) @retry_on_failure() @@ -524,14 +522,14 @@ def test_multi_sockets_procs(self): for _ in range(times): fname = self.get_testfn() fnames.append(fname) - src = textwrap.dedent("""\ + src = textwrap.dedent(f"""\ import time, os from psutil.tests import create_sockets with create_sockets(): - with open(r'%s', 'w') as f: + with open(r'{fname}', 'w') as f: f.write("hello") - time.sleep(60) - """ % fname) + [time.sleep(0.1) for x in range(100)] + """) sproc = self.pyrun(src) pids.append(sproc.pid) @@ -543,11 +541,9 @@ def test_multi_sockets_procs(self): x for x in psutil.net_connections(kind='all') if x.pid in pids ] for pid in pids: - self.assertEqual( - len([x for x in syscons if x.pid == pid]), expected - ) + assert len([x for x in syscons if x.pid == pid]) == expected p = psutil.Process(pid) - self.assertEqual(len(p.net_connections('all')), expected) + assert len(p.net_connections('all')) == expected class TestMisc(PsutilTestCase): @@ -559,18 +555,12 @@ def test_net_connection_constants(self): num = getattr(psutil, name) str_ = str(num) assert str_.isupper(), str_ - self.assertNotIn(str, strs) - self.assertNotIn(num, ints) + assert str not in strs + assert num not in ints ints.append(num) strs.append(str_) if SUNOS: - psutil.CONN_IDLE # noqa - psutil.CONN_BOUND # noqa + psutil.CONN_IDLE # noqa: B018 + psutil.CONN_BOUND # noqa: B018 if WINDOWS: - psutil.CONN_DELETE_TCB # noqa - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + psutil.CONN_DELETE_TCB # noqa: B018 diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index e768e7d52..7406d98ad 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -11,7 +11,6 @@ import platform import signal -import unittest import psutil from psutil import AIX @@ -23,19 +22,19 @@ from psutil import POSIX from psutil import SUNOS from psutil import WINDOWS -from psutil._compat import long from psutil.tests import GITHUB_ACTIONS from psutil.tests import HAS_CPU_FREQ from psutil.tests import HAS_NET_IO_COUNTERS from psutil.tests import HAS_SENSORS_FANS from psutil.tests import HAS_SENSORS_TEMPERATURES -from psutil.tests import PYPY +from psutil.tests import QEMU_USER from psutil.tests import SKIP_SYSCONS from psutil.tests import PsutilTestCase from psutil.tests import create_sockets from psutil.tests import enum from psutil.tests import is_namedtuple from psutil.tests import kernel_version +from psutil.tests import pytest # =================================================================== @@ -48,7 +47,7 @@ class TestAvailConstantsAPIs(PsutilTestCase): def test_PROCFS_PATH(self): - self.assertEqual(hasattr(psutil, "PROCFS_PATH"), LINUX or SUNOS or AIX) + assert hasattr(psutil, "PROCFS_PATH") == (LINUX or SUNOS or AIX) def test_win_priority(self): ae = self.assertEqual @@ -73,8 +72,9 @@ def test_linux_ioprio_windows(self): ae(hasattr(psutil, "IOPRIO_LOW"), WINDOWS) ae(hasattr(psutil, "IOPRIO_VERYLOW"), WINDOWS) - @unittest.skipIf( - GITHUB_ACTIONS and LINUX, "unsupported on GITHUB_ACTIONS + LINUX" + @pytest.mark.skipif( + GITHUB_ACTIONS and LINUX, + reason="unsupported on GITHUB_ACTIONS + LINUX", ) def test_rlimit(self): ae = self.assertEqual @@ -110,36 +110,31 @@ def test_rlimit(self): class TestAvailSystemAPIs(PsutilTestCase): def test_win_service_iter(self): - self.assertEqual(hasattr(psutil, "win_service_iter"), WINDOWS) + assert hasattr(psutil, "win_service_iter") == WINDOWS def test_win_service_get(self): - self.assertEqual(hasattr(psutil, "win_service_get"), WINDOWS) + assert hasattr(psutil, "win_service_get") == WINDOWS def test_cpu_freq(self): - self.assertEqual( - hasattr(psutil, "cpu_freq"), - LINUX or MACOS or WINDOWS or FREEBSD or OPENBSD, + assert hasattr(psutil, "cpu_freq") == ( + LINUX or MACOS or WINDOWS or FREEBSD or OPENBSD ) def test_sensors_temperatures(self): - self.assertEqual( - hasattr(psutil, "sensors_temperatures"), LINUX or FREEBSD - ) + assert hasattr(psutil, "sensors_temperatures") == (LINUX or FREEBSD) def test_sensors_fans(self): - self.assertEqual(hasattr(psutil, "sensors_fans"), LINUX) + assert hasattr(psutil, "sensors_fans") == LINUX def test_battery(self): - self.assertEqual( - hasattr(psutil, "sensors_battery"), - LINUX or WINDOWS or FREEBSD or MACOS, + assert hasattr(psutil, "sensors_battery") == ( + LINUX or WINDOWS or FREEBSD or MACOS ) class TestAvailProcessAPIs(PsutilTestCase): def test_environ(self): - self.assertEqual( - hasattr(psutil.Process, "environ"), + assert hasattr(psutil.Process, "environ") == ( LINUX or MACOS or WINDOWS @@ -147,51 +142,51 @@ def test_environ(self): or SUNOS or FREEBSD or OPENBSD - or NETBSD, + or NETBSD ) def test_uids(self): - self.assertEqual(hasattr(psutil.Process, "uids"), POSIX) + assert hasattr(psutil.Process, "uids") == POSIX def test_gids(self): - self.assertEqual(hasattr(psutil.Process, "uids"), POSIX) + assert hasattr(psutil.Process, "uids") == POSIX def test_terminal(self): - self.assertEqual(hasattr(psutil.Process, "terminal"), POSIX) + assert hasattr(psutil.Process, "terminal") == POSIX def test_ionice(self): - self.assertEqual(hasattr(psutil.Process, "ionice"), LINUX or WINDOWS) + assert hasattr(psutil.Process, "ionice") == (LINUX or WINDOWS) - @unittest.skipIf( - GITHUB_ACTIONS and LINUX, "unsupported on GITHUB_ACTIONS + LINUX" + @pytest.mark.skipif( + GITHUB_ACTIONS and LINUX, + reason="unsupported on GITHUB_ACTIONS + LINUX", ) def test_rlimit(self): - self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX or FREEBSD) + assert hasattr(psutil.Process, "rlimit") == (LINUX or FREEBSD) def test_io_counters(self): hasit = hasattr(psutil.Process, "io_counters") - self.assertEqual(hasit, not (MACOS or SUNOS)) + assert hasit == (not (MACOS or SUNOS)) def test_num_fds(self): - self.assertEqual(hasattr(psutil.Process, "num_fds"), POSIX) + assert hasattr(psutil.Process, "num_fds") == POSIX def test_num_handles(self): - self.assertEqual(hasattr(psutil.Process, "num_handles"), WINDOWS) + assert hasattr(psutil.Process, "num_handles") == WINDOWS def test_cpu_affinity(self): - self.assertEqual( - hasattr(psutil.Process, "cpu_affinity"), - LINUX or WINDOWS or FREEBSD, + assert hasattr(psutil.Process, "cpu_affinity") == ( + LINUX or WINDOWS or FREEBSD ) def test_cpu_num(self): - self.assertEqual( - hasattr(psutil.Process, "cpu_num"), LINUX or FREEBSD or SUNOS + assert hasattr(psutil.Process, "cpu_num") == ( + LINUX or FREEBSD or SUNOS ) def test_memory_maps(self): hasit = hasattr(psutil.Process, "memory_maps") - self.assertEqual(hasit, not (OPENBSD or NETBSD or AIX or MACOS)) + assert hasit == (not (OPENBSD or NETBSD or AIX or MACOS)) # =================================================================== @@ -201,7 +196,6 @@ def test_memory_maps(self): class TestSystemAPITypes(PsutilTestCase): """Check the return types of system related APIs. - Mainly we want to test we never return unicode on Python 2, see: https://github.com/giampaolo/psutil/issues/1039. """ @@ -212,9 +206,9 @@ def setUpClass(cls): def assert_ntuple_of_nums(self, nt, type_=float, gezero=True): assert is_namedtuple(nt) for n in nt: - self.assertIsInstance(n, type_) + assert isinstance(n, type_) if gezero: - self.assertGreaterEqual(n, 0) + assert n >= 0 def test_cpu_times(self): self.assert_ntuple_of_nums(psutil.cpu_times()) @@ -222,126 +216,112 @@ def test_cpu_times(self): self.assert_ntuple_of_nums(nt) def test_cpu_percent(self): - self.assertIsInstance(psutil.cpu_percent(interval=None), float) - self.assertIsInstance(psutil.cpu_percent(interval=0.00001), float) + assert isinstance(psutil.cpu_percent(interval=None), float) + assert isinstance(psutil.cpu_percent(interval=0.00001), float) def test_cpu_times_percent(self): self.assert_ntuple_of_nums(psutil.cpu_times_percent(interval=None)) self.assert_ntuple_of_nums(psutil.cpu_times_percent(interval=0.0001)) def test_cpu_count(self): - self.assertIsInstance(psutil.cpu_count(), int) + assert isinstance(psutil.cpu_count(), int) # TODO: remove this once 1892 is fixed - @unittest.skipIf( - MACOS and platform.machine() == 'arm64', "skipped due to #1892" + @pytest.mark.skipif( + MACOS and platform.machine() == 'arm64', reason="skipped due to #1892" ) - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") def test_cpu_freq(self): if psutil.cpu_freq() is None: - raise unittest.SkipTest("cpu_freq() returns None") - self.assert_ntuple_of_nums(psutil.cpu_freq(), type_=(float, int, long)) + raise pytest.skip("cpu_freq() returns None") + self.assert_ntuple_of_nums(psutil.cpu_freq(), type_=(float, int)) def test_disk_io_counters(self): # Duplicate of test_system.py. Keep it anyway. for k, v in psutil.disk_io_counters(perdisk=True).items(): - self.assertIsInstance(k, str) - self.assert_ntuple_of_nums(v, type_=(int, long)) + assert isinstance(k, str) + self.assert_ntuple_of_nums(v, type_=int) def test_disk_partitions(self): # Duplicate of test_system.py. Keep it anyway. for disk in psutil.disk_partitions(): - self.assertIsInstance(disk.device, str) - self.assertIsInstance(disk.mountpoint, str) - self.assertIsInstance(disk.fstype, str) - self.assertIsInstance(disk.opts, str) + assert isinstance(disk.device, str) + assert isinstance(disk.mountpoint, str) + assert isinstance(disk.fstype, str) + assert isinstance(disk.opts, str) - @unittest.skipIf(SKIP_SYSCONS, "requires root") + @pytest.mark.skipif(SKIP_SYSCONS, reason="requires root") def test_net_connections(self): with create_sockets(): ret = psutil.net_connections('all') - self.assertEqual(len(ret), len(set(ret))) + assert len(ret) == len(set(ret)) for conn in ret: assert is_namedtuple(conn) def test_net_if_addrs(self): # Duplicate of test_system.py. Keep it anyway. for ifname, addrs in psutil.net_if_addrs().items(): - self.assertIsInstance(ifname, str) + assert isinstance(ifname, str) for addr in addrs: - if enum is not None and not PYPY: - self.assertIsInstance(addr.family, enum.IntEnum) - else: - self.assertIsInstance(addr.family, int) - self.assertIsInstance(addr.address, str) - self.assertIsInstance(addr.netmask, (str, type(None))) - self.assertIsInstance(addr.broadcast, (str, type(None))) + assert isinstance(addr.family, enum.IntEnum) + assert isinstance(addr.address, str) + assert isinstance(addr.netmask, (str, type(None))) + assert isinstance(addr.broadcast, (str, type(None))) + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_net_if_stats(self): # Duplicate of test_system.py. Keep it anyway. for ifname, info in psutil.net_if_stats().items(): - self.assertIsInstance(ifname, str) - self.assertIsInstance(info.isup, bool) - if enum is not None: - self.assertIsInstance(info.duplex, enum.IntEnum) - else: - self.assertIsInstance(info.duplex, int) - self.assertIsInstance(info.speed, int) - self.assertIsInstance(info.mtu, int) - - @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') + assert isinstance(ifname, str) + assert isinstance(info.isup, bool) + assert isinstance(info.duplex, enum.IntEnum) + assert isinstance(info.speed, int) + assert isinstance(info.mtu, int) + + @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported") def test_net_io_counters(self): # Duplicate of test_system.py. Keep it anyway. for ifname in psutil.net_io_counters(pernic=True): - self.assertIsInstance(ifname, str) + assert isinstance(ifname, str) - @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_FANS, reason="not supported") def test_sensors_fans(self): # Duplicate of test_system.py. Keep it anyway. for name, units in psutil.sensors_fans().items(): - self.assertIsInstance(name, str) + assert isinstance(name, str) for unit in units: - self.assertIsInstance(unit.label, str) - self.assertIsInstance(unit.current, (float, int, type(None))) + assert isinstance(unit.label, str) + assert isinstance(unit.current, (float, int, type(None))) - @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_TEMPERATURES, reason="not supported") def test_sensors_temperatures(self): # Duplicate of test_system.py. Keep it anyway. for name, units in psutil.sensors_temperatures().items(): - self.assertIsInstance(name, str) + assert isinstance(name, str) for unit in units: - self.assertIsInstance(unit.label, str) - self.assertIsInstance(unit.current, (float, int, type(None))) - self.assertIsInstance(unit.high, (float, int, type(None))) - self.assertIsInstance(unit.critical, (float, int, type(None))) + assert isinstance(unit.label, str) + assert isinstance(unit.current, (float, int, type(None))) + assert isinstance(unit.high, (float, int, type(None))) + assert isinstance(unit.critical, (float, int, type(None))) def test_boot_time(self): # Duplicate of test_system.py. Keep it anyway. - self.assertIsInstance(psutil.boot_time(), float) + assert isinstance(psutil.boot_time(), float) def test_users(self): # Duplicate of test_system.py. Keep it anyway. for user in psutil.users(): - self.assertIsInstance(user.name, str) - self.assertIsInstance(user.terminal, (str, type(None))) - self.assertIsInstance(user.host, (str, type(None))) - self.assertIsInstance(user.pid, (int, type(None))) + assert isinstance(user.name, str) + assert isinstance(user.terminal, (str, type(None))) + assert isinstance(user.host, (str, type(None))) + assert isinstance(user.pid, (int, type(None))) class TestProcessWaitType(PsutilTestCase): - @unittest.skipIf(not POSIX, "not POSIX") + @pytest.mark.skipif(not POSIX, reason="not POSIX") def test_negative_signal(self): p = psutil.Process(self.spawn_testproc().pid) p.terminate() code = p.wait() - self.assertEqual(code, -signal.SIGTERM) - if enum is not None: - self.assertIsInstance(code, enum.IntEnum) - else: - self.assertIsInstance(code, int) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert code == -signal.SIGTERM + assert isinstance(code, enum.IntEnum) diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index 233584c84..3672bc959 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -6,12 +6,10 @@ """Linux specific tests.""" -from __future__ import division import collections import contextlib import errno -import glob import io import os import re @@ -20,14 +18,12 @@ import struct import textwrap import time -import unittest import warnings +from unittest import mock import psutil from psutil import LINUX -from psutil._compat import PY3 -from psutil._compat import FileNotFoundError -from psutil._compat import basestring +from psutil.tests import AARCH64 from psutil.tests import GITHUB_ACTIONS from psutil.tests import GLOBAL_TIMEOUT from psutil.tests import HAS_BATTERY @@ -35,18 +31,19 @@ from psutil.tests import HAS_GETLOADAVG from psutil.tests import HAS_RLIMIT from psutil.tests import PYPY +from psutil.tests import PYTEST_PARALLEL +from psutil.tests import QEMU_USER from psutil.tests import TOLERANCE_DISK_USAGE from psutil.tests import TOLERANCE_SYS_MEM from psutil.tests import PsutilTestCase from psutil.tests import ThreadTask from psutil.tests import call_until -from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import reload_module from psutil.tests import retry_on_failure from psutil.tests import safe_rmpath from psutil.tests import sh from psutil.tests import skip_on_not_implemented -from psutil.tests import which if LINUX: @@ -58,15 +55,11 @@ HERE = os.path.abspath(os.path.dirname(__file__)) SIOCGIFADDR = 0x8915 -SIOCGIFCONF = 0x8912 SIOCGIFHWADDR = 0x8927 SIOCGIFNETMASK = 0x891B SIOCGIFBRDADDR = 0x8919 if LINUX: SECTOR_SIZE = 512 -EMPTY_TEMPERATURES = not glob.glob('/sys/class/hwmon/hwmon*') - - # ===================================================================== # --- utils # ===================================================================== @@ -75,11 +68,8 @@ def get_ipv4_address(ifname): import fcntl - ifname = ifname[:15] - if PY3: - ifname = bytes(ifname, 'ascii') - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - with contextlib.closing(s): + ifname = bytes(ifname[:15], "ascii") + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: return socket.inet_ntoa( fcntl.ioctl(s.fileno(), SIOCGIFADDR, struct.pack('256s', ifname))[ 20:24 @@ -90,11 +80,8 @@ def get_ipv4_address(ifname): def get_ipv4_netmask(ifname): import fcntl - ifname = ifname[:15] - if PY3: - ifname = bytes(ifname, 'ascii') - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - with contextlib.closing(s): + ifname = bytes(ifname[:15], "ascii") + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: return socket.inet_ntoa( fcntl.ioctl( s.fileno(), SIOCGIFNETMASK, struct.pack('256s', ifname) @@ -105,11 +92,8 @@ def get_ipv4_netmask(ifname): def get_ipv4_broadcast(ifname): import fcntl - ifname = ifname[:15] - if PY3: - ifname = bytes(ifname, 'ascii') - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - with contextlib.closing(s): + ifname = bytes(ifname[:15], "ascii") + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: return socket.inet_ntoa( fcntl.ioctl( s.fileno(), SIOCGIFBRDADDR, struct.pack('256s', ifname) @@ -126,7 +110,7 @@ def get_ipv6_addresses(ifname): all_fields.append(fields) if len(all_fields) == 0: - raise ValueError("could not find interface %r" % ifname) + raise ValueError(f"could not find interface {ifname!r}") for i in range(len(all_fields)): unformatted = all_fields[i][0] @@ -142,24 +126,12 @@ def get_ipv6_addresses(ifname): def get_mac_address(ifname): import fcntl - ifname = ifname[:15] - if PY3: - ifname = bytes(ifname, 'ascii') - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - with contextlib.closing(s): + ifname = bytes(ifname[:15], "ascii") + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: info = fcntl.ioctl( s.fileno(), SIOCGIFHWADDR, struct.pack('256s', ifname) ) - if PY3: - - def ord(x): - return x - - else: - import __builtin__ - - ord = __builtin__.ord - return ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1] + return "".join([f"{char:02x}:" for char in info[18:24]])[:-1] def free_swap(): @@ -173,9 +145,7 @@ def free_swap(): _, total, used, free = line.split() nt = collections.namedtuple('free', 'total used free') return nt(int(total), int(used), int(free)) - raise ValueError( - "can't find 'Swap' in 'free' output:\n%s" % '\n'.join(lines) - ) + raise ValueError(f"can't find 'Swap' in 'free' output:\n{out}") def free_physmem(): @@ -195,9 +165,7 @@ def free_physmem(): 'free', 'total used free shared output' ) return nt(total, used, free, shared, out) - raise ValueError( - "can't find 'Mem' in 'free' output:\n%s" % '\n'.join(lines) - ) + raise ValueError(f"can't find 'Mem' in 'free' output:\n{out}") def vmstat(stat): @@ -206,13 +174,13 @@ def vmstat(stat): line = line.strip() if stat in line: return int(line.split(' ')[0]) - raise ValueError("can't find %r in 'vmstat' output" % stat) + raise ValueError(f"can't find {stat!r} in 'vmstat' output") def get_free_version_info(): out = sh(["free", "-V"]).strip() if 'UNKNOWN' in out: - raise unittest.SkipTest("can't determine free version") + raise pytest.skip("can't determine free version") return tuple(map(int, re.findall(r'\d+', out.split()[-1]))) @@ -225,19 +193,15 @@ def mock_open_content(pairs): def open_mock(name, *args, **kwargs): if name in pairs: content = pairs[name] - if PY3: - if isinstance(content, basestring): - return io.StringIO(content) - else: - return io.BytesIO(content) + if isinstance(content, str): + return io.StringIO(content) else: return io.BytesIO(content) else: return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, create=True, side_effect=open_mock) as m: + with mock.patch("builtins.open", create=True, side_effect=open_mock) as m: yield m @@ -250,12 +214,10 @@ def mock_open_exception(for_path, exc): def open_mock(name, *args, **kwargs): if name == for_path: raise exc - else: - return orig_open(name, *args, **kwargs) + return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, create=True, side_effect=open_mock) as m: + with mock.patch("builtins.open", create=True, side_effect=open_mock) as m: yield m @@ -264,12 +226,12 @@ def open_mock(name, *args, **kwargs): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemVirtualMemoryAgainstFree(PsutilTestCase): def test_total(self): cli_value = free_physmem().total psutil_value = psutil.virtual_memory().total - self.assertEqual(cli_value, psutil_value) + assert cli_value == psutil_value @retry_on_failure() def test_used(self): @@ -277,35 +239,34 @@ def test_used(self): # This got changed in: # https://gitlab.com/procps-ng/procps/commit/ # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e + # Newer versions of procps are using yet another way to compute used + # memory. + # https://gitlab.com/procps-ng/procps/commit/ + # 2184e90d2e7cdb582f9a5b706b47015e56707e4d if get_free_version_info() < (3, 3, 12): - raise unittest.SkipTest("old free version") + raise pytest.skip("free version too old") + if get_free_version_info() >= (4, 0, 0): + raise pytest.skip("free version too recent") cli_value = free_physmem().used psutil_value = psutil.virtual_memory().used - self.assertAlmostEqual( - cli_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(cli_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_free(self): cli_value = free_physmem().free psutil_value = psutil.virtual_memory().free - self.assertAlmostEqual( - cli_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(cli_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_shared(self): free = free_physmem() free_value = free.shared if free_value == 0: - raise unittest.SkipTest("free does not support 'shared' column") + raise pytest.skip("free does not support 'shared' column") psutil_value = psutil.virtual_memory().shared - self.assertAlmostEqual( - free_value, - psutil_value, - delta=TOLERANCE_SYS_MEM, - msg='%s %s \n%s' % (free_value, psutil_value, free.output), - ) + assert ( + abs(free_value - psutil_value) < TOLERANCE_SYS_MEM + ), f"{free_value} {psutil_value} \n{free.output}" @retry_on_failure() def test_available(self): @@ -314,26 +275,18 @@ def test_available(self): out = sh(["free", "-b"]) lines = out.split('\n') if 'available' not in lines[0]: - raise unittest.SkipTest("free does not support 'available' column") - else: - free_value = int(lines[1].split()[-1]) - psutil_value = psutil.virtual_memory().available - self.assertAlmostEqual( - free_value, - psutil_value, - delta=TOLERANCE_SYS_MEM, - msg='%s %s \n%s' % (free_value, psutil_value, out), - ) + raise pytest.skip("free does not support 'available' column") + free_value = int(lines[1].split()[-1]) + psutil_value = psutil.virtual_memory().available + assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemVirtualMemoryAgainstVmstat(PsutilTestCase): def test_total(self): vmstat_value = vmstat('total memory') * 1024 psutil_value = psutil.virtual_memory().total - self.assertAlmostEqual( - vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_used(self): @@ -341,48 +294,44 @@ def test_used(self): # This got changed in: # https://gitlab.com/procps-ng/procps/commit/ # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e + # Newer versions of procps are using yet another way to compute used + # memory. + # https://gitlab.com/procps-ng/procps/commit/ + # 2184e90d2e7cdb582f9a5b706b47015e56707e4d if get_free_version_info() < (3, 3, 12): - raise unittest.SkipTest("old free version") + raise pytest.skip("free version too old") + if get_free_version_info() >= (4, 0, 0): + raise pytest.skip("free version too recent") vmstat_value = vmstat('used memory') * 1024 psutil_value = psutil.virtual_memory().used - self.assertAlmostEqual( - vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_free(self): vmstat_value = vmstat('free memory') * 1024 psutil_value = psutil.virtual_memory().free - self.assertAlmostEqual( - vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_buffers(self): vmstat_value = vmstat('buffer memory') * 1024 psutil_value = psutil.virtual_memory().buffers - self.assertAlmostEqual( - vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_active(self): vmstat_value = vmstat('active memory') * 1024 psutil_value = psutil.virtual_memory().active - self.assertAlmostEqual( - vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_inactive(self): vmstat_value = vmstat('inactive memory') * 1024 psutil_value = psutil.virtual_memory().inactive - self.assertAlmostEqual( - vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemVirtualMemoryMocks(PsutilTestCase): def test_warnings_on_misses(self): # Emulate a case where /proc/meminfo provides few info. @@ -403,24 +352,22 @@ def test_warnings_on_misses(self): warnings.simplefilter("always") ret = psutil.virtual_memory() assert m.called - self.assertEqual(len(ws), 1) + assert len(ws) == 1 w = ws[0] - self.assertIn( - "memory stats couldn't be determined", str(w.message) - ) - self.assertIn("cached", str(w.message)) - self.assertIn("shared", str(w.message)) - self.assertIn("active", str(w.message)) - self.assertIn("inactive", str(w.message)) - self.assertIn("buffers", str(w.message)) - self.assertIn("available", str(w.message)) - self.assertEqual(ret.cached, 0) - self.assertEqual(ret.active, 0) - self.assertEqual(ret.inactive, 0) - self.assertEqual(ret.shared, 0) - self.assertEqual(ret.buffers, 0) - self.assertEqual(ret.available, 0) - self.assertEqual(ret.slab, 0) + assert "memory stats couldn't be determined" in str(w.message) + assert "cached" in str(w.message) + assert "shared" in str(w.message) + assert "active" in str(w.message) + assert "inactive" in str(w.message) + assert "buffers" in str(w.message) + assert "available" in str(w.message) + assert ret.cached == 0 + assert ret.active == 0 + assert ret.inactive == 0 + assert ret.shared == 0 + assert ret.buffers == 0 + assert ret.available == 0 + assert ret.slab == 0 @retry_on_failure() def test_avail_old_percent(self): @@ -436,7 +383,7 @@ def test_avail_old_percent(self): if b'MemAvailable:' in mems: b = mems[b'MemAvailable:'] diff_percent = abs(a - b) / a * 100 - self.assertLess(diff_percent, 15) + assert diff_percent < 15 def test_avail_old_comes_from_kernel(self): # Make sure "MemAvailable:" coluimn is used instead of relying @@ -460,10 +407,10 @@ def test_avail_old_comes_from_kernel(self): with warnings.catch_warnings(record=True) as ws: ret = psutil.virtual_memory() assert m.called - self.assertEqual(ret.available, 6574984 * 1024) + assert ret.available == 6574984 * 1024 w = ws[0] - self.assertIn( - "inactive memory stats couldn't be determined", str(w.message) + assert "inactive memory stats couldn't be determined" in str( + w.message ) def test_avail_old_missing_fields(self): @@ -485,10 +432,10 @@ def test_avail_old_missing_fields(self): with warnings.catch_warnings(record=True) as ws: ret = psutil.virtual_memory() assert m.called - self.assertEqual(ret.available, 2057400 * 1024 + 4818144 * 1024) + assert ret.available == 2057400 * 1024 + 4818144 * 1024 w = ws[0] - self.assertIn( - "inactive memory stats couldn't be determined", str(w.message) + assert "inactive memory stats couldn't be determined" in str( + w.message ) def test_avail_old_missing_zoneinfo(self): @@ -509,19 +456,14 @@ def test_avail_old_missing_zoneinfo(self): SReclaimable: 346648 kB """).encode() with mock_open_content({"/proc/meminfo": content}): - with mock_open_exception( - "/proc/zoneinfo", - IOError(errno.ENOENT, 'no such file or directory'), - ): + with mock_open_exception("/proc/zoneinfo", FileNotFoundError): with warnings.catch_warnings(record=True) as ws: ret = psutil.virtual_memory() - self.assertEqual( - ret.available, 2057400 * 1024 + 4818144 * 1024 - ) + assert ret.available == 2057400 * 1024 + 4818144 * 1024 w = ws[0] - self.assertIn( - "inactive memory stats couldn't be determined", - str(w.message), + assert ( + "inactive memory stats couldn't be determined" + in str(w.message) ) def test_virtual_memory_mocked(self): @@ -579,16 +521,16 @@ def test_virtual_memory_mocked(self): with mock_open_content({"/proc/meminfo": content}) as m: mem = psutil.virtual_memory() assert m.called - self.assertEqual(mem.total, 100 * 1024) - self.assertEqual(mem.free, 2 * 1024) - self.assertEqual(mem.buffers, 4 * 1024) + assert mem.total == 100 * 1024 + assert mem.free == 2 * 1024 + assert mem.buffers == 4 * 1024 # cached mem also includes reclaimable memory - self.assertEqual(mem.cached, (5 + 23) * 1024) - self.assertEqual(mem.shared, 21 * 1024) - self.assertEqual(mem.active, 7 * 1024) - self.assertEqual(mem.inactive, 8 * 1024) - self.assertEqual(mem.slab, 22 * 1024) - self.assertEqual(mem.available, 3 * 1024) + assert mem.cached == (5 + 23) * 1024 + assert mem.shared == 21 * 1024 + assert mem.active == 7 * 1024 + assert mem.inactive == 8 * 1024 + assert mem.slab == 22 * 1024 + assert mem.available == 3 * 1024 # ===================================================================== @@ -596,7 +538,7 @@ def test_virtual_memory_mocked(self): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemSwapMemory(PsutilTestCase): @staticmethod def meminfo_has_swap_info(): @@ -608,25 +550,19 @@ def meminfo_has_swap_info(): def test_total(self): free_value = free_swap().total psutil_value = psutil.swap_memory().total - return self.assertAlmostEqual( - free_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_used(self): free_value = free_swap().used psutil_value = psutil.swap_memory().used - return self.assertAlmostEqual( - free_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM @retry_on_failure() def test_free(self): free_value = free_swap().free psutil_value = psutil.swap_memory().free - return self.assertAlmostEqual( - free_value, psutil_value, delta=TOLERANCE_SYS_MEM - ) + assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM def test_missing_sin_sout(self): with mock.patch('psutil._common.open', create=True) as m: @@ -634,41 +570,38 @@ def test_missing_sin_sout(self): warnings.simplefilter("always") ret = psutil.swap_memory() assert m.called - self.assertEqual(len(ws), 1) + assert len(ws) == 1 w = ws[0] - self.assertIn( - "'sin' and 'sout' swap memory stats couldn't " - "be determined", - str(w.message), + assert ( + "'sin' and 'sout' swap memory stats couldn't be determined" + in str(w.message) ) - self.assertEqual(ret.sin, 0) - self.assertEqual(ret.sout, 0) + assert ret.sin == 0 + assert ret.sout == 0 def test_no_vmstat_mocked(self): # see https://github.com/giampaolo/psutil/issues/722 - with mock_open_exception( - "/proc/vmstat", IOError(errno.ENOENT, 'no such file or directory') - ) as m: + with mock_open_exception("/proc/vmstat", FileNotFoundError) as m: with warnings.catch_warnings(record=True) as ws: warnings.simplefilter("always") ret = psutil.swap_memory() assert m.called - self.assertEqual(len(ws), 1) + assert len(ws) == 1 w = ws[0] - self.assertIn( + assert ( "'sin' and 'sout' swap memory stats couldn't " - "be determined and were set to 0", - str(w.message), + "be determined and were set to 0" + in str(w.message) ) - self.assertEqual(ret.sin, 0) - self.assertEqual(ret.sout, 0) + assert ret.sin == 0 + assert ret.sout == 0 def test_meminfo_against_sysinfo(self): # Make sure the content of /proc/meminfo about swap memory # matches sysinfo() syscall, see: # https://github.com/giampaolo/psutil/issues/1015 if not self.meminfo_has_swap_info(): - return unittest.skip("/proc/meminfo has no swap metrics") + raise pytest.skip("/proc/meminfo has no swap metrics") with mock.patch('psutil._pslinux.cext.linux_sysinfo') as m: swap = psutil.swap_memory() assert not m.called @@ -677,8 +610,8 @@ def test_meminfo_against_sysinfo(self): _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() total *= unit_multiplier free *= unit_multiplier - self.assertEqual(swap.total, total) - self.assertAlmostEqual(swap.free, free, delta=TOLERANCE_SYS_MEM) + assert swap.total == total + assert abs(swap.free - free) < TOLERANCE_SYS_MEM def test_emulate_meminfo_has_no_metrics(self): # Emulate a case where /proc/meminfo provides no swap metrics @@ -694,58 +627,62 @@ def test_emulate_meminfo_has_no_metrics(self): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemCPUTimes(PsutilTestCase): def test_fields(self): fields = psutil.cpu_times()._fields kernel_ver = re.findall(r'\d+\.\d+\.\d+', os.uname()[2])[0] kernel_ver_info = tuple(map(int, kernel_ver.split('.'))) if kernel_ver_info >= (2, 6, 11): - self.assertIn('steal', fields) + assert 'steal' in fields else: - self.assertNotIn('steal', fields) + assert 'steal' not in fields if kernel_ver_info >= (2, 6, 24): - self.assertIn('guest', fields) + assert 'guest' in fields else: - self.assertNotIn('guest', fields) + assert 'guest' not in fields if kernel_ver_info >= (3, 2, 0): - self.assertIn('guest_nice', fields) + assert 'guest_nice' in fields else: - self.assertNotIn('guest_nice', fields) + assert 'guest_nice' not in fields -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemCPUCountLogical(PsutilTestCase): - @unittest.skipIf( + @pytest.mark.skipif( not os.path.exists("/sys/devices/system/cpu/online"), - "/sys/devices/system/cpu/online does not exist", + reason="/sys/devices/system/cpu/online does not exist", ) def test_against_sysdev_cpu_online(self): with open("/sys/devices/system/cpu/online") as f: value = f.read().strip() if "-" in str(value): value = int(value.split('-')[1]) + 1 - self.assertEqual(psutil.cpu_count(), value) + assert psutil.cpu_count() == value - @unittest.skipIf( + @pytest.mark.skipif( not os.path.exists("/sys/devices/system/cpu"), - "/sys/devices/system/cpu does not exist", + reason="/sys/devices/system/cpu does not exist", ) def test_against_sysdev_cpu_num(self): ls = os.listdir("/sys/devices/system/cpu") count = len([x for x in ls if re.search(r"cpu\d+$", x) is not None]) - self.assertEqual(psutil.cpu_count(), count) + assert psutil.cpu_count() == count - @unittest.skipIf(not which("nproc"), "nproc utility not available") + @pytest.mark.skipif( + not shutil.which("nproc"), reason="nproc utility not available" + ) def test_against_nproc(self): num = int(sh("nproc --all")) - self.assertEqual(psutil.cpu_count(logical=True), num) + assert psutil.cpu_count(logical=True) == num - @unittest.skipIf(not which("lscpu"), "lscpu utility not available") + @pytest.mark.skipif( + not shutil.which("lscpu"), reason="lscpu utility not available" + ) def test_against_lscpu(self): out = sh("lscpu -p") num = len([x for x in out.split('\n') if not x.startswith('#')]) - self.assertEqual(psutil.cpu_count(logical=True), num) + assert psutil.cpu_count(logical=True) == num def test_emulate_fallbacks(self): import psutil._pslinux @@ -756,16 +693,16 @@ def test_emulate_fallbacks(self): with mock.patch( 'psutil._pslinux.os.sysconf', side_effect=ValueError ) as m: - self.assertEqual(psutil._pslinux.cpu_count_logical(), original) + assert psutil._pslinux.cpu_count_logical() == original assert m.called # Let's have open() return empty data and make sure None is # returned ('cause we mimic os.cpu_count()). with mock.patch('psutil._common.open', create=True) as m: - self.assertIsNone(psutil._pslinux.cpu_count_logical()) - self.assertEqual(m.call_count, 2) + assert psutil._pslinux.cpu_count_logical() is None + assert m.call_count == 2 # /proc/stat should be the last one - self.assertEqual(m.call_args[0][0], '/proc/stat') + assert m.call_args[0][0] == '/proc/stat' # Let's push this a bit further and make sure /proc/cpuinfo # parsing works as expected. @@ -775,18 +712,20 @@ def test_emulate_fallbacks(self): with mock.patch( 'psutil._common.open', return_value=fake_file, create=True ) as m: - self.assertEqual(psutil._pslinux.cpu_count_logical(), original) + assert psutil._pslinux.cpu_count_logical() == original # Finally, let's make /proc/cpuinfo return meaningless data; # this way we'll fall back on relying on /proc/stat with mock_open_content({"/proc/cpuinfo": b""}) as m: - self.assertEqual(psutil._pslinux.cpu_count_logical(), original) + assert psutil._pslinux.cpu_count_logical() == original assert m.called -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemCPUCountCores(PsutilTestCase): - @unittest.skipIf(not which("lscpu"), "lscpu utility not available") + @pytest.mark.skipif( + not shutil.which("lscpu"), reason="lscpu utility not available" + ) def test_against_lscpu(self): out = sh("lscpu -p") core_ids = set() @@ -794,7 +733,7 @@ def test_against_lscpu(self): if not line.startswith('#'): fields = line.split(',') core_ids.add(fields[1]) - self.assertEqual(psutil.cpu_count(logical=False), len(core_ids)) + assert psutil.cpu_count(logical=False) == len(core_ids) def test_method_2(self): meth_1 = psutil._pslinux.cpu_count_cores() @@ -802,19 +741,19 @@ def test_method_2(self): meth_2 = psutil._pslinux.cpu_count_cores() assert m.called if meth_1 is not None: - self.assertEqual(meth_1, meth_2) + assert meth_1 == meth_2 def test_emulate_none(self): with mock.patch('glob.glob', return_value=[]) as m1: with mock.patch('psutil._common.open', create=True) as m2: - self.assertIsNone(psutil._pslinux.cpu_count_cores()) + assert psutil._pslinux.cpu_count_cores() is None assert m1.called assert m2.called -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemCPUFrequency(PsutilTestCase): - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") def test_emulate_use_second_file(self): # https://github.com/giampaolo/psutil/issues/981 def path_exists_mock(path): @@ -829,7 +768,10 @@ def path_exists_mock(path): ): assert psutil.cpu_freq() - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") + @pytest.mark.skipif( + AARCH64, reason="aarch64 does not report mhz in /proc/cpuinfo" + ) def test_emulate_use_cpuinfo(self): # Emulate a case where /sys/devices/system/cpu/cpufreq* does not # exist and /proc/cpuinfo is used instead. @@ -845,16 +787,16 @@ def path_exists_mock(path): reload_module(psutil._pslinux) ret = psutil.cpu_freq() assert ret, ret - self.assertEqual(ret.max, 0.0) - self.assertEqual(ret.min, 0.0) + assert ret.max == 0.0 + assert ret.min == 0.0 for freq in psutil.cpu_freq(percpu=True): - self.assertEqual(freq.max, 0.0) - self.assertEqual(freq.min, 0.0) + assert freq.max == 0.0 + assert freq.min == 0.0 finally: reload_module(psutil._pslinux) reload_module(psutil) - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") def test_emulate_data(self): def open_mock(name, *args, **kwargs): if name.endswith('/scaling_cur_freq') and name.startswith( @@ -875,19 +817,18 @@ def open_mock(name, *args, **kwargs): return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock): + with mock.patch("builtins.open", side_effect=open_mock): with mock.patch('os.path.exists', return_value=True): freq = psutil.cpu_freq() - self.assertEqual(freq.current, 500.0) + assert freq.current == 500.0 # when /proc/cpuinfo is used min and max frequencies are not # available and are set to 0. if freq.min != 0.0: - self.assertEqual(freq.min, 600.0) + assert freq.min == 600.0 if freq.max != 0.0: - self.assertEqual(freq.max, 700.0) + assert freq.max == 700.0 - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") def test_emulate_multi_cpu(self): def open_mock(name, *args, **kwargs): n = name @@ -921,31 +862,30 @@ def open_mock(name, *args, **kwargs): return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock): + with mock.patch("builtins.open", side_effect=open_mock): with mock.patch('os.path.exists', return_value=True): with mock.patch( 'psutil._pslinux.cpu_count_logical', return_value=2 ): freq = psutil.cpu_freq(percpu=True) - self.assertEqual(freq[0].current, 100.0) + assert freq[0].current == 100.0 if freq[0].min != 0.0: - self.assertEqual(freq[0].min, 200.0) + assert freq[0].min == 200.0 if freq[0].max != 0.0: - self.assertEqual(freq[0].max, 300.0) - self.assertEqual(freq[1].current, 400.0) + assert freq[0].max == 300.0 + assert freq[1].current == 400.0 if freq[1].min != 0.0: - self.assertEqual(freq[1].min, 500.0) + assert freq[1].min == 500.0 if freq[1].max != 0.0: - self.assertEqual(freq[1].max, 600.0) + assert freq[1].max == 600.0 - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") def test_emulate_no_scaling_cur_freq_file(self): # See: https://github.com/giampaolo/psutil/issues/1071 def open_mock(name, *args, **kwargs): if name.endswith('/scaling_cur_freq'): - raise IOError(errno.ENOENT, "") - elif name.endswith('/cpuinfo_cur_freq'): + raise FileNotFoundError + if name.endswith('/cpuinfo_cur_freq'): return io.BytesIO(b"200000") elif name == '/proc/cpuinfo': return io.BytesIO(b"cpu MHz : 200") @@ -953,40 +893,41 @@ def open_mock(name, *args, **kwargs): return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock): + with mock.patch("builtins.open", side_effect=open_mock): with mock.patch('os.path.exists', return_value=True): with mock.patch( 'psutil._pslinux.cpu_count_logical', return_value=1 ): freq = psutil.cpu_freq() - self.assertEqual(freq.current, 200) + assert freq.current == 200 -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemCPUStats(PsutilTestCase): - def test_ctx_switches(self): - vmstat_value = vmstat("context switches") - psutil_value = psutil.cpu_stats().ctx_switches - self.assertAlmostEqual(vmstat_value, psutil_value, delta=500) + + # XXX: fails too often. + # def test_ctx_switches(self): + # vmstat_value = vmstat("context switches") + # psutil_value = psutil.cpu_stats().ctx_switches + # self.assertAlmostEqual(vmstat_value, psutil_value, delta=500) def test_interrupts(self): vmstat_value = vmstat("interrupts") psutil_value = psutil.cpu_stats().interrupts - self.assertAlmostEqual(vmstat_value, psutil_value, delta=500) + assert abs(vmstat_value - psutil_value) < 500 -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestLoadAvg(PsutilTestCase): - @unittest.skipIf(not HAS_GETLOADAVG, "not supported") + @pytest.mark.skipif(not HAS_GETLOADAVG, reason="not supported") def test_getloadavg(self): psutil_value = psutil.getloadavg() with open("/proc/loadavg") as f: proc_value = f.read().split() - self.assertAlmostEqual(float(proc_value[0]), psutil_value[0], delta=1) - self.assertAlmostEqual(float(proc_value[1]), psutil_value[1], delta=1) - self.assertAlmostEqual(float(proc_value[2]), psutil_value[2], delta=1) + assert abs(float(proc_value[0]) - psutil_value[0]) < 1 + assert abs(float(proc_value[1]) - psutil_value[1]) < 1 + assert abs(float(proc_value[2]) - psutil_value[2]) < 1 # ===================================================================== @@ -994,22 +935,20 @@ def test_getloadavg(self): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemNetIfAddrs(PsutilTestCase): def test_ips(self): for name, addrs in psutil.net_if_addrs().items(): for addr in addrs: if addr.family == psutil.AF_LINK: - self.assertEqual(addr.address, get_mac_address(name)) + assert addr.address == get_mac_address(name) elif addr.family == socket.AF_INET: - self.assertEqual(addr.address, get_ipv4_address(name)) - self.assertEqual(addr.netmask, get_ipv4_netmask(name)) + assert addr.address == get_ipv4_address(name) + assert addr.netmask == get_ipv4_netmask(name) if addr.broadcast is not None: - self.assertEqual( - addr.broadcast, get_ipv4_broadcast(name) - ) + assert addr.broadcast == get_ipv4_broadcast(name) else: - self.assertEqual(get_ipv4_broadcast(name), '0.0.0.0') + assert get_ipv4_broadcast(name) == '0.0.0.0' elif addr.family == socket.AF_INET6: # IPv6 addresses can have a percent symbol at the end. # E.g. these 2 are equivalent: @@ -1018,10 +957,11 @@ def test_ips(self): # That is the "zone id" portion, which usually is the name # of the network interface. address = addr.address.split('%')[0] - self.assertIn(address, get_ipv6_addresses(name)) + assert address in get_ipv6_addresses(name) # XXX - not reliable when having virtual NICs installed by Docker. - # @unittest.skipIf(not which('ip'), "'ip' utility not available") + # @pytest.mark.skipif(not shutil.which("ip"), + # reason="'ip' utility not available") # def test_net_if_names(self): # out = sh("ip addr").strip() # nics = [x for x in psutil.net_if_addrs().keys() if ':' not in x] @@ -1032,38 +972,43 @@ def test_ips(self): # found += 1 # name = line.split(':')[1].strip() # self.assertIn(name, nics) - # self.assertEqual(len(nics), found, msg="%s\n---\n%s" % ( + # self.assertEqual(len(nics), found, msg="{}\n---\n{}".format( # pprint.pformat(nics), out)) -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") +@pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") class TestSystemNetIfStats(PsutilTestCase): - @unittest.skipIf(not which("ifconfig"), "ifconfig utility not available") + @pytest.mark.skipif( + not shutil.which("ifconfig"), reason="ifconfig utility not available" + ) def test_against_ifconfig(self): for name, stats in psutil.net_if_stats().items(): try: - out = sh("ifconfig %s" % name) + out = sh(f"ifconfig {name}") except RuntimeError: pass else: - self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) - self.assertEqual( - stats.mtu, int(re.findall(r'(?i)MTU[: ](\d+)', out)[0]) + assert stats.isup == ('RUNNING' in out), out + assert stats.mtu == int( + re.findall(r'(?i)MTU[: ](\d+)', out)[0] ) def test_mtu(self): for name, stats in psutil.net_if_stats().items(): - with open("/sys/class/net/%s/mtu" % name) as f: - self.assertEqual(stats.mtu, int(f.read().strip())) + with open(f"/sys/class/net/{name}/mtu") as f: + assert stats.mtu == int(f.read().strip()) - @unittest.skipIf(not which("ifconfig"), "ifconfig utility not available") + @pytest.mark.skipif( + not shutil.which("ifconfig"), reason="ifconfig utility not available" + ) def test_flags(self): # first line looks like this: # "eth0: flags=4163 mtu 1500" matches_found = 0 for name, stats in psutil.net_if_stats().items(): try: - out = sh("ifconfig %s" % name) + out = sh(f"ifconfig {name}") except RuntimeError: pass else: @@ -1072,7 +1017,7 @@ def test_flags(self): matches_found += 1 ifconfig_flags = set(match.group(2).lower().split(",")) psutil_flags = set(stats.flags.split(",")) - self.assertEqual(ifconfig_flags, psutil_flags) + assert ifconfig_flags == psutil_flags else: # ifconfig has a different output on CentOS 6 # let's try that @@ -1081,20 +1026,22 @@ def test_flags(self): matches_found += 1 ifconfig_flags = set(match.group(1).lower().split()) psutil_flags = set(stats.flags.split(",")) - self.assertEqual(ifconfig_flags, psutil_flags) + assert ifconfig_flags == psutil_flags if not matches_found: raise self.fail("no matches were found") -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemNetIOCounters(PsutilTestCase): - @unittest.skipIf(not which("ifconfig"), "ifconfig utility not available") + @pytest.mark.skipif( + not shutil.which("ifconfig"), reason="ifconfig utility not available" + ) @retry_on_failure() def test_against_ifconfig(self): def ifconfig(nic): ret = {} - out = sh("ifconfig %s" % nic) + out = sh(f"ifconfig {nic}") ret['packets_recv'] = int( re.findall(r'RX packets[: ](\d+)', out)[0] ) @@ -1119,33 +1066,25 @@ def ifconfig(nic): ifconfig_ret = ifconfig(name) except RuntimeError: continue - self.assertAlmostEqual( - stats.bytes_recv, ifconfig_ret['bytes_recv'], delta=1024 * 5 + assert ( + abs(stats.bytes_recv - ifconfig_ret['bytes_recv']) < 1024 * 10 ) - self.assertAlmostEqual( - stats.bytes_sent, ifconfig_ret['bytes_sent'], delta=1024 * 5 + assert ( + abs(stats.bytes_sent - ifconfig_ret['bytes_sent']) < 1024 * 10 ) - self.assertAlmostEqual( - stats.packets_recv, ifconfig_ret['packets_recv'], delta=1024 + assert ( + abs(stats.packets_recv - ifconfig_ret['packets_recv']) < 1024 ) - self.assertAlmostEqual( - stats.packets_sent, ifconfig_ret['packets_sent'], delta=1024 - ) - self.assertAlmostEqual( - stats.errin, ifconfig_ret['errin'], delta=10 - ) - self.assertAlmostEqual( - stats.errout, ifconfig_ret['errout'], delta=10 - ) - self.assertAlmostEqual( - stats.dropin, ifconfig_ret['dropin'], delta=10 - ) - self.assertAlmostEqual( - stats.dropout, ifconfig_ret['dropout'], delta=10 + assert ( + abs(stats.packets_sent - ifconfig_ret['packets_sent']) < 1024 ) + assert abs(stats.errin - ifconfig_ret['errin']) < 10 + assert abs(stats.errout - ifconfig_ret['errout']) < 10 + assert abs(stats.dropin - ifconfig_ret['dropin']) < 10 + assert abs(stats.dropout - ifconfig_ret['dropout']) < 10 -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemNetConnections(PsutilTestCase): @mock.patch('psutil._pslinux.socket.inet_ntop', side_effect=ValueError) @mock.patch('psutil._pslinux.supports_ipv6', return_value=False) @@ -1155,7 +1094,7 @@ def test_emulate_ipv6_unsupported(self, supports_ipv6, inet_ntop): s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.addCleanup(s.close) s.bind(("::1", 0)) - except socket.error: + except OSError: pass psutil.net_connections(kind='inet6') @@ -1176,15 +1115,17 @@ def test_emulate_unix(self): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemDiskPartitions(PsutilTestCase): - @unittest.skipIf(not hasattr(os, 'statvfs'), "os.statvfs() not available") + @pytest.mark.skipif( + not hasattr(os, 'statvfs'), reason="os.statvfs() not available" + ) @skip_on_not_implemented() def test_against_df(self): # test psutil.disk_usage() and psutil.disk_partitions() # against "df -a" def df(path): - out = sh('df -P -B 1 "%s"' % path).strip() + out = sh(f'df -P -B 1 "{path}"').strip() lines = out.split('\n') lines.pop(0) line = lines.pop(0) @@ -1197,13 +1138,9 @@ def df(path): for part in psutil.disk_partitions(all=False): usage = psutil.disk_usage(part.mountpoint) _, total, used, free = df(part.mountpoint) - self.assertEqual(usage.total, total) - self.assertAlmostEqual( - usage.free, free, delta=TOLERANCE_DISK_USAGE - ) - self.assertAlmostEqual( - usage.used, used, delta=TOLERANCE_DISK_USAGE - ) + assert usage.total == total + assert abs(usage.free - free) < TOLERANCE_DISK_USAGE + assert abs(usage.used - used) < TOLERANCE_DISK_USAGE def test_zfs_fs(self): # Test that ZFS partitions are returned. @@ -1212,24 +1149,22 @@ def test_zfs_fs(self): if 'zfs' in data: for part in psutil.disk_partitions(): if part.fstype == 'zfs': - break - else: - 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") + return + + # No ZFS partitions on this system. Let's fake one. + fake_file = io.StringIO("nodev\tzfs\n") + with mock.patch( + 'psutil._common.open', return_value=fake_file, create=True + ) as m1: with mock.patch( - 'psutil._common.open', return_value=fake_file, create=True - ) as m1: - with mock.patch( - 'psutil._pslinux.cext.disk_partitions', - return_value=[('/dev/sdb3', '/', 'zfs', 'rw')], - ) as m2: - ret = psutil.disk_partitions() - assert m1.called - assert m2.called - assert ret - self.assertEqual(ret[0].fstype, 'zfs') + 'psutil._pslinux.cext.disk_partitions', + return_value=[('/dev/sdb3', '/', 'zfs', 'rw')], + ) as m2: + ret = psutil.disk_partitions() + assert m1.called + assert m2.called + assert ret + assert ret[0].fstype == 'zfs' def test_emulate_realpath_fail(self): # See: https://github.com/giampaolo/psutil/issues/1307 @@ -1237,14 +1172,14 @@ def test_emulate_realpath_fail(self): with mock.patch( 'os.path.realpath', return_value='/non/existent' ) as m: - with self.assertRaises(FileNotFoundError): + with pytest.raises(FileNotFoundError): psutil.disk_partitions() assert m.called finally: psutil.PROCFS_PATH = "/proc" -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestSystemDiskIoCounters(PsutilTestCase): def test_emulate_kernel_2_4(self): # Tests /proc/diskstats parsing format for 2.4 kernels, see: @@ -1255,15 +1190,15 @@ def test_emulate_kernel_2_4(self): 'psutil._pslinux.is_storage_device', return_value=True ): ret = psutil.disk_io_counters(nowrap=False) - self.assertEqual(ret.read_count, 1) - self.assertEqual(ret.read_merged_count, 2) - self.assertEqual(ret.read_bytes, 3 * SECTOR_SIZE) - self.assertEqual(ret.read_time, 4) - self.assertEqual(ret.write_count, 5) - self.assertEqual(ret.write_merged_count, 6) - self.assertEqual(ret.write_bytes, 7 * SECTOR_SIZE) - self.assertEqual(ret.write_time, 8) - self.assertEqual(ret.busy_time, 10) + assert ret.read_count == 1 + assert ret.read_merged_count == 2 + assert ret.read_bytes == 3 * SECTOR_SIZE + assert ret.read_time == 4 + assert ret.write_count == 5 + assert ret.write_merged_count == 6 + assert ret.write_bytes == 7 * SECTOR_SIZE + assert ret.write_time == 8 + assert ret.busy_time == 10 def test_emulate_kernel_2_6_full(self): # Tests /proc/diskstats parsing format for 2.6 kernels, @@ -1275,15 +1210,15 @@ def test_emulate_kernel_2_6_full(self): 'psutil._pslinux.is_storage_device', return_value=True ): ret = psutil.disk_io_counters(nowrap=False) - self.assertEqual(ret.read_count, 1) - self.assertEqual(ret.read_merged_count, 2) - self.assertEqual(ret.read_bytes, 3 * SECTOR_SIZE) - self.assertEqual(ret.read_time, 4) - self.assertEqual(ret.write_count, 5) - self.assertEqual(ret.write_merged_count, 6) - self.assertEqual(ret.write_bytes, 7 * SECTOR_SIZE) - self.assertEqual(ret.write_time, 8) - self.assertEqual(ret.busy_time, 10) + assert ret.read_count == 1 + assert ret.read_merged_count == 2 + assert ret.read_bytes == 3 * SECTOR_SIZE + assert ret.read_time == 4 + assert ret.write_count == 5 + assert ret.write_merged_count == 6 + assert ret.write_bytes == 7 * SECTOR_SIZE + assert ret.write_time == 8 + assert ret.busy_time == 10 def test_emulate_kernel_2_6_limited(self): # Tests /proc/diskstats parsing format for 2.6 kernels, @@ -1296,16 +1231,16 @@ def test_emulate_kernel_2_6_limited(self): 'psutil._pslinux.is_storage_device', return_value=True ): ret = psutil.disk_io_counters(nowrap=False) - self.assertEqual(ret.read_count, 1) - self.assertEqual(ret.read_bytes, 2 * SECTOR_SIZE) - self.assertEqual(ret.write_count, 3) - self.assertEqual(ret.write_bytes, 4 * SECTOR_SIZE) + assert ret.read_count == 1 + assert ret.read_bytes == 2 * SECTOR_SIZE + assert ret.write_count == 3 + assert ret.write_bytes == 4 * SECTOR_SIZE - self.assertEqual(ret.read_merged_count, 0) - self.assertEqual(ret.read_time, 0) - self.assertEqual(ret.write_merged_count, 0) - self.assertEqual(ret.write_time, 0) - self.assertEqual(ret.busy_time, 0) + assert ret.read_merged_count == 0 + assert ret.read_time == 0 + assert ret.write_merged_count == 0 + assert ret.write_time == 0 + assert ret.busy_time == 0 def test_emulate_include_partitions(self): # Make sure that when perdisk=True disk partitions are returned, @@ -1320,11 +1255,11 @@ def test_emulate_include_partitions(self): 'psutil._pslinux.is_storage_device', return_value=False ): ret = psutil.disk_io_counters(perdisk=True, nowrap=False) - self.assertEqual(len(ret), 2) - self.assertEqual(ret['nvme0n1'].read_count, 1) - self.assertEqual(ret['nvme0n1p1'].read_count, 1) - self.assertEqual(ret['nvme0n1'].write_count, 5) - self.assertEqual(ret['nvme0n1p1'].write_count, 5) + assert len(ret) == 2 + assert ret['nvme0n1'].read_count == 1 + assert ret['nvme0n1p1'].read_count == 1 + assert ret['nvme0n1'].write_count == 5 + assert ret['nvme0n1p1'].write_count == 5 def test_emulate_exclude_partitions(self): # Make sure that when perdisk=False partitions (e.g. 'sda1', @@ -1339,7 +1274,7 @@ def test_emulate_exclude_partitions(self): 'psutil._pslinux.is_storage_device', return_value=False ): ret = psutil.disk_io_counters(perdisk=False, nowrap=False) - self.assertIsNone(ret) + assert ret is None def is_storage_device(name): return name == 'nvme0n1' @@ -1355,8 +1290,8 @@ def is_storage_device(name): side_effect=is_storage_device, ): ret = psutil.disk_io_counters(perdisk=False, nowrap=False) - self.assertEqual(ret.read_count, 1) - self.assertEqual(ret.write_count, 5) + assert ret.read_count == 1 + assert ret.write_count == 5 def test_emulate_use_sysfs(self): def exists(path): @@ -1367,7 +1302,7 @@ def exists(path): 'psutil._pslinux.os.path.exists', create=True, side_effect=exists ): wsysfs = psutil.disk_io_counters(perdisk=True) - self.assertEqual(len(wprocfs), len(wsysfs)) + assert len(wprocfs) == len(wsysfs) def test_emulate_not_impl(self): def exists(path): @@ -1376,10 +1311,11 @@ def exists(path): with mock.patch( 'psutil._pslinux.os.path.exists', create=True, side_effect=exists ): - self.assertRaises(NotImplementedError, psutil.disk_io_counters) + with pytest.raises(NotImplementedError): + psutil.disk_io_counters() -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestRootFsDeviceFinder(PsutilTestCase): def setUp(self): dev = os.stat("/").st_dev @@ -1391,43 +1327,43 @@ def test_call_methods(self): if os.path.exists("/proc/partitions"): finder.ask_proc_partitions() else: - self.assertRaises(FileNotFoundError, finder.ask_proc_partitions) - if os.path.exists( - "/sys/dev/block/%s:%s/uevent" % (self.major, self.minor) - ): + with pytest.raises(FileNotFoundError): + finder.ask_proc_partitions() + if os.path.exists(f"/sys/dev/block/{self.major}:{self.minor}/uevent"): finder.ask_sys_dev_block() else: - self.assertRaises(FileNotFoundError, finder.ask_sys_dev_block) + with pytest.raises(FileNotFoundError): + finder.ask_sys_dev_block() finder.ask_sys_class_block() - @unittest.skipIf(GITHUB_ACTIONS, "unsupported on GITHUB_ACTIONS") + @pytest.mark.skipif(GITHUB_ACTIONS, reason="unsupported on GITHUB_ACTIONS") def test_comparisons(self): finder = RootFsDeviceFinder() - self.assertIsNotNone(finder.find()) + assert finder.find() is not None a = b = c = None if os.path.exists("/proc/partitions"): a = finder.ask_proc_partitions() - if os.path.exists( - "/sys/dev/block/%s:%s/uevent" % (self.major, self.minor) - ): + if os.path.exists(f"/sys/dev/block/{self.major}:{self.minor}/uevent"): b = finder.ask_sys_class_block() c = finder.ask_sys_dev_block() base = a or b or c if base and a: - self.assertEqual(base, a) + assert base == a if base and b: - self.assertEqual(base, b) + assert base == b if base and c: - self.assertEqual(base, c) + assert base == c - @unittest.skipIf(not which("findmnt"), "findmnt utility not available") - @unittest.skipIf(GITHUB_ACTIONS, "unsupported on GITHUB_ACTIONS") + @pytest.mark.skipif( + not shutil.which("findmnt"), reason="findmnt utility not available" + ) + @pytest.mark.skipif(GITHUB_ACTIONS, reason="unsupported on GITHUB_ACTIONS") def test_against_findmnt(self): psutil_value = RootFsDeviceFinder().find() findmnt_value = sh("findmnt -o SOURCE -rn /") - self.assertEqual(psutil_value, findmnt_value) + assert psutil_value == findmnt_value def test_disk_partitions_mocked(self): with mock.patch( @@ -1437,10 +1373,10 @@ def test_disk_partitions_mocked(self): part = psutil.disk_partitions()[0] assert m.called if not GITHUB_ACTIONS: - self.assertNotEqual(part.device, "/dev/root") - self.assertEqual(part.device, RootFsDeviceFinder().find()) + assert part.device != "/dev/root" + assert part.device == RootFsDeviceFinder().find() else: - self.assertEqual(part.device, "/dev/root") + assert part.device == "/dev/root" # ===================================================================== @@ -1448,12 +1384,12 @@ def test_disk_partitions_mocked(self): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestMisc(PsutilTestCase): def test_boot_time(self): vmstat_value = vmstat('boot time') psutil_value = psutil.boot_time() - self.assertEqual(int(vmstat_value), int(psutil_value)) + assert int(vmstat_value) == int(psutil_value) def test_no_procfs_on_import(self): my_procfs = self.get_testfn() @@ -1469,35 +1405,38 @@ def test_no_procfs_on_import(self): def open_mock(name, *args, **kwargs): if name.startswith('/proc'): - raise IOError(errno.ENOENT, 'rejecting access for test') + raise FileNotFoundError return orig_open(name, *args, **kwargs) - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock): + with mock.patch("builtins.open", side_effect=open_mock): reload_module(psutil) - self.assertRaises(IOError, psutil.cpu_times) - self.assertRaises(IOError, psutil.cpu_times, percpu=True) - self.assertRaises(IOError, psutil.cpu_percent) - self.assertRaises(IOError, psutil.cpu_percent, percpu=True) - self.assertRaises(IOError, psutil.cpu_times_percent) - self.assertRaises( - IOError, psutil.cpu_times_percent, percpu=True - ) + with pytest.raises(OSError): + psutil.cpu_times() + with pytest.raises(OSError): + psutil.cpu_times(percpu=True) + with pytest.raises(OSError): + psutil.cpu_percent() + with pytest.raises(OSError): + psutil.cpu_percent(percpu=True) + with pytest.raises(OSError): + psutil.cpu_times_percent() + with pytest.raises(OSError): + psutil.cpu_times_percent(percpu=True) psutil.PROCFS_PATH = my_procfs - self.assertEqual(psutil.cpu_percent(), 0) - self.assertEqual(sum(psutil.cpu_times_percent()), 0) + assert psutil.cpu_percent() == 0 + assert sum(psutil.cpu_times_percent()) == 0 # since we don't know the number of CPUs at import time, # we awkwardly say there are none until the second call per_cpu_percent = psutil.cpu_percent(percpu=True) - self.assertEqual(sum(per_cpu_percent), 0) + assert sum(per_cpu_percent) == 0 # ditto awkward length per_cpu_times_percent = psutil.cpu_times_percent(percpu=True) - self.assertEqual(sum(map(sum, per_cpu_times_percent)), 0) + assert sum(map(sum, per_cpu_times_percent)) == 0 # much user, very busy with open(os.path.join(my_procfs, 'stat'), 'w') as f: @@ -1505,17 +1444,17 @@ def open_mock(name, *args, **kwargs): f.write('cpu0 1 0 0 0 0 0 0 0 0 0\n') f.write('cpu1 1 0 0 0 0 0 0 0 0 0\n') - self.assertNotEqual(psutil.cpu_percent(), 0) - self.assertNotEqual(sum(psutil.cpu_percent(percpu=True)), 0) - self.assertNotEqual(sum(psutil.cpu_times_percent()), 0) - self.assertNotEqual( - sum(map(sum, psutil.cpu_times_percent(percpu=True))), 0 + assert psutil.cpu_percent() != 0 + assert sum(psutil.cpu_percent(percpu=True)) != 0 + assert sum(psutil.cpu_times_percent()) != 0 + assert ( + sum(map(sum, psutil.cpu_times_percent(percpu=True))) != 0 ) finally: shutil.rmtree(my_procfs) reload_module(psutil) - self.assertEqual(psutil.PROCFS_PATH, '/proc') + assert psutil.PROCFS_PATH == '/proc' def test_cpu_steal_decrease(self): # Test cumulative cpu stats decrease. We should ignore this. @@ -1547,46 +1486,57 @@ def test_cpu_steal_decrease(self): cpu_percent_percpu = psutil.cpu_percent(percpu=True) cpu_times_percent = psutil.cpu_times_percent() cpu_times_percent_percpu = psutil.cpu_times_percent(percpu=True) - self.assertNotEqual(cpu_percent, 0) - self.assertNotEqual(sum(cpu_percent_percpu), 0) - self.assertNotEqual(sum(cpu_times_percent), 0) - self.assertNotEqual(sum(cpu_times_percent), 100.0) - self.assertNotEqual(sum(map(sum, cpu_times_percent_percpu)), 0) - self.assertNotEqual(sum(map(sum, cpu_times_percent_percpu)), 100.0) - self.assertEqual(cpu_times_percent.steal, 0) - self.assertNotEqual(cpu_times_percent.user, 0) + assert cpu_percent != 0 + assert sum(cpu_percent_percpu) != 0 + assert sum(cpu_times_percent) != 0 + assert sum(cpu_times_percent) != 100.0 + assert sum(map(sum, cpu_times_percent_percpu)) != 0 + assert sum(map(sum, cpu_times_percent_percpu)) != 100.0 + assert cpu_times_percent.steal == 0 + assert cpu_times_percent.user != 0 def test_boot_time_mocked(self): with mock.patch('psutil._common.open', create=True) as m: - self.assertRaises(RuntimeError, psutil._pslinux.boot_time) + with pytest.raises(RuntimeError): + psutil._pslinux.boot_time() assert m.called def test_users(self): # Make sure the C extension converts ':0' and ':0.0' to # 'localhost'. for user in psutil.users(): - self.assertNotIn(user.host, (":0", ":0.0")) + assert user.host not in {":0", ":0.0"} def test_procfs_path(self): tdir = self.get_testfn() os.mkdir(tdir) try: psutil.PROCFS_PATH = tdir - self.assertRaises(IOError, psutil.virtual_memory) - self.assertRaises(IOError, psutil.cpu_times) - self.assertRaises(IOError, psutil.cpu_times, percpu=True) - self.assertRaises(IOError, psutil.boot_time) - # self.assertRaises(IOError, psutil.pids) - self.assertRaises(IOError, psutil.net_connections) - self.assertRaises(IOError, psutil.net_io_counters) - self.assertRaises(IOError, psutil.net_if_stats) - # self.assertRaises(IOError, psutil.disk_io_counters) - self.assertRaises(IOError, psutil.disk_partitions) - self.assertRaises(psutil.NoSuchProcess, psutil.Process) + with pytest.raises(OSError): + psutil.virtual_memory() + with pytest.raises(OSError): + psutil.cpu_times() + with pytest.raises(OSError): + psutil.cpu_times(percpu=True) + with pytest.raises(OSError): + psutil.boot_time() + # self.assertRaises(OSError, psutil.pids) + with pytest.raises(OSError): + psutil.net_connections() + with pytest.raises(OSError): + psutil.net_io_counters() + with pytest.raises(OSError): + psutil.net_if_stats() + # self.assertRaises(OSError, psutil.disk_io_counters) + with pytest.raises(OSError): + psutil.disk_partitions() + with pytest.raises(psutil.NoSuchProcess): + psutil.Process() finally: psutil.PROCFS_PATH = "/proc" @retry_on_failure() + @pytest.mark.skipif(PYTEST_PARALLEL, reason="skip if pytest-parallel") def test_issue_687(self): # In case of thread ID: # - pid_exists() is supposed to return False @@ -1596,12 +1546,12 @@ def test_issue_687(self): with ThreadTask(): p = psutil.Process() threads = p.threads() - self.assertEqual(len(threads), 2) + assert len(threads) == (3 if QEMU_USER else 2) tid = sorted(threads, key=lambda x: x.id)[1].id - self.assertNotEqual(p.pid, tid) + assert p.pid != tid pt = psutil.Process(tid) pt.as_dict() - self.assertNotIn(tid, psutil.pids()) + assert tid not in psutil.pids() def test_pid_exists_no_proc_status(self): # Internally pid_exists relies on /proc/{pid}/status. @@ -1617,15 +1567,17 @@ def test_pid_exists_no_proc_status(self): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") -@unittest.skipIf(not HAS_BATTERY, "no battery") +@pytest.mark.skipif(not LINUX, reason="LINUX only") +@pytest.mark.skipif(not HAS_BATTERY, reason="no battery") class TestSensorsBattery(PsutilTestCase): - @unittest.skipIf(not which("acpi"), "acpi utility not available") + @pytest.mark.skipif( + not shutil.which("acpi"), reason="acpi utility not available" + ) def test_percent(self): out = sh("acpi -b") acpi_value = int(out.split(",")[1].strip().replace('%', '')) psutil_value = psutil.sensors_battery().percent - self.assertAlmostEqual(acpi_value, psutil_value, delta=1) + assert abs(acpi_value - psutil_value) < 1 def test_emulate_power_plugged(self): # Pretend the AC power cable is connected. @@ -1636,11 +1588,11 @@ def open_mock(name, *args, **kwargs): return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock) as m: - self.assertEqual(psutil.sensors_battery().power_plugged, True) - self.assertEqual( - psutil.sensors_battery().secsleft, psutil.POWER_TIME_UNLIMITED + with mock.patch("builtins.open", side_effect=open_mock) as m: + assert psutil.sensors_battery().power_plugged is True + assert ( + psutil.sensors_battery().secsleft + == psutil.POWER_TIME_UNLIMITED ) assert m.called @@ -1649,16 +1601,15 @@ def test_emulate_power_plugged_2(self): # case code relies on /status file. def open_mock(name, *args, **kwargs): if name.endswith(('AC0/online', 'AC/online')): - raise IOError(errno.ENOENT, "") - elif name.endswith("/status"): - return io.StringIO(u"charging") + raise FileNotFoundError + if name.endswith("/status"): + return io.StringIO("charging") else: return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock) as m: - self.assertEqual(psutil.sensors_battery().power_plugged, True) + with mock.patch("builtins.open", side_effect=open_mock) as m: + assert psutil.sensors_battery().power_plugged is True assert m.called def test_emulate_power_not_plugged(self): @@ -1670,9 +1621,8 @@ def open_mock(name, *args, **kwargs): return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock) as m: - self.assertEqual(psutil.sensors_battery().power_plugged, False) + with mock.patch("builtins.open", side_effect=open_mock) as m: + assert psutil.sensors_battery().power_plugged is False assert m.called def test_emulate_power_not_plugged_2(self): @@ -1680,16 +1630,15 @@ def test_emulate_power_not_plugged_2(self): # case code relies on /status file. def open_mock(name, *args, **kwargs): if name.endswith(('AC0/online', 'AC/online')): - raise IOError(errno.ENOENT, "") - elif name.endswith("/status"): - return io.StringIO(u"discharging") + raise FileNotFoundError + if name.endswith("/status"): + return io.StringIO("discharging") else: return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock) as m: - self.assertEqual(psutil.sensors_battery().power_plugged, False) + with mock.patch("builtins.open", side_effect=open_mock) as m: + assert psutil.sensors_battery().power_plugged is False assert m.called def test_emulate_power_undetermined(self): @@ -1700,16 +1649,15 @@ def open_mock(name, *args, **kwargs): '/sys/class/power_supply/AC0/online', '/sys/class/power_supply/AC/online', )): - raise IOError(errno.ENOENT, "") - elif name.startswith("/sys/class/power_supply/BAT0/status"): + raise FileNotFoundError + if name.startswith("/sys/class/power_supply/BAT0/status"): return io.BytesIO(b"???") else: return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock) as m: - self.assertIsNone(psutil.sensors_battery().power_plugged) + with mock.patch("builtins.open", side_effect=open_mock) as m: + assert psutil.sensors_battery().power_plugged is None assert m.called def test_emulate_energy_full_0(self): @@ -1717,7 +1665,7 @@ def test_emulate_energy_full_0(self): with mock_open_content( {"/sys/class/power_supply/BAT0/energy_full": b"0"} ) as m: - self.assertEqual(psutil.sensors_battery().percent, 0) + assert psutil.sensors_battery().percent == 0 assert m.called def test_emulate_energy_full_not_avail(self): @@ -1725,62 +1673,61 @@ def test_emulate_energy_full_not_avail(self): # Expected fallback on /capacity. with mock_open_exception( "/sys/class/power_supply/BAT0/energy_full", - IOError(errno.ENOENT, ""), + FileNotFoundError, ): with mock_open_exception( "/sys/class/power_supply/BAT0/charge_full", - IOError(errno.ENOENT, ""), + FileNotFoundError, ): with mock_open_content( {"/sys/class/power_supply/BAT0/capacity": b"88"} ): - self.assertEqual(psutil.sensors_battery().percent, 88) + assert psutil.sensors_battery().percent == 88 def test_emulate_no_power(self): # Emulate a case where /AC0/online file nor /BAT0/status exist. with mock_open_exception( - "/sys/class/power_supply/AC/online", IOError(errno.ENOENT, "") + "/sys/class/power_supply/AC/online", FileNotFoundError ): with mock_open_exception( - "/sys/class/power_supply/AC0/online", IOError(errno.ENOENT, "") + "/sys/class/power_supply/AC0/online", FileNotFoundError ): with mock_open_exception( "/sys/class/power_supply/BAT0/status", - IOError(errno.ENOENT, ""), + FileNotFoundError, ): - self.assertIsNone(psutil.sensors_battery().power_plugged) + assert psutil.sensors_battery().power_plugged is None -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") 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("60000000") elif name.endswith("/power_now"): - return io.StringIO(u"0") + return io.StringIO("0") elif name.endswith("/energy_full"): - return io.StringIO(u"60000001") + return io.StringIO("60000001") else: return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' with mock.patch('os.listdir', return_value=["BAT0"]) as mlistdir: - with mock.patch(patch_point, side_effect=open_mock) as mopen: - self.assertIsNotNone(psutil.sensors_battery()) + with mock.patch("builtins.open", side_effect=open_mock) as mopen: + assert psutil.sensors_battery() is not None assert mlistdir.called assert mopen.called -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") 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("name") elif name.endswith('/temp1_label'): - return io.StringIO(u"label") + return io.StringIO("label") elif name.endswith('/temp1_input'): return io.BytesIO(b"30000") elif name.endswith('/temp1_max'): @@ -1791,17 +1738,16 @@ def open_mock(name, *args, **kwargs): return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock): + with mock.patch("builtins.open", side_effect=open_mock): # Test case with /sys/class/hwmon with mock.patch( 'glob.glob', return_value=['/sys/class/hwmon/hwmon0/temp1'] ): temp = psutil.sensors_temperatures()['name'][0] - self.assertEqual(temp.label, 'label') - self.assertEqual(temp.current, 30.0) - self.assertEqual(temp.high, 40.0) - self.assertEqual(temp.critical, 50.0) + assert temp.label == 'label' + assert temp.current == 30.0 + assert temp.high == 40.0 + assert temp.critical == 50.0 def test_emulate_class_thermal(self): def open_mock(name, *args, **kwargs): @@ -1810,16 +1756,17 @@ def open_mock(name, *args, **kwargs): elif name.endswith('temp'): return io.BytesIO(b"30000") elif name.endswith('0_type'): - return io.StringIO(u"critical") + return io.StringIO("critical") elif name.endswith('type'): - return io.StringIO(u"name") + return io.StringIO("name") else: return orig_open(name, *args, **kwargs) def glob_mock(path): - if path == '/sys/class/hwmon/hwmon*/temp*_*': # noqa - return [] - elif path == '/sys/class/hwmon/hwmon*/device/temp*_*': + if path in { + '/sys/class/hwmon/hwmon*/temp*_*', + '/sys/class/hwmon/hwmon*/device/temp*_*', + }: return [] elif path == '/sys/class/thermal/thermal_zone*': return ['/sys/class/thermal/thermal_zone0'] @@ -1831,38 +1778,36 @@ def glob_mock(path): return [] orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock): + with mock.patch("builtins.open", side_effect=open_mock): with mock.patch('glob.glob', create=True, side_effect=glob_mock): temp = psutil.sensors_temperatures()['name'][0] - self.assertEqual(temp.label, '') - self.assertEqual(temp.current, 30.0) - self.assertEqual(temp.high, 50.0) - self.assertEqual(temp.critical, 50.0) + assert temp.label == '' + assert temp.current == 30.0 + assert temp.high == 50.0 + assert temp.critical == 50.0 -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") 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("name") elif name.endswith('/fan1_label'): - return io.StringIO(u"label") + return io.StringIO("label") elif name.endswith('/fan1_input'): - return io.StringIO(u"2000") + return io.StringIO("2000") else: return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock): + with mock.patch("builtins.open", side_effect=open_mock): with mock.patch( 'glob.glob', return_value=['/sys/class/hwmon/hwmon2/fan1'] ): fan = psutil.sensors_fans()['name'][0] - self.assertEqual(fan.label, 'label') - self.assertEqual(fan.current, 2000) + assert fan.label == 'label' + assert fan.current == 2000 # ===================================================================== @@ -1870,20 +1815,19 @@ def open_mock(name, *args, **kwargs): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestProcess(PsutilTestCase): @retry_on_failure() def test_parse_smaps_vs_memory_maps(self): sproc = self.spawn_testproc() uss, pss, swap = psutil._pslinux.Process(sproc.pid)._parse_smaps() maps = psutil.Process(sproc.pid).memory_maps(grouped=False) - self.assertAlmostEqual( - uss, - sum([x.private_dirty + x.private_clean for x in maps]), - delta=4096, + assert ( + abs(uss - sum([x.private_dirty + x.private_clean for x in maps])) + < 4096 ) - self.assertAlmostEqual(pss, sum([x.pss for x in maps]), delta=4096) - self.assertAlmostEqual(swap, sum([x.swap for x in maps]), delta=4096) + assert abs(pss - sum([x.pss for x in maps])) < 4096 + assert abs(swap - sum([x.swap for x in maps])) < 4096 def test_parse_smaps_mocked(self): # See: https://github.com/giampaolo/psutil/issues/1222 @@ -1910,16 +1854,16 @@ def test_parse_smaps_mocked(self): Locked: 19 kB VmFlags: rd ex """).encode() - with mock_open_content({"/proc/%s/smaps" % os.getpid(): content}) as m: + with mock_open_content({f"/proc/{os.getpid()}/smaps": content}) as m: p = psutil._pslinux.Process(os.getpid()) uss, pss, swap = p._parse_smaps() assert m.called - self.assertEqual(uss, (6 + 7 + 14) * 1024) - self.assertEqual(pss, 3 * 1024) - self.assertEqual(swap, 15 * 1024) + assert uss == (6 + 7 + 14) * 1024 + assert pss == 3 * 1024 + assert swap == 15 * 1024 # On PYPY file descriptors are not closed fast enough. - @unittest.skipIf(PYPY, "unreliable on PYPY") + @pytest.mark.skipif(PYPY, reason="unreliable on PYPY") def test_open_files_mode(self): def get_test_file(fname): p = psutil.Process() @@ -1934,25 +1878,24 @@ def get_test_file(fname): testfn = self.get_testfn() with open(testfn, "w"): - self.assertEqual(get_test_file(testfn).mode, "w") + assert get_test_file(testfn).mode == "w" with open(testfn): - self.assertEqual(get_test_file(testfn).mode, "r") + assert get_test_file(testfn).mode == "r" with open(testfn, "a"): - self.assertEqual(get_test_file(testfn).mode, "a") + assert get_test_file(testfn).mode == "a" with open(testfn, "r+"): - self.assertEqual(get_test_file(testfn).mode, "r+") + assert get_test_file(testfn).mode == "r+" with open(testfn, "w+"): - self.assertEqual(get_test_file(testfn).mode, "r+") + assert get_test_file(testfn).mode == "r+" with open(testfn, "a+"): - self.assertEqual(get_test_file(testfn).mode, "a+") - # note: "x" bit is not supported - if PY3: - safe_rmpath(testfn) - with open(testfn, "x"): - self.assertEqual(get_test_file(testfn).mode, "w") - safe_rmpath(testfn) - with open(testfn, "x+"): - self.assertEqual(get_test_file(testfn).mode, "r+") + assert get_test_file(testfn).mode == "a+" + + safe_rmpath(testfn) + with open(testfn, "x"): + assert get_test_file(testfn).mode == "w" + safe_rmpath(testfn) + with open(testfn, "x+"): + assert get_test_file(testfn).mode == "r+" def test_open_files_file_gone(self): # simulates a file which gets deleted during open_files() @@ -1961,12 +1904,12 @@ def test_open_files_file_gone(self): files = p.open_files() with open(self.get_testfn(), 'w'): # give the kernel some time to see the new file - call_until(p.open_files, "len(ret) != %i" % len(files)) + call_until(lambda: len(p.open_files()) != len(files)) with mock.patch( 'psutil._pslinux.os.readlink', - side_effect=OSError(errno.ENOENT, ""), + side_effect=FileNotFoundError, ) as m: - self.assertEqual(p.open_files(), []) + assert p.open_files() == [] assert m.called # also simulate the case where os.readlink() returns EINVAL # in which case psutil is supposed to 'continue' @@ -1974,7 +1917,7 @@ def test_open_files_file_gone(self): 'psutil._pslinux.os.readlink', side_effect=OSError(errno.EINVAL, ""), ) as m: - self.assertEqual(p.open_files(), []) + assert p.open_files() == [] assert m.called def test_open_files_fd_gone(self): @@ -1985,12 +1928,11 @@ def test_open_files_fd_gone(self): files = p.open_files() with open(self.get_testfn(), 'w'): # give the kernel some time to see the new file - call_until(p.open_files, "len(ret) != %i" % len(files)) - patch_point = 'builtins.open' if PY3 else '__builtin__.open' + call_until(lambda: len(p.open_files()) != len(files)) with mock.patch( - patch_point, side_effect=IOError(errno.ENOENT, "") + "builtins.open", side_effect=FileNotFoundError ) as m: - self.assertEqual(p.open_files(), []) + assert p.open_files() == [] assert m.called def test_open_files_enametoolong(self): @@ -2001,13 +1943,13 @@ def test_open_files_enametoolong(self): files = p.open_files() with open(self.get_testfn(), 'w'): # give the kernel some time to see the new file - call_until(p.open_files, "len(ret) != %i" % len(files)) + call_until(lambda: len(p.open_files()) != len(files)) patch_point = 'psutil._pslinux.os.readlink' with mock.patch( patch_point, side_effect=OSError(errno.ENAMETOOLONG, "") ) as m: with mock.patch("psutil._pslinux.debug"): - self.assertEqual(p.open_files(), []) + assert p.open_files() == [] assert m.called # --- mocked tests @@ -2016,7 +1958,7 @@ def test_terminal_mocked(self): with mock.patch( 'psutil._pslinux._psposix.get_terminal_map', return_value={} ) as m: - self.assertIsNone(psutil._pslinux.Process(os.getpid()).terminal()) + assert psutil._pslinux.Process(os.getpid()).terminal() is None assert m.called # TODO: re-enable this test. @@ -2030,52 +1972,52 @@ def test_terminal_mocked(self): 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('foo\x00bar\x00') with mock.patch( 'psutil._common.open', return_value=fake_file, create=True ) as m: - self.assertEqual(p.cmdline(), ['foo', 'bar']) + assert p.cmdline() == ['foo', 'bar'] assert m.called - fake_file = io.StringIO(u'foo\x00bar\x00\x00') + fake_file = io.StringIO('foo\x00bar\x00\x00') with mock.patch( 'psutil._common.open', return_value=fake_file, create=True ) as m: - self.assertEqual(p.cmdline(), ['foo', 'bar', '']) + assert p.cmdline() == ['foo', 'bar', ''] assert m.called 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('foo bar ') with mock.patch( 'psutil._common.open', return_value=fake_file, create=True ) as m: - self.assertEqual(p.cmdline(), ['foo', 'bar']) + assert p.cmdline() == ['foo', 'bar'] assert m.called - fake_file = io.StringIO(u'foo bar ') + fake_file = io.StringIO('foo bar ') with mock.patch( 'psutil._common.open', return_value=fake_file, create=True ) as m: - self.assertEqual(p.cmdline(), ['foo', 'bar', '']) + assert p.cmdline() == ['foo', 'bar', ''] assert m.called def test_cmdline_mixed_separators(self): # https://github.com/giampaolo/psutil/issues/ # 1179#issuecomment-552984549 p = psutil.Process() - fake_file = io.StringIO(u'foo\x20bar\x00') + fake_file = io.StringIO('foo\x20bar\x00') with mock.patch( 'psutil._common.open', return_value=fake_file, create=True ) as m: - self.assertEqual(p.cmdline(), ['foo', 'bar']) + assert p.cmdline() == ['foo', 'bar'] assert m.called def test_readlink_path_deleted_mocked(self): with mock.patch( 'psutil._pslinux.os.readlink', return_value='/home/foo (deleted)' ): - self.assertEqual(psutil.Process().exe(), "/home/foo") - self.assertEqual(psutil.Process().cwd(), "/home/foo") + assert psutil.Process().exe() == "/home/foo" + assert psutil.Process().cwd() == "/home/foo" def test_threads_mocked(self): # Test the case where os.listdir() returns a file (thread) @@ -2083,67 +2025,78 @@ def test_threads_mocked(self): # condition). threads() is supposed to ignore that instead # of raising NSP. def open_mock_1(name, *args, **kwargs): - if name.startswith('/proc/%s/task' % os.getpid()): - raise IOError(errno.ENOENT, "") - else: - return orig_open(name, *args, **kwargs) + if name.startswith(f"/proc/{os.getpid()}/task"): + raise FileNotFoundError + return orig_open(name, *args, **kwargs) orig_open = open - patch_point = 'builtins.open' if PY3 else '__builtin__.open' - with mock.patch(patch_point, side_effect=open_mock_1) as m: + with mock.patch("builtins.open", side_effect=open_mock_1) as m: ret = psutil.Process().threads() assert m.called - self.assertEqual(ret, []) + assert ret == [] # ...but if it bumps into something != ENOENT we want an # exception. def open_mock_2(name, *args, **kwargs): - if name.startswith('/proc/%s/task' % os.getpid()): - raise IOError(errno.EPERM, "") - else: - return orig_open(name, *args, **kwargs) + if name.startswith(f"/proc/{os.getpid()}/task"): + raise PermissionError + return orig_open(name, *args, **kwargs) - with mock.patch(patch_point, side_effect=open_mock_2): - self.assertRaises(psutil.AccessDenied, psutil.Process().threads) + with mock.patch("builtins.open", side_effect=open_mock_2): + with pytest.raises(psutil.AccessDenied): + psutil.Process().threads() def test_exe_mocked(self): with mock.patch( - 'psutil._pslinux.readlink', side_effect=OSError(errno.ENOENT, "") + 'psutil._pslinux.readlink', side_effect=FileNotFoundError ) as m: - ret = psutil.Process().exe() - assert m.called - self.assertEqual(ret, "") + # de-activate guessing from cmdline() + with mock.patch( + 'psutil._pslinux.Process.cmdline', return_value=[] + ): + ret = psutil.Process().exe() + assert m.called + assert ret == "" def test_issue_1014(self): # Emulates a case where smaps file does not exist. In this case # wrap_exception decorator should not raise NoSuchProcess. with mock_open_exception( - '/proc/%s/smaps' % os.getpid(), IOError(errno.ENOENT, "") + f"/proc/{os.getpid()}/smaps", FileNotFoundError ) as m: p = psutil.Process() - with self.assertRaises(FileNotFoundError): + with pytest.raises(FileNotFoundError): p.memory_maps() assert m.called - @unittest.skipIf(not HAS_RLIMIT, "not supported") + def test_issue_2418(self): + p = psutil.Process() + with mock_open_exception( + f"/proc/{os.getpid()}/statm", FileNotFoundError + ): + with mock.patch("os.path.exists", return_value=False): + with pytest.raises(psutil.NoSuchProcess): + p.memory_info() + + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit_zombie(self): # Emulate a case where rlimit() raises ENOSYS, which may # happen in case of zombie process: # https://travis-ci.org/giampaolo/psutil/jobs/51368273 with mock.patch( - "psutil._pslinux.prlimit", side_effect=OSError(errno.ENOSYS, "") + "resource.prlimit", side_effect=OSError(errno.ENOSYS, "") ) as m1: with mock.patch( "psutil._pslinux.Process._is_zombie", return_value=True ) as m2: p = psutil.Process() p.name() - with self.assertRaises(psutil.ZombieProcess) as exc: + with pytest.raises(psutil.ZombieProcess) as cm: p.rlimit(psutil.RLIMIT_NOFILE) assert m1.called assert m2.called - self.assertEqual(exc.exception.pid, p.pid) - self.assertEqual(exc.exception.name, p.name()) + assert cm.value.pid == p.pid + assert cm.value.name == p.name() def test_stat_file_parsing(self): args = [ @@ -2191,21 +2144,19 @@ def test_stat_file_parsing(self): "7", # delayacct_blkio_ticks ] content = " ".join(args).encode() - with mock_open_content({"/proc/%s/stat" % os.getpid(): content}): + with mock_open_content({f"/proc/{os.getpid()}/stat": content}): p = psutil.Process() - self.assertEqual(p.name(), 'cat') - self.assertEqual(p.status(), psutil.STATUS_ZOMBIE) - self.assertEqual(p.ppid(), 1) - self.assertEqual( - p.create_time(), 6 / CLOCK_TICKS + psutil.boot_time() - ) + assert p.name() == 'cat' + assert p.status() == psutil.STATUS_ZOMBIE + assert p.ppid() == 1 + assert p.create_time() == 6 / CLOCK_TICKS + psutil.boot_time() cpu = p.cpu_times() - self.assertEqual(cpu.user, 2 / CLOCK_TICKS) - self.assertEqual(cpu.system, 3 / CLOCK_TICKS) - self.assertEqual(cpu.children_user, 4 / CLOCK_TICKS) - self.assertEqual(cpu.children_system, 5 / CLOCK_TICKS) - self.assertEqual(cpu.iowait, 7 / CLOCK_TICKS) - self.assertEqual(p.cpu_num(), 6) + assert cpu.user == 2 / CLOCK_TICKS + assert cpu.system == 3 / CLOCK_TICKS + assert cpu.children_user == 4 / CLOCK_TICKS + assert cpu.children_system == 5 / CLOCK_TICKS + assert cpu.iowait == 7 / CLOCK_TICKS + assert p.cpu_num() == 6 def test_status_file_parsing(self): content = textwrap.dedent("""\ @@ -2216,20 +2167,20 @@ def test_status_file_parsing(self): Cpus_allowed_list:\t0-7 voluntary_ctxt_switches:\t12 nonvoluntary_ctxt_switches:\t13""").encode() - with mock_open_content({"/proc/%s/status" % os.getpid(): content}): + with mock_open_content({f"/proc/{os.getpid()}/status": content}): p = psutil.Process() - self.assertEqual(p.num_ctx_switches().voluntary, 12) - self.assertEqual(p.num_ctx_switches().involuntary, 13) - self.assertEqual(p.num_threads(), 66) + assert p.num_ctx_switches().voluntary == 12 + assert p.num_ctx_switches().involuntary == 13 + assert p.num_threads() == 66 uids = p.uids() - self.assertEqual(uids.real, 1000) - self.assertEqual(uids.effective, 1001) - self.assertEqual(uids.saved, 1002) + assert uids.real == 1000 + assert uids.effective == 1001 + assert uids.saved == 1002 gids = p.gids() - self.assertEqual(gids.real, 1004) - self.assertEqual(gids.effective, 1005) - self.assertEqual(gids.saved, 1006) - self.assertEqual(p._proc._get_eligible_cpus(), list(range(8))) + assert gids.real == 1004 + assert gids.effective == 1005 + assert gids.saved == 1006 + assert p._proc._get_eligible_cpus() == list(range(8)) def test_net_connections_enametoolong(self): # Simulate a case where /proc/{pid}/fd/{fd} symlink points to @@ -2241,11 +2192,11 @@ def test_net_connections_enametoolong(self): ) as m: p = psutil.Process() with mock.patch("psutil._pslinux.debug"): - self.assertEqual(p.net_connections(), []) + assert p.net_connections() == [] assert m.called -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestProcessAgainstStatus(PsutilTestCase): """/proc/pid/stat and /proc/pid/status have many values in common. Whenever possible, psutil uses /proc/pid/stat (it's faster). @@ -2260,7 +2211,7 @@ def setUpClass(cls): def read_status_file(self, linestart): with psutil._psplatform.open_text( - '/proc/%s/status' % self.proc.pid + f"/proc/{self.proc.pid}/status" ) as f: for line in f: line = line.strip() @@ -2270,50 +2221,49 @@ def read_status_file(self, linestart): return int(value) except ValueError: return value - raise ValueError("can't find %r" % linestart) + raise ValueError(f"can't find {linestart!r}") def test_name(self): value = self.read_status_file("Name:") - self.assertEqual(self.proc.name(), value) + assert self.proc.name() == value + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_status(self): value = self.read_status_file("State:") value = value[value.find('(') + 1 : value.rfind(')')] value = value.replace(' ', '-') - self.assertEqual(self.proc.status(), value) + assert self.proc.status() == value def test_ppid(self): value = self.read_status_file("PPid:") - self.assertEqual(self.proc.ppid(), value) + assert self.proc.ppid() == value def test_num_threads(self): value = self.read_status_file("Threads:") - self.assertEqual(self.proc.num_threads(), value) + assert self.proc.num_threads() == value def test_uids(self): value = self.read_status_file("Uid:") value = tuple(map(int, value.split()[1:4])) - self.assertEqual(self.proc.uids(), value) + assert self.proc.uids() == value def test_gids(self): value = self.read_status_file("Gid:") value = tuple(map(int, value.split()[1:4])) - self.assertEqual(self.proc.gids(), value) + assert self.proc.gids() == value @retry_on_failure() def test_num_ctx_switches(self): value = self.read_status_file("voluntary_ctxt_switches:") - self.assertEqual(self.proc.num_ctx_switches().voluntary, value) + assert self.proc.num_ctx_switches().voluntary == value value = self.read_status_file("nonvoluntary_ctxt_switches:") - self.assertEqual(self.proc.num_ctx_switches().involuntary, value) + assert self.proc.num_ctx_switches().involuntary == value def test_cpu_affinity(self): value = self.read_status_file("Cpus_allowed_list:") if '-' in str(value): min_, max_ = map(int, value.split('-')) - self.assertEqual( - self.proc.cpu_affinity(), list(range(min_, max_ + 1)) - ) + assert self.proc.cpu_affinity() == list(range(min_, max_ + 1)) def test_cpu_affinity_eligible_cpus(self): value = self.read_status_file("Cpus_allowed_list:") @@ -2330,15 +2280,9 @@ def test_cpu_affinity_eligible_cpus(self): # ===================================================================== -@unittest.skipIf(not LINUX, "LINUX only") +@pytest.mark.skipif(not LINUX, reason="LINUX only") class TestUtils(PsutilTestCase): def test_readlink(self): with mock.patch("os.readlink", return_value="foo (deleted)") as m: - self.assertEqual(psutil._psplatform.readlink("bar"), "foo") + assert psutil._psplatform.readlink("bar") == "foo" assert m.called - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) diff --git a/psutil/tests/test_memleaks.py b/psutil/tests/test_memleaks.py index 8232b18ad..fd4cd09a4 100755 --- a/psutil/tests/test_memleaks.py +++ b/psutil/tests/test_memleaks.py @@ -14,12 +14,10 @@ because of how its JIT handles memory, so tests are skipped. """ -from __future__ import print_function import functools import os import platform -import unittest import psutil import psutil._common @@ -29,8 +27,6 @@ from psutil import POSIX from psutil import SUNOS from psutil import WINDOWS -from psutil._compat import ProcessLookupError -from psutil._compat import super from psutil.tests import HAS_CPU_AFFINITY from psutil.tests import HAS_CPU_FREQ from psutil.tests import HAS_ENVIRON @@ -43,10 +39,12 @@ from psutil.tests import HAS_SENSORS_BATTERY from psutil.tests import HAS_SENSORS_FANS from psutil.tests import HAS_SENSORS_TEMPERATURES +from psutil.tests import QEMU_USER from psutil.tests import TestMemoryLeak from psutil.tests import create_sockets from psutil.tests import get_testfn from psutil.tests import process_namespace +from psutil.tests import pytest from psutil.tests import skip_on_access_denied from psutil.tests import spawn_testproc from psutil.tests import system_namespace @@ -111,12 +109,12 @@ def test_exe(self): def test_ppid(self): self.execute(self.proc.ppid) - @unittest.skipIf(not POSIX, "POSIX only") + @pytest.mark.skipif(not POSIX, reason="POSIX only") @fewtimes_if_linux() def test_uids(self): self.execute(self.proc.uids) - @unittest.skipIf(not POSIX, "POSIX only") + @pytest.mark.skipif(not POSIX, reason="POSIX only") @fewtimes_if_linux() def test_gids(self): self.execute(self.proc.gids) @@ -132,11 +130,11 @@ def test_nice_set(self): niceness = thisproc.nice() self.execute(lambda: self.proc.nice(niceness)) - @unittest.skipIf(not HAS_IONICE, "not supported") + @pytest.mark.skipif(not HAS_IONICE, reason="not supported") def test_ionice(self): self.execute(self.proc.ionice) - @unittest.skipIf(not HAS_IONICE, "not supported") + @pytest.mark.skipif(not HAS_IONICE, reason="not supported") def test_ionice_set(self): if WINDOWS: value = thisproc.ionice() @@ -146,12 +144,12 @@ def test_ionice_set(self): fun = functools.partial(cext.proc_ioprio_set, os.getpid(), -1, 0) self.execute_w_exc(OSError, fun) - @unittest.skipIf(not HAS_PROC_IO_COUNTERS, "not supported") + @pytest.mark.skipif(not HAS_PROC_IO_COUNTERS, reason="not supported") @fewtimes_if_linux() def test_io_counters(self): self.execute(self.proc.io_counters) - @unittest.skipIf(POSIX, "worthless on POSIX") + @pytest.mark.skipif(POSIX, reason="worthless on POSIX") def test_username(self): # always open 1 handle on Windows (only once) psutil.Process().username() @@ -166,11 +164,11 @@ def test_create_time(self): def test_num_threads(self): self.execute(self.proc.num_threads) - @unittest.skipIf(not WINDOWS, "WINDOWS only") + @pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") def test_num_handles(self): self.execute(self.proc.num_handles) - @unittest.skipIf(not POSIX, "POSIX only") + @pytest.mark.skipif(not POSIX, reason="POSIX only") @fewtimes_if_linux() def test_num_fds(self): self.execute(self.proc.num_fds) @@ -189,7 +187,7 @@ def test_cpu_times(self): self.execute(self.proc.cpu_times) @fewtimes_if_linux() - @unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported") + @pytest.mark.skipif(not HAS_PROC_CPU_NUM, reason="not supported") def test_cpu_num(self): self.execute(self.proc.cpu_num) @@ -201,7 +199,7 @@ def test_memory_info(self): def test_memory_full_info(self): self.execute(self.proc.memory_full_info) - @unittest.skipIf(not POSIX, "POSIX only") + @pytest.mark.skipif(not POSIX, reason="POSIX only") @fewtimes_if_linux() def test_terminal(self): self.execute(self.proc.terminal) @@ -214,11 +212,11 @@ def test_resume(self): def test_cwd(self): self.execute(self.proc.cwd) - @unittest.skipIf(not HAS_CPU_AFFINITY, "not supported") + @pytest.mark.skipif(not HAS_CPU_AFFINITY, reason="not supported") def test_cpu_affinity(self): self.execute(self.proc.cpu_affinity) - @unittest.skipIf(not HAS_CPU_AFFINITY, "not supported") + @pytest.mark.skipif(not HAS_CPU_AFFINITY, reason="not supported") def test_cpu_affinity_set(self): affinity = thisproc.cpu_affinity() self.execute(lambda: self.proc.cpu_affinity(affinity)) @@ -229,18 +227,18 @@ def test_open_files(self): with open(get_testfn(), 'w'): self.execute(self.proc.open_files) - @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") + @pytest.mark.skipif(not HAS_MEMORY_MAPS, reason="not supported") @fewtimes_if_linux() def test_memory_maps(self): self.execute(self.proc.memory_maps) - @unittest.skipIf(not LINUX, "LINUX only") - @unittest.skipIf(not HAS_RLIMIT, "not supported") + @pytest.mark.skipif(not LINUX, reason="LINUX only") + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit(self): self.execute(lambda: self.proc.rlimit(psutil.RLIMIT_NOFILE)) - @unittest.skipIf(not LINUX, "LINUX only") - @unittest.skipIf(not HAS_RLIMIT, "not supported") + @pytest.mark.skipif(not LINUX, reason="LINUX only") + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit_set(self): limit = thisproc.rlimit(psutil.RLIMIT_NOFILE) self.execute(lambda: self.proc.rlimit(psutil.RLIMIT_NOFILE, limit)) @@ -249,7 +247,7 @@ def test_rlimit_set(self): @fewtimes_if_linux() # Windows implementation is based on a single system-wide # function (tested later). - @unittest.skipIf(WINDOWS, "worthless on WINDOWS") + @pytest.mark.skipif(WINDOWS, reason="worthless on WINDOWS") def test_net_connections(self): # TODO: UNIX sockets are temporarily implemented by parsing # 'pfiles' cmd output; we don't want that part of the code to @@ -258,11 +256,11 @@ def test_net_connections(self): kind = 'inet' if SUNOS else 'all' self.execute(lambda: self.proc.net_connections(kind)) - @unittest.skipIf(not HAS_ENVIRON, "not supported") + @pytest.mark.skipif(not HAS_ENVIRON, reason="not supported") def test_environ(self): self.execute(self.proc.environ) - @unittest.skipIf(not WINDOWS, "WINDOWS only") + @pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") def test_proc_info(self): self.execute(lambda: cext.proc_info(os.getpid())) @@ -321,7 +319,7 @@ def call(): self.execute(call) -@unittest.skipIf(not WINDOWS, "WINDOWS only") +@pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") class TestProcessDualImplementation(TestMemoryLeak): def test_cmdline_peb_true(self): self.execute(lambda: cext.proc_cmdline(os.getpid(), use_peb=True)) @@ -366,14 +364,14 @@ def test_cpu_stats(self): @fewtimes_if_linux() # TODO: remove this once 1892 is fixed - @unittest.skipIf( - MACOS and platform.machine() == 'arm64', "skipped due to #1892" + @pytest.mark.skipif( + MACOS and platform.machine() == 'arm64', reason="skipped due to #1892" ) - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") def test_cpu_freq(self): self.execute(psutil.cpu_freq) - @unittest.skipIf(not WINDOWS, "WINDOWS only") + @pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") def test_getloadavg(self): psutil.getloadavg() self.execute(psutil.getloadavg) @@ -384,7 +382,7 @@ def test_virtual_memory(self): self.execute(psutil.virtual_memory) # TODO: remove this skip when this gets fixed - @unittest.skipIf(SUNOS, "worthless on SUNOS (uses a subprocess)") + @pytest.mark.skipif(SUNOS, reason="worthless on SUNOS (uses a subprocess)") def test_swap_memory(self): self.execute(psutil.swap_memory) @@ -398,12 +396,13 @@ def test_disk_usage(self): times = FEW_TIMES if POSIX else self.times self.execute(lambda: psutil.disk_usage('.'), times=times) + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_disk_partitions(self): self.execute(psutil.disk_partitions) - @unittest.skipIf( + @pytest.mark.skipif( LINUX and not os.path.exists('/proc/diskstats'), - '/proc/diskstats not available on this Linux version', + reason="/proc/diskstats not available on this Linux version", ) @fewtimes_if_linux() def test_disk_io_counters(self): @@ -418,12 +417,12 @@ def test_pids(self): # --- net @fewtimes_if_linux() - @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') + @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported") def test_net_io_counters(self): self.execute(lambda: psutil.net_io_counters(nowrap=False)) @fewtimes_if_linux() - @unittest.skipIf(MACOS and os.getuid() != 0, "need root access") + @pytest.mark.skipif(MACOS and os.getuid() != 0, reason="need root access") def test_net_connections(self): # always opens and handle on Windows() (once) psutil.net_connections(kind='all') @@ -435,23 +434,24 @@ def test_net_if_addrs(self): tolerance = 80 * 1024 if WINDOWS else self.tolerance self.execute(psutil.net_if_addrs, tolerance=tolerance) + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_net_if_stats(self): self.execute(psutil.net_if_stats) # --- sensors @fewtimes_if_linux() - @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_BATTERY, reason="not supported") def test_sensors_battery(self): self.execute(psutil.sensors_battery) @fewtimes_if_linux() - @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_TEMPERATURES, reason="not supported") def test_sensors_temperatures(self): self.execute(psutil.sensors_temperatures) @fewtimes_if_linux() - @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_FANS, reason="not supported") def test_sensors_fans(self): self.execute(psutil.sensors_fans) @@ -488,9 +488,3 @@ def test_win_service_get_status(self): def test_win_service_get_description(self): name = next(psutil.win_service_iter()).name() self.execute(lambda: cext.winservice_query_descr(name)) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 93204fa06..848b9284e 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- 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 @@ -7,20 +6,18 @@ """Miscellaneous tests.""" -import ast import collections -import errno +import contextlib +import io import json import os import pickle import socket -import stat import sys -import unittest +from unittest import mock import psutil import psutil.tests -from psutil import POSIX from psutil import WINDOWS from psutil._common import bcat from psutil._common import cat @@ -31,24 +28,12 @@ from psutil._common import parse_environ_block from psutil._common import supports_ipv6 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 CI_TESTING -from psutil.tests import HAS_BATTERY -from psutil.tests import HAS_MEMORY_MAPS from psutil.tests import HAS_NET_IO_COUNTERS -from psutil.tests import HAS_SENSORS_BATTERY -from psutil.tests import HAS_SENSORS_FANS -from psutil.tests import HAS_SENSORS_TEMPERATURES -from psutil.tests import PYTHON_EXE -from psutil.tests import PYTHON_EXE_ENV -from psutil.tests import SCRIPTS_DIR +from psutil.tests import QEMU_USER from psutil.tests import PsutilTestCase -from psutil.tests import mock from psutil.tests import process_namespace +from psutil.tests import pytest from psutil.tests import reload_module -from psutil.tests import sh from psutil.tests import system_namespace @@ -59,26 +44,24 @@ class TestSpecialMethods(PsutilTestCase): def test_check_pid_range(self): - with self.assertRaises(OverflowError): + with pytest.raises(OverflowError): psutil._psplatform.cext.check_pid_range(2**128) - with self.assertRaises(psutil.NoSuchProcess): + with pytest.raises(psutil.NoSuchProcess): psutil.Process(2**128) def test_process__repr__(self, func=repr): p = psutil.Process(self.spawn_testproc().pid) r = func(p) - self.assertIn("psutil.Process", r) - self.assertIn("pid=%s" % p.pid, r) - self.assertIn( - "name='%s'" % str(p.name()), r.replace("name=u'", "name='") - ) - self.assertIn("status=", r) - self.assertNotIn("exitcode=", r) + assert "psutil.Process" in r + assert f"pid={p.pid}" in r + assert f"name='{p.name()}'" in r.replace("name=u'", "name='") + assert "status=" in r + assert "exitcode=" not in r p.terminate() p.wait() r = func(p) - self.assertIn("status='terminated'", r) - self.assertIn("exitcode=", r) + assert "status='terminated'" in r + assert "exitcode=" in r with mock.patch.object( psutil.Process, @@ -87,9 +70,9 @@ def test_process__repr__(self, func=repr): ): p = psutil.Process() r = func(p) - self.assertIn("pid=%s" % p.pid, r) - self.assertIn("status='zombie'", r) - self.assertNotIn("name=", r) + assert f"pid={p.pid}" in r + assert "status='zombie'" in r + assert "name=" not in r with mock.patch.object( psutil.Process, "name", @@ -97,9 +80,9 @@ def test_process__repr__(self, func=repr): ): p = psutil.Process() r = func(p) - self.assertIn("pid=%s" % p.pid, r) - self.assertIn("terminated", r) - self.assertNotIn("name=", r) + assert f"pid={p.pid}" in r + assert "terminated" in r + assert "name=" not in r with mock.patch.object( psutil.Process, "name", @@ -107,106 +90,104 @@ def test_process__repr__(self, func=repr): ): p = psutil.Process() r = func(p) - self.assertIn("pid=%s" % p.pid, r) - self.assertNotIn("name=", r) + assert f"pid={p.pid}" in r + assert "name=" not in r def test_process__str__(self): self.test_process__repr__(func=str) def test_error__repr__(self): - self.assertEqual(repr(psutil.Error()), "psutil.Error()") + assert repr(psutil.Error()) == "psutil.Error()" def test_error__str__(self): - self.assertEqual(str(psutil.Error()), "") + assert str(psutil.Error()) == "" def test_no_such_process__repr__(self): - self.assertEqual( - repr(psutil.NoSuchProcess(321)), - "psutil.NoSuchProcess(pid=321, msg='process no longer exists')", + assert ( + repr(psutil.NoSuchProcess(321)) + == "psutil.NoSuchProcess(pid=321, msg='process no longer exists')" ) - self.assertEqual( - repr(psutil.NoSuchProcess(321, name="name", msg="msg")), - "psutil.NoSuchProcess(pid=321, name='name', msg='msg')", + assert ( + repr(psutil.NoSuchProcess(321, name="name", msg="msg")) + == "psutil.NoSuchProcess(pid=321, name='name', msg='msg')" ) def test_no_such_process__str__(self): - self.assertEqual( - str(psutil.NoSuchProcess(321)), - "process no longer exists (pid=321)", + assert ( + str(psutil.NoSuchProcess(321)) + == "process no longer exists (pid=321)" ) - self.assertEqual( - str(psutil.NoSuchProcess(321, name="name", msg="msg")), - "msg (pid=321, name='name')", + assert ( + str(psutil.NoSuchProcess(321, name="name", msg="msg")) + == "msg (pid=321, name='name')" ) def test_zombie_process__repr__(self): - self.assertEqual( - repr(psutil.ZombieProcess(321)), - 'psutil.ZombieProcess(pid=321, msg="PID still ' - 'exists but it\'s a zombie")', + assert ( + repr(psutil.ZombieProcess(321)) + == 'psutil.ZombieProcess(pid=321, msg="PID still ' + 'exists but it\'s a zombie")' ) - self.assertEqual( - repr(psutil.ZombieProcess(321, name="name", ppid=320, msg="foo")), - "psutil.ZombieProcess(pid=321, ppid=320, name='name', msg='foo')", + assert ( + repr(psutil.ZombieProcess(321, name="name", ppid=320, msg="foo")) + == "psutil.ZombieProcess(pid=321, ppid=320, name='name'," + " msg='foo')" ) def test_zombie_process__str__(self): - self.assertEqual( - str(psutil.ZombieProcess(321)), - "PID still exists but it's a zombie (pid=321)", + assert ( + str(psutil.ZombieProcess(321)) + == "PID still exists but it's a zombie (pid=321)" ) - self.assertEqual( - str(psutil.ZombieProcess(321, name="name", ppid=320, msg="foo")), - "foo (pid=321, ppid=320, name='name')", + assert ( + str(psutil.ZombieProcess(321, name="name", ppid=320, msg="foo")) + == "foo (pid=321, ppid=320, name='name')" ) def test_access_denied__repr__(self): - self.assertEqual( - repr(psutil.AccessDenied(321)), "psutil.AccessDenied(pid=321)" - ) - self.assertEqual( - repr(psutil.AccessDenied(321, name="name", msg="msg")), - "psutil.AccessDenied(pid=321, name='name', msg='msg')", + assert repr(psutil.AccessDenied(321)) == "psutil.AccessDenied(pid=321)" + assert ( + repr(psutil.AccessDenied(321, name="name", msg="msg")) + == "psutil.AccessDenied(pid=321, name='name', msg='msg')" ) def test_access_denied__str__(self): - self.assertEqual(str(psutil.AccessDenied(321)), "(pid=321)") - self.assertEqual( - str(psutil.AccessDenied(321, name="name", msg="msg")), - "msg (pid=321, name='name')", + assert str(psutil.AccessDenied(321)) == "(pid=321)" + assert ( + str(psutil.AccessDenied(321, name="name", msg="msg")) + == "msg (pid=321, name='name')" ) def test_timeout_expired__repr__(self): - self.assertEqual( - repr(psutil.TimeoutExpired(5)), - "psutil.TimeoutExpired(seconds=5, msg='timeout after 5 seconds')", + assert ( + repr(psutil.TimeoutExpired(5)) + == "psutil.TimeoutExpired(seconds=5, msg='timeout after 5" + " seconds')" ) - self.assertEqual( - repr(psutil.TimeoutExpired(5, pid=321, name="name")), - "psutil.TimeoutExpired(pid=321, name='name', seconds=5, " - "msg='timeout after 5 seconds')", + assert ( + repr(psutil.TimeoutExpired(5, pid=321, name="name")) + == "psutil.TimeoutExpired(pid=321, name='name', seconds=5, " + "msg='timeout after 5 seconds')" ) def test_timeout_expired__str__(self): - self.assertEqual( - str(psutil.TimeoutExpired(5)), "timeout after 5 seconds" - ) - self.assertEqual( - str(psutil.TimeoutExpired(5, pid=321, name="name")), - "timeout after 5 seconds (pid=321, name='name')", + assert str(psutil.TimeoutExpired(5)) == "timeout after 5 seconds" + assert ( + str(psutil.TimeoutExpired(5, pid=321, name="name")) + == "timeout after 5 seconds (pid=321, name='name')" ) def test_process__eq__(self): p1 = psutil.Process() p2 = psutil.Process() - self.assertEqual(p1, p2) + assert p1 == p2 p2._ident = (0, 0) - self.assertNotEqual(p1, p2) - self.assertNotEqual(p1, 'foo') + assert p1 != p2 + assert p1 != 'foo' def test_process__hash__(self): - s = set([psutil.Process(), psutil.Process()]) - self.assertEqual(len(s), 1) + s = {psutil.Process(), psutil.Process()} + assert len(s) == 1 # =================================================================== @@ -218,13 +199,13 @@ class TestMisc(PsutilTestCase): def test__all__(self): dir_psutil = dir(psutil) for name in dir_psutil: - if name in ( - 'long', + if name in { + 'debug', 'tests', 'test', 'PermissionError', 'ProcessLookupError', - ): + }: continue if not name.startswith('_'): try: @@ -238,25 +219,26 @@ def test__all__(self): fun.__doc__ is not None and 'deprecated' not in fun.__doc__.lower() ): - raise self.fail('%r not in psutil.__all__' % name) + raise self.fail(f"{name!r} not in psutil.__all__") # Import 'star' will break if __all__ is inconsistent, see: # https://github.com/giampaolo/psutil/issues/656 - # Can't do `from psutil import *` as it won't work on python 3 + # Can't do `from psutil import *` as it won't work # so we simply iterate over __all__. for name in psutil.__all__: - self.assertIn(name, dir_psutil) + assert name in dir_psutil def test_version(self): - self.assertEqual( - '.'.join([str(x) for x in psutil.version_info]), psutil.__version__ + assert ( + '.'.join([str(x) for x in psutil.version_info]) + == psutil.__version__ ) def test_process_as_dict_no_new_names(self): # See https://github.com/giampaolo/psutil/issues/813 p = psutil.Process() p.foo = '1' - self.assertNotIn('foo', p.as_dict()) + assert 'foo' not in p.as_dict() def test_serialization(self): def check(ret): @@ -264,7 +246,7 @@ def check(ret): a = pickle.dumps(ret) b = pickle.loads(a) - self.assertEqual(ret, b) + assert ret == b # --- process APIs @@ -287,6 +269,9 @@ def check(ret): for fun, name in ns.iter(ns.getters): if name in {"win_service_iter", "win_service_get"}: continue + if QEMU_USER and name == "net_if_stats": + # OSError: [Errno 38] ioctl(SIOCETHTOOL) not implemented + continue with self.subTest(name=name): try: ret = fun() @@ -302,67 +287,66 @@ def check(ret): 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') + assert isinstance(b, psutil.NoSuchProcess) + assert b.pid == 4567 + assert b.name == 'name' + assert 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') + assert isinstance(b, psutil.ZombieProcess) + assert b.pid == 4567 + assert b.ppid == 42 + assert b.name == 'name' + assert 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') + assert isinstance(b, psutil.AccessDenied) + assert b.pid == 123 + assert b.name == 'name' + assert 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): - # 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__) + assert isinstance(b, psutil.TimeoutExpired) + assert b.seconds == 33 + assert b.pid == 4567 + assert b.name == 'name' def test_ad_on_process_creation(self): # We are supposed to be able to instantiate Process also in case # of zombie processes or access denied. with mock.patch.object( - psutil.Process, 'create_time', side_effect=psutil.AccessDenied + psutil.Process, '_get_ident', side_effect=psutil.AccessDenied ) as meth: psutil.Process() assert meth.called + with mock.patch.object( - psutil.Process, 'create_time', side_effect=psutil.ZombieProcess(1) + psutil.Process, '_get_ident', side_effect=psutil.ZombieProcess(1) ) as meth: psutil.Process() assert meth.called + + with mock.patch.object( + psutil.Process, '_get_ident', side_effect=ValueError + ) as meth: + with pytest.raises(ValueError): + psutil.Process() + assert meth.called + with mock.patch.object( - psutil.Process, 'create_time', side_effect=ValueError + psutil.Process, '_get_ident', side_effect=psutil.NoSuchProcess(1) ) as meth: - with self.assertRaises(ValueError): + with self.assertRaises(psutil.NoSuchProcess): psutil.Process() assert meth.called @@ -371,9 +355,9 @@ def test_sanity_version_check(self): with mock.patch( "psutil._psplatform.cext.version", return_value="0.0.0" ): - with self.assertRaises(ImportError) as cm: + with pytest.raises(ImportError) as cm: reload_module(psutil) - self.assertIn("version conflict", str(cm.exception).lower()) + assert "version conflict" in str(cm.value).lower() # =================================================================== @@ -391,32 +375,30 @@ def run_against(self, obj, expected_retval=None): # no args for _ in range(2): ret = obj() - self.assertEqual(self.calls, [((), {})]) + assert self.calls == [((), {})] if expected_retval is not None: - self.assertEqual(ret, expected_retval) + assert ret == expected_retval # with args for _ in range(2): ret = obj(1) - self.assertEqual(self.calls, [((), {}), ((1,), {})]) + assert self.calls == [((), {}), ((1,), {})] if expected_retval is not None: - self.assertEqual(ret, expected_retval) + assert ret == expected_retval # with args + kwargs for _ in range(2): ret = obj(1, bar=2) - self.assertEqual( - self.calls, [((), {}), ((1,), {}), ((1,), {'bar': 2})] - ) + assert self.calls == [((), {}), ((1,), {}), ((1,), {'bar': 2})] if expected_retval is not None: - self.assertEqual(ret, expected_retval) + assert ret == expected_retval # clear cache - self.assertEqual(len(self.calls), 3) + assert len(self.calls) == 3 obj.cache_clear() ret = obj() if expected_retval is not None: - self.assertEqual(ret, expected_retval) - self.assertEqual(len(self.calls), 4) + assert ret == expected_retval + assert len(self.calls) == 4 # docstring - self.assertEqual(obj.__doc__, "My docstring.") + assert obj.__doc__ == "My docstring." def test_function(self): @memoize @@ -441,7 +423,7 @@ def bar(self): baseclass = self self.run_against(Foo, expected_retval=None) - self.assertEqual(Foo().bar(), 22) + assert Foo().bar() == 22 def test_class_singleton(self): # @memoize can be used against classes to create singletons @@ -450,11 +432,11 @@ class Bar: def __init__(self, *args, **kwargs): pass - self.assertIs(Bar(), Bar()) - self.assertEqual(id(Bar()), id(Bar())) - self.assertEqual(id(Bar(1)), id(Bar(1))) - self.assertEqual(id(Bar(1, foo=3)), id(Bar(1, foo=3))) - self.assertNotEqual(id(Bar(1)), id(Bar(2))) + assert Bar() is Bar() + assert id(Bar()) == id(Bar()) + assert id(Bar(1)) == id(Bar(1)) + assert id(Bar(1, foo=3)) == id(Bar(1, foo=3)) + assert id(Bar(1)) != id(Bar(2)) def test_staticmethod(self): class Foo: @@ -494,28 +476,28 @@ def foo(*args, **kwargs): for _ in range(2): ret = foo() expected = ((), {}) - self.assertEqual(ret, expected) - self.assertEqual(len(calls), 1) + assert ret == expected + assert len(calls) == 1 # with args for _ in range(2): ret = foo(1) expected = ((1,), {}) - self.assertEqual(ret, expected) - self.assertEqual(len(calls), 2) + assert ret == expected + assert len(calls) == 2 # with args + kwargs for _ in range(2): ret = foo(1, bar=2) expected = ((1,), {'bar': 2}) - self.assertEqual(ret, expected) - self.assertEqual(len(calls), 3) + assert ret == expected + assert len(calls) == 3 # clear cache foo.cache_clear() ret = foo() expected = ((), {}) - self.assertEqual(ret, expected) - self.assertEqual(len(calls), 4) + assert ret == expected + assert len(calls) == 4 # docstring - self.assertEqual(foo.__doc__, "Foo docstring.") + assert foo.__doc__ == "Foo docstring." class TestCommonModule(PsutilTestCase): @@ -529,43 +511,42 @@ def foo(self): calls = [] f.foo() f.foo() - self.assertEqual(len(calls), 2) + assert len(calls) == 2 # activate calls = [] f.foo.cache_activate(f) f.foo() f.foo() - self.assertEqual(len(calls), 1) + assert len(calls) == 1 # deactivate calls = [] f.foo.cache_deactivate(f) f.foo() f.foo() - self.assertEqual(len(calls), 2) + assert len(calls) == 2 def test_parse_environ_block(self): def k(s): return s.upper() if WINDOWS else s - self.assertEqual(parse_environ_block("a=1\0"), {k("a"): "1"}) - self.assertEqual( - parse_environ_block("a=1\0b=2\0\0"), {k("a"): "1", k("b"): "2"} - ) - self.assertEqual( - parse_environ_block("a=1\0b=\0\0"), {k("a"): "1", k("b"): ""} - ) + assert parse_environ_block("a=1\0") == {k("a"): "1"} + assert parse_environ_block("a=1\0b=2\0\0") == { + k("a"): "1", + k("b"): "2", + } + assert parse_environ_block("a=1\0b=\0\0") == {k("a"): "1", k("b"): ""} # ignore everything after \0\0 - self.assertEqual( - parse_environ_block("a=1\0b=2\0\0c=3\0"), - {k("a"): "1", k("b"): "2"}, - ) + assert parse_environ_block("a=1\0b=2\0\0c=3\0") == { + k("a"): "1", + k("b"): "2", + } # ignore everything that is not an assignment - self.assertEqual(parse_environ_block("xxx\0a=1\0"), {k("a"): "1"}) - self.assertEqual(parse_environ_block("a=1\0=b=2\0"), {k("a"): "1"}) + assert parse_environ_block("xxx\0a=1\0") == {k("a"): "1"} + assert parse_environ_block("a=1\0=b=2\0") == {k("a"): "1"} # do not fail if the block is incomplete - self.assertEqual(parse_environ_block("a=1\0b=2"), {k("a"): "1"}) + assert parse_environ_block("a=1\0b=2") == {k("a"): "1"} def test_supports_ipv6(self): self.addCleanup(supports_ipv6.cache_clear) @@ -577,7 +558,7 @@ def test_supports_ipv6(self): supports_ipv6.cache_clear() with mock.patch( - 'psutil._common.socket.socket', side_effect=socket.error + 'psutil._common.socket.socket', side_effect=OSError ) as s: assert not supports_ipv6() assert s.called @@ -599,7 +580,7 @@ def test_supports_ipv6(self): supports_ipv6.cache_clear() assert s.called else: - with self.assertRaises(socket.error): + with pytest.raises(OSError): sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) try: sock.bind(("::1", 0)) @@ -610,61 +591,56 @@ def test_isfile_strict(self): this_file = os.path.abspath(__file__) assert isfile_strict(this_file) assert not isfile_strict(os.path.dirname(this_file)) + with mock.patch('psutil._common.os.stat', side_effect=PermissionError): + with pytest.raises(OSError): + isfile_strict(this_file) with mock.patch( - 'psutil._common.os.stat', side_effect=OSError(errno.EPERM, "foo") - ): - self.assertRaises(OSError, isfile_strict, this_file) - with mock.patch( - 'psutil._common.os.stat', side_effect=OSError(errno.EACCES, "foo") - ): - self.assertRaises(OSError, isfile_strict, this_file) - with mock.patch( - 'psutil._common.os.stat', side_effect=OSError(errno.ENOENT, "foo") + 'psutil._common.os.stat', side_effect=FileNotFoundError ): assert not isfile_strict(this_file) with mock.patch('psutil._common.stat.S_ISREG', return_value=False): assert not isfile_strict(this_file) def test_debug(self): - if PY3: - from io import StringIO - else: - from StringIO import StringIO - - with redirect_stderr(StringIO()) as f: - debug("hello") - sys.stderr.flush() + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with contextlib.redirect_stderr(io.StringIO()) as f: + debug("hello") + sys.stderr.flush() msg = f.getvalue() assert msg.startswith("psutil-debug"), msg - self.assertIn("hello", msg) - self.assertIn(__file__.replace('.pyc', '.py'), msg) + assert "hello" in msg + assert __file__.replace('.pyc', '.py') in msg # supposed to use repr(exc) - with redirect_stderr(StringIO()) as f: - debug(ValueError("this is an error")) + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with contextlib.redirect_stderr(io.StringIO()) as f: + debug(ValueError("this is an error")) msg = f.getvalue() - self.assertIn("ignoring ValueError", msg) - self.assertIn("'this is an error'", msg) + assert "ignoring ValueError" in msg + assert "'this is an error'" in msg # supposed to use str(exc), because of extra info about file name - with redirect_stderr(StringIO()) as f: - exc = OSError(2, "no such file") - exc.filename = "/foo" - debug(exc) + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with contextlib.redirect_stderr(io.StringIO()) as f: + exc = OSError(2, "no such file") + exc.filename = "/foo" + debug(exc) msg = f.getvalue() - self.assertIn("no such file", msg) - self.assertIn("/foo", msg) + assert "no such file" in msg + assert "/foo" in msg def test_cat_bcat(self): testfn = self.get_testfn() with open(testfn, "w") as f: f.write("foo") - self.assertEqual(cat(testfn), "foo") - self.assertEqual(bcat(testfn), b"foo") - self.assertRaises(FileNotFoundError, cat, testfn + '-invalid') - self.assertRaises(FileNotFoundError, bcat, testfn + '-invalid') - self.assertEqual(cat(testfn + '-invalid', fallback="bar"), "bar") - self.assertEqual(bcat(testfn + '-invalid', fallback="bar"), "bar") + assert cat(testfn) == "foo" + assert bcat(testfn) == b"foo" + with pytest.raises(FileNotFoundError): + cat(testfn + '-invalid') + with pytest.raises(FileNotFoundError): + bcat(testfn + '-invalid') + assert cat(testfn + '-invalid', fallback="bar") == "bar" + assert bcat(testfn + '-invalid', fallback="bar") == "bar" # =================================================================== @@ -683,105 +659,89 @@ def setUp(self): def test_first_call(self): input = {'disk1': nt(5, 5, 5)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input def test_input_hasnt_changed(self): input = {'disk1': nt(5, 5, 5)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input + assert wrap_numbers(input, 'disk_io') == input def test_increase_but_no_wrap(self): input = {'disk1': nt(5, 5, 5)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input input = {'disk1': nt(10, 15, 20)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input input = {'disk1': nt(20, 25, 30)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input input = {'disk1': nt(20, 25, 30)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input def test_wrap(self): # let's say 100 is the threshold input = {'disk1': nt(100, 100, 100)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input # first wrap restarts from 10 input = {'disk1': nt(100, 100, 10)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(100, 100, 110)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 110)} # then it remains the same input = {'disk1': nt(100, 100, 10)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(100, 100, 110)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 110)} # then it goes up input = {'disk1': nt(100, 100, 90)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(100, 100, 190)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 190)} # then it wraps again input = {'disk1': nt(100, 100, 20)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(100, 100, 210)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 210)} # and remains the same input = {'disk1': nt(100, 100, 20)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(100, 100, 210)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 210)} # now wrap another num input = {'disk1': nt(50, 100, 20)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(150, 100, 210)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(150, 100, 210)} # and again input = {'disk1': nt(40, 100, 20)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(190, 100, 210)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(190, 100, 210)} # keep it the same input = {'disk1': nt(40, 100, 20)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), {'disk1': nt(190, 100, 210)} - ) + assert wrap_numbers(input, 'disk_io') == {'disk1': nt(190, 100, 210)} def test_changing_keys(self): # Emulate a case where the second call to disk_io() # (or whatever) provides a new disk, then the new disk # disappears on the third call. input = {'disk1': nt(5, 5, 5)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input input = {'disk1': nt(5, 5, 5), 'disk2': nt(7, 7, 7)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input input = {'disk1': nt(8, 8, 8)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input def test_changing_keys_w_wrap(self): input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 100)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input # disk 2 wraps input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 10)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), - {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 110)}, - ) + assert wrap_numbers(input, 'disk_io') == { + 'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 110), + } # disk 2 disappears input = {'disk1': nt(50, 50, 50)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input # then it appears again; the old wrap is supposed to be # gone. input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 100)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input # remains the same input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 100)} - self.assertEqual(wrap_numbers(input, 'disk_io'), input) + assert wrap_numbers(input, 'disk_io') == input # and then wraps again input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 10)} - self.assertEqual( - wrap_numbers(input, 'disk_io'), - {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 110)}, - ) + assert wrap_numbers(input, 'disk_io') == { + 'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 110), + } def test_real_data(self): d = { @@ -790,8 +750,8 @@ def test_real_data(self): 'nvme0n1p2': (54, 54, 2396160, 5165056, 4, 24, 30, 1207, 28), 'nvme0n1p3': (2389, 4539, 5154, 150, 4828, 1844, 2019, 398, 348), } - self.assertEqual(wrap_numbers(d, 'disk_io'), d) - self.assertEqual(wrap_numbers(d, 'disk_io'), d) + assert wrap_numbers(d, 'disk_io') == d + assert wrap_numbers(d, 'disk_io') == d # decrease this ↓ d = { 'nvme0n1': (100, 508, 640, 1571, 5970, 1987, 2049, 451751, 47048), @@ -800,7 +760,7 @@ def test_real_data(self): 'nvme0n1p3': (2389, 4539, 5154, 150, 4828, 1844, 2019, 398, 348), } out = wrap_numbers(d, 'disk_io') - self.assertEqual(out['nvme0n1'][0], 400) + assert out['nvme0n1'][0] == 400 # --- cache tests @@ -808,9 +768,9 @@ def test_cache_first_call(self): input = {'disk1': nt(5, 5, 5)} wrap_numbers(input, 'disk_io') cache = wrap_numbers.cache_info() - self.assertEqual(cache[0], {'disk_io': input}) - self.assertEqual(cache[1], {'disk_io': {}}) - self.assertEqual(cache[2], {'disk_io': {}}) + assert cache[0] == {'disk_io': input} + assert cache[1] == {'disk_io': {}} + assert cache[2] == {'disk_io': {}} def test_cache_call_twice(self): input = {'disk1': nt(5, 5, 5)} @@ -818,12 +778,11 @@ def test_cache_call_twice(self): input = {'disk1': nt(10, 10, 10)} wrap_numbers(input, 'disk_io') cache = wrap_numbers.cache_info() - self.assertEqual(cache[0], {'disk_io': input}) - self.assertEqual( - cache[1], - {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}}, - ) - self.assertEqual(cache[2], {'disk_io': {}}) + assert cache[0] == {'disk_io': input} + assert cache[1] == { + 'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0} + } + assert cache[2] == {'disk_io': {}} def test_cache_wrap(self): # let's say 100 is the threshold @@ -834,53 +793,46 @@ def test_cache_wrap(self): input = {'disk1': nt(100, 100, 10)} wrap_numbers(input, 'disk_io') cache = wrap_numbers.cache_info() - self.assertEqual(cache[0], {'disk_io': input}) - self.assertEqual( - cache[1], - {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 100}}, - ) - self.assertEqual(cache[2], {'disk_io': {'disk1': set([('disk1', 2)])}}) + assert cache[0] == {'disk_io': input} + assert cache[1] == { + 'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 100} + } + assert cache[2] == {'disk_io': {'disk1': {('disk1', 2)}}} def check_cache_info(): cache = wrap_numbers.cache_info() - self.assertEqual( - cache[1], - { - 'disk_io': { - ('disk1', 0): 0, - ('disk1', 1): 0, - ('disk1', 2): 100, - } - }, - ) - self.assertEqual( - cache[2], {'disk_io': {'disk1': set([('disk1', 2)])}} - ) + assert cache[1] == { + 'disk_io': { + ('disk1', 0): 0, + ('disk1', 1): 0, + ('disk1', 2): 100, + } + } + assert cache[2] == {'disk_io': {'disk1': {('disk1', 2)}}} # then it remains the same input = {'disk1': nt(100, 100, 10)} wrap_numbers(input, 'disk_io') cache = wrap_numbers.cache_info() - self.assertEqual(cache[0], {'disk_io': input}) + assert cache[0] == {'disk_io': input} check_cache_info() # then it goes up input = {'disk1': nt(100, 100, 90)} wrap_numbers(input, 'disk_io') cache = wrap_numbers.cache_info() - self.assertEqual(cache[0], {'disk_io': input}) + assert cache[0] == {'disk_io': input} check_cache_info() # then it wraps again input = {'disk1': nt(100, 100, 20)} wrap_numbers(input, 'disk_io') cache = wrap_numbers.cache_info() - self.assertEqual(cache[0], {'disk_io': input}) - self.assertEqual( - cache[1], - {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 190}}, - ) - self.assertEqual(cache[2], {'disk_io': {'disk1': set([('disk1', 2)])}}) + assert cache[0] == {'disk_io': input} + assert cache[1] == { + 'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 190} + } + assert cache[2] == {'disk_io': {'disk1': {('disk1', 2)}}} def test_cache_changing_keys(self): input = {'disk1': nt(5, 5, 5)} @@ -888,184 +840,38 @@ def test_cache_changing_keys(self): input = {'disk1': nt(5, 5, 5), 'disk2': nt(7, 7, 7)} wrap_numbers(input, 'disk_io') cache = wrap_numbers.cache_info() - self.assertEqual(cache[0], {'disk_io': input}) - self.assertEqual( - cache[1], - {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}}, - ) - self.assertEqual(cache[2], {'disk_io': {}}) + assert cache[0] == {'disk_io': input} + assert cache[1] == { + 'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0} + } + assert cache[2] == {'disk_io': {}} def test_cache_clear(self): input = {'disk1': nt(5, 5, 5)} wrap_numbers(input, 'disk_io') wrap_numbers(input, 'disk_io') wrap_numbers.cache_clear('disk_io') - self.assertEqual(wrap_numbers.cache_info(), ({}, {}, {})) + assert wrap_numbers.cache_info() == ({}, {}, {}) wrap_numbers.cache_clear('disk_io') wrap_numbers.cache_clear('?!?') - @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') + @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported") def test_cache_clear_public_apis(self): if not psutil.disk_io_counters() or not psutil.net_io_counters(): - raise unittest.SkipTest("no disks or NICs available") + raise pytest.skip("no disks or NICs available") psutil.disk_io_counters() psutil.net_io_counters() caches = wrap_numbers.cache_info() for cache in caches: - self.assertIn('psutil.disk_io_counters', cache) - self.assertIn('psutil.net_io_counters', cache) + assert 'psutil.disk_io_counters' in cache + assert 'psutil.net_io_counters' in cache psutil.disk_io_counters.cache_clear() caches = wrap_numbers.cache_info() for cache in caches: - self.assertIn('psutil.net_io_counters', cache) - self.assertNotIn('psutil.disk_io_counters', cache) + assert 'psutil.net_io_counters' in cache + assert 'psutil.disk_io_counters' not in cache psutil.net_io_counters.cache_clear() caches = wrap_numbers.cache_info() - self.assertEqual(caches, ({}, {}, {})) - - -# =================================================================== -# --- Example script tests -# =================================================================== - - -@unittest.skipIf( - not os.path.exists(SCRIPTS_DIR), "can't locate scripts directory" -) -class TestScripts(PsutilTestCase): - """Tests for scripts in the "scripts" directory.""" - - @staticmethod - def assert_stdout(exe, *args, **kwargs): - kwargs.setdefault("env", PYTHON_EXE_ENV) - exe = '%s' % os.path.join(SCRIPTS_DIR, exe) - cmd = [PYTHON_EXE, exe] - for arg in args: - cmd.append(arg) - try: - out = sh(cmd, **kwargs).strip() - except RuntimeError as err: - if 'AccessDenied' in str(err): - return str(err) - else: - raise - assert out, out - return out - - @staticmethod - def assert_syntax(exe): - exe = os.path.join(SCRIPTS_DIR, exe) - with open(exe, encoding="utf8") if PY3 else open(exe) as f: - src = f.read() - ast.parse(src) - - def test_coverage(self): - # make sure all example scripts have a test method defined - meths = dir(self) - for name in os.listdir(SCRIPTS_DIR): - if name.endswith('.py'): - if 'test_' + os.path.splitext(name)[0] not in meths: - # self.assert_stdout(name) - raise self.fail( - 'no test defined for %r script' - % os.path.join(SCRIPTS_DIR, name) - ) - - @unittest.skipIf(not POSIX, "POSIX only") - def test_executable(self): - for root, dirs, files in os.walk(SCRIPTS_DIR): - for file in files: - if file.endswith('.py'): - path = os.path.join(root, file) - if not stat.S_IXUSR & os.stat(path)[stat.ST_MODE]: - raise self.fail('%r is not executable' % path) - - def test_disk_usage(self): - self.assert_stdout('disk_usage.py') - - def test_free(self): - self.assert_stdout('free.py') - - def test_meminfo(self): - self.assert_stdout('meminfo.py') - - def test_procinfo(self): - self.assert_stdout('procinfo.py', str(os.getpid())) - - @unittest.skipIf(CI_TESTING and not psutil.users(), "no users") - def test_who(self): - self.assert_stdout('who.py') - - def test_ps(self): - self.assert_stdout('ps.py') - - def test_pstree(self): - self.assert_stdout('pstree.py') - - def test_netstat(self): - self.assert_stdout('netstat.py') - - def test_ifconfig(self): - self.assert_stdout('ifconfig.py') - - @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") - def test_pmap(self): - self.assert_stdout('pmap.py', str(os.getpid())) - - def test_procsmem(self): - if 'uss' not in psutil.Process().memory_full_info()._fields: - raise unittest.SkipTest("not supported") - self.assert_stdout('procsmem.py') - - def test_killall(self): - self.assert_syntax('killall.py') - - def test_nettop(self): - self.assert_syntax('nettop.py') - - def test_top(self): - self.assert_syntax('top.py') - - def test_iotop(self): - self.assert_syntax('iotop.py') - - def test_pidof(self): - output = self.assert_stdout('pidof.py', psutil.Process().name()) - self.assertIn(str(os.getpid()), output) - - @unittest.skipIf(not WINDOWS, "WINDOWS only") - def test_winservices(self): - self.assert_stdout('winservices.py') - - def test_cpu_distribution(self): - self.assert_syntax('cpu_distribution.py') - - @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") - def test_temperatures(self): - if not psutil.sensors_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(): - raise unittest.SkipTest("no fans") - self.assert_stdout('fans.py') - - @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") - @unittest.skipIf(not HAS_BATTERY, "no battery") - def test_battery(self): - self.assert_stdout('battery.py') - - @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") - @unittest.skipIf(not HAS_BATTERY, "no battery") - def test_sensors(self): - self.assert_stdout('sensors.py') - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert caches == ({}, {}, {}) diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py index 1fe02d3e4..62895ccdd 100755 --- a/psutil/tests/test_osx.py +++ b/psutil/tests/test_osx.py @@ -9,7 +9,6 @@ import platform import re import time -import unittest import psutil from psutil import MACOS @@ -18,6 +17,7 @@ from psutil.tests import TOLERANCE_DISK_USAGE from psutil.tests import TOLERANCE_SYS_MEM from psutil.tests import PsutilTestCase +from psutil.tests import pytest from psutil.tests import retry_on_failure from psutil.tests import sh from psutil.tests import spawn_testproc @@ -51,7 +51,7 @@ def vm_stat(field): return int(re.search(r'\d+', line).group(0)) * getpagesize() -@unittest.skipIf(not MACOS, "MACOS only") +@pytest.mark.skipif(not MACOS, reason="MACOS only") class TestProcess(PsutilTestCase): @classmethod def setUpClass(cls): @@ -62,20 +62,18 @@ def tearDownClass(cls): terminate(cls.pid) def test_process_create_time(self): - output = sh("ps -o lstart -p %s" % self.pid) + output = sh(f"ps -o lstart -p {self.pid}") start_ps = output.replace('STARTED', '').strip() hhmmss = start_ps.split(' ')[-2] year = start_ps.split(' ')[-1] start_psutil = psutil.Process(self.pid).create_time() - self.assertEqual( - hhmmss, time.strftime("%H:%M:%S", time.localtime(start_psutil)) - ) - self.assertEqual( - year, time.strftime("%Y", time.localtime(start_psutil)) + assert hhmmss == time.strftime( + "%H:%M:%S", time.localtime(start_psutil) ) + assert year == time.strftime("%Y", time.localtime(start_psutil)) -@unittest.skipIf(not MACOS, "MACOS only") +@pytest.mark.skipif(not MACOS, reason="MACOS only") class TestSystemAPIs(PsutilTestCase): # --- disk @@ -85,7 +83,7 @@ def test_disks(self): # test psutil.disk_usage() and psutil.disk_partitions() # against "df -a" def df(path): - out = sh('df -k "%s"' % path).strip() + out = sh(f'df -k "{path}"').strip() lines = out.split('\n') lines.pop(0) line = lines.pop(0) @@ -100,70 +98,60 @@ def df(path): for part in psutil.disk_partitions(all=False): usage = psutil.disk_usage(part.mountpoint) dev, total, used, free = df(part.mountpoint) - self.assertEqual(part.device, dev) - self.assertEqual(usage.total, total) - self.assertAlmostEqual( - usage.free, free, delta=TOLERANCE_DISK_USAGE - ) - self.assertAlmostEqual( - usage.used, used, delta=TOLERANCE_DISK_USAGE - ) + assert part.device == dev + assert usage.total == total + assert abs(usage.free - free) < TOLERANCE_DISK_USAGE + assert abs(usage.used - used) < TOLERANCE_DISK_USAGE # --- cpu def test_cpu_count_logical(self): num = sysctl("sysctl hw.logicalcpu") - self.assertEqual(num, psutil.cpu_count(logical=True)) + assert num == psutil.cpu_count(logical=True) def test_cpu_count_cores(self): num = sysctl("sysctl hw.physicalcpu") - self.assertEqual(num, psutil.cpu_count(logical=False)) + assert num == psutil.cpu_count(logical=False) # TODO: remove this once 1892 is fixed - @unittest.skipIf( - MACOS and platform.machine() == 'arm64', "skipped due to #1892" + @pytest.mark.skipif( + MACOS and platform.machine() == 'arm64', reason="skipped due to #1892" ) def test_cpu_freq(self): freq = psutil.cpu_freq() - self.assertEqual( - freq.current * 1000 * 1000, sysctl("sysctl hw.cpufrequency") - ) - self.assertEqual( - freq.min * 1000 * 1000, sysctl("sysctl hw.cpufrequency_min") - ) - self.assertEqual( - freq.max * 1000 * 1000, sysctl("sysctl hw.cpufrequency_max") - ) + assert freq.current * 1000 * 1000 == sysctl("sysctl hw.cpufrequency") + assert freq.min * 1000 * 1000 == sysctl("sysctl hw.cpufrequency_min") + assert freq.max * 1000 * 1000 == sysctl("sysctl hw.cpufrequency_max") # --- virtual mem def test_vmem_total(self): sysctl_hwphymem = sysctl('sysctl hw.memsize') - self.assertEqual(sysctl_hwphymem, psutil.virtual_memory().total) + assert sysctl_hwphymem == psutil.virtual_memory().total @retry_on_failure() def test_vmem_free(self): vmstat_val = vm_stat("free") psutil_val = psutil.virtual_memory().free - self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM) + assert abs(psutil_val - vmstat_val) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_active(self): vmstat_val = vm_stat("active") psutil_val = psutil.virtual_memory().active - self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM) + assert abs(psutil_val - vmstat_val) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_inactive(self): vmstat_val = vm_stat("inactive") psutil_val = psutil.virtual_memory().inactive - self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM) + assert abs(psutil_val - vmstat_val) < TOLERANCE_SYS_MEM @retry_on_failure() def test_vmem_wired(self): vmstat_val = vm_stat("wired") psutil_val = psutil.virtual_memory().wired - self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM) + assert abs(psutil_val - vmstat_val) < TOLERANCE_SYS_MEM # --- swap mem @@ -171,42 +159,34 @@ def test_vmem_wired(self): def test_swapmem_sin(self): vmstat_val = vm_stat("Pageins") psutil_val = psutil.swap_memory().sin - self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM) + assert abs(psutil_val - vmstat_val) < TOLERANCE_SYS_MEM @retry_on_failure() def test_swapmem_sout(self): vmstat_val = vm_stat("Pageout") psutil_val = psutil.swap_memory().sout - self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM) + assert abs(psutil_val - vmstat_val) < TOLERANCE_SYS_MEM # --- network def test_net_if_stats(self): for name, stats in psutil.net_if_stats().items(): try: - out = sh("ifconfig %s" % name) + out = sh(f"ifconfig {name}") except RuntimeError: pass else: - self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) - self.assertEqual( - stats.mtu, int(re.findall(r'mtu (\d+)', out)[0]) - ) + assert stats.isup == ('RUNNING' in out), out + assert stats.mtu == int(re.findall(r'mtu (\d+)', out)[0]) # --- sensors_battery - @unittest.skipIf(not HAS_BATTERY, "no battery") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") def test_sensors_battery(self): out = sh("pmset -g batt") percent = re.search(r"(\d+)%", out).group(1) - drawing_from = re.search("Now drawing from '([^']+)'", out).group(1) + drawing_from = re.search(r"Now drawing from '([^']+)'", out).group(1) power_plugged = drawing_from == "AC Power" psutil_result = psutil.sensors_battery() - self.assertEqual(psutil_result.power_plugged, power_plugged) - self.assertEqual(psutil_result.percent, int(percent)) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert psutil_result.power_plugged == power_plugged + assert psutil_result.percent == int(percent) diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index 5203c2707..93e6df6e3 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- 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 @@ -11,9 +10,10 @@ import errno import os import re +import shutil import subprocess import time -import unittest +from unittest import mock import psutil from psutil import AIX @@ -23,16 +23,17 @@ from psutil import OPENBSD from psutil import POSIX from psutil import SUNOS +from psutil.tests import AARCH64 from psutil.tests import HAS_NET_IO_COUNTERS from psutil.tests import PYTHON_EXE +from psutil.tests import QEMU_USER from psutil.tests import PsutilTestCase -from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import retry_on_failure from psutil.tests import sh from psutil.tests import skip_on_access_denied from psutil.tests import spawn_testproc from psutil.tests import terminate -from psutil.tests import which if POSIX: @@ -54,11 +55,10 @@ def ps(fmt, pid=None): if pid is not None: cmd.extend(['-p', str(pid)]) + elif SUNOS or AIX: + cmd.append('-A') else: - if SUNOS or AIX: - cmd.append('-A') - else: - cmd.append('ax') + cmd.append('ax') if SUNOS: fmt = fmt.replace("start", "stime") @@ -102,7 +102,11 @@ def ps_name(pid): field = "command" if SUNOS: field = "comm" - return ps(field, pid).split()[0] + command = ps(field, pid).split() + if QEMU_USER: + assert "/bin/qemu-" in command[0] + return command[1] + return command[0] def ps_args(pid): @@ -129,7 +133,23 @@ def ps_vsz(pid): return ps(field, pid) -@unittest.skipIf(not POSIX, "POSIX only") +def df(device): + try: + out = sh(f"df -k {device}").strip() + except RuntimeError as err: + if "device busy" in str(err).lower(): + raise pytest.skip("df returned EBUSY") + raise + line = out.split('\n')[1] + fields = line.split() + sys_total = int(fields[1]) * 1024 + sys_used = int(fields[2]) * 1024 + sys_free = int(fields[3]) * 1024 + sys_percent = float(fields[4].replace('%', '')) + return (sys_total, sys_used, sys_free, sys_percent) + + +@pytest.mark.skipif(not POSIX, reason="POSIX only") class TestProcess(PsutilTestCase): """Compare psutil results against 'ps' command line utility (mainly).""" @@ -146,22 +166,22 @@ def tearDownClass(cls): def test_ppid(self): ppid_ps = ps('ppid', self.pid) ppid_psutil = psutil.Process(self.pid).ppid() - self.assertEqual(ppid_ps, ppid_psutil) + assert ppid_ps == ppid_psutil def test_uid(self): uid_ps = ps('uid', self.pid) uid_psutil = psutil.Process(self.pid).uids().real - self.assertEqual(uid_ps, uid_psutil) + assert uid_ps == uid_psutil def test_gid(self): gid_ps = ps('rgid', self.pid) gid_psutil = psutil.Process(self.pid).gids().real - self.assertEqual(gid_ps, gid_psutil) + assert gid_ps == gid_psutil def test_username(self): username_ps = ps('user', self.pid) username_psutil = psutil.Process(self.pid).username() - self.assertEqual(username_ps, username_psutil) + assert username_ps == username_psutil def test_username_no_resolution(self): # Emulate a case where the system can't resolve the uid to @@ -169,7 +189,7 @@ def test_username_no_resolution(self): # the stringified uid. p = psutil.Process() with mock.patch("psutil.pwd.getpwuid", side_effect=KeyError) as fun: - self.assertEqual(p.username(), str(p.uids().real)) + assert p.username() == str(p.uids().real) assert fun.called @skip_on_access_denied() @@ -180,7 +200,7 @@ def test_rss_memory(self): time.sleep(0.1) rss_ps = ps_rss(self.pid) rss_psutil = psutil.Process(self.pid).memory_info()[0] / 1024 - self.assertEqual(rss_ps, rss_psutil) + assert rss_ps == rss_psutil @skip_on_access_denied() @retry_on_failure() @@ -190,7 +210,7 @@ def test_vsz_memory(self): time.sleep(0.1) vsz_ps = ps_vsz(self.pid) vsz_psutil = psutil.Process(self.pid).memory_info()[1] / 1024 - self.assertEqual(vsz_ps, vsz_psutil) + assert vsz_ps == vsz_psutil def test_name(self): name_ps = ps_name(self.pid) @@ -204,7 +224,7 @@ def test_name(self): # ...may also be "python.X" name_ps = re.sub(r"\d", "", name_ps) name_psutil = re.sub(r"\d", "", name_psutil) - self.assertEqual(name_ps, name_psutil) + assert name_ps == name_psutil def test_name_long(self): # On UNIX the kernel truncates the name to the first 15 @@ -217,7 +237,7 @@ def test_name_long(self): "psutil._psplatform.Process.cmdline", return_value=cmdline ): p = psutil.Process() - self.assertEqual(p.name(), "long-program-name-extended") + assert p.name() == "long-program-name-extended" def test_name_long_cmdline_ad_exc(self): # Same as above but emulates a case where cmdline() raises @@ -230,7 +250,7 @@ def test_name_long_cmdline_ad_exc(self): side_effect=psutil.AccessDenied(0, ""), ): p = psutil.Process() - self.assertEqual(p.name(), "long-program-name") + assert p.name() == "long-program-name" def test_name_long_cmdline_nsp_exc(self): # Same as above but emulates a case where cmdline() raises NSP @@ -242,9 +262,10 @@ def test_name_long_cmdline_nsp_exc(self): side_effect=psutil.NoSuchProcess(0, ""), ): p = psutil.Process() - self.assertRaises(psutil.NoSuchProcess, p.name) + with pytest.raises(psutil.NoSuchProcess): + p.name() - @unittest.skipIf(MACOS or BSD, 'ps -o start not available') + @pytest.mark.skipif(MACOS or BSD, reason="ps -o start not available") def test_create_time(self): time_ps = ps('start', self.pid) time_psutil = psutil.Process(self.pid).create_time() @@ -257,22 +278,22 @@ def test_create_time(self): round_time_psutil_tstamp = datetime.datetime.fromtimestamp( round_time_psutil ).strftime("%H:%M:%S") - self.assertIn(time_ps, [time_psutil_tstamp, round_time_psutil_tstamp]) + assert time_ps in {time_psutil_tstamp, round_time_psutil_tstamp} def test_exe(self): ps_pathname = ps_name(self.pid) psutil_pathname = psutil.Process(self.pid).exe() try: - self.assertEqual(ps_pathname, psutil_pathname) + assert ps_pathname == psutil_pathname except AssertionError: # certain platforms such as BSD are more accurate returning: - # "/usr/local/bin/python2.7" + # "/usr/local/bin/python3.7" # ...instead of: # "/usr/local/bin/python" # We do not want to consider this difference in accuracy # an error. adjusted_ps_pathname = ps_pathname[: len(ps_pathname)] - self.assertEqual(ps_pathname, adjusted_ps_pathname) + assert ps_pathname == adjusted_ps_pathname # On macOS the official python installer exposes a python wrapper that # executes a python executable hidden inside an application bundle inside @@ -283,22 +304,25 @@ def test_exe(self): def test_cmdline(self): ps_cmdline = ps_args(self.pid) psutil_cmdline = " ".join(psutil.Process(self.pid).cmdline()) - self.assertEqual(ps_cmdline, psutil_cmdline) + if AARCH64 and len(ps_cmdline) < len(psutil_cmdline): + assert psutil_cmdline.startswith(ps_cmdline) + else: + assert ps_cmdline == psutil_cmdline # On SUNOS "ps" reads niceness /proc/pid/psinfo which returns an # incorrect value (20); the real deal is getpriority(2) which # returns 0; psutil relies on it, see: # https://github.com/giampaolo/psutil/issues/1082 # AIX has the same issue - @unittest.skipIf(SUNOS, "not reliable on SUNOS") - @unittest.skipIf(AIX, "not reliable on AIX") + @pytest.mark.skipif(SUNOS, reason="not reliable on SUNOS") + @pytest.mark.skipif(AIX, reason="not reliable on AIX") def test_nice(self): ps_nice = ps('nice', self.pid) psutil_nice = psutil.Process().nice() - self.assertEqual(ps_nice, psutil_nice) + assert ps_nice == psutil_nice -@unittest.skipIf(not POSIX, "POSIX only") +@pytest.mark.skipif(not POSIX, reason="POSIX only") class TestSystemAPIs(PsutilTestCase): """Test some system APIs.""" @@ -310,7 +334,7 @@ def test_pids(self): pids_psutil = psutil.pids() # on MACOS and OPENBSD ps doesn't show pid 0 - if MACOS or OPENBSD and 0 not in pids_ps: + if MACOS or (OPENBSD and 0 not in pids_ps): pids_ps.insert(0, 0) # There will often be one more process in pids_ps for ps itself @@ -322,9 +346,9 @@ def test_pids(self): # for some reason ifconfig -a does not report all interfaces # returned by psutil - @unittest.skipIf(SUNOS, "unreliable on SUNOS") - @unittest.skipIf(not which('ifconfig'), "no ifconfig cmd") - @unittest.skipIf(not HAS_NET_IO_COUNTERS, "not supported") + @pytest.mark.skipif(SUNOS, reason="unreliable on SUNOS") + @pytest.mark.skipif(not shutil.which("ifconfig"), reason="no ifconfig cmd") + @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported") def test_nic_names(self): output = sh("ifconfig -a") for nic in psutil.net_io_counters(pernic=True): @@ -333,24 +357,25 @@ def test_nic_names(self): break else: raise self.fail( - "couldn't find %s nic in 'ifconfig -a' output\n%s" - % (nic, output) + f"couldn't find {nic} nic in 'ifconfig -a'" + f" output\n{output}" ) - # @unittest.skipIf(CI_TESTING and not psutil.users(), "unreliable on CI") + # @pytest.mark.skipif(CI_TESTING and not psutil.users(), + # reason="unreliable on CI") @retry_on_failure() def test_users(self): out = sh("who -u") if not out.strip(): - raise unittest.SkipTest("no users on this system") + raise pytest.skip("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] - self.assertEqual(len(users), len(psutil.users())) + assert len(users) == len(psutil.users()) with self.subTest(psutil=psutil.users(), who=out): for idx, u in enumerate(psutil.users()): - self.assertEqual(u.name, users[idx]) - self.assertEqual(u.terminal, terminals[idx]) + assert u.name == users[idx] + assert u.terminal == terminals[idx] if u.pid is not None: # None on OpenBSD psutil.Process(u.pid) @@ -358,7 +383,7 @@ def test_users(self): def test_users_started(self): out = sh("who -u") if not out.strip(): - raise unittest.SkipTest("no users on this system") + raise pytest.skip("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) @@ -382,16 +407,14 @@ def test_users_started(self): started = [x.capitalize() for x in started] if not tstamp: - raise unittest.SkipTest( - "cannot interpret tstamp in who output\n%s" % (out) - ) + raise pytest.skip(f"cannot interpret tstamp in who output\n{out}") with self.subTest(psutil=psutil.users(), who=out): for idx, u in enumerate(psutil.users()): psutil_value = datetime.datetime.fromtimestamp( u.started ).strftime(tstamp) - self.assertEqual(psutil_value, started[idx]) + assert psutil_value == started[idx] def test_pid_exists_let_raise(self): # According to "man 2 kill" possible error values for kill @@ -400,7 +423,8 @@ def test_pid_exists_let_raise(self): with mock.patch( "psutil._psposix.os.kill", side_effect=OSError(errno.EBADF, "") ) as m: - self.assertRaises(OSError, psutil._psposix.pid_exists, os.getpid()) + with pytest.raises(OSError): + psutil._psposix.pid_exists(os.getpid()) assert m.called def test_os_waitpid_let_raise(self): @@ -409,7 +433,8 @@ def test_os_waitpid_let_raise(self): with mock.patch( "psutil._psposix.os.waitpid", side_effect=OSError(errno.EBADF, "") ) as m: - self.assertRaises(OSError, psutil._psposix.wait_pid, os.getpid()) + with pytest.raises(OSError): + psutil._psposix.wait_pid(os.getpid()) assert m.called def test_os_waitpid_eintr(self): @@ -417,12 +442,8 @@ def test_os_waitpid_eintr(self): with mock.patch( "psutil._psposix.os.waitpid", side_effect=OSError(errno.EINTR, "") ) as m: - self.assertRaises( - psutil._psposix.TimeoutExpired, - psutil._psposix.wait_pid, - os.getpid(), - timeout=0.01, - ) + with pytest.raises(psutil._psposix.TimeoutExpired): + psutil._psposix.wait_pid(os.getpid(), timeout=0.01) assert m.called def test_os_waitpid_bad_ret_status(self): @@ -430,35 +451,19 @@ def test_os_waitpid_bad_ret_status(self): with mock.patch( "psutil._psposix.os.waitpid", return_value=(1, -1) ) as m: - self.assertRaises( - ValueError, psutil._psposix.wait_pid, os.getpid() - ) + with pytest.raises(ValueError): + psutil._psposix.wait_pid(os.getpid()) assert m.called # AIX can return '-' in df output instead of numbers, e.g. for /proc - @unittest.skipIf(AIX, "unreliable on AIX") + @pytest.mark.skipif(AIX, reason="unreliable on AIX") @retry_on_failure() def test_disk_usage(self): - def df(device): - try: - out = sh("df -k %s" % device).strip() - except RuntimeError as err: - if "device busy" in str(err).lower(): - raise unittest.SkipTest("df returned EBUSY") - raise - line = out.split('\n')[1] - fields = line.split() - total = int(fields[1]) * 1024 - used = int(fields[2]) * 1024 - free = int(fields[3]) * 1024 - percent = float(fields[4].replace('%', '')) - return (total, used, free, percent) - tolerance = 4 * 1024 * 1024 # 4MB for part in psutil.disk_partitions(all=False): usage = psutil.disk_usage(part.mountpoint) try: - total, used, free, percent = df(part.device) + sys_total, sys_used, sys_free, sys_percent = df(part.device) except RuntimeError as err: # see: # https://travis-ci.org/giampaolo/psutil/jobs/138338464 @@ -472,22 +477,16 @@ def df(device): continue raise else: - self.assertAlmostEqual(usage.total, total, delta=tolerance) - self.assertAlmostEqual(usage.used, used, delta=tolerance) - self.assertAlmostEqual(usage.free, free, delta=tolerance) - self.assertAlmostEqual(usage.percent, percent, delta=1) + assert abs(usage.total - sys_total) < tolerance + assert abs(usage.used - sys_used) < tolerance + assert abs(usage.free - sys_free) < tolerance + assert abs(usage.percent - sys_percent) <= 1 -@unittest.skipIf(not POSIX, "POSIX only") +@pytest.mark.skipif(not POSIX, reason="POSIX only") class TestMisc(PsutilTestCase): def test_getpagesize(self): pagesize = getpagesize() - self.assertGreater(pagesize, 0) - self.assertEqual(pagesize, resource.getpagesize()) - self.assertEqual(pagesize, mmap.PAGESIZE) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert pagesize > 0 + assert pagesize == resource.getpagesize() + assert pagesize == mmap.PAGESIZE diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index 307d3dfa9..1d81d498f 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -7,19 +7,21 @@ """Tests for psutil.Process class.""" import collections +import contextlib import errno import getpass +import io import itertools import os import signal import socket import stat +import string import subprocess import sys import textwrap import time -import types -import unittest +from unittest import mock import psutil from psutil import AIX @@ -30,14 +32,8 @@ from psutil import OPENBSD from psutil import OSX from psutil import POSIX -from psutil import SUNOS from psutil import WINDOWS from psutil._common import open_text -from psutil._compat import PY3 -from psutil._compat import FileNotFoundError -from psutil._compat import long -from psutil._compat import super -from psutil.tests import APPVEYOR from psutil.tests import CI_TESTING from psutil.tests import GITHUB_ACTIONS from psutil.tests import GLOBAL_TIMEOUT @@ -53,14 +49,15 @@ from psutil.tests import PYPY from psutil.tests import PYTHON_EXE from psutil.tests import PYTHON_EXE_ENV +from psutil.tests import QEMU_USER from psutil.tests import PsutilTestCase from psutil.tests import ThreadTask from psutil.tests import call_until from psutil.tests import copyload_shared_lib from psutil.tests import create_c_exe from psutil.tests import create_py_exe -from psutil.tests import mock from psutil.tests import process_namespace +from psutil.tests import pytest from psutil.tests import reap_children from psutil.tests import retry_on_failure from psutil.tests import sh @@ -89,8 +86,8 @@ def spawn_psproc(self, *args, **kwargs): def test_pid(self): p = psutil.Process() - self.assertEqual(p.pid, os.getpid()) - with self.assertRaises(AttributeError): + assert p.pid == os.getpid() + with pytest.raises(AttributeError): p.pid = 33 def test_kill(self): @@ -98,9 +95,9 @@ def test_kill(self): p.kill() code = p.wait() if WINDOWS: - self.assertEqual(code, signal.SIGTERM) + assert code == signal.SIGTERM else: - self.assertEqual(code, -signal.SIGKILL) + assert code == -signal.SIGKILL self.assertProcessGone(p) def test_terminate(self): @@ -108,9 +105,9 @@ def test_terminate(self): p.terminate() code = p.wait() if WINDOWS: - self.assertEqual(code, signal.SIGTERM) + assert code == signal.SIGTERM else: - self.assertEqual(code, -signal.SIGTERM) + assert code == -signal.SIGTERM self.assertProcessGone(p) def test_send_signal(self): @@ -119,25 +116,23 @@ def test_send_signal(self): p.send_signal(sig) code = p.wait() if WINDOWS: - self.assertEqual(code, sig) + assert code == sig else: - self.assertEqual(code, -sig) + assert code == -sig self.assertProcessGone(p) - @unittest.skipIf(not POSIX, "not POSIX") + @pytest.mark.skipif(not POSIX, reason="not POSIX") def test_send_signal_mocked(self): sig = signal.SIGTERM p = self.spawn_psproc() - with mock.patch( - 'psutil.os.kill', side_effect=OSError(errno.ESRCH, "") - ): - self.assertRaises(psutil.NoSuchProcess, p.send_signal, sig) + with mock.patch('psutil.os.kill', side_effect=ProcessLookupError): + with pytest.raises(psutil.NoSuchProcess): + p.send_signal(sig) p = self.spawn_psproc() - with mock.patch( - 'psutil.os.kill', side_effect=OSError(errno.EPERM, "") - ): - self.assertRaises(psutil.AccessDenied, p.send_signal, sig) + with mock.patch('psutil.os.kill', side_effect=PermissionError): + with pytest.raises(psutil.AccessDenied): + p.send_signal(sig) def test_wait_exited(self): # Test waitpid() + WIFEXITED -> WEXITSTATUS. @@ -145,55 +140,61 @@ def test_wait_exited(self): cmd = [PYTHON_EXE, "-c", "pass"] p = self.spawn_psproc(cmd) code = p.wait() - self.assertEqual(code, 0) + assert code == 0 self.assertProcessGone(p) # exit(1), implicit in case of error cmd = [PYTHON_EXE, "-c", "1 / 0"] p = self.spawn_psproc(cmd, stderr=subprocess.PIPE) code = p.wait() - self.assertEqual(code, 1) + assert code == 1 self.assertProcessGone(p) # via sys.exit() cmd = [PYTHON_EXE, "-c", "import sys; sys.exit(5);"] p = self.spawn_psproc(cmd) code = p.wait() - self.assertEqual(code, 5) + assert code == 5 self.assertProcessGone(p) # via os._exit() cmd = [PYTHON_EXE, "-c", "import os; os._exit(5);"] p = self.spawn_psproc(cmd) code = p.wait() - self.assertEqual(code, 5) + assert code == 5 self.assertProcessGone(p) - @unittest.skipIf(NETBSD, "fails on NETBSD") + @pytest.mark.skipif(NETBSD, reason="fails on NETBSD") def test_wait_stopped(self): p = self.spawn_psproc() if POSIX: # Test waitpid() + WIFSTOPPED and WIFCONTINUED. # Note: if a process is stopped it ignores SIGTERM. p.send_signal(signal.SIGSTOP) - self.assertRaises(psutil.TimeoutExpired, p.wait, timeout=0.001) + with pytest.raises(psutil.TimeoutExpired): + p.wait(timeout=0.001) p.send_signal(signal.SIGCONT) - self.assertRaises(psutil.TimeoutExpired, p.wait, timeout=0.001) + with pytest.raises(psutil.TimeoutExpired): + p.wait(timeout=0.001) p.send_signal(signal.SIGTERM) - self.assertEqual(p.wait(), -signal.SIGTERM) - self.assertEqual(p.wait(), -signal.SIGTERM) + assert p.wait() == -signal.SIGTERM + assert p.wait() == -signal.SIGTERM else: p.suspend() - self.assertRaises(psutil.TimeoutExpired, p.wait, timeout=0.001) + with pytest.raises(psutil.TimeoutExpired): + p.wait(timeout=0.001) p.resume() - self.assertRaises(psutil.TimeoutExpired, p.wait, timeout=0.001) + with pytest.raises(psutil.TimeoutExpired): + p.wait(timeout=0.001) p.terminate() - self.assertEqual(p.wait(), signal.SIGTERM) - self.assertEqual(p.wait(), signal.SIGTERM) + assert p.wait() == signal.SIGTERM + assert p.wait() == signal.SIGTERM def test_wait_non_children(self): # Test wait() against a process which is not our direct # child. child, grandchild = self.spawn_children_pair() - self.assertRaises(psutil.TimeoutExpired, child.wait, 0.01) - self.assertRaises(psutil.TimeoutExpired, grandchild.wait, 0.01) + with pytest.raises(psutil.TimeoutExpired): + child.wait(0.01) + with pytest.raises(psutil.TimeoutExpired): + grandchild.wait(0.01) # We also terminate the direct child otherwise the # grandchild will hang until the parent is gone. child.terminate() @@ -201,24 +202,28 @@ def test_wait_non_children(self): child_ret = child.wait() grandchild_ret = grandchild.wait() if POSIX: - self.assertEqual(child_ret, -signal.SIGTERM) + assert child_ret == -signal.SIGTERM # For processes which are not our children we're supposed # to get None. - self.assertEqual(grandchild_ret, None) + assert grandchild_ret is None else: - self.assertEqual(child_ret, signal.SIGTERM) - self.assertEqual(child_ret, signal.SIGTERM) + assert child_ret == signal.SIGTERM + assert child_ret == signal.SIGTERM def test_wait_timeout(self): p = self.spawn_psproc() p.name() - self.assertRaises(psutil.TimeoutExpired, p.wait, 0.01) - self.assertRaises(psutil.TimeoutExpired, p.wait, 0) - self.assertRaises(ValueError, p.wait, -1) + with pytest.raises(psutil.TimeoutExpired): + p.wait(0.01) + with pytest.raises(psutil.TimeoutExpired): + p.wait(0) + with pytest.raises(ValueError): + p.wait(-1) def test_wait_timeout_nonblocking(self): p = self.spawn_psproc() - self.assertRaises(psutil.TimeoutExpired, p.wait, 0) + with pytest.raises(psutil.TimeoutExpired): + p.wait(0) p.kill() stop_at = time.time() + GLOBAL_TIMEOUT while time.time() < stop_at: @@ -230,9 +235,9 @@ def test_wait_timeout_nonblocking(self): else: raise self.fail('timeout') if POSIX: - self.assertEqual(code, -signal.SIGKILL) + assert code == -signal.SIGKILL else: - self.assertEqual(code, signal.SIGTERM) + assert code == signal.SIGTERM self.assertProcessGone(p) def test_cpu_percent(self): @@ -241,9 +246,9 @@ def test_cpu_percent(self): p.cpu_percent(interval=0.001) for _ in range(100): percent = p.cpu_percent(interval=None) - self.assertIsInstance(percent, float) - self.assertGreaterEqual(percent, 0.0) - with self.assertRaises(ValueError): + assert isinstance(percent, float) + assert percent >= 0.0 + with pytest.raises(ValueError): p.cpu_percent(interval=-1) def test_cpu_percent_numcpus_none(self): @@ -252,9 +257,11 @@ def test_cpu_percent_numcpus_none(self): psutil.Process().cpu_percent() assert m.called + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_cpu_times(self): times = psutil.Process().cpu_times() - assert (times.user > 0.0) or (times.system > 0.0), times + assert times.user >= 0.0, times + assert times.system >= 0.0, times assert times.children_user >= 0.0, times assert times.children_system >= 0.0, times if LINUX: @@ -263,6 +270,7 @@ def test_cpu_times(self): for name in times._fields: time.strftime("%H:%M:%S", time.localtime(getattr(times, name))) + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_cpu_times_2(self): user_time, kernel_time = psutil.Process().cpu_times()[:2] utime, ktime = os.times()[:2] @@ -271,19 +279,19 @@ def test_cpu_times_2(self): # using a tolerance of +/- 0.1 seconds. # It will fail if the difference between the values is > 0.1s. if (max([user_time, utime]) - min([user_time, utime])) > 0.1: - raise self.fail("expected: %s, found: %s" % (utime, user_time)) + raise self.fail(f"expected: {utime}, found: {user_time}") if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > 0.1: - raise self.fail("expected: %s, found: %s" % (ktime, kernel_time)) + raise self.fail(f"expected: {ktime}, found: {kernel_time}") - @unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported") + @pytest.mark.skipif(not HAS_PROC_CPU_NUM, reason="not supported") def test_cpu_num(self): p = psutil.Process() num = p.cpu_num() - self.assertGreaterEqual(num, 0) + assert num >= 0 if psutil.cpu_count() == 1: - self.assertEqual(num, 0) - self.assertIn(p.cpu_num(), range(psutil.cpu_count())) + assert num == 0 + assert p.cpu_num() in range(psutil.cpu_count()) def test_create_time(self): p = self.spawn_psproc() @@ -296,21 +304,26 @@ def test_create_time(self): difference = abs(create_time - now) if difference > 2: raise self.fail( - "expected: %s, found: %s, difference: %s" - % (now, create_time, difference) + f"expected: {now}, found: {create_time}, difference:" + f" {difference}" ) # make sure returned value can be pretty printed with strftime time.strftime("%Y %m %d %H:%M:%S", time.localtime(p.create_time())) - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_terminal(self): terminal = psutil.Process().terminal() if terminal is not None: - tty = os.path.realpath(sh('tty')) - self.assertEqual(terminal, tty) + try: + tty = os.path.realpath(sh('tty')) + except RuntimeError: + # Note: happens if pytest is run without the `-s` opt. + raise pytest.skip("can't rely on `tty` CLI") + else: + assert terminal == tty - @unittest.skipIf(not HAS_PROC_IO_COUNTERS, 'not supported') + @pytest.mark.skipif(not HAS_PROC_IO_COUNTERS, reason="not supported") @skip_on_not_implemented(only_if=LINUX) def test_io_counters(self): p = psutil.Process() @@ -320,41 +333,38 @@ def test_io_counters(self): f.read() io2 = p.io_counters() if not BSD and not AIX: - self.assertGreater(io2.read_count, io1.read_count) - self.assertEqual(io2.write_count, io1.write_count) + assert io2.read_count > io1.read_count + assert io2.write_count == io1.write_count if LINUX: - self.assertGreater(io2.read_chars, io1.read_chars) - self.assertEqual(io2.write_chars, io1.write_chars) + assert io2.read_chars > io1.read_chars + assert io2.write_chars == io1.write_chars else: - self.assertGreaterEqual(io2.read_bytes, io1.read_bytes) - self.assertGreaterEqual(io2.write_bytes, io1.write_bytes) + assert io2.read_bytes >= io1.read_bytes + assert io2.write_bytes >= io1.write_bytes # test writes io1 = p.io_counters() with open(self.get_testfn(), 'wb') as f: - if PY3: - f.write(bytes("x" * 1000000, 'ascii')) - else: - f.write("x" * 1000000) + f.write(bytes("x" * 1000000, 'ascii')) io2 = p.io_counters() - self.assertGreaterEqual(io2.write_count, io1.write_count) - self.assertGreaterEqual(io2.write_bytes, io1.write_bytes) - self.assertGreaterEqual(io2.read_count, io1.read_count) - self.assertGreaterEqual(io2.read_bytes, io1.read_bytes) + assert io2.write_count >= io1.write_count + assert io2.write_bytes >= io1.write_bytes + assert io2.read_count >= io1.read_count + assert io2.read_bytes >= io1.read_bytes if LINUX: - self.assertGreater(io2.write_chars, io1.write_chars) - self.assertGreaterEqual(io2.read_chars, io1.read_chars) + assert io2.write_chars > io1.write_chars + assert io2.read_chars >= io1.read_chars # sanity check for i in range(len(io2)): if BSD and i >= 2: # On BSD read_bytes and write_bytes are always set to -1. continue - self.assertGreaterEqual(io2[i], 0) - self.assertGreaterEqual(io2[i], 0) + assert io2[i] >= 0 + assert io2[i] >= 0 - @unittest.skipIf(not HAS_IONICE, "not supported") - @unittest.skipIf(not LINUX, "linux only") + @pytest.mark.skipif(not HAS_IONICE, reason="not supported") + @pytest.mark.skipif(not LINUX, reason="linux only") def test_ionice_linux(self): def cleanup(init): ioclass, value = init @@ -364,69 +374,71 @@ def cleanup(init): p = psutil.Process() if not CI_TESTING: - self.assertEqual(p.ionice()[0], psutil.IOPRIO_CLASS_NONE) - self.assertEqual(psutil.IOPRIO_CLASS_NONE, 0) - self.assertEqual(psutil.IOPRIO_CLASS_RT, 1) # high - self.assertEqual(psutil.IOPRIO_CLASS_BE, 2) # normal - self.assertEqual(psutil.IOPRIO_CLASS_IDLE, 3) # low + assert p.ionice()[0] == psutil.IOPRIO_CLASS_NONE + assert psutil.IOPRIO_CLASS_NONE == 0 + assert psutil.IOPRIO_CLASS_RT == 1 # high + assert psutil.IOPRIO_CLASS_BE == 2 # normal + assert psutil.IOPRIO_CLASS_IDLE == 3 # low init = p.ionice() self.addCleanup(cleanup, init) # low p.ionice(psutil.IOPRIO_CLASS_IDLE) - self.assertEqual(tuple(p.ionice()), (psutil.IOPRIO_CLASS_IDLE, 0)) - with self.assertRaises(ValueError): # accepts no value + assert tuple(p.ionice()) == (psutil.IOPRIO_CLASS_IDLE, 0) + with pytest.raises(ValueError): # accepts no value p.ionice(psutil.IOPRIO_CLASS_IDLE, value=7) # normal p.ionice(psutil.IOPRIO_CLASS_BE) - self.assertEqual(tuple(p.ionice()), (psutil.IOPRIO_CLASS_BE, 0)) + assert tuple(p.ionice()) == (psutil.IOPRIO_CLASS_BE, 0) p.ionice(psutil.IOPRIO_CLASS_BE, value=7) - self.assertEqual(tuple(p.ionice()), (psutil.IOPRIO_CLASS_BE, 7)) - with self.assertRaises(ValueError): + assert tuple(p.ionice()) == (psutil.IOPRIO_CLASS_BE, 7) + with pytest.raises(ValueError): p.ionice(psutil.IOPRIO_CLASS_BE, value=8) try: p.ionice(psutil.IOPRIO_CLASS_RT, value=7) except psutil.AccessDenied: pass # errs - with self.assertRaisesRegex(ValueError, "ioclass accepts no value"): + with pytest.raises(ValueError, match="ioclass accepts no value"): p.ionice(psutil.IOPRIO_CLASS_NONE, 1) - with self.assertRaisesRegex(ValueError, "ioclass accepts no value"): + with pytest.raises(ValueError, match="ioclass accepts no value"): p.ionice(psutil.IOPRIO_CLASS_IDLE, 1) - with self.assertRaisesRegex( - ValueError, "'ioclass' argument must be specified" + with pytest.raises( + ValueError, match="'ioclass' argument must be specified" ): p.ionice(value=1) - @unittest.skipIf(not HAS_IONICE, "not supported") - @unittest.skipIf(not WINDOWS, 'not supported on this win version') + @pytest.mark.skipif(not HAS_IONICE, reason="not supported") + @pytest.mark.skipif( + not WINDOWS, reason="not supported on this win version" + ) def test_ionice_win(self): p = psutil.Process() if not CI_TESTING: - self.assertEqual(p.ionice(), psutil.IOPRIO_NORMAL) + assert p.ionice() == psutil.IOPRIO_NORMAL init = p.ionice() self.addCleanup(p.ionice, init) # base p.ionice(psutil.IOPRIO_VERYLOW) - self.assertEqual(p.ionice(), psutil.IOPRIO_VERYLOW) + assert p.ionice() == psutil.IOPRIO_VERYLOW p.ionice(psutil.IOPRIO_LOW) - self.assertEqual(p.ionice(), psutil.IOPRIO_LOW) + assert p.ionice() == psutil.IOPRIO_LOW try: p.ionice(psutil.IOPRIO_HIGH) except psutil.AccessDenied: pass else: - self.assertEqual(p.ionice(), psutil.IOPRIO_HIGH) + assert p.ionice() == psutil.IOPRIO_HIGH # errs - with self.assertRaisesRegex( - TypeError, "value argument not accepted on Windows" + with pytest.raises( + TypeError, match="value argument not accepted on Windows" ): p.ionice(psutil.IOPRIO_NORMAL, value=1) - with self.assertRaisesRegex(ValueError, "is not a valid priority"): + with pytest.raises(ValueError, match="is not a valid priority"): p.ionice(psutil.IOPRIO_HIGH + 1) - @unittest.skipIf(not HAS_RLIMIT, "not supported") + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit_get(self): import resource @@ -435,35 +447,35 @@ def test_rlimit_get(self): assert names, names for name in names: value = getattr(psutil, name) - self.assertGreaterEqual(value, 0) + assert value >= 0 if name in dir(resource): - self.assertEqual(value, getattr(resource, name)) + assert value == getattr(resource, name) # XXX - On PyPy RLIMIT_INFINITY returned by # resource.getrlimit() is reported as a very big long # number instead of -1. It looks like a bug with PyPy. if PYPY: continue - self.assertEqual(p.rlimit(value), resource.getrlimit(value)) + assert p.rlimit(value) == resource.getrlimit(value) else: ret = p.rlimit(value) - self.assertEqual(len(ret), 2) - self.assertGreaterEqual(ret[0], -1) - self.assertGreaterEqual(ret[1], -1) + assert len(ret) == 2 + assert ret[0] >= -1 + assert ret[1] >= -1 - @unittest.skipIf(not HAS_RLIMIT, "not supported") + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit_set(self): p = self.spawn_psproc() p.rlimit(psutil.RLIMIT_NOFILE, (5, 5)) - self.assertEqual(p.rlimit(psutil.RLIMIT_NOFILE), (5, 5)) + assert p.rlimit(psutil.RLIMIT_NOFILE) == (5, 5) # If pid is 0 prlimit() applies to the calling process and # we don't want that. if LINUX: - with self.assertRaisesRegex(ValueError, "can't use prlimit"): + with pytest.raises(ValueError, match="can't use prlimit"): psutil._psplatform.Process(0).rlimit(0) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): p.rlimit(psutil.RLIMIT_NOFILE, (5, 5, 5)) - @unittest.skipIf(not HAS_RLIMIT, "not supported") + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit(self): p = psutil.Process() testfn = self.get_testfn() @@ -474,17 +486,15 @@ def test_rlimit(self): f.write(b"X" * 1024) # write() or flush() doesn't always cause the exception # but close() will. - with self.assertRaises(IOError) as exc: + with pytest.raises(OSError) as exc: with open(testfn, "wb") as f: f.write(b"X" * 1025) - self.assertEqual( - exc.exception.errno if PY3 else exc.exception[0], errno.EFBIG - ) + assert exc.value.errno == errno.EFBIG finally: p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard)) - self.assertEqual(p.rlimit(psutil.RLIMIT_FSIZE), (soft, hard)) + assert p.rlimit(psutil.RLIMIT_FSIZE) == (soft, hard) - @unittest.skipIf(not HAS_RLIMIT, "not supported") + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit_infinity(self): # First set a limit, then re-set it by specifying INFINITY # and assume we overridden the previous limit. @@ -497,9 +507,9 @@ def test_rlimit_infinity(self): f.write(b"X" * 2048) finally: p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard)) - self.assertEqual(p.rlimit(psutil.RLIMIT_FSIZE), (soft, hard)) + assert p.rlimit(psutil.RLIMIT_FSIZE) == (soft, hard) - @unittest.skipIf(not HAS_RLIMIT, "not supported") + @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported") def test_rlimit_infinity_value(self): # RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really # big number on a platform with large file support. On these @@ -508,7 +518,7 @@ def test_rlimit_infinity_value(self): # conversion doesn't raise an error. p = psutil.Process() soft, hard = p.rlimit(psutil.RLIMIT_FSIZE) - self.assertEqual(psutil.RLIM_INFINITY, hard) + assert hard == psutil.RLIM_INFINITY p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard)) def test_num_threads(self): @@ -520,59 +530,60 @@ def test_num_threads(self): try: step1 = p.num_threads() except psutil.AccessDenied: - raise unittest.SkipTest("on OpenBSD this requires root access") + raise pytest.skip("on OpenBSD this requires root access") else: step1 = p.num_threads() with ThreadTask(): step2 = p.num_threads() - self.assertEqual(step2, step1 + 1) + assert step2 == step1 + 1 - @unittest.skipIf(not WINDOWS, 'WINDOWS only') + @pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") def test_num_handles(self): # a better test is done later into test/_windows.py p = psutil.Process() - self.assertGreater(p.num_handles(), 0) + assert p.num_handles() > 0 - @unittest.skipIf(not HAS_THREADS, 'not supported') + @pytest.mark.skipif(not HAS_THREADS, reason="not supported") def test_threads(self): p = psutil.Process() if OPENBSD: try: step1 = p.threads() except psutil.AccessDenied: - raise unittest.SkipTest("on OpenBSD this requires root access") + raise pytest.skip("on OpenBSD this requires root access") else: step1 = p.threads() with ThreadTask(): step2 = p.threads() - self.assertEqual(len(step2), len(step1) + 1) + assert len(step2) == len(step1) + 1 athread = step2[0] # test named tuple - self.assertEqual(athread.id, athread[0]) - self.assertEqual(athread.user_time, athread[1]) - self.assertEqual(athread.system_time, athread[2]) + assert athread.id == athread[0] + assert athread.user_time == athread[1] + assert athread.system_time == athread[2] @retry_on_failure() @skip_on_access_denied(only_if=MACOS) - @unittest.skipIf(not HAS_THREADS, 'not supported') + @pytest.mark.skipif(not HAS_THREADS, reason="not supported") def test_threads_2(self): p = self.spawn_psproc() if OPENBSD: try: p.threads() except psutil.AccessDenied: - raise unittest.SkipTest("on OpenBSD this requires root access") - self.assertAlmostEqual( - p.cpu_times().user, - sum([x.user_time for x in p.threads()]), - delta=0.1, + raise pytest.skip("on OpenBSD this requires root access") + assert ( + abs(p.cpu_times().user - sum([x.user_time for x in p.threads()])) + < 0.1 ) - self.assertAlmostEqual( - p.cpu_times().system, - sum([x.system_time for x in p.threads()]), - delta=0.1, + assert ( + abs( + p.cpu_times().system + - sum([x.system_time for x in p.threads()]) + ) + < 0.1 ) @retry_on_failure() @@ -582,8 +593,8 @@ def test_memory_info(self): # step 1 - get a base value to compare our results rss1, vms1 = p.memory_info()[:2] percent1 = p.memory_percent() - self.assertGreater(rss1, 0) - self.assertGreater(vms1, 0) + assert rss1 > 0 + assert vms1 > 0 # step 2 - allocate some memory memarr = [None] * 1500000 @@ -592,19 +603,19 @@ def test_memory_info(self): percent2 = p.memory_percent() # step 3 - make sure that the memory usage bumped up - self.assertGreater(rss2, rss1) - self.assertGreaterEqual(vms2, vms1) # vms might be equal - self.assertGreater(percent2, percent1) + assert rss2 > rss1 + assert vms2 >= vms1 # vms might be equal + assert percent2 > percent1 del memarr if WINDOWS: mem = p.memory_info() - self.assertEqual(mem.rss, mem.wset) - self.assertEqual(mem.vms, mem.pagefile) + assert mem.rss == mem.wset + assert mem.vms == mem.pagefile mem = p.memory_info() for name in mem._fields: - self.assertGreaterEqual(getattr(mem, name), 0) + assert getattr(mem, name) >= 0 def test_memory_full_info(self): p = psutil.Process() @@ -612,63 +623,68 @@ def test_memory_full_info(self): mem = p.memory_full_info() for name in mem._fields: value = getattr(mem, name) - self.assertGreaterEqual(value, 0, msg=(name, value)) - if name == 'vms' and OSX or LINUX: + assert value >= 0 + if (name == "vms" and OSX) or LINUX: continue - self.assertLessEqual(value, total, msg=(name, value, total)) + assert value <= total if LINUX or WINDOWS or MACOS: - self.assertGreaterEqual(mem.uss, 0) + assert mem.uss >= 0 if LINUX: - self.assertGreaterEqual(mem.pss, 0) - self.assertGreaterEqual(mem.swap, 0) + assert mem.pss >= 0 + assert mem.swap >= 0 - @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") + @pytest.mark.skipif(not HAS_MEMORY_MAPS, reason="not supported") def test_memory_maps(self): p = psutil.Process() maps = p.memory_maps() - self.assertEqual(len(maps), len(set(maps))) + assert len(maps) == len(set(maps)) ext_maps = p.memory_maps(grouped=False) for nt in maps: - if not nt.path.startswith('['): - assert os.path.isabs(nt.path), nt.path - if POSIX: - try: - assert os.path.exists(nt.path) or os.path.islink( - nt.path - ), nt.path - except AssertionError: - if not LINUX: - raise - else: - # https://github.com/giampaolo/psutil/issues/759 - with open_text('/proc/self/smaps') as f: - data = f.read() - if "%s (deleted)" % nt.path not in data: - raise + if nt.path.startswith('['): + continue + if BSD and nt.path == "pvclock": + continue + if QEMU_USER and "/bin/qemu-" in nt.path: + continue + assert os.path.isabs(nt.path), nt.path + + if POSIX: + try: + assert os.path.exists(nt.path) or os.path.islink( + nt.path + ), nt.path + except AssertionError: + if not LINUX: + raise + # https://github.com/giampaolo/psutil/issues/759 + with open_text('/proc/self/smaps') as f: + data = f.read() + if f"{nt.path} (deleted)" not in data: + raise + elif '64' not in os.path.basename(nt.path): + # XXX - On Windows we have this strange behavior with + # 64 bit dlls: they are visible via explorer but cannot + # be accessed via os.stat() (wtf?). + try: + st = os.stat(nt.path) + except FileNotFoundError: + pass else: - # XXX - On Windows we have this strange behavior with - # 64 bit dlls: they are visible via explorer but cannot - # be accessed via os.stat() (wtf?). - if '64' not in os.path.basename(nt.path): - try: - st = os.stat(nt.path) - except FileNotFoundError: - pass - else: - assert stat.S_ISREG(st.st_mode), nt.path + assert stat.S_ISREG(st.st_mode), nt.path + for nt in ext_maps: for fname in nt._fields: value = getattr(nt, fname) if fname == 'path': continue - if fname in ('addr', 'perms'): + if fname in {'addr', 'perms'}: assert value, value else: - self.assertIsInstance(value, (int, long)) + assert isinstance(value, int) assert value >= 0, value - @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") + @pytest.mark.skipif(not HAS_MEMORY_MAPS, reason="not supported") def test_memory_maps_lists_lib(self): # Make sure a newly loaded shared lib is listed. p = psutil.Process() @@ -678,12 +694,13 @@ def normpath(p): return os.path.realpath(os.path.normcase(p)) libpaths = [normpath(x.path) for x in p.memory_maps()] - self.assertIn(normpath(path), libpaths) + assert normpath(path) in libpaths def test_memory_percent(self): p = psutil.Process() p.memory_percent() - self.assertRaises(ValueError, p.memory_percent, memtype="?!?") + with pytest.raises(ValueError): + p.memory_percent(memtype="?!?") if LINUX or MACOS or WINDOWS: p.memory_percent(memtype='uss') @@ -696,70 +713,87 @@ def test_is_running(self): assert not p.is_running() assert not p.is_running() + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_exe(self): p = self.spawn_psproc() exe = p.exe() try: - self.assertEqual(exe, PYTHON_EXE) + assert exe == PYTHON_EXE except AssertionError: if WINDOWS and len(exe) == len(PYTHON_EXE): # on Windows we don't care about case sensitivity normcase = os.path.normcase - self.assertEqual(normcase(exe), normcase(PYTHON_EXE)) + assert normcase(exe) == normcase(PYTHON_EXE) else: # certain platforms such as BSD are more accurate returning: - # "/usr/local/bin/python2.7" + # "/usr/local/bin/python3.7" # ...instead of: # "/usr/local/bin/python" # We do not want to consider this difference in accuracy # an error. - ver = "%s.%s" % (sys.version_info[0], sys.version_info[1]) + ver = f"{sys.version_info[0]}.{sys.version_info[1]}" try: - self.assertEqual( - exe.replace(ver, ''), PYTHON_EXE.replace(ver, '') - ) + assert exe.replace(ver, '') == PYTHON_EXE.replace(ver, '') except AssertionError: # Typically MACOS. Really not sure what to do here. pass out = sh([exe, "-c", "import os; print('hey')"]) - self.assertEqual(out, 'hey') + assert out == 'hey' def test_cmdline(self): - cmdline = [PYTHON_EXE, "-c", "import time; time.sleep(60)"] + cmdline = [ + PYTHON_EXE, + "-c", + "import time; [time.sleep(0.1) for x in range(100)]", + ] p = self.spawn_psproc(cmdline) + + if NETBSD and p.cmdline() == []: + # https://github.com/giampaolo/psutil/issues/2250 + raise pytest.skip("OPENBSD: returned EBUSY") + # XXX - most of the times the underlying sysctl() call on Net # and Open BSD returns a truncated string. # Also /proc/pid/cmdline behaves the same so it looks # like this is a kernel bug. # XXX - AIX truncates long arguments in /proc/pid/cmdline if NETBSD or OPENBSD or AIX: - self.assertEqual(p.cmdline()[0], PYTHON_EXE) + assert p.cmdline()[0] == PYTHON_EXE else: if MACOS and CI_TESTING: pyexe = p.cmdline()[0] if pyexe != PYTHON_EXE: - self.assertEqual( - ' '.join(p.cmdline()[1:]), ' '.join(cmdline[1:]) - ) + assert ' '.join(p.cmdline()[1:]) == ' '.join(cmdline[1:]) return - self.assertEqual(' '.join(p.cmdline()), ' '.join(cmdline)) + if QEMU_USER: + assert ' '.join(p.cmdline()[2:]) == ' '.join(cmdline) + return + assert ' '.join(p.cmdline()) == ' '.join(cmdline) - @unittest.skipIf(PYPY, "broken on PYPY") + @pytest.mark.skipif(PYPY, reason="broken on PYPY") def test_long_cmdline(self): cmdline = [PYTHON_EXE] cmdline.extend(["-v"] * 50) - cmdline.extend(["-c", "import time; time.sleep(10)"]) + cmdline.extend( + ["-c", "import time; [time.sleep(0.1) for x in range(100)]"] + ) p = self.spawn_psproc(cmdline) if OPENBSD: # XXX: for some reason the test process may turn into a # zombie (don't know why). try: - self.assertEqual(p.cmdline(), cmdline) + assert p.cmdline() == cmdline except psutil.ZombieProcess: - raise unittest.SkipTest("OPENBSD: process turned into zombie") + raise pytest.skip("OPENBSD: process turned into zombie") + elif QEMU_USER: + assert p.cmdline()[2:] == cmdline else: - self.assertEqual(p.cmdline(), cmdline) + ret = p.cmdline() + if NETBSD and ret == []: + # https://github.com/giampaolo/psutil/issues/2250 + raise pytest.skip("OPENBSD: returned EBUSY") + assert ret == cmdline def test_name(self): p = self.spawn_psproc() @@ -767,10 +801,15 @@ def test_name(self): pyexe = os.path.basename(os.path.realpath(sys.executable)).lower() assert pyexe.startswith(name), (pyexe, name) - @unittest.skipIf(PYPY, "unreliable on PYPY") + @pytest.mark.skipif(PYPY or QEMU_USER, reason="unreliable on PYPY") + @pytest.mark.skipif(QEMU_USER, reason="unreliable on QEMU user") def test_long_name(self): - pyexe = create_py_exe(self.get_testfn(suffix="0123456789" * 2)) - cmdline = [pyexe, "-c", "import time; time.sleep(10)"] + pyexe = create_py_exe(self.get_testfn(suffix=string.digits * 2)) + cmdline = [ + pyexe, + "-c", + "import time; [time.sleep(0.1) for x in range(100)]", + ] p = self.spawn_psproc(cmdline) if OPENBSD: # XXX: for some reason the test process may turn into a @@ -781,57 +820,60 @@ def test_long_name(self): # just compare the first 15 chars. Full explanation: # https://github.com/giampaolo/psutil/issues/2239 try: - self.assertEqual(p.name(), os.path.basename(pyexe)) + assert p.name() == os.path.basename(pyexe) except AssertionError: if p.status() == psutil.STATUS_ZOMBIE: assert os.path.basename(pyexe).startswith(p.name()) else: raise else: - self.assertEqual(p.name(), os.path.basename(pyexe)) - - # XXX - @unittest.skipIf(SUNOS, "broken on SUNOS") - @unittest.skipIf(AIX, "broken on AIX") - @unittest.skipIf(PYPY, "broken on PYPY") - def test_prog_w_funky_name(self): - # Test that name(), exe() and cmdline() correctly handle programs - # with funky chars such as spaces and ")", see: - # https://github.com/giampaolo/psutil/issues/628 - pyexe = create_py_exe(self.get_testfn(suffix='foo bar )')) - cmdline = [pyexe, "-c", "import time; time.sleep(10)"] - p = self.spawn_psproc(cmdline) - self.assertEqual(p.cmdline(), cmdline) - self.assertEqual(p.name(), os.path.basename(pyexe)) - self.assertEqual(os.path.normcase(p.exe()), os.path.normcase(pyexe)) - - @unittest.skipIf(not POSIX, 'POSIX only') + assert p.name() == os.path.basename(pyexe) + + # XXX: fails too often + # @pytest.mark.skipif(SUNOS, reason="broken on SUNOS") + # @pytest.mark.skipif(AIX, reason="broken on AIX") + # @pytest.mark.skipif(PYPY, reason="broken on PYPY") + # @pytest.mark.skipif(QEMU_USER, reason="broken on QEMU user") + # def test_prog_w_funky_name(self): + # # Test that name(), exe() and cmdline() correctly handle programs + # # with funky chars such as spaces and ")", see: + # # https://github.com/giampaolo/psutil/issues/628 + # pyexe = create_py_exe(self.get_testfn(suffix='foo bar )')) + # cmdline = [ + # pyexe, + # "-c", + # "import time; [time.sleep(0.1) for x in range(100)]", + # ] + # p = self.spawn_psproc(cmdline) + # assert p.cmdline() == cmdline + # assert p.name() == os.path.basename(pyexe) + # assert os.path.normcase(p.exe()) == os.path.normcase(pyexe) + + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_uids(self): p = psutil.Process() - real, effective, saved = p.uids() + real, effective, _saved = p.uids() # os.getuid() refers to "real" uid - self.assertEqual(real, os.getuid()) + assert real == os.getuid() # os.geteuid() refers to "effective" uid - self.assertEqual(effective, os.geteuid()) - # No such thing as os.getsuid() ("saved" uid), but starting - # from python 2.7 we have os.getresuid() which returns all - # of them. + assert effective == os.geteuid() + # No such thing as os.getsuid() ("saved" uid), but we have + # os.getresuid() which returns all of them. if hasattr(os, "getresuid"): - self.assertEqual(os.getresuid(), p.uids()) + assert os.getresuid() == p.uids() - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_gids(self): p = psutil.Process() - real, effective, saved = p.gids() + real, effective, _saved = p.gids() # os.getuid() refers to "real" uid - self.assertEqual(real, os.getgid()) + assert real == os.getgid() # os.geteuid() refers to "effective" uid - self.assertEqual(effective, os.getegid()) - # No such thing as os.getsgid() ("saved" gid), but starting - # from python 2.7 we have os.getresgid() which returns all - # of them. + assert effective == os.getegid() + # No such thing as os.getsgid() ("saved" gid), but we have + # os.getresgid() which returns all of them. if hasattr(os, "getresuid"): - self.assertEqual(os.getresgid(), p.gids()) + assert os.getresgid() == p.gids() def test_nice(self): def cleanup(init): @@ -841,7 +883,8 @@ def cleanup(init): pass p = psutil.Process() - self.assertRaises(TypeError, p.nice, "str") + with pytest.raises(TypeError): + p.nice("str") init = p.nice() self.addCleanup(cleanup, init) @@ -866,39 +909,42 @@ def cleanup(init): # even if the function succeeds. For higher # priorities, we match either the expected # value or the highest so far. - if prio in ( + 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) + assert new_prio == highest_prio else: - self.assertEqual(new_prio, prio) + assert new_prio == prio else: try: if hasattr(os, "getpriority"): - self.assertEqual( - os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice() + assert ( + os.getpriority(os.PRIO_PROCESS, os.getpid()) + == p.nice() ) p.nice(1) - self.assertEqual(p.nice(), 1) + assert p.nice() == 1 if hasattr(os, "getpriority"): - self.assertEqual( - os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice() + assert ( + os.getpriority(os.PRIO_PROCESS, os.getpid()) + == p.nice() ) # XXX - going back to previous nice value raises # AccessDenied on MACOS if not MACOS: p.nice(0) - self.assertEqual(p.nice(), 0) + assert p.nice() == 0 except psutil.AccessDenied: pass + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_status(self): p = psutil.Process() - self.assertEqual(p.status(), psutil.STATUS_RUNNING) + assert p.status() == psutil.STATUS_RUNNING def test_username(self): p = self.spawn_psproc() @@ -910,27 +956,30 @@ def test_username(self): # When running as a service account (most likely to be # NetworkService), these user name calculations don't produce # the same result, causing the test to fail. - raise unittest.SkipTest('running as service account') - self.assertEqual(username, getpass_user) + raise pytest.skip('running as service account') + assert username == getpass_user if 'USERDOMAIN' in os.environ: - self.assertEqual(domain, os.environ['USERDOMAIN']) + assert domain == os.environ['USERDOMAIN'] else: - self.assertEqual(username, getpass.getuser()) + assert username == getpass.getuser() def test_cwd(self): p = self.spawn_psproc() - self.assertEqual(p.cwd(), os.getcwd()) + assert p.cwd() == os.getcwd() def test_cwd_2(self): cmd = [ PYTHON_EXE, "-c", - "import os, time; os.chdir('..'); time.sleep(60)", + ( + "import os, time; os.chdir('..'); [time.sleep(0.1) for x in" + " range(100)]" + ), ] p = self.spawn_psproc(cmd) - call_until(p.cwd, "ret == os.path.dirname(os.getcwd())") + call_until(lambda: p.cwd() == os.path.dirname(os.getcwd())) - @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported') + @pytest.mark.skipif(not HAS_CPU_AFFINITY, reason="not supported") def test_cpu_affinity(self): p = psutil.Process() initial = p.cpu_affinity() @@ -938,50 +987,51 @@ def test_cpu_affinity(self): self.addCleanup(p.cpu_affinity, initial) if hasattr(os, "sched_getaffinity"): - self.assertEqual(initial, list(os.sched_getaffinity(p.pid))) - self.assertEqual(len(initial), len(set(initial))) + assert initial == list(os.sched_getaffinity(p.pid)) + assert len(initial) == len(set(initial)) all_cpus = list(range(len(psutil.cpu_percent(percpu=True)))) for n in all_cpus: p.cpu_affinity([n]) - self.assertEqual(p.cpu_affinity(), [n]) + assert p.cpu_affinity() == [n] if hasattr(os, "sched_getaffinity"): - self.assertEqual( - p.cpu_affinity(), list(os.sched_getaffinity(p.pid)) - ) + assert p.cpu_affinity() == list(os.sched_getaffinity(p.pid)) # also test num_cpu() if hasattr(p, "num_cpu"): - self.assertEqual(p.cpu_affinity()[0], p.num_cpu()) + assert p.cpu_affinity()[0] == p.num_cpu() # [] is an alias for "all eligible CPUs"; on Linux this may # not be equal to all available CPUs, see: # https://github.com/giampaolo/psutil/issues/956 p.cpu_affinity([]) if LINUX: - self.assertEqual(p.cpu_affinity(), p._proc._get_eligible_cpus()) + assert p.cpu_affinity() == p._proc._get_eligible_cpus() else: - self.assertEqual(p.cpu_affinity(), all_cpus) + assert p.cpu_affinity() == all_cpus if hasattr(os, "sched_getaffinity"): - self.assertEqual( - p.cpu_affinity(), list(os.sched_getaffinity(p.pid)) - ) + assert p.cpu_affinity() == list(os.sched_getaffinity(p.pid)) - self.assertRaises(TypeError, p.cpu_affinity, 1) + with pytest.raises(TypeError): + p.cpu_affinity(1) p.cpu_affinity(initial) # it should work with all iterables, not only lists p.cpu_affinity(set(all_cpus)) p.cpu_affinity(tuple(all_cpus)) - @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported') + @pytest.mark.skipif(not HAS_CPU_AFFINITY, reason="not supported") def test_cpu_affinity_errs(self): p = self.spawn_psproc() invalid_cpu = [len(psutil.cpu_times(percpu=True)) + 10] - self.assertRaises(ValueError, p.cpu_affinity, invalid_cpu) - self.assertRaises(ValueError, p.cpu_affinity, range(10000, 11000)) - self.assertRaises(TypeError, p.cpu_affinity, [0, "1"]) - self.assertRaises(ValueError, p.cpu_affinity, [0, -1]) - - @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported') + with pytest.raises(ValueError): + p.cpu_affinity(invalid_cpu) + with pytest.raises(ValueError): + p.cpu_affinity(range(10000, 11000)) + with pytest.raises((TypeError, ValueError)): + p.cpu_affinity([0, "1"]) + with pytest.raises(ValueError): + p.cpu_affinity([0, -1]) + + @pytest.mark.skipif(not HAS_CPU_AFFINITY, reason="not supported") def test_cpu_affinity_all_combinations(self): p = psutil.Process() initial = p.cpu_affinity() @@ -999,33 +1049,35 @@ def test_cpu_affinity_all_combinations(self): for combo in combos: p.cpu_affinity(combo) - self.assertEqual(sorted(p.cpu_affinity()), sorted(combo)) + assert sorted(p.cpu_affinity()) == sorted(combo) # TODO: #595 - @unittest.skipIf(BSD, "broken on BSD") - # can't find any process file on Appveyor - @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR") + @pytest.mark.skipif(BSD, reason="broken on BSD") def test_open_files(self): p = psutil.Process() testfn = self.get_testfn() files = p.open_files() - self.assertNotIn(testfn, files) + assert testfn not in files with open(testfn, 'wb') as f: f.write(b'x' * 1024) f.flush() # give the kernel some time to see the new file - files = call_until(p.open_files, "len(ret) != %i" % len(files)) + call_until(lambda: len(p.open_files()) != len(files)) + files = p.open_files() filenames = [os.path.normcase(x.path) for x in files] - self.assertIn(os.path.normcase(testfn), filenames) + assert os.path.normcase(testfn) in filenames if LINUX: for file in files: if file.path == testfn: - self.assertEqual(file.position, 1024) + assert file.position == 1024 for file in files: assert os.path.isfile(file.path), file # another process - cmdline = "import time; f = open(r'%s', 'r'); time.sleep(60);" % testfn + cmdline = ( + f"import time; f = open(r'{testfn}', 'r'); [time.sleep(0.1) for x" + " in range(100)];" + ) p = self.spawn_psproc([PYTHON_EXE, "-c", cmdline]) for x in range(100): @@ -1034,14 +1086,12 @@ def test_open_files(self): break time.sleep(0.01) else: - self.assertIn(os.path.normcase(testfn), filenames) + assert os.path.normcase(testfn) in filenames for file in filenames: assert os.path.isfile(file), file # TODO: #595 - @unittest.skipIf(BSD, "broken on BSD") - # can't find any process file on Appveyor - @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR") + @pytest.mark.skipif(BSD, reason="broken on BSD") def test_open_files_2(self): # test fd and path fields p = psutil.Process() @@ -1055,38 +1105,38 @@ def test_open_files_2(self): ): break else: - raise self.fail( - "no file found; files=%s" % (repr(p.open_files())) - ) - self.assertEqual(normcase(file.path), normcase(fileobj.name)) + raise self.fail(f"no file found; files={p.open_files()!r}") + assert normcase(file.path) == normcase(fileobj.name) if WINDOWS: - self.assertEqual(file.fd, -1) + assert file.fd == -1 else: - self.assertEqual(file.fd, fileobj.fileno()) + assert file.fd == fileobj.fileno() # test positions ntuple = p.open_files()[0] - self.assertEqual(ntuple[0], ntuple.path) - self.assertEqual(ntuple[1], ntuple.fd) + assert ntuple[0] == ntuple.path + assert ntuple[1] == ntuple.fd # test file is gone - self.assertNotIn(fileobj.name, p.open_files()) + assert fileobj.name not in p.open_files() - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_num_fds(self): p = psutil.Process() testfn = self.get_testfn() start = p.num_fds() - file = open(testfn, 'w') + file = open(testfn, 'w') # noqa: SIM115 self.addCleanup(file.close) - self.assertEqual(p.num_fds(), start + 1) + assert p.num_fds() == start + 1 sock = socket.socket() self.addCleanup(sock.close) - self.assertEqual(p.num_fds(), start + 2) + assert p.num_fds() == start + 2 file.close() sock.close() - self.assertEqual(p.num_fds(), start) + assert p.num_fds() == start @skip_on_not_implemented(only_if=LINUX) - @unittest.skipIf(OPENBSD or NETBSD, "not reliable on OPENBSD & NETBSD") + @pytest.mark.skipif( + OPENBSD or NETBSD, reason="not reliable on OPENBSD & NETBSD" + ) def test_num_ctx_switches(self): p = psutil.Process() before = sum(p.num_ctx_switches()) @@ -1100,36 +1150,37 @@ def test_num_ctx_switches(self): def test_ppid(self): p = psutil.Process() if hasattr(os, 'getppid'): - self.assertEqual(p.ppid(), os.getppid()) + assert p.ppid() == os.getppid() p = self.spawn_psproc() - self.assertEqual(p.ppid(), os.getpid()) + assert p.ppid() == os.getpid() def test_parent(self): p = self.spawn_psproc() - self.assertEqual(p.parent().pid, os.getpid()) + assert p.parent().pid == os.getpid() lowest_pid = psutil.pids()[0] - self.assertIsNone(psutil.Process(lowest_pid).parent()) + assert psutil.Process(lowest_pid).parent() is None def test_parent_multi(self): parent = psutil.Process() child, grandchild = self.spawn_children_pair() - self.assertEqual(grandchild.parent(), child) - self.assertEqual(child.parent(), parent) + assert grandchild.parent() == child + assert child.parent() == parent + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") @retry_on_failure() def test_parents(self): parent = psutil.Process() assert parent.parents() child, grandchild = self.spawn_children_pair() - self.assertEqual(child.parents()[0], parent) - self.assertEqual(grandchild.parents()[0], child) - self.assertEqual(grandchild.parents()[1], parent) + assert child.parents()[0] == parent + assert grandchild.parents()[0] == child + assert grandchild.parents()[1] == parent def test_children(self): parent = psutil.Process() - self.assertEqual(parent.children(), []) - self.assertEqual(parent.children(recursive=True), []) + assert parent.children() == [] + assert parent.children(recursive=True) == [] # On Windows we set the flag to 0 in order to cancel out the # CREATE_NO_WINDOW flag (enabled by default) which creates # an extra "conhost.exe" child. @@ -1137,22 +1188,22 @@ def test_children(self): children1 = parent.children() children2 = parent.children(recursive=True) for children in (children1, children2): - self.assertEqual(len(children), 1) - self.assertEqual(children[0].pid, child.pid) - self.assertEqual(children[0].ppid(), parent.pid) + assert len(children) == 1 + assert children[0].pid == child.pid + assert children[0].ppid() == parent.pid def test_children_recursive(self): # Test children() against two sub processes, p1 and p2, where # p1 (our child) spawned p2 (our grandchild). parent = psutil.Process() child, grandchild = self.spawn_children_pair() - self.assertEqual(parent.children(), [child]) - self.assertEqual(parent.children(recursive=True), [child, grandchild]) + assert parent.children() == [child] + assert parent.children(recursive=True) == [child, grandchild] # If the intermediate process is gone there's no way for # children() to recursively find it. child.terminate() child.wait() - self.assertEqual(parent.children(recursive=True), []) + assert parent.children(recursive=True) == [] def test_children_duplicates(self): # find the process which has the highest number of children @@ -1163,29 +1214,29 @@ def test_children_duplicates(self): except psutil.Error: pass # this is the one, now let's make sure there are no duplicates - pid = sorted(table.items(), key=lambda x: x[1])[-1][0] + pid = max(table.items(), key=lambda x: x[1])[0] if LINUX and pid == 0: - raise unittest.SkipTest("PID 0") + raise pytest.skip("PID 0") p = psutil.Process(pid) try: c = p.children(recursive=True) except psutil.AccessDenied: # windows pass else: - self.assertEqual(len(c), len(set(c))) + assert len(c) == len(set(c)) def test_parents_and_children(self): parent = psutil.Process() child, grandchild = self.spawn_children_pair() # forward children = parent.children(recursive=True) - self.assertEqual(len(children), 2) - self.assertEqual(children[0], child) - self.assertEqual(children[1], grandchild) + assert len(children) == 2 + assert children[0] == child + assert children[1] == grandchild # backward parents = grandchild.parents() - self.assertEqual(parents[0], child) - self.assertEqual(parents[1], parent) + assert parents[0] == child + assert parents[1] == parent def test_suspend_resume(self): p = self.spawn_psproc() @@ -1195,29 +1246,29 @@ def test_suspend_resume(self): break time.sleep(0.01) p.resume() - self.assertNotEqual(p.status(), psutil.STATUS_STOPPED) + assert p.status() != psutil.STATUS_STOPPED def test_invalid_pid(self): - self.assertRaises(TypeError, psutil.Process, "1") - self.assertRaises(ValueError, psutil.Process, -1) + with pytest.raises(TypeError): + psutil.Process("1") + with pytest.raises(ValueError): + psutil.Process(-1) def test_as_dict(self): p = psutil.Process() d = p.as_dict(attrs=['exe', 'name']) - self.assertEqual(sorted(d.keys()), ['exe', 'name']) + assert sorted(d.keys()) == ['exe', 'name'] p = psutil.Process(min(psutil.pids())) d = p.as_dict(attrs=['net_connections'], ad_value='foo') if not isinstance(d['net_connections'], list): - self.assertEqual(d['net_connections'], 'foo') + assert d['net_connections'] == 'foo' # Test ad_value is set on AccessDenied. with mock.patch( 'psutil.Process.nice', create=True, side_effect=psutil.AccessDenied ): - self.assertEqual( - p.as_dict(attrs=["nice"], ad_value=1), {"nice": 1} - ) + assert p.as_dict(attrs=["nice"], ad_value=1) == {"nice": 1} # Test that NoSuchProcess bubbles up. with mock.patch( @@ -1225,7 +1276,8 @@ def test_as_dict(self): create=True, side_effect=psutil.NoSuchProcess(p.pid, "name"), ): - self.assertRaises(psutil.NoSuchProcess, p.as_dict, attrs=["nice"]) + with pytest.raises(psutil.NoSuchProcess): + p.as_dict(attrs=["nice"]) # Test that ZombieProcess is swallowed. with mock.patch( @@ -1233,9 +1285,7 @@ def test_as_dict(self): create=True, side_effect=psutil.ZombieProcess(p.pid, "name"), ): - self.assertEqual( - p.as_dict(attrs=["nice"], ad_value="foo"), {"nice": "foo"} - ) + assert p.as_dict(attrs=["nice"], ad_value="foo") == {"nice": "foo"} # By default APIs raising NotImplementedError are # supposed to be skipped. @@ -1243,17 +1293,17 @@ def test_as_dict(self): 'psutil.Process.nice', create=True, side_effect=NotImplementedError ): d = p.as_dict() - self.assertNotIn('nice', list(d.keys())) + assert 'nice' not in list(d.keys()) # ...unless the user explicitly asked for some attr. - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): p.as_dict(attrs=["nice"]) # errors - with self.assertRaises(TypeError): + with pytest.raises(TypeError): p.as_dict('name') - with self.assertRaises(ValueError): + with pytest.raises(ValueError): p.as_dict(['foo']) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): p.as_dict(['foo', 'bar']) def test_oneshot(self): @@ -1262,12 +1312,12 @@ def test_oneshot(self): with p.oneshot(): p.cpu_times() p.cpu_times() - self.assertEqual(m.call_count, 1) + assert m.call_count == 1 with mock.patch("psutil._psplatform.Process.cpu_times") as m: p.cpu_times() p.cpu_times() - self.assertEqual(m.call_count, 2) + assert m.call_count == 2 def test_oneshot_twice(self): # Test the case where the ctx manager is __enter__ed twice. @@ -1281,13 +1331,13 @@ def test_oneshot_twice(self): with p.oneshot(): p.cpu_times() p.cpu_times() - self.assertEqual(m1.call_count, 1) - self.assertEqual(m2.call_count, 1) + assert m1.call_count == 1 + assert m2.call_count == 1 with mock.patch("psutil._psplatform.Process.cpu_times") as m: p.cpu_times() p.cpu_times() - self.assertEqual(m.call_count, 2) + assert m.call_count == 2 def test_oneshot_cache(self): # Make sure oneshot() cache is nonglobal. Instead it's @@ -1296,13 +1346,13 @@ def test_oneshot_cache(self): p1, p2 = self.spawn_children_pair() p1_ppid = p1.ppid() p2_ppid = p2.ppid() - self.assertNotEqual(p1_ppid, p2_ppid) + assert p1_ppid != p2_ppid with p1.oneshot(): - self.assertEqual(p1.ppid(), p1_ppid) - self.assertEqual(p2.ppid(), p2_ppid) + assert p1.ppid() == p1_ppid + assert p2.ppid() == p2_ppid with p2.oneshot(): - self.assertEqual(p1.ppid(), p1_ppid) - self.assertEqual(p2.ppid(), p2_ppid) + assert p1.ppid() == p1_ppid + assert p2.ppid() == p2_ppid def test_halfway_terminated_process(self): # Test that NoSuchProcess exception gets raised in case the @@ -1320,34 +1370,34 @@ def assert_raises_nsp(fun, fun_name): except psutil.NoSuchProcess: pass except psutil.AccessDenied: - if OPENBSD and fun_name in ('threads', 'num_threads'): + if OPENBSD and fun_name in {'threads', 'num_threads'}: return raise else: # NtQuerySystemInformation succeeds even if process is gone. - if WINDOWS and fun_name in ('exe', 'name'): + if WINDOWS and fun_name in {'exe', 'name'}: return raise self.fail( - "%r didn't raise NSP and returned %r instead" % (fun, ret) + f"{fun!r} didn't raise NSP and returned {ret!r} instead" ) p = self.spawn_psproc() p.terminate() p.wait() if WINDOWS: # XXX - call_until(psutil.pids, "%s not in ret" % p.pid) + call_until(lambda: p.pid not in psutil.pids()) self.assertProcessGone(p) ns = process_namespace(p) for fun, name in ns.iter(ns.all): assert_raises_nsp(fun, name) - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_zombie_process(self): _parent, zombie = self.spawn_zombie() self.assertProcessZombie(zombie) - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_zombie_process_is_running_w_exc(self): # Emulate a case where internally is_running() raises # ZombieProcess. @@ -1358,7 +1408,7 @@ def test_zombie_process_is_running_w_exc(self): assert p.is_running() assert m.called - @unittest.skipIf(not POSIX, 'POSIX only') + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_zombie_process_status_w_exc(self): # Emulate a case where internally status() raises # ZombieProcess. @@ -1367,7 +1417,7 @@ def test_zombie_process_status_w_exc(self): "psutil._psplatform.Process.status", side_effect=psutil.ZombieProcess(0), ) as m: - self.assertEqual(p.status(), psutil.STATUS_ZOMBIE) + assert p.status() == psutil.STATUS_ZOMBIE assert m.called def test_reused_pid(self): @@ -1377,45 +1427,65 @@ def test_reused_pid(self): p._ident = (p.pid, p.create_time() + 100) list(psutil.process_iter()) - self.assertIn(p.pid, psutil._pmap) + assert p.pid in psutil._pmap assert not p.is_running() + # make sure is_running() removed PID from process_iter() # internal cache - self.assertNotIn(p.pid, psutil._pmap) + with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True): + with contextlib.redirect_stderr(io.StringIO()) as f: + list(psutil.process_iter()) + assert ( + f"refreshing Process instance for reused PID {p.pid}" + in f.getvalue() + ) + assert p.pid not in psutil._pmap assert p != psutil.Process(subp.pid) msg = "process no longer exists and its PID has been reused" ns = process_namespace(p) for fun, name in ns.iter(ns.setters + ns.killers, clear_cache=False): with self.subTest(name=name): - self.assertRaisesRegex(psutil.NoSuchProcess, msg, fun) + with pytest.raises(psutil.NoSuchProcess, match=msg): + fun() - self.assertIn("terminated + PID reused", str(p)) - self.assertIn("terminated + PID reused", repr(p)) + assert "terminated + PID reused" in str(p) + assert "terminated + PID reused" in repr(p) - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.ppid) - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.parent) - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.parents) - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.children) + with pytest.raises(psutil.NoSuchProcess, match=msg): + p.ppid() + with pytest.raises(psutil.NoSuchProcess, match=msg): + p.parent() + with pytest.raises(psutil.NoSuchProcess, match=msg): + p.parents() + with pytest.raises(psutil.NoSuchProcess, match=msg): + p.children() def test_pid_0(self): # Process(0) is supposed to work on all platforms except Linux if 0 not in psutil.pids(): - self.assertRaises(psutil.NoSuchProcess, psutil.Process, 0) + with pytest.raises(psutil.NoSuchProcess): + psutil.Process(0) # These 2 are a contradiction, but "ps" says PID 1's parent # is PID 0. assert not psutil.pid_exists(0) - self.assertEqual(psutil.Process(1).ppid(), 0) + assert psutil.Process(1).ppid() == 0 return p = psutil.Process(0) exc = psutil.AccessDenied if WINDOWS else ValueError - self.assertRaises(exc, p.wait) - self.assertRaises(exc, p.terminate) - self.assertRaises(exc, p.suspend) - self.assertRaises(exc, p.resume) - self.assertRaises(exc, p.kill) - self.assertRaises(exc, p.send_signal, signal.SIGTERM) + with pytest.raises(exc): + p.wait() + with pytest.raises(exc): + p.terminate() + with pytest.raises(exc): + p.suspend() + with pytest.raises(exc): + p.resume() + with pytest.raises(exc): + p.kill() + with pytest.raises(exc): + p.send_signal(signal.SIGTERM) # test all methods ns = process_namespace(p) @@ -1425,50 +1495,54 @@ def test_pid_0(self): except psutil.AccessDenied: pass else: - if name in ("uids", "gids"): - self.assertEqual(ret.real, 0) + if name in {"uids", "gids"}: + assert ret.real == 0 elif name == "username": user = 'NT AUTHORITY\\SYSTEM' if WINDOWS else 'root' - self.assertEqual(p.username(), user) + assert p.username() == user elif name == "name": assert name, name if not OPENBSD: - self.assertIn(0, psutil.pids()) + assert 0 in psutil.pids() assert psutil.pid_exists(0) - @unittest.skipIf(not HAS_ENVIRON, "not supported") + @pytest.mark.skipif(not HAS_ENVIRON, reason="not supported") def test_environ(self): def clean_dict(d): - # Most of these are problematic on Travis. - d.pop("PLAT", None) - d.pop("HOME", None) + exclude = ["PLAT", "HOME", "PYTEST_CURRENT_TEST", "PYTEST_VERSION"] if MACOS: - d.pop("__CF_USER_TEXT_ENCODING", None) - d.pop("VERSIONER_PYTHON_PREFER_32_BIT", None) - d.pop("VERSIONER_PYTHON_VERSION", None) - return dict([ - ( - k.replace("\r", "").replace("\n", ""), - v.replace("\r", "").replace("\n", ""), - ) + exclude.extend([ + "__CF_USER_TEXT_ENCODING", + "VERSIONER_PYTHON_PREFER_32_BIT", + "VERSIONER_PYTHON_VERSION", + "VERSIONER_PYTHON_VERSION", + ]) + for name in exclude: + d.pop(name, None) + return { + k.replace("\r", "").replace("\n", ""): v.replace( + "\r", "" + ).replace("\n", "") for k, v in d.items() - ]) + } self.maxDiff = None p = psutil.Process() d1 = clean_dict(p.environ()) d2 = clean_dict(os.environ.copy()) if not OSX and GITHUB_ACTIONS: - self.assertEqual(d1, d2) + assert d1 == d2 - @unittest.skipIf(not HAS_ENVIRON, "not supported") - @unittest.skipIf(not POSIX, "POSIX only") - @unittest.skipIf( + @pytest.mark.skipif(not HAS_ENVIRON, reason="not supported") + @pytest.mark.skipif(not POSIX, reason="POSIX only") + @pytest.mark.skipif( MACOS_11PLUS, - "macOS 11+ can't get another process environment, issue #2084", + reason="macOS 11+ can't get another process environment, issue #2084", + ) + @pytest.mark.skipif( + NETBSD, reason="sometimes fails on `assert is_running()`" ) - @unittest.skipIf(NETBSD, "sometimes fails on `assert is_running()`") def test_weird_environ(self): # environment variables can contain values without an equals sign code = textwrap.dedent(""" @@ -1494,7 +1568,7 @@ def test_weird_environ(self): wait_for_pid(p.pid) assert p.is_running() # Wait for process to exec or exit. - self.assertEqual(sproc.stderr.read(), b"") + assert sproc.stderr.read() == b"" if MACOS and CI_TESTING: try: env = p.environ() @@ -1504,65 +1578,9 @@ def test_weird_environ(self): return else: env = p.environ() - self.assertEqual(env, {"A": "1", "C": "3"}) + assert env == {"A": "1", "C": "3"} sproc.communicate() - self.assertEqual(sproc.returncode, 0) - - -# =================================================================== -# --- Limited user tests -# =================================================================== - - -if POSIX and os.getuid() == 0: - - class LimitedUserTestCase(TestProcess): - """Repeat the previous tests by using a limited user. - Executed only on UNIX and only if the user who run the test script - is root. - """ - - # the uid/gid the test suite runs under - if hasattr(os, 'getuid'): - PROCESS_UID = os.getuid() - PROCESS_GID = os.getgid() - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # re-define all existent test methods in order to - # ignore AccessDenied exceptions - for attr in [x for x in dir(self) if x.startswith('test')]: - meth = getattr(self, attr) - - def test_(self): - try: - meth() # noqa - except psutil.AccessDenied: - pass - - setattr(self, attr, types.MethodType(test_, self)) - - def setUp(self): - super().setUp() - os.setegid(1000) - os.seteuid(1000) - - def tearDown(self): - os.setegid(self.PROCESS_UID) - os.seteuid(self.PROCESS_GID) - super().tearDown() - - def test_nice(self): - try: - psutil.Process().nice(-1) - except psutil.AccessDenied: - pass - else: - raise self.fail("exception not raised") - - @unittest.skipIf(1, "causes problem as root") - def test_zombie_process(self): - pass + assert sproc.returncode == 0 # =================================================================== @@ -1578,10 +1596,14 @@ def tearDownClass(cls): reap_children() def test_misc(self): - # XXX this test causes a ResourceWarning on Python 3 because + # XXX this test causes a ResourceWarning because # psutil.__subproc instance doesn't get properly freed. # Not sure what to do though. - cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] + cmd = [ + PYTHON_EXE, + "-c", + "import time; [time.sleep(0.1) for x in range(100)];", + ] with psutil.Popen( cmd, stdout=subprocess.PIPE, @@ -1590,14 +1612,15 @@ def test_misc(self): ) as proc: proc.name() proc.cpu_times() - proc.stdin # noqa - self.assertTrue(dir(proc)) - self.assertRaises(AttributeError, getattr, proc, 'foo') + proc.stdin # noqa: B018 + assert dir(proc) + with pytest.raises(AttributeError): + proc.foo # noqa: B018 proc.terminate() if POSIX: - self.assertEqual(proc.wait(5), -signal.SIGTERM) + assert proc.wait(5) == -signal.SIGTERM else: - self.assertEqual(proc.wait(5), signal.SIGTERM) + assert proc.wait(5) == signal.SIGTERM def test_ctx_manager(self): with psutil.Popen( @@ -1611,13 +1634,17 @@ def test_ctx_manager(self): assert proc.stdout.closed assert proc.stderr.closed assert proc.stdin.closed - self.assertEqual(proc.returncode, 0) + assert proc.returncode == 0 def test_kill_terminate(self): # subprocess.Popen()'s terminate(), kill() and send_signal() do # not raise exception after the process is gone. psutil.Popen # diverges from that. - cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] + cmd = [ + PYTHON_EXE, + "-c", + "import time; [time.sleep(0.1) for x in range(100)];", + ] with psutil.Popen( cmd, stdout=subprocess.PIPE, @@ -1626,23 +1653,31 @@ def test_kill_terminate(self): ) as proc: proc.terminate() proc.wait() - self.assertRaises(psutil.NoSuchProcess, proc.terminate) - self.assertRaises(psutil.NoSuchProcess, proc.kill) - self.assertRaises( - psutil.NoSuchProcess, proc.send_signal, signal.SIGTERM - ) + with pytest.raises(psutil.NoSuchProcess): + proc.terminate() + with pytest.raises(psutil.NoSuchProcess): + proc.kill() + with pytest.raises(psutil.NoSuchProcess): + proc.send_signal(signal.SIGTERM) if WINDOWS: - self.assertRaises( - psutil.NoSuchProcess, proc.send_signal, signal.CTRL_C_EVENT - ) - self.assertRaises( - psutil.NoSuchProcess, - proc.send_signal, - signal.CTRL_BREAK_EVENT, - ) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name + with pytest.raises(psutil.NoSuchProcess): + proc.send_signal(signal.CTRL_C_EVENT) + with pytest.raises(psutil.NoSuchProcess): + proc.send_signal(signal.CTRL_BREAK_EVENT) - run_from_name(__file__) + def test__getattribute__(self): + cmd = [ + PYTHON_EXE, + "-c", + "import time; [time.sleep(0.1) for x in range(100)];", + ] + with psutil.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=PYTHON_EXE_ENV, + ) as proc: + proc.terminate() + proc.wait() + with pytest.raises(AttributeError): + proc.foo # noqa: B018 diff --git a/psutil/tests/test_process_all.py b/psutil/tests/test_process_all.py index 7c6ce7808..cb7264d73 100755 --- a/psutil/tests/test_process_all.py +++ b/psutil/tests/test_process_all.py @@ -27,11 +27,9 @@ 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 +from psutil.tests import PYTEST_PARALLEL +from psutil.tests import QEMU_USER from psutil.tests import VALID_PROC_STATUSES from psutil.tests import PsutilTestCase from psutil.tests import check_connection_ntuple @@ -39,12 +37,12 @@ from psutil.tests import is_namedtuple from psutil.tests import is_win_secure_system_proc from psutil.tests import process_namespace -from psutil.tests import serialrun +from psutil.tests import pytest # Cuts the time in half, but (e.g.) on macOS the process pool stays # alive after join() (multiprocessing bug?), messing up other tests. -USE_PROC_POOL = LINUX and not CI_TESTING +USE_PROC_POOL = LINUX and not CI_TESTING and not PYTEST_PARALLEL def proc_info(pid): @@ -96,7 +94,6 @@ def do_wait(): return info -@serialrun class TestFetchAllProcesses(PsutilTestCase): """Test which iterates over all running processes and performs some sanity checks against Process API's returned values. @@ -138,14 +135,16 @@ def test_all(self): meth(value, info) except Exception: # noqa: BLE001 s = '\n' + '=' * 70 + '\n' - s += "FAIL: name=test_%s, pid=%s, ret=%s\ninfo=%s\n" % ( - name, - info['pid'], - repr(value), - info, + s += ( + "FAIL: name=test_{}, pid={}, ret={}\ninfo={}\n".format( + name, + info['pid'], + repr(value), + info, + ) ) s += '-' * 70 - s += "\n%s" % traceback.format_exc() + s += f"\n{traceback.format_exc()}" s = "\n".join((" " * 4) + i for i in s.splitlines()) + "\n" failures.append(s) else: @@ -155,13 +154,13 @@ def test_all(self): raise self.fail(''.join(failures)) def cmdline(self, ret, info): - self.assertIsInstance(ret, list) + assert isinstance(ret, list) for part in ret: - self.assertIsInstance(part, str) + assert isinstance(part, str) def exe(self, ret, info): - self.assertIsInstance(ret, (str, unicode)) - self.assertEqual(ret.strip(), ret) + assert isinstance(ret, str) + assert ret.strip() == ret if ret: if WINDOWS and not ret.endswith('.exe'): return # May be "Registry", "MemCompression", ... @@ -179,16 +178,16 @@ def exe(self, ret, info): raise def pid(self, ret, info): - self.assertIsInstance(ret, int) - self.assertGreaterEqual(ret, 0) + assert isinstance(ret, int) + assert ret >= 0 def ppid(self, ret, info): - self.assertIsInstance(ret, (int, long)) - self.assertGreaterEqual(ret, 0) + assert isinstance(ret, int) + assert ret >= 0 proc_info(ret) def name(self, ret, info): - self.assertIsInstance(ret, (str, unicode)) + assert isinstance(ret, str) if WINDOWS and not ret and is_win_secure_system_proc(info['pid']): # https://github.com/giampaolo/psutil/issues/2338 return @@ -197,9 +196,9 @@ def name(self, ret, info): assert ret, repr(ret) def create_time(self, ret, info): - self.assertIsInstance(ret, float) + assert isinstance(ret, float) try: - self.assertGreaterEqual(ret, 0) + assert ret >= 0 except AssertionError: # XXX if OPENBSD and info['status'] == psutil.STATUS_ZOMBIE: @@ -215,42 +214,45 @@ def create_time(self, ret, info): def uids(self, ret, info): assert is_namedtuple(ret) for uid in ret: - self.assertIsInstance(uid, int) - self.assertGreaterEqual(uid, 0) + assert isinstance(uid, int) + assert uid >= 0 def gids(self, ret, info): assert is_namedtuple(ret) # note: testing all gids as above seems not to be reliable for # gid == 30 (nodoby); not sure why. for gid in ret: - self.assertIsInstance(gid, int) + assert isinstance(gid, int) if not MACOS and not NETBSD: - self.assertGreaterEqual(gid, 0) + assert gid >= 0 def username(self, ret, info): - self.assertIsInstance(ret, str) - self.assertEqual(ret.strip(), ret) + assert isinstance(ret, str) + assert ret.strip() == ret assert ret.strip() def status(self, ret, info): - self.assertIsInstance(ret, str) + assert isinstance(ret, str) assert ret, ret - self.assertNotEqual(ret, '?') # XXX - self.assertIn(ret, VALID_PROC_STATUSES) + if QEMU_USER: + # status does not work under qemu user + return + assert ret != '?' # XXX + assert ret in VALID_PROC_STATUSES def io_counters(self, ret, info): assert is_namedtuple(ret) for field in ret: - self.assertIsInstance(field, (int, long)) + assert isinstance(field, int) if field != -1: - self.assertGreaterEqual(field, 0) + assert field >= 0 def ionice(self, ret, info): if LINUX: - self.assertIsInstance(ret.ioclass, int) - self.assertIsInstance(ret.value, int) - self.assertGreaterEqual(ret.ioclass, 0) - self.assertGreaterEqual(ret.value, 0) + assert isinstance(ret.ioclass, int) + assert isinstance(ret.value, int) + assert ret.ioclass >= 0 + assert ret.value >= 0 else: # Windows, Cygwin choices = [ psutil.IOPRIO_VERYLOW, @@ -258,89 +260,89 @@ def ionice(self, ret, info): psutil.IOPRIO_NORMAL, psutil.IOPRIO_HIGH, ] - self.assertIsInstance(ret, int) - self.assertGreaterEqual(ret, 0) - self.assertIn(ret, choices) + assert isinstance(ret, int) + assert ret >= 0 + assert ret in choices def num_threads(self, ret, info): - self.assertIsInstance(ret, int) + assert isinstance(ret, int) 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) + assert ret >= 1 def threads(self, ret, info): - self.assertIsInstance(ret, list) + assert isinstance(ret, list) for t in ret: assert is_namedtuple(t) - self.assertGreaterEqual(t.id, 0) - self.assertGreaterEqual(t.user_time, 0) - self.assertGreaterEqual(t.system_time, 0) + assert t.id >= 0 + assert t.user_time >= 0 + assert t.system_time >= 0 for field in t: - self.assertIsInstance(field, (int, float)) + assert isinstance(field, (int, float)) def cpu_times(self, ret, info): assert is_namedtuple(ret) for n in ret: - self.assertIsInstance(n, float) - self.assertGreaterEqual(n, 0) + assert isinstance(n, float) + assert n >= 0 # TODO: check ntuple fields def cpu_percent(self, ret, info): - self.assertIsInstance(ret, float) + assert isinstance(ret, float) assert 0.0 <= ret <= 100.0, ret def cpu_num(self, ret, info): - self.assertIsInstance(ret, int) + assert isinstance(ret, int) if FREEBSD and ret == -1: return - self.assertGreaterEqual(ret, 0) + assert ret >= 0 if psutil.cpu_count() == 1: - self.assertEqual(ret, 0) - self.assertIn(ret, list(range(psutil.cpu_count()))) + assert ret == 0 + assert ret in list(range(psutil.cpu_count())) def memory_info(self, ret, info): assert is_namedtuple(ret) for value in ret: - self.assertIsInstance(value, (int, long)) - self.assertGreaterEqual(value, 0) + assert isinstance(value, int) + assert value >= 0 if WINDOWS: - self.assertGreaterEqual(ret.peak_wset, ret.wset) - self.assertGreaterEqual(ret.peak_paged_pool, ret.paged_pool) - self.assertGreaterEqual(ret.peak_nonpaged_pool, ret.nonpaged_pool) - self.assertGreaterEqual(ret.peak_pagefile, ret.pagefile) + assert ret.peak_wset >= ret.wset + assert ret.peak_paged_pool >= ret.paged_pool + assert ret.peak_nonpaged_pool >= ret.nonpaged_pool + assert ret.peak_pagefile >= ret.pagefile def memory_full_info(self, ret, info): assert is_namedtuple(ret) total = psutil.virtual_memory().total for name in ret._fields: value = getattr(ret, name) - self.assertIsInstance(value, (int, long)) - self.assertGreaterEqual(value, 0, msg=(name, value)) - if LINUX or OSX and name in ('vms', 'data'): + assert isinstance(value, int) + assert value >= 0 + if LINUX or (OSX and name in {'vms', 'data'}): # On Linux there are processes (e.g. 'goa-daemon') whose # VMS is incredibly high for some reason. continue - self.assertLessEqual(value, total, msg=(name, value, total)) + assert value <= total, name if LINUX: - self.assertGreaterEqual(ret.pss, ret.uss) + assert ret.pss >= ret.uss def open_files(self, ret, info): - self.assertIsInstance(ret, list) + assert isinstance(ret, list) for f in ret: - self.assertIsInstance(f.fd, int) - self.assertIsInstance(f.path, str) - self.assertEqual(f.path.strip(), f.path) + assert isinstance(f.fd, int) + assert isinstance(f.path, str) + assert f.path.strip() == f.path if WINDOWS: - self.assertEqual(f.fd, -1) + assert f.fd == -1 elif LINUX: - self.assertIsInstance(f.position, int) - self.assertIsInstance(f.mode, str) - self.assertIsInstance(f.flags, int) - self.assertGreaterEqual(f.position, 0) - self.assertIn(f.mode, ('r', 'w', 'a', 'r+', 'a+')) - self.assertGreater(f.flags, 0) + assert isinstance(f.position, int) + assert isinstance(f.mode, str) + assert isinstance(f.flags, int) + assert f.position >= 0 + assert f.mode in {'r', 'w', 'a', 'r+', 'a+'} + assert f.flags > 0 elif BSD and not f.path: # XXX see: https://github.com/giampaolo/psutil/issues/595 continue @@ -353,19 +355,19 @@ def open_files(self, ret, info): assert stat.S_ISREG(st.st_mode), f def num_fds(self, ret, info): - self.assertIsInstance(ret, int) - self.assertGreaterEqual(ret, 0) + assert isinstance(ret, int) + assert ret >= 0 def net_connections(self, ret, info): with create_sockets(): - self.assertEqual(len(ret), len(set(ret))) + assert len(ret) == len(set(ret)) for conn in ret: assert is_namedtuple(conn) check_connection_ntuple(conn) def cwd(self, ret, info): - self.assertIsInstance(ret, (str, unicode)) - self.assertEqual(ret.strip(), ret) + assert isinstance(ret, str) + assert ret.strip() == ret if ret: assert os.path.isabs(ret), ret try: @@ -380,54 +382,57 @@ def cwd(self, ret, info): assert stat.S_ISDIR(st.st_mode) def memory_percent(self, ret, info): - self.assertIsInstance(ret, float) + assert isinstance(ret, float) assert 0 <= ret <= 100, ret def is_running(self, ret, info): - self.assertIsInstance(ret, bool) + assert isinstance(ret, bool) def cpu_affinity(self, ret, info): - self.assertIsInstance(ret, list) - self.assertNotEqual(ret, []) + assert isinstance(ret, list) + assert ret != [] cpus = list(range(psutil.cpu_count())) for n in ret: - self.assertIsInstance(n, int) - self.assertIn(n, cpus) + assert isinstance(n, int) + assert n in cpus def terminal(self, ret, info): - self.assertIsInstance(ret, (str, type(None))) + assert isinstance(ret, (str, type(None))) if ret is not None: assert os.path.isabs(ret), ret assert os.path.exists(ret), ret def memory_maps(self, ret, info): for nt in ret: - self.assertIsInstance(nt.addr, str) - self.assertIsInstance(nt.perms, str) - self.assertIsInstance(nt.path, str) + assert isinstance(nt.addr, str) + assert isinstance(nt.perms, str) + assert isinstance(nt.path, str) for fname in nt._fields: value = getattr(nt, fname) if fname == 'path': - if not value.startswith(("[", "anon_inode:")): - assert os.path.isabs(nt.path), nt.path - # commented as on Linux we might get - # '/foo/bar (deleted)' - # assert os.path.exists(nt.path), nt.path + if value.startswith(("[", "anon_inode:")): # linux + continue + if BSD and value == "pvclock": # seen on FreeBSD + continue + assert os.path.isabs(nt.path), nt.path + # commented as on Linux we might get + # '/foo/bar (deleted)' + # assert os.path.exists(nt.path), nt.path elif fname == 'addr': assert value, repr(value) elif fname == 'perms': if not WINDOWS: assert value, repr(value) else: - self.assertIsInstance(value, (int, long)) - self.assertGreaterEqual(value, 0) + assert isinstance(value, int) + assert value >= 0 def num_handles(self, ret, info): - self.assertIsInstance(ret, int) - self.assertGreaterEqual(ret, 0) + assert isinstance(ret, int) + assert ret >= 0 def nice(self, ret, info): - self.assertIsInstance(ret, int) + assert isinstance(ret, int) if POSIX: assert -20 <= ret <= 20, ret else: @@ -436,29 +441,26 @@ def nice(self, ret, info): for x in dir(psutil) if x.endswith('_PRIORITY_CLASS') ] - self.assertIn(ret, priorities) - if PY3: - self.assertIsInstance(ret, enum.IntEnum) - else: - self.assertIsInstance(ret, int) + assert ret in priorities + assert isinstance(ret, enum.IntEnum) def num_ctx_switches(self, ret, info): assert is_namedtuple(ret) for value in ret: - self.assertIsInstance(value, (int, long)) - self.assertGreaterEqual(value, 0) + assert isinstance(value, int) + assert value >= 0 def rlimit(self, ret, info): - self.assertIsInstance(ret, tuple) - self.assertEqual(len(ret), 2) - self.assertGreaterEqual(ret[0], -1) - self.assertGreaterEqual(ret[1], -1) + assert isinstance(ret, tuple) + assert len(ret) == 2 + assert ret[0] >= -1 + assert ret[1] >= -1 def environ(self, ret, info): - self.assertIsInstance(ret, dict) + assert isinstance(ret, dict) for k, v in ret.items(): - self.assertIsInstance(k, str) - self.assertIsInstance(v, str) + assert isinstance(k, str) + assert isinstance(v, str) class TestPidsRange(PsutilTestCase): @@ -487,7 +489,7 @@ def tearDown(self): def test_it(self): def is_linux_tid(pid): try: - f = open("/proc/%s/status" % pid, "rb") + f = open(f"/proc/{pid}/status", "rb") # noqa: SIM115 except FileNotFoundError: return False else: @@ -512,17 +514,17 @@ def check(pid): if exists: psutil.Process(pid) if not WINDOWS: # see docstring - self.assertIn(pid, psutil.pids()) + assert pid in psutil.pids() else: # On OpenBSD thread IDs can be instantiated, # and oneshot() succeeds, but other APIs fail # with EINVAL. if not OPENBSD: - with self.assertRaises(psutil.NoSuchProcess): + with pytest.raises(psutil.NoSuchProcess): psutil.Process(pid) if not WINDOWS: # see docstring - self.assertNotIn(pid, psutil.pids()) - except (psutil.Error, AssertionError) as err: + assert pid not in psutil.pids() + except (psutil.Error, AssertionError): x -= 1 if x == 0: raise @@ -537,9 +539,3 @@ def check(pid): continue with self.subTest(pid=pid): check(pid) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) diff --git a/psutil/tests/test_scripts.py b/psutil/tests/test_scripts.py new file mode 100755 index 000000000..c354d2ad3 --- /dev/null +++ b/psutil/tests/test_scripts.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 + +# 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. + +"""Test various scripts.""" + +import ast +import os +import shutil +import stat +import subprocess + +import pytest + +from psutil import POSIX +from psutil import WINDOWS +from psutil.tests import CI_TESTING +from psutil.tests import HAS_BATTERY +from psutil.tests import HAS_MEMORY_MAPS +from psutil.tests import HAS_SENSORS_BATTERY +from psutil.tests import HAS_SENSORS_FANS +from psutil.tests import HAS_SENSORS_TEMPERATURES +from psutil.tests import PYTHON_EXE +from psutil.tests import PYTHON_EXE_ENV +from psutil.tests import QEMU_USER +from psutil.tests import ROOT_DIR +from psutil.tests import SCRIPTS_DIR +from psutil.tests import PsutilTestCase +from psutil.tests import import_module_by_path +from psutil.tests import psutil +from psutil.tests import sh + + +INTERNAL_SCRIPTS_DIR = os.path.join(SCRIPTS_DIR, "internal") +SETUP_PY = os.path.join(ROOT_DIR, 'setup.py') + + +# =================================================================== +# --- Tests scripts in scripts/ directory +# =================================================================== + + +@pytest.mark.skipif( + CI_TESTING and not os.path.exists(SCRIPTS_DIR), + reason="can't find scripts/ directory", +) +class TestExampleScripts(PsutilTestCase): + @staticmethod + def assert_stdout(exe, *args, **kwargs): + kwargs.setdefault("env", PYTHON_EXE_ENV) + exe = os.path.join(SCRIPTS_DIR, exe) + cmd = [PYTHON_EXE, exe] + for arg in args: + cmd.append(arg) + try: + out = sh(cmd, **kwargs).strip() + except RuntimeError as err: + if 'AccessDenied' in str(err): + return str(err) + else: + raise + assert out, out + return out + + @staticmethod + def assert_syntax(exe): + exe = os.path.join(SCRIPTS_DIR, exe) + with open(exe, encoding="utf8") as f: + src = f.read() + ast.parse(src) + + def test_coverage(self): + # make sure all example scripts have a test method defined + meths = dir(self) + for name in os.listdir(SCRIPTS_DIR): + if name.endswith('.py'): + if 'test_' + os.path.splitext(name)[0] not in meths: + # self.assert_stdout(name) + raise self.fail( + "no test defined for" + f" {os.path.join(SCRIPTS_DIR, name)!r} script" + ) + + @pytest.mark.skipif(not POSIX, reason="POSIX only") + def test_executable(self): + for root, dirs, files in os.walk(SCRIPTS_DIR): + for file in files: + if file.endswith('.py'): + path = os.path.join(root, file) + if not stat.S_IXUSR & os.stat(path)[stat.ST_MODE]: + raise self.fail(f"{path!r} is not executable") + + def test_disk_usage(self): + self.assert_stdout('disk_usage.py') + + def test_free(self): + self.assert_stdout('free.py') + + def test_meminfo(self): + self.assert_stdout('meminfo.py') + + def test_procinfo(self): + self.assert_stdout('procinfo.py', str(os.getpid())) + + @pytest.mark.skipif(CI_TESTING and not psutil.users(), reason="no users") + def test_who(self): + self.assert_stdout('who.py') + + def test_ps(self): + self.assert_stdout('ps.py') + + def test_pstree(self): + self.assert_stdout('pstree.py') + + def test_netstat(self): + self.assert_stdout('netstat.py') + + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") + def test_ifconfig(self): + self.assert_stdout('ifconfig.py') + + @pytest.mark.skipif(not HAS_MEMORY_MAPS, reason="not supported") + def test_pmap(self): + self.assert_stdout('pmap.py', str(os.getpid())) + + def test_procsmem(self): + if 'uss' not in psutil.Process().memory_full_info()._fields: + raise pytest.skip("not supported") + self.assert_stdout('procsmem.py') + + def test_killall(self): + self.assert_syntax('killall.py') + + def test_nettop(self): + self.assert_syntax('nettop.py') + + def test_top(self): + self.assert_syntax('top.py') + + def test_iotop(self): + self.assert_syntax('iotop.py') + + def test_pidof(self): + output = self.assert_stdout('pidof.py', psutil.Process().name()) + assert str(os.getpid()) in output + + @pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") + def test_winservices(self): + self.assert_stdout('winservices.py') + + def test_cpu_distribution(self): + self.assert_syntax('cpu_distribution.py') + + @pytest.mark.skipif(not HAS_SENSORS_TEMPERATURES, reason="not supported") + def test_temperatures(self): + if not psutil.sensors_temperatures(): + raise pytest.skip("no temperatures") + self.assert_stdout('temperatures.py') + + @pytest.mark.skipif(not HAS_SENSORS_FANS, reason="not supported") + def test_fans(self): + if not psutil.sensors_fans(): + raise pytest.skip("no fans") + self.assert_stdout('fans.py') + + @pytest.mark.skipif(not HAS_SENSORS_BATTERY, reason="not supported") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") + def test_battery(self): + self.assert_stdout('battery.py') + + @pytest.mark.skipif(not HAS_SENSORS_BATTERY, reason="not supported") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") + def test_sensors(self): + self.assert_stdout('sensors.py') + + +# =================================================================== +# --- Tests scripts in scripts/internal/ directory +# =================================================================== + + +@pytest.mark.skipif( + CI_TESTING and not os.path.exists(INTERNAL_SCRIPTS_DIR), + reason="can't find scripts/internal/ directory", +) +class TestInternalScripts(PsutilTestCase): + @staticmethod + def ls(): + for name in os.listdir(INTERNAL_SCRIPTS_DIR): + if name.endswith(".py"): + yield os.path.join(INTERNAL_SCRIPTS_DIR, name) + + def test_syntax_all(self): + for path in self.ls(): + with open(path, encoding="utf8") as f: + data = f.read() + ast.parse(data) + + @pytest.mark.skipif(CI_TESTING, reason="not on CI") + def test_import_all(self): + for path in self.ls(): + try: + import_module_by_path(path) + except SystemExit: + pass + + +# =================================================================== +# --- Tests for setup.py script +# =================================================================== + + +@pytest.mark.skipif( + CI_TESTING and not os.path.exists(SETUP_PY), reason="can't find setup.py" +) +class TestSetupScript(PsutilTestCase): + def test_invocation(self): + module = import_module_by_path(SETUP_PY) + with pytest.raises(SystemExit): + module.setup() + assert module.get_version() == psutil.__version__ + + @pytest.mark.skipif( + not shutil.which("python2.7"), reason="python2.7 not installed" + ) + def test_python2(self): + # There's a duplicate of this test in scripts/internal + # directory, which is only executed by CI. We replicate it here + # to run it when developing locally. + p = subprocess.Popen( + [shutil.which("python2.7"), SETUP_PY], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) + stdout, stderr = p.communicate() + assert p.wait() == 1 + assert not stdout + assert "psutil no longer supports Python 2.7" in stderr + assert "Latest version supporting Python 2.7 is" in stderr diff --git a/psutil/tests/test_sunos.py b/psutil/tests/test_sunos.py index 548352281..b5d9d353b 100755 --- a/psutil/tests/test_sunos.py +++ b/psutil/tests/test_sunos.py @@ -7,18 +7,18 @@ """Sun OS specific tests.""" import os -import unittest import psutil from psutil import SUNOS from psutil.tests import PsutilTestCase +from psutil.tests import pytest from psutil.tests import sh -@unittest.skipIf(not SUNOS, "SUNOS only") +@pytest.mark.skipif(not SUNOS, reason="SUNOS only") class SunOSSpecificTestCase(PsutilTestCase): def test_swap_memory(self): - out = sh('env PATH=/usr/sbin:/sbin:%s swap -l' % os.environ['PATH']) + out = sh(f"env PATH=/usr/sbin:/sbin:{os.environ['PATH']} swap -l") lines = out.strip().split('\n')[1:] if not lines: raise ValueError('no swap device(s) configured') @@ -30,16 +30,10 @@ def test_swap_memory(self): used = total - free psutil_swap = psutil.swap_memory() - self.assertEqual(psutil_swap.total, total) - self.assertEqual(psutil_swap.used, used) - self.assertEqual(psutil_swap.free, free) + assert psutil_swap.total == total + assert psutil_swap.used == used + assert psutil_swap.free == free def test_cpu_count(self): out = sh("/usr/sbin/psrinfo") - self.assertEqual(psutil.cpu_count(), len(out.split('\n'))) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert psutil.cpu_count() == len(out.split('\n')) diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index 32ac62cbe..167172f0e 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -6,8 +6,8 @@ """Tests for system APIS.""" -import contextlib import datetime +import enum import errno import os import platform @@ -17,7 +17,7 @@ import socket import sys import time -import unittest +from unittest import mock import psutil from psutil import AIX @@ -30,12 +30,8 @@ from psutil import POSIX from psutil import SUNOS from psutil import WINDOWS -from psutil._compat import PY3 -from psutil._compat import FileNotFoundError -from psutil._compat import long from psutil.tests import ASCII_FS from psutil.tests import CI_TESTING -from psutil.tests import DEVNULL from psutil.tests import GITHUB_ACTIONS from psutil.tests import GLOBAL_TIMEOUT from psutil.tests import HAS_BATTERY @@ -48,11 +44,11 @@ from psutil.tests import IS_64BIT from psutil.tests import MACOS_12PLUS from psutil.tests import PYPY +from psutil.tests import QEMU_USER from psutil.tests import UNICODE_SUFFIX from psutil.tests import PsutilTestCase from psutil.tests import check_net_address -from psutil.tests import enum -from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import retry_on_failure @@ -63,19 +59,18 @@ class TestProcessIter(PsutilTestCase): def test_pid_presence(self): - self.assertIn(os.getpid(), [x.pid for x in psutil.process_iter()]) + assert os.getpid() in [x.pid for x in psutil.process_iter()] sproc = self.spawn_testproc() - self.assertIn(sproc.pid, [x.pid for x in psutil.process_iter()]) + assert sproc.pid in [x.pid for x in psutil.process_iter()] p = psutil.Process(sproc.pid) p.kill() p.wait() - self.assertNotIn(sproc.pid, [x.pid for x in psutil.process_iter()]) + assert sproc.pid not in [x.pid for x in psutil.process_iter()] def test_no_duplicates(self): ls = [x for x in psutil.process_iter()] - self.assertEqual( - sorted(ls, key=lambda x: x.pid), - sorted(set(ls), key=lambda x: x.pid), + assert sorted(ls, key=lambda x: x.pid) == sorted( + set(ls), key=lambda x: x.pid ) def test_emulate_nsp(self): @@ -85,9 +80,7 @@ def test_emulate_nsp(self): 'psutil.Process.as_dict', side_effect=psutil.NoSuchProcess(os.getpid()), ): - self.assertEqual( - list(psutil.process_iter(attrs=["cpu_times"])), [] - ) + assert list(psutil.process_iter(attrs=["cpu_times"])) == [] psutil.process_iter.cache_clear() # repeat test without cache def test_emulate_access_denied(self): @@ -97,25 +90,25 @@ def test_emulate_access_denied(self): 'psutil.Process.as_dict', side_effect=psutil.AccessDenied(os.getpid()), ): - with self.assertRaises(psutil.AccessDenied): + with pytest.raises(psutil.AccessDenied): list(psutil.process_iter(attrs=["cpu_times"])) psutil.process_iter.cache_clear() # repeat test without cache def test_attrs(self): for p in psutil.process_iter(attrs=['pid']): - self.assertEqual(list(p.info.keys()), ['pid']) + assert list(p.info.keys()) == ['pid'] # yield again for p in psutil.process_iter(attrs=['pid']): - self.assertEqual(list(p.info.keys()), ['pid']) - with self.assertRaises(ValueError): + assert list(p.info.keys()) == ['pid'] + with pytest.raises(ValueError): list(psutil.process_iter(attrs=['foo'])) with mock.patch( "psutil._psplatform.Process.cpu_times", side_effect=psutil.AccessDenied(0, ""), ) as m: for p in psutil.process_iter(attrs=["pid", "cpu_times"]): - self.assertIsNone(p.info['cpu_times']) - self.assertGreaterEqual(p.info['pid'], 0) + assert p.info['cpu_times'] is None + assert p.info['pid'] >= 0 assert m.called with mock.patch( "psutil._psplatform.Process.cpu_times", @@ -125,8 +118,8 @@ def test_attrs(self): for p in psutil.process_iter( attrs=["pid", "cpu_times"], ad_value=flag ): - self.assertIs(p.info['cpu_times'], flag) - self.assertGreaterEqual(p.info['pid'], 0) + assert p.info['cpu_times'] is flag + assert p.info['pid'] >= 0 assert m.called def test_cache_clear(self): @@ -137,8 +130,9 @@ def test_cache_clear(self): class TestProcessAPIs(PsutilTestCase): - @unittest.skipIf( - PYPY and WINDOWS, "spawn_testproc() unreliable on PYPY + WINDOWS" + @pytest.mark.skipif( + PYPY and WINDOWS, + reason="spawn_testproc() unreliable on PYPY + WINDOWS", ) def test_wait_procs(self): def callback(p): @@ -149,56 +143,59 @@ def callback(p): sproc2 = self.spawn_testproc() sproc3 = self.spawn_testproc() procs = [psutil.Process(x.pid) for x in (sproc1, sproc2, sproc3)] - self.assertRaises(ValueError, psutil.wait_procs, procs, timeout=-1) - self.assertRaises(TypeError, psutil.wait_procs, procs, callback=1) + with pytest.raises(ValueError): + psutil.wait_procs(procs, timeout=-1) + with pytest.raises(TypeError): + psutil.wait_procs(procs, callback=1) t = time.time() gone, alive = psutil.wait_procs(procs, timeout=0.01, callback=callback) - self.assertLess(time.time() - t, 0.5) - self.assertEqual(gone, []) - self.assertEqual(len(alive), 3) - self.assertEqual(pids, []) + assert time.time() - t < 0.5 + assert gone == [] + assert len(alive) == 3 + assert pids == [] for p in alive: - self.assertFalse(hasattr(p, 'returncode')) + assert not hasattr(p, 'returncode') @retry_on_failure(30) def test_1(procs, callback): gone, alive = psutil.wait_procs( procs, timeout=0.03, callback=callback ) - self.assertEqual(len(gone), 1) - self.assertEqual(len(alive), 2) + assert len(gone) == 1 + assert len(alive) == 2 return gone, alive sproc3.terminate() gone, alive = test_1(procs, callback) - self.assertIn(sproc3.pid, [x.pid for x in gone]) + assert sproc3.pid in [x.pid for x in gone] if POSIX: - self.assertEqual(gone.pop().returncode, -signal.SIGTERM) + assert gone.pop().returncode == -signal.SIGTERM else: - self.assertEqual(gone.pop().returncode, 1) - self.assertEqual(pids, [sproc3.pid]) + assert gone.pop().returncode == 1 + assert pids == [sproc3.pid] for p in alive: - self.assertFalse(hasattr(p, 'returncode')) + assert not hasattr(p, 'returncode') @retry_on_failure(30) def test_2(procs, callback): gone, alive = psutil.wait_procs( procs, timeout=0.03, callback=callback ) - self.assertEqual(len(gone), 3) - self.assertEqual(len(alive), 0) + assert len(gone) == 3 + assert len(alive) == 0 return gone, alive sproc1.terminate() sproc2.terminate() gone, alive = test_2(procs, callback) - self.assertEqual(set(pids), set([sproc1.pid, sproc2.pid, sproc3.pid])) + assert set(pids) == {sproc1.pid, sproc2.pid, sproc3.pid} for p in gone: - self.assertTrue(hasattr(p, 'returncode')) + assert hasattr(p, 'returncode') - @unittest.skipIf( - PYPY and WINDOWS, "spawn_testproc() unreliable on PYPY + WINDOWS" + @pytest.mark.skipif( + PYPY and WINDOWS, + reason="spawn_testproc() unreliable on PYPY + WINDOWS", ) def test_wait_procs_no_timeout(self): sproc1 = self.spawn_testproc() @@ -211,13 +208,13 @@ def test_wait_procs_no_timeout(self): def test_pid_exists(self): sproc = self.spawn_testproc() - self.assertTrue(psutil.pid_exists(sproc.pid)) + assert psutil.pid_exists(sproc.pid) p = psutil.Process(sproc.pid) p.kill() p.wait() - self.assertFalse(psutil.pid_exists(sproc.pid)) - self.assertFalse(psutil.pid_exists(-1)) - self.assertEqual(psutil.pid_exists(0), 0 in psutil.pids()) + assert not psutil.pid_exists(sproc.pid) + assert not psutil.pid_exists(-1) + assert psutil.pid_exists(0) == (0 in psutil.pids()) def test_pid_exists_2(self): pids = psutil.pids() @@ -228,48 +225,41 @@ def test_pid_exists_2(self): # in case the process disappeared in meantime fail only # if it is no longer in psutil.pids() time.sleep(0.1) - self.assertNotIn(pid, psutil.pids()) + assert pid not in psutil.pids() pids = range(max(pids) + 15000, max(pids) + 16000) for pid in pids: - self.assertFalse(psutil.pid_exists(pid), msg=pid) + assert not psutil.pid_exists(pid) class TestMiscAPIs(PsutilTestCase): def test_boot_time(self): bt = psutil.boot_time() - self.assertIsInstance(bt, float) - self.assertGreater(bt, 0) - self.assertLess(bt, time.time()) + assert isinstance(bt, float) + assert bt > 0 + assert bt < time.time() - @unittest.skipIf(CI_TESTING and not psutil.users(), "unreliable on CI") + @pytest.mark.skipif( + CI_TESTING and not psutil.users(), reason="unreliable on CI" + ) def test_users(self): users = psutil.users() - self.assertNotEqual(users, []) + assert users != [] for user in users: with self.subTest(user=user): assert user.name - self.assertIsInstance(user.name, str) - self.assertIsInstance(user.terminal, (str, type(None))) + assert isinstance(user.name, str) + assert isinstance(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) + assert isinstance(user.host, (str, type(None))) + user.terminal # noqa: B018 + user.host # noqa: B018 + assert user.started > 0.0 datetime.datetime.fromtimestamp(user.started) if WINDOWS or OPENBSD: - self.assertIsNone(user.pid) + assert user.pid is None else: psutil.Process(user.pid) - def test_test(self): - # test for psutil.test() function - stdout = sys.stdout - sys.stdout = DEVNULL - try: - psutil.test() - finally: - sys.stdout = stdout - def test_os_constants(self): names = [ "POSIX", @@ -283,7 +273,7 @@ def test_os_constants(self): "SUNOS", ] for name in names: - self.assertIsInstance(getattr(psutil, name), bool, msg=name) + assert isinstance(getattr(psutil, name), bool), name if os.name == 'posix': assert psutil.POSIX @@ -294,12 +284,9 @@ def test_os_constants(self): names.remove("LINUX") elif "bsd" in sys.platform.lower(): assert psutil.BSD - self.assertEqual( - [psutil.FREEBSD, psutil.OPENBSD, psutil.NETBSD].count( - True - ), - 1, - ) + assert [psutil.FREEBSD, psutil.OPENBSD, psutil.NETBSD].count( + True + ) == 1 names.remove("BSD") names.remove("FREEBSD") names.remove("OPENBSD") @@ -320,7 +307,7 @@ def test_os_constants(self): # assert all other constants are set to False for name in names: - self.assertFalse(getattr(psutil, name), msg=name) + assert not getattr(psutil, name), name class TestMemoryAPIs(PsutilTestCase): @@ -334,20 +321,24 @@ def test_virtual_memory(self): for name in mem._fields: value = getattr(mem, name) if name != 'percent': - self.assertIsInstance(value, (int, long)) + assert isinstance(value, int) if name != 'total': if not value >= 0: - raise self.fail("%r < 0 (%s)" % (name, value)) + raise self.fail(f"{name!r} < 0 ({value})") if value > mem.total: raise self.fail( - "%r > total (total=%s, %s=%s)" - % (name, mem.total, name, value) + f"{name!r} > total (total={mem.total}, {name}={value})" ) def test_swap_memory(self): mem = psutil.swap_memory() - self.assertEqual( - mem._fields, ('total', 'used', 'free', 'percent', 'sin', 'sout') + assert mem._fields == ( + 'total', + 'used', + 'free', + 'percent', + 'sin', + 'sout', ) assert mem.total >= 0, mem @@ -365,26 +356,26 @@ def test_swap_memory(self): class TestCpuAPIs(PsutilTestCase): def test_cpu_count_logical(self): logical = psutil.cpu_count() - self.assertIsNotNone(logical) - self.assertEqual(logical, len(psutil.cpu_times(percpu=True))) - self.assertGreaterEqual(logical, 1) + assert logical is not None + assert logical == len(psutil.cpu_times(percpu=True)) + assert logical >= 1 if os.path.exists("/proc/cpuinfo"): with open("/proc/cpuinfo") as fd: cpuinfo_data = fd.read() if "physical id" not in cpuinfo_data: - raise unittest.SkipTest("cpuinfo doesn't include physical id") + raise pytest.skip("cpuinfo doesn't include physical id") def test_cpu_count_cores(self): logical = psutil.cpu_count() cores = psutil.cpu_count(logical=False) if cores is None: - raise unittest.SkipTest("cpu_count_cores() is None") + raise pytest.skip("cpu_count_cores() is None") if WINDOWS and sys.getwindowsversion()[:2] <= (6, 1): # <= Vista - self.assertIsNone(cores) + assert cores is None else: - self.assertGreaterEqual(cores, 1) - self.assertGreaterEqual(logical, cores) + assert cores >= 1 + assert logical >= cores def test_cpu_count_none(self): # https://github.com/giampaolo/psutil/issues/1085 @@ -392,12 +383,12 @@ def test_cpu_count_none(self): with mock.patch( 'psutil._psplatform.cpu_count_logical', return_value=val ) as m: - self.assertIsNone(psutil.cpu_count()) + assert psutil.cpu_count() is None assert m.called with mock.patch( 'psutil._psplatform.cpu_count_cores', return_value=val ) as m: - self.assertIsNone(psutil.cpu_count(logical=False)) + assert psutil.cpu_count(logical=False) is None assert m.called def test_cpu_times(self): @@ -406,10 +397,10 @@ def test_cpu_times(self): times = psutil.cpu_times() sum(times) for cp_time in times: - self.assertIsInstance(cp_time, float) - self.assertGreaterEqual(cp_time, 0.0) + assert isinstance(cp_time, float) + assert cp_time >= 0.0 total += cp_time - self.assertAlmostEqual(total, sum(times), places=6) + assert round(abs(total - sum(times)), 6) == 0 str(times) # CPU times are always supposed to increase over time # or at least remain the same and that's because time @@ -425,8 +416,9 @@ def test_cpu_times(self): # for field in new._fields: # new_t = getattr(new, field) # last_t = getattr(last, field) - # self.assertGreaterEqual(new_t, last_t, - # msg="%s %s" % (new_t, last_t)) + # self.assertGreaterEqual( + # new_t, last_t, + # msg="{} {}".format(new_t, last_t)) # last = new def test_cpu_times_time_increases(self): @@ -445,14 +437,13 @@ def test_per_cpu_times(self): total = 0 sum(times) for cp_time in times: - self.assertIsInstance(cp_time, float) - self.assertGreaterEqual(cp_time, 0.0) + assert isinstance(cp_time, float) + assert cp_time >= 0.0 total += cp_time - self.assertAlmostEqual(total, sum(times), places=6) + assert round(abs(total - sum(times)), 6) == 0 str(times) - self.assertEqual( - len(psutil.cpu_times(percpu=True)[0]), - len(psutil.cpu_times(percpu=False)), + assert len(psutil.cpu_times(percpu=True)[0]) == len( + psutil.cpu_times(percpu=False) ) # Note: in theory CPU times are always supposed to increase over @@ -471,7 +462,7 @@ def test_per_cpu_times(self): # new_t = getattr(newcpu, field) # last_t = getattr(lastcpu, field) # self.assertGreaterEqual( - # new_t, last_t, msg="%s %s" % (lastcpu, newcpu)) + # new_t, last_t, msg="{} {}".format(lastcpu, newcpu)) # last = new def test_per_cpu_times_2(self): @@ -487,9 +478,12 @@ def test_per_cpu_times_2(self): t1, t2 = psutil._cpu_busy_time(t1), psutil._cpu_busy_time(t2) difference = t2 - t1 if difference >= 0.05: - return + return None - @unittest.skipIf(CI_TESTING and OPENBSD, "unreliable on OPENBSD + CI") + @pytest.mark.skipif( + CI_TESTING and OPENBSD, reason="unreliable on OPENBSD + CI" + ) + @retry_on_failure(30) def test_cpu_times_comparison(self): # Make sure the sum of all per cpu times is almost equal to # base "one cpu" times. On OpenBSD the sum of per-CPUs is @@ -499,22 +493,21 @@ def test_cpu_times_comparison(self): summed_values = base._make([sum(num) for num in zip(*per_cpu)]) for field in base._fields: with self.subTest(field=field, base=base, per_cpu=per_cpu): - self.assertAlmostEqual( - getattr(base, field), - getattr(summed_values, field), - delta=1, + assert ( + abs(getattr(base, field) - getattr(summed_values, field)) + < 2 ) def _test_cpu_percent(self, percent, last_ret, new_ret): try: - self.assertIsInstance(percent, float) - self.assertGreaterEqual(percent, 0.0) - self.assertIsNot(percent, -0.0) - self.assertLessEqual(percent, 100.0 * psutil.cpu_count()) + assert isinstance(percent, float) + assert percent >= 0.0 + assert percent <= 100.0 * psutil.cpu_count() except AssertionError as err: raise AssertionError( - "\n%s\nlast=%s\nnew=%s" - % (err, pprint.pformat(last_ret), pprint.pformat(new_ret)) + "\n{}\nlast={}\nnew={}".format( + err, pprint.pformat(last_ret), pprint.pformat(new_ret) + ) ) def test_cpu_percent(self): @@ -523,18 +516,18 @@ def test_cpu_percent(self): new = psutil.cpu_percent(interval=None) self._test_cpu_percent(new, last, new) last = new - with self.assertRaises(ValueError): + with pytest.raises(ValueError): psutil.cpu_percent(interval=-1) def test_per_cpu_percent(self): last = psutil.cpu_percent(interval=0.001, percpu=True) - self.assertEqual(len(last), psutil.cpu_count()) + assert len(last) == psutil.cpu_count() for _ in range(100): new = psutil.cpu_percent(interval=None, percpu=True) for percent in new: self._test_cpu_percent(percent, last, new) last = new - with self.assertRaises(ValueError): + with pytest.raises(ValueError): psutil.cpu_percent(interval=-1, percpu=True) def test_cpu_times_percent(self): @@ -545,12 +538,12 @@ def test_cpu_times_percent(self): self._test_cpu_percent(percent, last, new) self._test_cpu_percent(sum(new), last, new) last = new - with self.assertRaises(ValueError): + with pytest.raises(ValueError): psutil.cpu_times_percent(interval=-1) def test_per_cpu_times_percent(self): last = psutil.cpu_times_percent(interval=0.001, percpu=True) - self.assertEqual(len(last), psutil.cpu_count()) + assert len(last) == psutil.cpu_count() for _ in range(100): new = psutil.cpu_times_percent(interval=None, percpu=True) for cpu in new: @@ -574,57 +567,61 @@ def test_per_cpu_times_percent_negative(self): def test_cpu_stats(self): # Tested more extensively in per-platform test modules. infos = psutil.cpu_stats() - self.assertEqual( - infos._fields, - ('ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'), + assert infos._fields == ( + 'ctx_switches', + 'interrupts', + 'soft_interrupts', + 'syscalls', ) for name in infos._fields: value = getattr(infos, name) - self.assertGreaterEqual(value, 0) + assert value >= 0 # on AIX, ctx_switches is always 0 - if not AIX and name in ('ctx_switches', 'interrupts'): - self.assertGreater(value, 0) + if not AIX and name in {'ctx_switches', 'interrupts'}: + assert value > 0 # TODO: remove this once 1892 is fixed - @unittest.skipIf( - MACOS and platform.machine() == 'arm64', "skipped due to #1892" + @pytest.mark.skipif( + MACOS and platform.machine() == 'arm64', reason="skipped due to #1892" ) - @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported") def test_cpu_freq(self): def check_ls(ls): for nt in ls: - self.assertEqual(nt._fields, ('current', 'min', 'max')) + assert nt._fields == ('current', 'min', 'max') if nt.max != 0.0: - self.assertLessEqual(nt.current, nt.max) + assert nt.current <= nt.max for name in nt._fields: value = getattr(nt, name) - self.assertIsInstance(value, (int, long, float)) - self.assertGreaterEqual(value, 0) + assert isinstance(value, (int, float)) + assert value >= 0 ls = psutil.cpu_freq(percpu=True) if FREEBSD and not ls: - raise unittest.SkipTest("returns empty list on FreeBSD") + raise pytest.skip("returns empty list on FreeBSD") assert ls, ls check_ls([psutil.cpu_freq(percpu=False)]) if LINUX: - self.assertEqual(len(ls), psutil.cpu_count()) + assert len(ls) == psutil.cpu_count() - @unittest.skipIf(not HAS_GETLOADAVG, "not supported") + @pytest.mark.skipif(not HAS_GETLOADAVG, reason="not supported") def test_getloadavg(self): loadavg = psutil.getloadavg() - self.assertEqual(len(loadavg), 3) + assert len(loadavg) == 3 for load in loadavg: - self.assertIsInstance(load, float) - self.assertGreaterEqual(load, 0.0) + assert isinstance(load, float) + assert load >= 0.0 class TestDiskAPIs(PsutilTestCase): - @unittest.skipIf(PYPY and not IS_64BIT, "unreliable on PYPY32 + 32BIT") + @pytest.mark.skipif( + PYPY and not IS_64BIT, reason="unreliable on PYPY32 + 32BIT" + ) def test_disk_usage(self): usage = psutil.disk_usage(os.getcwd()) - self.assertEqual(usage._fields, ('total', 'used', 'free', 'percent')) + assert usage._fields == ('total', 'used', 'free', 'percent') assert usage.total > 0, usage assert usage.used > 0, usage @@ -636,26 +633,22 @@ def test_disk_usage(self): # py >= 3.3, see: http://bugs.python.org/issue12442 shutil_usage = shutil.disk_usage(os.getcwd()) tolerance = 5 * 1024 * 1024 # 5MB - self.assertEqual(usage.total, shutil_usage.total) - self.assertAlmostEqual( - usage.free, shutil_usage.free, delta=tolerance - ) + assert usage.total == shutil_usage.total + assert abs(usage.free - shutil_usage.free) < tolerance if not MACOS_12PLUS: # see https://github.com/giampaolo/psutil/issues/2147 - self.assertAlmostEqual( - usage.used, shutil_usage.used, delta=tolerance - ) + assert abs(usage.used - shutil_usage.used) < tolerance # if path does not exist OSError ENOENT is expected across # all platforms fname = self.get_testfn() - with self.assertRaises(FileNotFoundError): + with pytest.raises(FileNotFoundError): psutil.disk_usage(fname) - @unittest.skipIf(not ASCII_FS, "not an ASCII fs") + @pytest.mark.skipif(not ASCII_FS, reason="not an ASCII fs") def test_disk_usage_unicode(self): # See: https://github.com/giampaolo/psutil/issues/416 - with self.assertRaises(UnicodeEncodeError): + with pytest.raises(UnicodeEncodeError): psutil.disk_usage(UNICODE_SUFFIX) def test_disk_usage_bytes(self): @@ -663,14 +656,14 @@ def test_disk_usage_bytes(self): def test_disk_partitions(self): def check_ntuple(nt): - self.assertIsInstance(nt.device, str) - self.assertIsInstance(nt.mountpoint, str) - self.assertIsInstance(nt.fstype, str) - self.assertIsInstance(nt.opts, str) + assert isinstance(nt.device, str) + assert isinstance(nt.mountpoint, str) + assert isinstance(nt.fstype, str) + assert isinstance(nt.opts, str) # all = False ls = psutil.disk_partitions(all=False) - self.assertTrue(ls, msg=ls) + assert ls for disk in ls: check_ntuple(disk) if WINDOWS and 'cdrom' in disk.opts: @@ -680,14 +673,14 @@ def check_ntuple(nt): else: # we cannot make any assumption about this, see: # http://goo.gl/p9c43 - disk.device # noqa + disk.device # noqa: B018 # on modern systems mount points can also be files assert os.path.exists(disk.mountpoint), disk assert disk.fstype, disk # all = True ls = psutil.disk_partitions(all=True) - self.assertTrue(ls, msg=ls) + assert ls for disk in psutil.disk_partitions(all=True): check_ntuple(disk) if not WINDOWS and disk.mountpoint: @@ -698,7 +691,7 @@ def check_ntuple(nt): continue # http://mail.python.org/pipermail/python-dev/ # 2012-June/120787.html - if err.errno not in (errno.EPERM, errno.EACCES): + if err.errno not in {errno.EPERM, errno.EACCES}: raise else: assert os.path.exists(disk.mountpoint), disk @@ -717,30 +710,30 @@ def find_mount_point(path): for x in psutil.disk_partitions(all=True) if x.mountpoint ] - self.assertIn(mount, mounts) + assert mount in mounts - @unittest.skipIf( + @pytest.mark.skipif( LINUX and not os.path.exists('/proc/diskstats'), - '/proc/diskstats not available on this linux version', + reason="/proc/diskstats not available on this linux version", ) - @unittest.skipIf( - CI_TESTING and not psutil.disk_io_counters(), "unreliable on CI" + @pytest.mark.skipif( + CI_TESTING and not psutil.disk_io_counters(), reason="unreliable on CI" ) # no visible disks def test_disk_io_counters(self): def check_ntuple(nt): - self.assertEqual(nt[0], nt.read_count) - self.assertEqual(nt[1], nt.write_count) - self.assertEqual(nt[2], nt.read_bytes) - self.assertEqual(nt[3], nt.write_bytes) + assert nt[0] == nt.read_count + assert nt[1] == nt.write_count + assert nt[2] == nt.read_bytes + assert nt[3] == nt.write_bytes if not (OPENBSD or NETBSD): - self.assertEqual(nt[4], nt.read_time) - self.assertEqual(nt[5], nt.write_time) + assert nt[4] == nt.read_time + assert nt[5] == nt.write_time if LINUX: - self.assertEqual(nt[6], nt.read_merged_count) - self.assertEqual(nt[7], nt.write_merged_count) - self.assertEqual(nt[8], nt.busy_time) + assert nt[6] == nt.read_merged_count + assert nt[7] == nt.write_merged_count + assert nt[8] == nt.busy_time elif FREEBSD: - self.assertEqual(nt[6], nt.busy_time) + assert nt[6] == nt.busy_time for name in nt._fields: assert getattr(nt, name) >= 0, nt @@ -749,7 +742,7 @@ def check_ntuple(nt): check_ntuple(ret) ret = psutil.disk_io_counters(perdisk=True) # make sure there are no duplicates - self.assertEqual(len(ret), len(set(ret))) + assert len(ret) == len(set(ret)) for key in ret: assert key, key check_ntuple(ret[key]) @@ -760,23 +753,23 @@ def test_disk_io_counters_no_disks(self): with mock.patch( 'psutil._psplatform.disk_io_counters', return_value={} ) as m: - self.assertIsNone(psutil.disk_io_counters(perdisk=False)) - self.assertEqual(psutil.disk_io_counters(perdisk=True), {}) + assert psutil.disk_io_counters(perdisk=False) is None + assert psutil.disk_io_counters(perdisk=True) == {} assert m.called class TestNetAPIs(PsutilTestCase): - @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') + @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported") def test_net_io_counters(self): def check_ntuple(nt): - self.assertEqual(nt[0], nt.bytes_sent) - self.assertEqual(nt[1], nt.bytes_recv) - self.assertEqual(nt[2], nt.packets_sent) - self.assertEqual(nt[3], nt.packets_recv) - self.assertEqual(nt[4], nt.errin) - self.assertEqual(nt[5], nt.errout) - self.assertEqual(nt[6], nt.dropin) - self.assertEqual(nt[7], nt.dropout) + assert nt[0] == nt.bytes_sent + assert nt[1] == nt.bytes_recv + assert nt[2] == nt.packets_sent + assert nt[3] == nt.packets_recv + assert nt[4] == nt.errin + assert nt[5] == nt.errout + assert nt[6] == nt.dropin + assert nt[7] == nt.dropout assert nt.bytes_sent >= 0, nt assert nt.bytes_recv >= 0, nt assert nt.packets_sent >= 0, nt @@ -789,23 +782,24 @@ def check_ntuple(nt): ret = psutil.net_io_counters(pernic=False) check_ntuple(ret) ret = psutil.net_io_counters(pernic=True) - self.assertNotEqual(ret, []) + assert ret != [] for key in ret: - self.assertTrue(key) - self.assertIsInstance(key, str) + assert key + assert isinstance(key, str) check_ntuple(ret[key]) - @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') + @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported") def test_net_io_counters_no_nics(self): # Emulate a case where no NICs are installed, see: # https://github.com/giampaolo/psutil/issues/1062 with mock.patch( 'psutil._psplatform.net_io_counters', return_value={} ) as m: - self.assertIsNone(psutil.net_io_counters(pernic=False)) - self.assertEqual(psutil.net_io_counters(pernic=True), {}) + assert psutil.net_io_counters(pernic=False) is None + assert psutil.net_io_counters(pernic=True) == {} assert m.called + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_net_if_addrs(self): nics = psutil.net_if_addrs() assert nics, nics @@ -817,24 +811,22 @@ def test_net_if_addrs(self): # self.assertEqual(sorted(nics.keys()), # sorted(psutil.net_io_counters(pernic=True).keys())) - families = set([socket.AF_INET, socket.AF_INET6, psutil.AF_LINK]) + families = {socket.AF_INET, socket.AF_INET6, psutil.AF_LINK} for nic, addrs in nics.items(): - self.assertIsInstance(nic, str) - self.assertEqual(len(set(addrs)), len(addrs)) + assert isinstance(nic, str) + assert len(set(addrs)) == len(addrs) for addr in addrs: - self.assertIsInstance(addr.family, int) - self.assertIsInstance(addr.address, str) - self.assertIsInstance(addr.netmask, (str, type(None))) - self.assertIsInstance(addr.broadcast, (str, type(None))) - self.assertIn(addr.family, families) - if PY3 and not PYPY: - self.assertIsInstance(addr.family, enum.IntEnum) + assert isinstance(addr.family, int) + assert isinstance(addr.address, str) + assert isinstance(addr.netmask, (str, type(None))) + assert isinstance(addr.broadcast, (str, type(None))) + assert addr.family in families + assert isinstance(addr.family, enum.IntEnum) if nic_stats[nic].isup: # Do not test binding to addresses of interfaces # that are down if addr.family == socket.AF_INET: - s = socket.socket(addr.family) - with contextlib.closing(s): + with socket.socket(addr.family) as s: s.bind((addr.address, 0)) elif addr.family == socket.AF_INET6: info = socket.getaddrinfo( @@ -845,9 +837,8 @@ def test_net_if_addrs(self): 0, socket.AI_PASSIVE, )[0] - af, socktype, proto, canonname, sa = info - s = socket.socket(af, socktype, proto) - with contextlib.closing(s): + af, socktype, proto, _canonname, sa = info + with socket.socket(af, socktype, proto) as s: s.bind(sa) for ip in ( addr.address, @@ -863,17 +854,17 @@ def test_net_if_addrs(self): check_net_address(ip, addr.family) # broadcast and ptp addresses are mutually exclusive if addr.broadcast: - self.assertIsNone(addr.ptp) + assert addr.ptp is None elif addr.ptp: - self.assertIsNone(addr.broadcast) + assert addr.broadcast is None if BSD or MACOS or SUNOS: if hasattr(socket, "AF_LINK"): - self.assertEqual(psutil.AF_LINK, socket.AF_LINK) + assert psutil.AF_LINK == socket.AF_LINK elif LINUX: - self.assertEqual(psutil.AF_LINK, socket.AF_PACKET) + assert psutil.AF_LINK == socket.AF_PACKET elif WINDOWS: - self.assertEqual(psutil.AF_LINK, -1) + assert psutil.AF_LINK == -1 def test_net_if_addrs_mac_null_bytes(self): # Simulate that the underlying C function returns an incomplete @@ -889,10 +880,11 @@ def test_net_if_addrs_mac_null_bytes(self): addr = psutil.net_if_addrs()['em1'][0] assert m.called if POSIX: - self.assertEqual(addr.address, '06:3d:29:00:00:00') + assert addr.address == '06:3d:29:00:00:00' else: - self.assertEqual(addr.address, '06-3d-29-00-00-00') + assert addr.address == '06-3d-29-00-00-00' + @pytest.mark.skipif(QEMU_USER, reason="QEMU user not supported") def test_net_if_stats(self): nics = psutil.net_if_stats() assert nics, nics @@ -902,17 +894,17 @@ def test_net_if_stats(self): psutil.NIC_DUPLEX_UNKNOWN, ) for name, stats in nics.items(): - self.assertIsInstance(name, str) + assert isinstance(name, str) isup, duplex, speed, mtu, flags = stats - self.assertIsInstance(isup, bool) - self.assertIn(duplex, all_duplexes) - self.assertIn(duplex, all_duplexes) - self.assertGreaterEqual(speed, 0) - self.assertGreaterEqual(mtu, 0) - self.assertIsInstance(flags, str) - - @unittest.skipIf( - not (LINUX or BSD or MACOS), "LINUX or BSD or MACOS specific" + assert isinstance(isup, bool) + assert duplex in all_duplexes + assert duplex in all_duplexes + assert speed >= 0 + assert mtu >= 0 + assert isinstance(flags, str) + + @pytest.mark.skipif( + not (LINUX or BSD or MACOS), reason="LINUX or BSD or MACOS specific" ) def test_net_if_stats_enodev(self): # See: https://github.com/giampaolo/psutil/issues/1279 @@ -921,26 +913,26 @@ def test_net_if_stats_enodev(self): side_effect=OSError(errno.ENODEV, ""), ) as m: ret = psutil.net_if_stats() - self.assertEqual(ret, {}) + assert ret == {} assert m.called class TestSensorsAPIs(PsutilTestCase): - @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_TEMPERATURES, reason="not supported") def test_sensors_temperatures(self): temps = psutil.sensors_temperatures() for name, entries in temps.items(): - self.assertIsInstance(name, str) + assert isinstance(name, str) for entry in entries: - self.assertIsInstance(entry.label, str) + assert isinstance(entry.label, str) if entry.current is not None: - self.assertGreaterEqual(entry.current, 0) + assert entry.current >= 0 if entry.high is not None: - self.assertGreaterEqual(entry.high, 0) + assert entry.high >= 0 if entry.critical is not None: - self.assertGreaterEqual(entry.critical, 0) + assert entry.critical >= 0 - @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_TEMPERATURES, reason="not supported") def test_sensors_temperatures_fahreneit(self): d = {'coretemp': [('label', 50.0, 60.0, 70.0)]} with mock.patch( @@ -948,38 +940,31 @@ def test_sensors_temperatures_fahreneit(self): ) as m: temps = psutil.sensors_temperatures(fahrenheit=True)['coretemp'][0] assert m.called - self.assertEqual(temps.current, 122.0) - self.assertEqual(temps.high, 140.0) - self.assertEqual(temps.critical, 158.0) + assert temps.current == 122.0 + assert temps.high == 140.0 + assert temps.critical == 158.0 - @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") - @unittest.skipIf(not HAS_BATTERY, "no battery") + @pytest.mark.skipif(not HAS_SENSORS_BATTERY, reason="not supported") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") def test_sensors_battery(self): ret = psutil.sensors_battery() - self.assertGreaterEqual(ret.percent, 0) - self.assertLessEqual(ret.percent, 100) - if ret.secsleft not in ( + assert ret.percent >= 0 + assert ret.percent <= 100 + if ret.secsleft not in { psutil.POWER_TIME_UNKNOWN, psutil.POWER_TIME_UNLIMITED, - ): - self.assertGreaterEqual(ret.secsleft, 0) - else: - if ret.secsleft == psutil.POWER_TIME_UNLIMITED: - self.assertTrue(ret.power_plugged) - self.assertIsInstance(ret.power_plugged, bool) + }: + assert ret.secsleft >= 0 + elif ret.secsleft == psutil.POWER_TIME_UNLIMITED: + assert ret.power_plugged + assert isinstance(ret.power_plugged, bool) - @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") + @pytest.mark.skipif(not HAS_SENSORS_FANS, reason="not supported") def test_sensors_fans(self): fans = psutil.sensors_fans() for name, entries in fans.items(): - self.assertIsInstance(name, str) + assert isinstance(name, str) for entry in entries: - self.assertIsInstance(entry.label, str) - self.assertIsInstance(entry.current, (int, long)) - self.assertGreaterEqual(entry.current, 0) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert isinstance(entry.label, str) + assert isinstance(entry.current, int) + assert entry.current >= 0 diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index 17cc9eb08..85f20c14e 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- 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 @@ -8,13 +7,15 @@ """Tests for testing utils (psutil.tests namespace).""" import collections -import contextlib import errno import os import socket import stat import subprocess +import textwrap import unittest +import warnings +from unittest import mock import psutil import psutil.tests @@ -27,6 +28,7 @@ from psutil.tests import CI_TESTING from psutil.tests import COVERAGE from psutil.tests import HAS_NET_CONNECTIONS_UNIX +from psutil.tests import HERE from psutil.tests import PYTHON_EXE from psutil.tests import PYTHON_EXE_ENV from psutil.tests import PsutilTestCase @@ -36,17 +38,17 @@ from psutil.tests import call_until from psutil.tests import chdir from psutil.tests import create_sockets +from psutil.tests import fake_pytest from psutil.tests import filter_proc_net_connections from psutil.tests import get_free_port from psutil.tests import is_namedtuple -from psutil.tests import mock from psutil.tests import process_namespace +from psutil.tests import pytest from psutil.tests import reap_children from psutil.tests import retry from psutil.tests import retry_on_failure from psutil.tests import safe_mkdir from psutil.tests import safe_rmpath -from psutil.tests import serialrun from psutil.tests import system_namespace from psutil.tests import tcp_socketpair from psutil.tests import terminate @@ -69,12 +71,12 @@ def test_retry_success(self, sleep): def foo(): while queue: queue.pop() - 1 / 0 # noqa + 1 / 0 # noqa: B018 return 1 queue = list(range(3)) - self.assertEqual(foo(), 1) - self.assertEqual(sleep.call_count, 3) + assert foo() == 1 + assert sleep.call_count == 3 @mock.patch('time.sleep') def test_retry_failure(self, sleep): @@ -83,12 +85,13 @@ def test_retry_failure(self, sleep): def foo(): while queue: queue.pop() - 1 / 0 # noqa + 1 / 0 # noqa: B018 return 1 queue = list(range(6)) - self.assertRaises(ZeroDivisionError, foo) - self.assertEqual(sleep.call_count, 5) + with pytest.raises(ZeroDivisionError): + foo() + assert sleep.call_count == 5 @mock.patch('time.sleep') def test_exception_arg(self, sleep): @@ -96,8 +99,9 @@ def test_exception_arg(self, sleep): def foo(): raise TypeError - self.assertRaises(TypeError, foo) - self.assertEqual(sleep.call_count, 0) + with pytest.raises(TypeError): + foo() + assert sleep.call_count == 0 @mock.patch('time.sleep') def test_no_interval_arg(self, sleep): @@ -105,23 +109,26 @@ def test_no_interval_arg(self, sleep): @retry(retries=5, interval=None, logfun=None) def foo(): - 1 / 0 # noqa + 1 / 0 # noqa: B018 - self.assertRaises(ZeroDivisionError, foo) - self.assertEqual(sleep.call_count, 0) + with pytest.raises(ZeroDivisionError): + foo() + assert sleep.call_count == 0 @mock.patch('time.sleep') def test_retries_arg(self, sleep): @retry(retries=5, interval=1, logfun=None) def foo(): - 1 / 0 # noqa + 1 / 0 # noqa: B018 - self.assertRaises(ZeroDivisionError, foo) - self.assertEqual(sleep.call_count, 5) + with pytest.raises(ZeroDivisionError): + foo() + assert sleep.call_count == 5 @mock.patch('time.sleep') def test_retries_and_timeout_args(self, sleep): - self.assertRaises(ValueError, retry, retries=5, timeout=1) + with pytest.raises(ValueError): + retry(retries=5, timeout=1) class TestSyncTestUtils(PsutilTestCase): @@ -129,7 +136,8 @@ def test_wait_for_pid(self): wait_for_pid(os.getpid()) nopid = max(psutil.pids()) + 99999 with mock.patch('psutil.tests.retry.__iter__', return_value=iter([0])): - self.assertRaises(psutil.NoSuchProcess, wait_for_pid, nopid) + with pytest.raises(psutil.NoSuchProcess): + wait_for_pid(nopid) def test_wait_for_file(self): testfn = self.get_testfn() @@ -148,7 +156,8 @@ def test_wait_for_file_empty(self): def test_wait_for_file_no_file(self): testfn = self.get_testfn() with mock.patch('psutil.tests.retry.__iter__', return_value=iter([0])): - self.assertRaises(IOError, wait_for_file, testfn) + with pytest.raises(OSError): + wait_for_file(testfn) def test_wait_for_file_no_delete(self): testfn = self.get_testfn() @@ -158,18 +167,18 @@ def test_wait_for_file_no_delete(self): assert os.path.exists(testfn) def test_call_until(self): - ret = call_until(lambda: 1, "ret == 1") - self.assertEqual(ret, 1) + call_until(lambda: 1) + # TODO: test for timeout class TestFSTestUtils(PsutilTestCase): def test_open_text(self): with open_text(__file__) as f: - self.assertEqual(f.mode, 'r') + assert f.mode == 'r' def test_open_binary(self): with open_binary(__file__) as f: - self.assertEqual(f.mode, 'rb') + assert f.mode == 'rb' def test_safe_mkdir(self): testfn = self.get_testfn() @@ -194,7 +203,7 @@ def test_safe_rmpath(self): with mock.patch( 'psutil.tests.os.stat', side_effect=OSError(errno.EINVAL, "") ) as m: - with self.assertRaises(OSError): + with pytest.raises(OSError): safe_rmpath(testfn) assert m.called @@ -203,8 +212,8 @@ def test_chdir(self): base = os.getcwd() os.mkdir(testfn) with chdir(testfn): - self.assertEqual(os.getcwd(), os.path.join(base, testfn)) - self.assertEqual(os.getcwd(), base) + assert os.getcwd() == os.path.join(base, testfn) + assert os.getcwd() == base class TestProcessUtils(PsutilTestCase): @@ -219,17 +228,17 @@ def test_reap_children(self): def test_spawn_children_pair(self): child, grandchild = self.spawn_children_pair() - self.assertNotEqual(child.pid, grandchild.pid) + assert child.pid != grandchild.pid assert child.is_running() assert grandchild.is_running() children = psutil.Process().children() - self.assertEqual(children, [child]) + assert children == [child] children = psutil.Process().children(recursive=True) - self.assertEqual(len(children), 2) - self.assertIn(child, children) - self.assertIn(grandchild, children) - self.assertEqual(child.ppid(), os.getpid()) - self.assertEqual(grandchild.ppid(), child.pid) + assert len(children) == 2 + assert child in children + assert grandchild in children + assert child.ppid() == os.getpid() + assert grandchild.ppid() == child.pid terminate(child) assert not child.is_running() @@ -238,10 +247,10 @@ def test_spawn_children_pair(self): terminate(grandchild) assert not grandchild.is_running() - @unittest.skipIf(not POSIX, "POSIX only") + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_spawn_zombie(self): - parent, zombie = self.spawn_zombie() - self.assertEqual(zombie.status(), psutil.STATUS_ZOMBIE) + _parent, zombie = self.spawn_zombie() + assert zombie.status() == psutil.STATUS_ZOMBIE def test_terminate(self): # by subprocess.Popen @@ -255,7 +264,11 @@ def test_terminate(self): self.assertPidGone(p.pid) terminate(p) # by psutil.Popen - cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] + cmd = [ + PYTHON_EXE, + "-c", + "import time; [time.sleep(0.1) for x in range(100)];", + ] p = psutil.Popen( cmd, stdout=subprocess.PIPE, @@ -282,60 +295,56 @@ def test_terminate(self): class TestNetUtils(PsutilTestCase): def bind_socket(self): port = get_free_port() - with contextlib.closing(bind_socket(addr=('', port))) as s: - self.assertEqual(s.getsockname()[1], port) + with bind_socket(addr=('', port)) as s: + assert s.getsockname()[1] == port - @unittest.skipIf(not POSIX, "POSIX only") + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_bind_unix_socket(self): name = self.get_testfn() - sock = bind_unix_socket(name) - with contextlib.closing(sock): - self.assertEqual(sock.family, socket.AF_UNIX) - self.assertEqual(sock.type, socket.SOCK_STREAM) - self.assertEqual(sock.getsockname(), name) + with bind_unix_socket(name) as sock: + assert sock.family == socket.AF_UNIX + assert sock.type == socket.SOCK_STREAM + assert sock.getsockname() == name assert os.path.exists(name) assert stat.S_ISSOCK(os.stat(name).st_mode) # UDP name = self.get_testfn() - sock = bind_unix_socket(name, type=socket.SOCK_DGRAM) - with contextlib.closing(sock): - self.assertEqual(sock.type, socket.SOCK_DGRAM) + with bind_unix_socket(name, type=socket.SOCK_DGRAM) as sock: + assert sock.type == socket.SOCK_DGRAM - def tcp_tcp_socketpair(self): + def test_tcp_socketpair(self): addr = ("127.0.0.1", get_free_port()) server, client = tcp_socketpair(socket.AF_INET, addr=addr) - with contextlib.closing(server): - with contextlib.closing(client): - # Ensure they are connected and the positions are - # correct. - self.assertEqual(server.getsockname(), addr) - self.assertEqual(client.getpeername(), addr) - self.assertNotEqual(client.getsockname(), addr) - - @unittest.skipIf(not POSIX, "POSIX only") - @unittest.skipIf( - NETBSD or FREEBSD, "/var/run/log UNIX socket opened by default" + with server, client: + # Ensure they are connected and the positions are correct. + assert server.getsockname() == addr + assert client.getpeername() == addr + assert client.getsockname() != addr + + @pytest.mark.skipif(not POSIX, reason="POSIX only") + @pytest.mark.skipif( + NETBSD or FREEBSD, reason="/var/run/log UNIX socket opened by default" ) def test_unix_socketpair(self): p = psutil.Process() num_fds = p.num_fds() - self.assertEqual( - filter_proc_net_connections(p.net_connections(kind='unix')), [] + assert ( + filter_proc_net_connections(p.net_connections(kind='unix')) == [] ) name = self.get_testfn() server, client = unix_socketpair(name) try: assert os.path.exists(name) assert stat.S_ISSOCK(os.stat(name).st_mode) - self.assertEqual(p.num_fds() - num_fds, 2) - self.assertEqual( + assert p.num_fds() - num_fds == 2 + assert ( len( filter_proc_net_connections(p.net_connections(kind='unix')) - ), - 2, + ) + == 2 ) - self.assertEqual(server.getsockname(), name) - self.assertEqual(client.getpeername(), name) + assert server.getsockname() == name + assert client.getpeername() == name finally: client.close() server.close() @@ -348,16 +357,16 @@ def test_create_sockets(self): fams[s.family] += 1 # work around http://bugs.python.org/issue30204 types[s.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE)] += 1 - self.assertGreaterEqual(fams[socket.AF_INET], 2) + assert fams[socket.AF_INET] >= 2 if supports_ipv6(): - self.assertGreaterEqual(fams[socket.AF_INET6], 2) + assert fams[socket.AF_INET6] >= 2 if POSIX and HAS_NET_CONNECTIONS_UNIX: - self.assertGreaterEqual(fams[socket.AF_UNIX], 2) - self.assertGreaterEqual(types[socket.SOCK_STREAM], 2) - self.assertGreaterEqual(types[socket.SOCK_DGRAM], 2) + assert fams[socket.AF_UNIX] >= 2 + assert types[socket.SOCK_STREAM] >= 2 + assert types[socket.SOCK_DGRAM] >= 2 -@serialrun +@pytest.mark.xdist_group(name="serial") class TestMemLeakClass(TestMemoryLeak): @retry_on_failure() def test_times(self): @@ -366,43 +375,46 @@ def fun(): cnt = {'cnt': 0} self.execute(fun, times=10, warmup_times=15) - self.assertEqual(cnt['cnt'], 26) + assert cnt['cnt'] == 26 def test_param_err(self): - self.assertRaises(ValueError, self.execute, lambda: 0, times=0) - self.assertRaises(ValueError, self.execute, lambda: 0, times=-1) - self.assertRaises(ValueError, self.execute, lambda: 0, warmup_times=-1) - self.assertRaises(ValueError, self.execute, lambda: 0, tolerance=-1) - self.assertRaises(ValueError, self.execute, lambda: 0, retries=-1) + with pytest.raises(ValueError): + self.execute(lambda: 0, times=0) + with pytest.raises(ValueError): + self.execute(lambda: 0, times=-1) + with pytest.raises(ValueError): + self.execute(lambda: 0, warmup_times=-1) + with pytest.raises(ValueError): + self.execute(lambda: 0, tolerance=-1) + with pytest.raises(ValueError): + self.execute(lambda: 0, retries=-1) @retry_on_failure() - @unittest.skipIf(CI_TESTING, "skipped on CI") - @unittest.skipIf(COVERAGE, "skipped during test coverage") + @pytest.mark.skipif(CI_TESTING, reason="skipped on CI") + @pytest.mark.skipif(COVERAGE, reason="skipped during test coverage") def test_leak_mem(self): ls = [] def fun(ls=ls): - ls.append("x" * 124 * 1024) + ls.append("x" * 248 * 1024) try: - # will consume around 30M in total - self.assertRaisesRegex( - AssertionError, "extra-mem", self.execute, fun, times=50 - ) + # will consume around 60M in total + with pytest.raises(AssertionError, match="extra-mem"): + self.execute(fun, times=100) finally: del ls def test_unclosed_files(self): def fun(): - f = open(__file__) + f = open(__file__) # noqa: SIM115 self.addCleanup(f.close) box.append(f) box = [] kind = "fd" if POSIX else "handle" - self.assertRaisesRegex( - AssertionError, "unclosed " + kind, self.execute, fun - ) + with pytest.raises(AssertionError, match="unclosed " + kind): + self.execute(fun) def test_tolerance(self): def fun(): @@ -413,44 +425,155 @@ def fun(): self.execute( fun, times=times, warmup_times=0, tolerance=200 * 1024 * 1024 ) - self.assertEqual(len(ls), times + 1) + assert len(ls) == times + 1 def test_execute_w_exc(self): def fun_1(): - 1 / 0 # noqa + 1 / 0 # noqa: B018 self.execute_w_exc(ZeroDivisionError, fun_1) - with self.assertRaises(ZeroDivisionError): + with pytest.raises(ZeroDivisionError): self.execute_w_exc(OSError, fun_1) def fun_2(): pass - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.execute_w_exc(ZeroDivisionError, fun_2) +class TestFakePytest(PsutilTestCase): + def run_test_class(self, klass): + suite = unittest.TestSuite() + suite.addTest(klass) + runner = unittest.TextTestRunner() + result = runner.run(suite) + return result + + def test_raises(self): + with fake_pytest.raises(ZeroDivisionError) as cm: + 1 / 0 # noqa: B018 + assert isinstance(cm.value, ZeroDivisionError) + + with fake_pytest.raises(ValueError, match="foo") as cm: + raise ValueError("foo") + + try: + with fake_pytest.raises(ValueError, match="foo") as cm: + raise ValueError("bar") + except AssertionError as err: + assert str(err) == '"foo" does not match "bar"' + else: + raise self.fail("exception not raised") + + def test_mark(self): + @fake_pytest.mark.xdist_group(name="serial") + def foo(): + return 1 + + assert foo() == 1 + + @fake_pytest.mark.xdist_group(name="serial") + class Foo: + def bar(self): + return 1 + + assert Foo().bar() == 1 + + def test_skipif(self): + class TestCase(unittest.TestCase): + @fake_pytest.mark.skipif(True, reason="reason") + def foo(self): + assert 1 == 1 # noqa: PLR0133 + + result = self.run_test_class(TestCase("foo")) + assert result.wasSuccessful() + assert len(result.skipped) == 1 + assert result.skipped[0][1] == "reason" + + class TestCase(unittest.TestCase): + @fake_pytest.mark.skipif(False, reason="reason") + def foo(self): + assert 1 == 1 # noqa: PLR0133 + + result = self.run_test_class(TestCase("foo")) + assert result.wasSuccessful() + assert len(result.skipped) == 0 + + def test_skip(self): + class TestCase(unittest.TestCase): + def foo(self): + fake_pytest.skip("reason") + assert 1 == 0 # noqa: PLR0133 + + result = self.run_test_class(TestCase("foo")) + assert result.wasSuccessful() + assert len(result.skipped) == 1 + assert result.skipped[0][1] == "reason" + + def test_main(self): + tmpdir = self.get_testfn(dir=HERE) + os.mkdir(tmpdir) + with open(os.path.join(tmpdir, "__init__.py"), "w"): + pass + with open(os.path.join(tmpdir, "test_file.py"), "w") as f: + f.write(textwrap.dedent("""\ + import unittest + + class TestCase(unittest.TestCase): + def test_passed(self): + pass + """).lstrip()) + with mock.patch.object(psutil.tests, "HERE", tmpdir): + with self.assertWarnsRegex( + UserWarning, "Fake pytest module was used" + ): + suite = fake_pytest.main() + assert suite.countTestCases() == 1 + + def test_warns(self): + # success + with fake_pytest.warns(UserWarning): + warnings.warn("foo", UserWarning, stacklevel=1) + + # failure + try: + with fake_pytest.warns(UserWarning): + warnings.warn("foo", DeprecationWarning, stacklevel=1) + except AssertionError: + pass + else: + raise self.fail("exception not raised") + + # match success + with fake_pytest.warns(UserWarning, match="foo"): + warnings.warn("foo", UserWarning, stacklevel=1) + + # match failure + try: + with fake_pytest.warns(UserWarning, match="foo"): + warnings.warn("bar", UserWarning, stacklevel=1) + except AssertionError: + pass + else: + raise self.fail("exception not raised") + + class TestTestingUtils(PsutilTestCase): def test_process_namespace(self): p = psutil.Process() ns = process_namespace(p) ns.test() - fun = [x for x in ns.iter(ns.getters) if x[1] == 'ppid'][0][0] - self.assertEqual(fun(), p.ppid()) + fun = next(x for x in ns.iter(ns.getters) if x[1] == 'ppid')[0] + assert fun() == p.ppid() def test_system_namespace(self): ns = system_namespace() - fun = [x for x in ns.iter(ns.getters) if x[1] == 'net_if_addrs'][0][0] - self.assertEqual(fun(), psutil.net_if_addrs()) + fun = next(x for x in ns.iter(ns.getters) if x[1] == 'net_if_addrs')[0] + assert fun() == psutil.net_if_addrs() class TestOtherUtils(PsutilTestCase): def test_is_namedtuple(self): assert is_namedtuple(collections.namedtuple('foo', 'a b c')(1, 2, 3)) assert not is_namedtuple(tuple()) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py index 7fb2ef199..d8a8c4bfc 100755 --- a/psutil/tests/test_unicode.py +++ b/psutil/tests/test_unicode.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- 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 @@ -20,13 +19,8 @@ * instead, in case of badly encoded data returned by the OS, the following error handlers are used to replace the corrupted characters in the string: - * Python 3: sys.getfilesystemencodeerrors() (PY 3.6+) or - "surrogatescape" on POSIX and "replace" on Windows - * Python 2: "replace" -* on Python 2 all APIs return bytes (str type), never unicode -* on Python 2, you can go back to unicode by doing: - - >>> unicode(p.exe(), sys.getdefaultencoding(), errors="replace") + * sys.getfilesystemencodeerrors() or "surrogatescape" on POSIX and + "replace" on Windows. For a detailed explanation of how psutil handles unicode see #1040. @@ -74,8 +68,6 @@ import os import shutil -import traceback -import unittest import warnings from contextlib import closing @@ -83,9 +75,6 @@ from psutil import BSD from psutil import POSIX from psutil import WINDOWS -from psutil._compat import PY3 -from psutil._compat import super -from psutil.tests import APPVEYOR from psutil.tests import ASCII_FS from psutil.tests import CI_TESTING from psutil.tests import HAS_ENVIRON @@ -101,35 +90,14 @@ from psutil.tests import copyload_shared_lib from psutil.tests import create_py_exe from psutil.tests import get_testfn +from psutil.tests import pytest from psutil.tests import safe_mkdir from psutil.tests import safe_rmpath -from psutil.tests import serialrun from psutil.tests import skip_on_access_denied from psutil.tests import spawn_testproc from psutil.tests import terminate -if APPVEYOR: - - def safe_rmpath(path): # NOQA - # TODO - this is quite random and I'm not sure why it happens, - # nor I can reproduce it locally: - # https://ci.appveyor.com/project/giampaolo/psutil/build/job/ - # jiq2cgd6stsbtn60 - # safe_rmpath() happens after reap_children() so this is weird - # Perhaps wait_procs() on Windows is broken? Maybe because - # of STILL_ACTIVE? - # https://github.com/giampaolo/psutil/blob/ - # 68c7a70728a31d8b8b58f4be6c4c0baa2f449eda/psutil/arch/ - # windows/process_info.c#L146 - from psutil.tests import safe_rmpath as rm - - try: - return rm(path) - except WindowsError: - traceback.print_exc() - - def try_unicode(suffix): """Return True if both the fs and the subprocess module can deal with a unicode file name. @@ -142,7 +110,7 @@ def try_unicode(suffix): sproc = spawn_testproc(cmd=[testfn]) shutil.copyfile(testfn, testfn + '-2') safe_rmpath(testfn + '-2') - except (UnicodeEncodeError, IOError): + except (UnicodeEncodeError, OSError): return False else: return True @@ -175,55 +143,61 @@ def setUpClass(cls): def setUp(self): super().setUp() if self.skip_tests: - raise unittest.SkipTest("can't handle unicode str") + raise pytest.skip("can't handle unicode str") -@serialrun -@unittest.skipIf(ASCII_FS, "ASCII fs") -@unittest.skipIf(PYPY and not PY3, "too much trouble on PYPY2") +@pytest.mark.xdist_group(name="serial") +@pytest.mark.skipif(ASCII_FS, reason="ASCII fs") class TestFSAPIs(BaseUnicodeTest): """Test FS APIs with a funky, valid, UTF8 path name.""" funky_suffix = UNICODE_SUFFIX 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'.' with warnings.catch_warnings(): warnings.simplefilter("ignore") - return self.funky_name in os.listdir(here) + return self.funky_name in os.listdir(".") # --- def test_proc_exe(self): - cmd = [self.funky_name, "-c", "import time; time.sleep(10)"] + cmd = [ + self.funky_name, + "-c", + "import time; [time.sleep(0.1) for x in range(100)]", + ] subp = self.spawn_testproc(cmd) p = psutil.Process(subp.pid) exe = p.exe() - self.assertIsInstance(exe, str) + assert isinstance(exe, str) if self.expect_exact_path_match(): - self.assertEqual( - os.path.normcase(exe), os.path.normcase(self.funky_name) - ) + assert os.path.normcase(exe) == os.path.normcase(self.funky_name) def test_proc_name(self): - cmd = [self.funky_name, "-c", "import time; time.sleep(10)"] + cmd = [ + self.funky_name, + "-c", + "import time; [time.sleep(0.1) for x in range(100)]", + ] subp = self.spawn_testproc(cmd) name = psutil.Process(subp.pid).name() - self.assertIsInstance(name, str) + assert isinstance(name, str) if self.expect_exact_path_match(): - self.assertEqual(name, os.path.basename(self.funky_name)) + assert name == os.path.basename(self.funky_name) def test_proc_cmdline(self): - cmd = [self.funky_name, "-c", "import time; time.sleep(10)"] + cmd = [ + self.funky_name, + "-c", + "import time; [time.sleep(0.1) for x in range(100)]", + ] subp = self.spawn_testproc(cmd) p = psutil.Process(subp.pid) cmdline = p.cmdline() for part in cmdline: - self.assertIsInstance(part, str) + assert isinstance(part, str) if self.expect_exact_path_match(): - self.assertEqual(cmdline, cmd) + assert cmdline == cmd def test_proc_cwd(self): dname = self.funky_name + "2" @@ -232,43 +206,37 @@ def test_proc_cwd(self): with chdir(dname): p = psutil.Process() cwd = p.cwd() - self.assertIsInstance(p.cwd(), str) + assert isinstance(p.cwd(), str) if self.expect_exact_path_match(): - self.assertEqual(cwd, dname) + assert cwd == dname - @unittest.skipIf(PYPY and WINDOWS, "fails on PYPY + WINDOWS") + @pytest.mark.skipif(PYPY and WINDOWS, reason="fails on PYPY + WINDOWS") def test_proc_open_files(self): p = psutil.Process() start = set(p.open_files()) with open(self.funky_name, 'rb'): new = set(p.open_files()) path = (new - start).pop().path - self.assertIsInstance(path, str) + assert isinstance(path, str) if BSD and not path: # XXX - see https://github.com/giampaolo/psutil/issues/595 - raise unittest.SkipTest("open_files on BSD is broken") + raise pytest.skip("open_files on BSD is broken") if self.expect_exact_path_match(): - self.assertEqual( - os.path.normcase(path), os.path.normcase(self.funky_name) - ) + assert os.path.normcase(path) == os.path.normcase(self.funky_name) - @unittest.skipIf(not POSIX, "POSIX only") + @pytest.mark.skipif(not POSIX, reason="POSIX only") def test_proc_net_connections(self): name = self.get_testfn(suffix=self.funky_suffix) - try: - sock = bind_unix_socket(name) - except UnicodeEncodeError: - if PY3: - raise - else: - raise unittest.SkipTest("not supported") + sock = bind_unix_socket(name) with closing(sock): conn = psutil.Process().net_connections('unix')[0] - self.assertIsInstance(conn.laddr, str) - self.assertEqual(conn.laddr, name) + assert isinstance(conn.laddr, str) + assert conn.laddr == name - @unittest.skipIf(not POSIX, "POSIX only") - @unittest.skipIf(not HAS_NET_CONNECTIONS_UNIX, "can't list UNIX sockets") + @pytest.mark.skipif(not POSIX, reason="POSIX only") + @pytest.mark.skipif( + not HAS_NET_CONNECTIONS_UNIX, reason="can't list UNIX sockets" + ) @skip_on_access_denied() def test_net_connections(self): def find_sock(cons): @@ -278,18 +246,12 @@ def find_sock(cons): raise ValueError("connection not found") name = self.get_testfn(suffix=self.funky_suffix) - try: - sock = bind_unix_socket(name) - except UnicodeEncodeError: - if PY3: - raise - else: - raise unittest.SkipTest("not supported") + sock = bind_unix_socket(name) with closing(sock): cons = psutil.net_connections(kind='unix') conn = find_sock(cons) - self.assertIsInstance(conn.laddr, str) - self.assertEqual(conn.laddr, name) + assert isinstance(conn.laddr, str) + assert conn.laddr == name def test_disk_usage(self): dname = self.funky_name + "2" @@ -297,12 +259,9 @@ def test_disk_usage(self): safe_mkdir(dname) psutil.disk_usage(dname) - @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") - @unittest.skipIf(not PY3, "ctypes does not support unicode on PY2") - @unittest.skipIf(PYPY, "unstable on PYPY") + @pytest.mark.skipif(not HAS_MEMORY_MAPS, reason="not supported") + @pytest.mark.skipif(PYPY, reason="unstable on PYPY") def test_memory_maps(self): - # XXX: on Python 2, using ctypes.CDLL with a unicode path - # opens a message box which blocks the test run. with copyload_shared_lib(suffix=self.funky_suffix) as funky_path: def normpath(p): @@ -313,19 +272,18 @@ def normpath(p): ] # ...just to have a clearer msg in case of failure libpaths = [x for x in libpaths if TESTFN_PREFIX in x] - self.assertIn(normpath(funky_path), libpaths) + assert normpath(funky_path) in libpaths for path in libpaths: - self.assertIsInstance(path, str) + assert isinstance(path, str) -@unittest.skipIf(CI_TESTING, "unreliable on CI") +@pytest.mark.skipif(CI_TESTING, reason="unreliable on CI") class TestFSAPIsWithInvalidPath(TestFSAPIs): """Test FS APIs with a funky, invalid path name.""" funky_suffix = INVALID_UNICODE_SUFFIX def expect_exact_path_match(self): - # Invalid unicode names are supposed to work on Python 2. return True @@ -337,28 +295,19 @@ def expect_exact_path_match(self): class TestNonFSAPIS(BaseUnicodeTest): """Unicode tests for non fs-related APIs.""" - funky_suffix = UNICODE_SUFFIX if PY3 else 'è' + funky_suffix = UNICODE_SUFFIX - @unittest.skipIf(not HAS_ENVIRON, "not supported") - @unittest.skipIf(PYPY and WINDOWS, "segfaults on PYPY + WINDOWS") + @pytest.mark.skipif(not HAS_ENVIRON, reason="not supported") + @pytest.mark.skipif(PYPY and WINDOWS, reason="segfaults on PYPY + WINDOWS") def test_proc_environ(self): # Note: differently from others, this test does not deal - # with fs paths. On Python 2 subprocess module is broken as - # it's not able to handle with non-ASCII env vars, so - # we use "è", which is part of the extended ASCII table - # (unicode point <= 255). + # with fs paths. env = os.environ.copy() env['FUNNY_ARG'] = self.funky_suffix sproc = self.spawn_testproc(env=env) p = psutil.Process(sproc.pid) env = p.environ() for k, v in env.items(): - self.assertIsInstance(k, str) - self.assertIsInstance(v, str) - self.assertEqual(env['FUNNY_ARG'], self.funky_suffix) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert isinstance(k, str) + assert isinstance(v, str) + assert env['FUNNY_ARG'] == self.funky_suffix diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 5983af70a..9f54cf534 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- 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 @@ -8,33 +7,28 @@ """Windows specific tests.""" import datetime -import errno import glob import os import platform import re +import shutil import signal import subprocess import sys import time -import unittest import warnings +from unittest import mock import psutil from psutil import WINDOWS -from psutil._compat import FileNotFoundError -from psutil._compat import super -from psutil._compat import which -from psutil.tests import APPVEYOR from psutil.tests import GITHUB_ACTIONS from psutil.tests import HAS_BATTERY from psutil.tests import IS_64BIT -from psutil.tests import PY3 from psutil.tests import PYPY from psutil.tests import TOLERANCE_DISK_USAGE from psutil.tests import TOLERANCE_SYS_MEM from psutil.tests import PsutilTestCase -from psutil.tests import mock +from psutil.tests import pytest from psutil.tests import retry_on_failure from psutil.tests import sh from psutil.tests import spawn_testproc @@ -47,7 +41,7 @@ import win32api # requires "pip install pywin32" import win32con import win32process - import wmi # requires "pip install wmi" / "make setup-dev-env" + import wmi # requires "pip install wmi" / "make install-pydeps-test" if WINDOWS: from psutil._pswindows import convert_oserror @@ -56,10 +50,8 @@ cext = psutil._psplatform.cext -@unittest.skipIf(not WINDOWS, "WINDOWS only") -@unittest.skipIf(PYPY, "pywin32 not available on PYPY") -# https://github.com/giampaolo/psutil/pull/1762#issuecomment-632892692 -@unittest.skipIf(GITHUB_ACTIONS and not PY3, "pywin32 broken on GITHUB + PY2") +@pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") +@pytest.mark.skipif(PYPY, reason="pywin32 not available on PYPY") class WindowsTestCase(PsutilTestCase): pass @@ -70,11 +62,11 @@ def powershell(cmd): >>> powershell( "Get-CIMInstance Win32_PageFileUsage | Select AllocatedBaseSize") """ - if not which("powershell.exe"): - raise unittest.SkipTest("powershell.exe not available") + if not shutil.which("powershell.exe"): + raise pytest.skip("powershell.exe not available") cmdline = ( - 'powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive ' - + '-NoProfile -WindowStyle Hidden -Command "%s"' % cmd + "powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive " + f"-NoProfile -WindowStyle Hidden -Command \"{cmd}\"" # noqa: Q003 ) return sh(cmdline) @@ -85,7 +77,7 @@ def wmic(path, what, converter=int): >>> wmic("Win32_OperatingSystem", "FreePhysicalMemory") 2134124534 """ - out = sh("wmic path %s get %s" % (path, what)).strip() + out = sh(f"wmic path {path} get {what}").strip() data = "".join(out.splitlines()[1:]).strip() # get rid of the header if converter is not None: if "," in what: @@ -102,45 +94,43 @@ def wmic(path, what, converter=int): class TestCpuAPIs(WindowsTestCase): - @unittest.skipIf( + @pytest.mark.skipif( 'NUMBER_OF_PROCESSORS' not in os.environ, - 'NUMBER_OF_PROCESSORS env var is not available', + reason="NUMBER_OF_PROCESSORS env var is not available", ) def test_cpu_count_vs_NUMBER_OF_PROCESSORS(self): # Will likely fail on many-cores systems: # https://stackoverflow.com/questions/31209256 num_cpus = int(os.environ['NUMBER_OF_PROCESSORS']) - self.assertEqual(num_cpus, psutil.cpu_count()) + assert num_cpus == psutil.cpu_count() def test_cpu_count_vs_GetSystemInfo(self): # Will likely fail on many-cores systems: # https://stackoverflow.com/questions/31209256 sys_value = win32api.GetSystemInfo()[5] psutil_value = psutil.cpu_count() - self.assertEqual(sys_value, psutil_value) + assert sys_value == psutil_value def test_cpu_count_logical_vs_wmi(self): w = wmi.WMI() procs = sum( proc.NumberOfLogicalProcessors for proc in w.Win32_Processor() ) - self.assertEqual(psutil.cpu_count(), procs) + assert psutil.cpu_count() == procs def test_cpu_count_cores_vs_wmi(self): w = wmi.WMI() cores = sum(proc.NumberOfCores for proc in w.Win32_Processor()) - self.assertEqual(psutil.cpu_count(logical=False), cores) + assert psutil.cpu_count(logical=False) == cores def test_cpu_count_vs_cpu_times(self): - self.assertEqual( - psutil.cpu_count(), len(psutil.cpu_times(percpu=True)) - ) + assert psutil.cpu_count() == len(psutil.cpu_times(percpu=True)) def test_cpu_freq(self): w = wmi.WMI() proc = w.Win32_Processor()[0] - self.assertEqual(proc.CurrentClockSpeed, psutil.cpu_freq().current) - self.assertEqual(proc.MaxClockSpeed, psutil.cpu_freq().max) + assert proc.CurrentClockSpeed == psutil.cpu_freq().current + assert proc.MaxClockSpeed == psutil.cpu_freq().max class TestSystemAPIs(WindowsTestCase): @@ -152,32 +142,29 @@ def test_nic_names(self): continue if nic not in out: raise self.fail( - "%r nic wasn't found in 'ipconfig /all' output" % nic + f"{nic!r} nic wasn't found in 'ipconfig /all' output" ) def test_total_phymem(self): w = wmi.WMI().Win32_ComputerSystem()[0] - self.assertEqual( - int(w.TotalPhysicalMemory), psutil.virtual_memory().total - ) + assert int(w.TotalPhysicalMemory) == psutil.virtual_memory().total def test_free_phymem(self): w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0] - self.assertAlmostEqual( - int(w.AvailableBytes), - psutil.virtual_memory().free, - delta=TOLERANCE_SYS_MEM, + assert ( + abs(int(w.AvailableBytes) - psutil.virtual_memory().free) + < TOLERANCE_SYS_MEM ) def test_total_swapmem(self): w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0] - self.assertEqual( - int(w.CommitLimit) - psutil.virtual_memory().total, - psutil.swap_memory().total, + assert ( + int(w.CommitLimit) - psutil.virtual_memory().total + == psutil.swap_memory().total ) if psutil.swap_memory().total == 0: - self.assertEqual(0, psutil.swap_memory().free) - self.assertEqual(0, psutil.swap_memory().used) + assert psutil.swap_memory().free == 0 + assert psutil.swap_memory().used == 0 def test_percent_swapmem(self): if psutil.swap_memory().total > 0: @@ -186,13 +173,11 @@ def test_percent_swapmem(self): percentSwap = int(w.PercentUsage) * 100 / int(w.PercentUsage_Base) # exact percent may change but should be reasonable # assert within +/- 5% and between 0 and 100% - self.assertGreaterEqual(psutil.swap_memory().percent, 0) - self.assertAlmostEqual( - psutil.swap_memory().percent, percentSwap, delta=5 - ) - self.assertLessEqual(psutil.swap_memory().percent, 100) + assert psutil.swap_memory().percent >= 0 + assert abs(psutil.swap_memory().percent - percentSwap) < 5 + assert psutil.swap_memory().percent <= 100 - # @unittest.skipIf(wmi is None, "wmi module is not installed") + # @pytest.mark.skipif(wmi is None, reason="wmi module is not installed") # def test__UPTIME(self): # # _UPTIME constant is not public but it is used internally # # as value to return for pid 0 creation time. @@ -204,15 +189,14 @@ def test_percent_swapmem(self): # time.localtime(p.create_time())) # Note: this test is not very reliable - @unittest.skipIf(APPVEYOR, "test not relieable on appveyor") @retry_on_failure() def test_pids(self): # Note: this test might fail if the OS is starting/killing # other processes in the meantime w = wmi.WMI().Win32_Process() - wmi_pids = set([x.ProcessId for x in w]) + wmi_pids = {x.ProcessId for x in w} psutil_pids = set(psutil.pids()) - self.assertEqual(wmi_pids, psutil_pids) + assert wmi_pids == psutil_pids @retry_on_failure() def test_disks(self): @@ -233,17 +217,15 @@ def test_disks(self): except FileNotFoundError: # usually this is the floppy break - self.assertEqual(usage.total, int(wmi_part.Size)) + assert usage.total == int(wmi_part.Size) wmi_free = int(wmi_part.FreeSpace) - self.assertEqual(usage.free, wmi_free) + assert usage.free == wmi_free # 10 MB tolerance if abs(usage.free - wmi_free) > 10 * 1024 * 1024: - raise self.fail( - "psutil=%s, wmi=%s" % (usage.free, wmi_free) - ) + raise self.fail(f"psutil={usage.free}, wmi={wmi_free}") break else: - raise self.fail("can't find partition %s" % repr(ps_part)) + raise self.fail(f"can't find partition {ps_part!r}") @retry_on_failure() def test_disk_usage(self): @@ -252,15 +234,11 @@ def test_disk_usage(self): continue sys_value = win32api.GetDiskFreeSpaceEx(disk.mountpoint) psutil_value = psutil.disk_usage(disk.mountpoint) - self.assertAlmostEqual( - sys_value[0], psutil_value.free, delta=TOLERANCE_DISK_USAGE - ) - self.assertAlmostEqual( - sys_value[1], psutil_value.total, delta=TOLERANCE_DISK_USAGE - ) - self.assertEqual( - psutil_value.used, psutil_value.total - psutil_value.free + assert abs(sys_value[0] - psutil_value.free) < TOLERANCE_DISK_USAGE + assert ( + abs(sys_value[1] - psutil_value.total) < TOLERANCE_DISK_USAGE ) + assert psutil_value.used == psutil_value.total - psutil_value.free def test_disk_partitions(self): sys_value = [ @@ -273,7 +251,7 @@ def test_disk_partitions(self): for x in psutil.disk_partitions(all=True) if not x.mountpoint.startswith('A:') ] - self.assertEqual(sys_value, psutil_value) + assert sys_value == psutil_value def test_net_if_stats(self): ps_names = set(cext.net_if_stats()) @@ -282,10 +260,9 @@ def test_net_if_stats(self): for wmi_adapter in wmi_adapters: wmi_names.add(wmi_adapter.Name) wmi_names.add(wmi_adapter.NetConnectionID) - self.assertTrue( - ps_names & wmi_names, - "no common entries in %s, %s" % (ps_names, wmi_names), - ) + assert ( + ps_names & wmi_names + ), f"no common entries in {ps_names}, {wmi_names}" def test_boot_time(self): wmi_os = wmi.WMI().Win32_OperatingSystem() @@ -295,18 +272,18 @@ def test_boot_time(self): ) psutil_dt = datetime.datetime.fromtimestamp(psutil.boot_time()) diff = abs((wmi_btime_dt - psutil_dt).total_seconds()) - self.assertLessEqual(diff, 5) + assert diff <= 5 def test_boot_time_fluctuation(self): # https://github.com/giampaolo/psutil/issues/1007 with mock.patch('psutil._pswindows.cext.boot_time', return_value=5): - self.assertEqual(psutil.boot_time(), 5) + assert psutil.boot_time() == 5 with mock.patch('psutil._pswindows.cext.boot_time', return_value=4): - self.assertEqual(psutil.boot_time(), 5) + assert psutil.boot_time() == 5 with mock.patch('psutil._pswindows.cext.boot_time', return_value=6): - self.assertEqual(psutil.boot_time(), 5) + assert psutil.boot_time() == 5 with mock.patch('psutil._pswindows.cext.boot_time', return_value=333): - self.assertEqual(psutil.boot_time(), 333) + assert psutil.boot_time() == 333 # =================================================================== @@ -317,46 +294,44 @@ def test_boot_time_fluctuation(self): class TestSensorsBattery(WindowsTestCase): def test_has_battery(self): if win32api.GetPwrCapabilities()['SystemBatteriesPresent']: - self.assertIsNotNone(psutil.sensors_battery()) + assert psutil.sensors_battery() is not None else: - self.assertIsNone(psutil.sensors_battery()) + assert psutil.sensors_battery() is None - @unittest.skipIf(not HAS_BATTERY, "no battery") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") def test_percent(self): w = wmi.WMI() battery_wmi = w.query('select * from Win32_Battery')[0] battery_psutil = psutil.sensors_battery() - self.assertAlmostEqual( - battery_psutil.percent, - battery_wmi.EstimatedChargeRemaining, - delta=1, + assert ( + abs(battery_psutil.percent - battery_wmi.EstimatedChargeRemaining) + < 1 ) - @unittest.skipIf(not HAS_BATTERY, "no battery") + @pytest.mark.skipif(not HAS_BATTERY, reason="no battery") def test_power_plugged(self): w = wmi.WMI() battery_wmi = w.query('select * from Win32_Battery')[0] battery_psutil = psutil.sensors_battery() # Status codes: # https://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx - self.assertEqual( - battery_psutil.power_plugged, battery_wmi.BatteryStatus == 2 - ) + assert battery_psutil.power_plugged == (battery_wmi.BatteryStatus == 2) def test_emulate_no_battery(self): with mock.patch( "psutil._pswindows.cext.sensors_battery", return_value=(0, 128, 0, 0), ) as m: - self.assertIsNone(psutil.sensors_battery()) + assert psutil.sensors_battery() is None assert m.called def test_emulate_power_connected(self): with mock.patch( "psutil._pswindows.cext.sensors_battery", return_value=(1, 0, 0, 0) ) as m: - self.assertEqual( - psutil.sensors_battery().secsleft, psutil.POWER_TIME_UNLIMITED + assert ( + psutil.sensors_battery().secsleft + == psutil.POWER_TIME_UNLIMITED ) assert m.called @@ -364,8 +339,9 @@ def test_emulate_power_charging(self): with mock.patch( "psutil._pswindows.cext.sensors_battery", return_value=(0, 8, 0, 0) ) as m: - self.assertEqual( - psutil.sensors_battery().secsleft, psutil.POWER_TIME_UNLIMITED + assert ( + psutil.sensors_battery().secsleft + == psutil.POWER_TIME_UNLIMITED ) assert m.called @@ -374,8 +350,8 @@ def test_emulate_secs_left_unknown(self): "psutil._pswindows.cext.sensors_battery", return_value=(0, 0, 0, -1), ) as m: - self.assertEqual( - psutil.sensors_battery().secsleft, psutil.POWER_TIME_UNKNOWN + assert ( + psutil.sensors_battery().secsleft == psutil.POWER_TIME_UNKNOWN ) assert m.called @@ -396,28 +372,30 @@ def tearDownClass(cls): def test_issue_24(self): p = psutil.Process(0) - self.assertRaises(psutil.AccessDenied, p.kill) + with pytest.raises(psutil.AccessDenied): + p.kill() def test_special_pid(self): p = psutil.Process(4) - self.assertEqual(p.name(), 'System') + assert p.name() == 'System' # use __str__ to access all common Process properties to check # that nothing strange happens str(p) p.username() - self.assertGreaterEqual(p.create_time(), 0.0) + assert p.create_time() >= 0.0 try: - rss, vms = p.memory_info()[:2] + rss, _vms = p.memory_info()[:2] except psutil.AccessDenied: # expected on Windows Vista and Windows 7 - if platform.uname()[1] not in ('vista', 'win-7', 'win7'): + if platform.uname()[1] not in {'vista', 'win-7', 'win7'}: raise else: - self.assertGreater(rss, 0) + assert rss > 0 def test_send_signal(self): p = psutil.Process(self.pid) - self.assertRaises(ValueError, p.send_signal, signal.SIGINT) + with pytest.raises(ValueError): + p.send_signal(signal.SIGINT) def test_num_handles_increment(self): p = psutil.Process(os.getpid()) @@ -426,9 +404,9 @@ def test_num_handles_increment(self): win32con.PROCESS_QUERY_INFORMATION, win32con.FALSE, os.getpid() ) after = p.num_handles() - self.assertEqual(after, before + 1) + assert after == before + 1 win32api.CloseHandle(handle) - self.assertEqual(p.num_handles(), before) + assert p.num_handles() == before def test_ctrl_signals(self): p = psutil.Process(self.spawn_testproc().pid) @@ -436,12 +414,10 @@ def test_ctrl_signals(self): p.send_signal(signal.CTRL_BREAK_EVENT) p.kill() p.wait() - self.assertRaises( - psutil.NoSuchProcess, p.send_signal, signal.CTRL_C_EVENT - ) - self.assertRaises( - psutil.NoSuchProcess, p.send_signal, signal.CTRL_BREAK_EVENT - ) + with pytest.raises(psutil.NoSuchProcess): + p.send_signal(signal.CTRL_C_EVENT) + with pytest.raises(psutil.NoSuchProcess): + p.send_signal(signal.CTRL_BREAK_EVENT) def test_username(self): name = win32api.GetUserNameEx(win32con.NameSamCompatible) @@ -449,11 +425,11 @@ def test_username(self): # When running as a service account (most likely to be # NetworkService), these user name calculations don't produce the # same result, causing the test to fail. - raise unittest.SkipTest('running as service account') - self.assertEqual(psutil.Process().username(), name) + raise pytest.skip('running as service account') + assert psutil.Process().username() == name def test_cmdline(self): - sys_value = re.sub('[ ]+', ' ', win32api.GetCommandLine()).strip() + sys_value = re.sub(r"[ ]+", " ", win32api.GetCommandLine()).strip() psutil_value = ' '.join(psutil.Process().cmdline()) if sys_value[0] == '"' != psutil_value[0]: # The PyWin32 command line may retain quotes around argv[0] if they @@ -461,7 +437,7 @@ def test_cmdline(self): # the first 2 quotes from sys_value if not in psutil_value. # A path to an executable will not contain quotes, so this is safe. sys_value = sys_value.replace('"', '', 2) - self.assertEqual(sys_value, psutil_value) + assert sys_value == psutil_value # XXX - occasional failures @@ -485,7 +461,7 @@ def test_nice(self): self.addCleanup(win32api.CloseHandle, handle) sys_value = win32process.GetPriorityClass(handle) psutil_value = psutil.Process().nice() - self.assertEqual(psutil_value, sys_value) + assert psutil_value == sys_value def test_memory_info(self): handle = win32api.OpenProcess( @@ -494,30 +470,25 @@ def test_memory_info(self): self.addCleanup(win32api.CloseHandle, handle) sys_value = win32process.GetProcessMemoryInfo(handle) psutil_value = psutil.Process(self.pid).memory_info() - self.assertEqual( - sys_value['PeakWorkingSetSize'], psutil_value.peak_wset - ) - self.assertEqual(sys_value['WorkingSetSize'], psutil_value.wset) - self.assertEqual( - sys_value['QuotaPeakPagedPoolUsage'], psutil_value.peak_paged_pool - ) - self.assertEqual( - sys_value['QuotaPagedPoolUsage'], psutil_value.paged_pool + assert sys_value['PeakWorkingSetSize'] == psutil_value.peak_wset + assert sys_value['WorkingSetSize'] == psutil_value.wset + assert ( + sys_value['QuotaPeakPagedPoolUsage'] + == psutil_value.peak_paged_pool ) - self.assertEqual( - sys_value['QuotaPeakNonPagedPoolUsage'], - psutil_value.peak_nonpaged_pool, + assert sys_value['QuotaPagedPoolUsage'] == psutil_value.paged_pool + assert ( + sys_value['QuotaPeakNonPagedPoolUsage'] + == psutil_value.peak_nonpaged_pool ) - self.assertEqual( - sys_value['QuotaNonPagedPoolUsage'], psutil_value.nonpaged_pool - ) - self.assertEqual(sys_value['PagefileUsage'], psutil_value.pagefile) - self.assertEqual( - sys_value['PeakPagefileUsage'], psutil_value.peak_pagefile + assert ( + sys_value['QuotaNonPagedPoolUsage'] == psutil_value.nonpaged_pool ) + assert sys_value['PagefileUsage'] == psutil_value.pagefile + assert sys_value['PeakPagefileUsage'] == psutil_value.peak_pagefile - self.assertEqual(psutil_value.rss, psutil_value.wset) - self.assertEqual(psutil_value.vms, psutil_value.pagefile) + assert psutil_value.rss == psutil_value.wset + assert psutil_value.vms == psutil_value.pagefile def test_wait(self): handle = win32api.OpenProcess( @@ -528,7 +499,7 @@ def test_wait(self): p.terminate() psutil_value = p.wait() sys_value = win32process.GetExitCodeProcess(handle) - self.assertEqual(psutil_value, sys_value) + assert psutil_value == sys_value def test_cpu_affinity(self): def from_bitmask(x): @@ -542,7 +513,7 @@ def from_bitmask(x): win32process.GetProcessAffinityMask(handle)[0] ) psutil_value = psutil.Process(self.pid).cpu_affinity() - self.assertEqual(psutil_value, sys_value) + assert psutil_value == sys_value def test_io_counters(self): handle = win32api.OpenProcess( @@ -551,24 +522,12 @@ def test_io_counters(self): self.addCleanup(win32api.CloseHandle, handle) sys_value = win32process.GetProcessIoCounters(handle) psutil_value = psutil.Process().io_counters() - self.assertEqual( - psutil_value.read_count, sys_value['ReadOperationCount'] - ) - self.assertEqual( - psutil_value.write_count, sys_value['WriteOperationCount'] - ) - self.assertEqual( - psutil_value.read_bytes, sys_value['ReadTransferCount'] - ) - self.assertEqual( - psutil_value.write_bytes, sys_value['WriteTransferCount'] - ) - self.assertEqual( - psutil_value.other_count, sys_value['OtherOperationCount'] - ) - self.assertEqual( - psutil_value.other_bytes, sys_value['OtherTransferCount'] - ) + assert psutil_value.read_count == sys_value['ReadOperationCount'] + assert psutil_value.write_count == sys_value['WriteOperationCount'] + assert psutil_value.read_bytes == sys_value['ReadTransferCount'] + assert psutil_value.write_bytes == sys_value['WriteTransferCount'] + assert psutil_value.other_count == sys_value['OtherOperationCount'] + assert psutil_value.other_bytes == sys_value['OtherTransferCount'] def test_num_handles(self): import ctypes @@ -586,24 +545,26 @@ def test_num_handles(self): ) sys_value = hndcnt.value psutil_value = psutil.Process(self.pid).num_handles() - self.assertEqual(psutil_value, sys_value) + assert psutil_value == sys_value def test_error_partial_copy(self): # https://github.com/giampaolo/psutil/issues/875 - exc = WindowsError() + exc = OSError() exc.winerror = 299 with mock.patch("psutil._psplatform.cext.proc_cwd", side_effect=exc): with mock.patch("time.sleep") as m: p = psutil.Process() - self.assertRaises(psutil.AccessDenied, p.cwd) - self.assertGreaterEqual(m.call_count, 5) + with pytest.raises(psutil.AccessDenied): + p.cwd() + assert m.call_count >= 5 def test_exe(self): # NtQuerySystemInformation succeeds if process is gone. Make sure # it raises NSP for a non existent pid. pid = psutil.pids()[-1] + 99999 proc = psutil._psplatform.Process(pid) - self.assertRaises(psutil.NoSuchProcess, proc.exe) + with pytest.raises(psutil.NoSuchProcess): + proc.exe() class TestProcessWMI(WindowsTestCase): @@ -620,35 +581,37 @@ def tearDownClass(cls): def test_name(self): w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] p = psutil.Process(self.pid) - self.assertEqual(p.name(), w.Caption) + assert p.name() == w.Caption # This fail on github because using virtualenv for test environment - @unittest.skipIf(GITHUB_ACTIONS, "unreliable path on GITHUB_ACTIONS") + @pytest.mark.skipif( + GITHUB_ACTIONS, reason="unreliable path on GITHUB_ACTIONS" + ) def test_exe(self): w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] p = psutil.Process(self.pid) # Note: wmi reports the exe as a lower case string. # Being Windows paths case-insensitive we ignore that. - self.assertEqual(p.exe().lower(), w.ExecutablePath.lower()) + assert p.exe().lower() == w.ExecutablePath.lower() def test_cmdline(self): w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] p = psutil.Process(self.pid) - self.assertEqual(' '.join(p.cmdline()), w.CommandLine.replace('"', '')) + assert ' '.join(p.cmdline()) == w.CommandLine.replace('"', '') def test_username(self): w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] p = psutil.Process(self.pid) domain, _, username = w.GetOwner() - username = "%s\\%s" % (domain, username) - self.assertEqual(p.username(), username) + username = f"{domain}\\{username}" + assert p.username() == username @retry_on_failure() def test_memory_rss(self): w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] p = psutil.Process(self.pid) rss = p.memory_info().rss - self.assertEqual(rss, int(w.WorkingSetSize)) + assert rss == int(w.WorkingSetSize) @retry_on_failure() def test_memory_vms(self): @@ -660,8 +623,8 @@ def test_memory_vms(self): # bytes but funnily enough on certain platforms bytes are # returned instead. wmi_usage = int(w.PageFileUsage) - if vms not in (wmi_usage, wmi_usage * 1024): - raise self.fail("wmi=%s, psutil=%s" % (wmi_usage, vms)) + if vms not in {wmi_usage, wmi_usage * 1024}: + raise self.fail(f"wmi={wmi_usage}, psutil={vms}") def test_create_time(self): w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] @@ -670,13 +633,13 @@ def test_create_time(self): psutil_create = time.strftime( "%Y%m%d%H%M%S", time.localtime(p.create_time()) ) - self.assertEqual(wmic_create, psutil_create) + assert wmic_create == psutil_create # --- -@unittest.skipIf(not WINDOWS, "WINDOWS only") +@pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") class TestDualProcessImplementation(PsutilTestCase): """Certain APIs on Windows have 2 internal implementations, one based on documented Windows APIs, another one based @@ -699,62 +662,54 @@ def test_memory_info(self): mem_1 = psutil.Process(self.pid).memory_info() with mock.patch( "psutil._psplatform.cext.proc_memory_info", - side_effect=OSError(errno.EPERM, "msg"), + side_effect=PermissionError, ) as fun: mem_2 = psutil.Process(self.pid).memory_info() - self.assertEqual(len(mem_1), len(mem_2)) + assert len(mem_1) == len(mem_2) for i in range(len(mem_1)): - self.assertGreaterEqual(mem_1[i], 0) - self.assertGreaterEqual(mem_2[i], 0) - self.assertAlmostEqual(mem_1[i], mem_2[i], delta=512) + assert mem_1[i] >= 0 + assert mem_2[i] >= 0 + assert abs(mem_1[i] - mem_2[i]) < 512 assert fun.called def test_create_time(self): ctime = psutil.Process(self.pid).create_time() with mock.patch( "psutil._psplatform.cext.proc_times", - side_effect=OSError(errno.EPERM, "msg"), + side_effect=PermissionError, ) as fun: - self.assertEqual(psutil.Process(self.pid).create_time(), ctime) + assert psutil.Process(self.pid).create_time() == ctime assert fun.called def test_cpu_times(self): cpu_times_1 = psutil.Process(self.pid).cpu_times() with mock.patch( "psutil._psplatform.cext.proc_times", - side_effect=OSError(errno.EPERM, "msg"), + side_effect=PermissionError, ) as fun: cpu_times_2 = psutil.Process(self.pid).cpu_times() assert fun.called - self.assertAlmostEqual( - cpu_times_1.user, cpu_times_2.user, delta=0.01 - ) - self.assertAlmostEqual( - cpu_times_1.system, cpu_times_2.system, delta=0.01 - ) + assert abs(cpu_times_1.user - cpu_times_2.user) < 0.01 + assert abs(cpu_times_1.system - cpu_times_2.system) < 0.01 def test_io_counters(self): io_counters_1 = psutil.Process(self.pid).io_counters() with mock.patch( "psutil._psplatform.cext.proc_io_counters", - side_effect=OSError(errno.EPERM, "msg"), + side_effect=PermissionError, ) as fun: io_counters_2 = psutil.Process(self.pid).io_counters() for i in range(len(io_counters_1)): - self.assertAlmostEqual( - io_counters_1[i], io_counters_2[i], delta=5 - ) + assert abs(io_counters_1[i] - io_counters_2[i]) < 5 assert fun.called def test_num_handles(self): num_handles = psutil.Process(self.pid).num_handles() with mock.patch( "psutil._psplatform.cext.proc_num_handles", - side_effect=OSError(errno.EPERM, "msg"), + side_effect=PermissionError, ) as fun: - self.assertEqual( - psutil.Process(self.pid).num_handles(), num_handles - ) + assert psutil.Process(self.pid).num_handles() == num_handles assert fun.called def test_cmdline(self): @@ -769,10 +724,10 @@ def test_cmdline(self): ): raise else: - self.assertEqual(a, b) + assert a == b -@unittest.skipIf(not WINDOWS, "WINDOWS only") +@pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") class RemoteProcessTestCase(PsutilTestCase): """Certain functions require calling ReadProcessMemory. This trivially works when called on the current process. @@ -805,7 +760,7 @@ def setUp(self): other_python = self.find_other_interpreter() if other_python is None: - raise unittest.SkipTest( + raise pytest.skip( "could not find interpreter with opposite bitness" ) if IS_64BIT: @@ -831,27 +786,27 @@ def tearDown(self): def test_cmdline_32(self): p = psutil.Process(self.proc32.pid) - self.assertEqual(len(p.cmdline()), 3) - self.assertEqual(p.cmdline()[1:], self.test_args) + assert len(p.cmdline()) == 3 + assert p.cmdline()[1:] == self.test_args def test_cmdline_64(self): p = psutil.Process(self.proc64.pid) - self.assertEqual(len(p.cmdline()), 3) - self.assertEqual(p.cmdline()[1:], self.test_args) + assert len(p.cmdline()) == 3 + assert p.cmdline()[1:] == self.test_args def test_cwd_32(self): p = psutil.Process(self.proc32.pid) - self.assertEqual(p.cwd(), os.getcwd()) + assert p.cwd() == os.getcwd() def test_cwd_64(self): p = psutil.Process(self.proc64.pid) - self.assertEqual(p.cwd(), os.getcwd()) + assert p.cwd() == os.getcwd() def test_environ_32(self): p = psutil.Process(self.proc32.pid) e = p.environ() - self.assertIn("THINK_OF_A_NUMBER", e) - self.assertEqual(e["THINK_OF_A_NUMBER"], str(os.getpid())) + assert "THINK_OF_A_NUMBER" in e + assert e["THINK_OF_A_NUMBER"] == str(os.getpid()) def test_environ_64(self): p = psutil.Process(self.proc64.pid) @@ -866,10 +821,10 @@ def test_environ_64(self): # =================================================================== -@unittest.skipIf(not WINDOWS, "WINDOWS only") +@pytest.mark.skipif(not WINDOWS, reason="WINDOWS only") class TestServices(PsutilTestCase): def test_win_service_iter(self): - valid_statuses = set([ + valid_statuses = { "running", "paused", "start", @@ -877,9 +832,9 @@ def test_win_service_iter(self): "continue", "stop", "stopped", - ]) - valid_start_types = set(["automatic", "manual", "disabled"]) - valid_statuses = set([ + } + valid_start_types = {"automatic", "manual", "disabled"} + valid_statuses = { "running", "paused", "start_pending", @@ -887,30 +842,30 @@ def test_win_service_iter(self): "continue_pending", "stop_pending", "stopped", - ]) + } for serv in psutil.win_service_iter(): data = serv.as_dict() - self.assertIsInstance(data['name'], str) - self.assertNotEqual(data['name'].strip(), "") - self.assertIsInstance(data['display_name'], str) - self.assertIsInstance(data['username'], str) - self.assertIn(data['status'], valid_statuses) + assert isinstance(data['name'], str) + assert data['name'].strip() + assert isinstance(data['display_name'], str) + assert isinstance(data['username'], str) + assert data['status'] in valid_statuses if data['pid'] is not None: psutil.Process(data['pid']) - self.assertIsInstance(data['binpath'], str) - self.assertIsInstance(data['username'], str) - self.assertIsInstance(data['start_type'], str) - self.assertIn(data['start_type'], valid_start_types) - self.assertIn(data['status'], valid_statuses) - self.assertIsInstance(data['description'], str) + assert isinstance(data['binpath'], str) + assert isinstance(data['username'], str) + assert isinstance(data['start_type'], str) + assert data['start_type'] in valid_start_types + assert data['status'] in valid_statuses + assert isinstance(data['description'], str) pid = serv.pid() if pid is not None: p = psutil.Process(pid) - self.assertTrue(p.is_running()) + assert p.is_running() # win_service_get s = psutil.win_service_get(serv.name()) # test __eq__ - self.assertEqual(serv, s) + assert serv == s def test_win_service_get(self): ERROR_SERVICE_DOES_NOT_EXIST = ( @@ -919,49 +874,41 @@ def test_win_service_get(self): ERROR_ACCESS_DENIED = psutil._psplatform.cext.ERROR_ACCESS_DENIED name = next(psutil.win_service_iter()).name() - with self.assertRaises(psutil.NoSuchProcess) as cm: + with pytest.raises(psutil.NoSuchProcess) as cm: psutil.win_service_get(name + '???') - self.assertEqual(cm.exception.name, name + '???') + assert cm.value.name == name + '???' # test NoSuchProcess service = psutil.win_service_get(name) - if PY3: - args = (0, "msg", 0, ERROR_SERVICE_DOES_NOT_EXIST) - else: - args = (ERROR_SERVICE_DOES_NOT_EXIST, "msg") - exc = WindowsError(*args) + exc = OSError(0, "msg", 0) + exc.winerror = ERROR_SERVICE_DOES_NOT_EXIST with mock.patch( "psutil._psplatform.cext.winservice_query_status", side_effect=exc ): - self.assertRaises(psutil.NoSuchProcess, service.status) + with pytest.raises(psutil.NoSuchProcess): + service.status() with mock.patch( "psutil._psplatform.cext.winservice_query_config", side_effect=exc ): - self.assertRaises(psutil.NoSuchProcess, service.username) + with pytest.raises(psutil.NoSuchProcess): + service.username() # test AccessDenied - if PY3: - args = (0, "msg", 0, ERROR_ACCESS_DENIED) - else: - args = (ERROR_ACCESS_DENIED, "msg") - exc = WindowsError(*args) + exc = OSError(0, "msg", 0) + exc.winerror = ERROR_ACCESS_DENIED with mock.patch( "psutil._psplatform.cext.winservice_query_status", side_effect=exc ): - self.assertRaises(psutil.AccessDenied, service.status) + with pytest.raises(psutil.AccessDenied): + service.status() with mock.patch( "psutil._psplatform.cext.winservice_query_config", side_effect=exc ): - self.assertRaises(psutil.AccessDenied, service.username) + with pytest.raises(psutil.AccessDenied): + service.username() # test __str__ and __repr__ - self.assertIn(service.name(), str(service)) - self.assertIn(service.display_name(), str(service)) - self.assertIn(service.name(), repr(service)) - self.assertIn(service.display_name(), repr(service)) - - -if __name__ == '__main__': - from psutil.tests.runner import run_from_name - - run_from_name(__file__) + assert service.name() in str(service) + assert service.display_name() in str(service) + assert service.name() in repr(service) + assert service.display_name() in repr(service) diff --git a/pyproject.toml b/pyproject.toml index 99a9b44d5..f6eb772d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,8 @@ select = [ "D301", # Use `r"""` if any backslashes in a docstring "D403", # [*] First word of the first line should be capitalized "PERF102", # [*] When using only the keys of a dict use the `keys()` method + "RET502", # [*] Do not implicitly `return None` in function able to return non-`None` value + "RET506", # [*] Unnecessary `else` after `raise` statement "RET507", # Unnecessary `elif` after `continue` statement "S113", # Probable use of requests call without timeout "S602", # `subprocess` call with `shell=True` identified, security issue @@ -33,33 +35,28 @@ ignore = [ "ARG001", # unused-function-argument "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) + "C4", # flake8-comprehensions "C90", # mccabe (function `X` is too complex) "COM812", # Trailing comma missing "D", # pydocstyle + "DOC", # various docstring warnings "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) + "FLY002", # static-join-to-f-string / Consider {expression} instead of string join "FURB101", # `open` and `read` should be replaced by `Path(src).read_text()` - "FURB103", # `open` and `write` should be replaced by `Path(...).write_text(...)` - "FURB113", # Use `x.extend(('a', 'b', 'c'))` instead of repeatedly calling `x.append()` + "FURB103", # `open` and `write` should be replaced by `Path(src).write_text()` + "FURB113", # Use `ls.extend(...)` instead of repeatedly calling `ls.append()` "FURB116", # [*] Replace `hex` call with `f"{start:x}"` "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) - "FURB192", # [*] Prefer `min` over `sorted()` to compute the minimum value in a sequence "INP", # flake8-no-pep420 "N801", # Class name `async_chat` should use CapWords convention (ASYNCORE COMPAT) "N802", # Function name X should be lowercase. "N806", # Variable X in function should be lowercase. "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) @@ -72,8 +69,6 @@ ignore = [ "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 @@ -82,38 +77,54 @@ ignore = [ "PTH", # flake8-use-pathlib "PYI", # flake8-pyi (python types stuff) "Q000", # Single quotes found but double quotes preferred - "RET", # flake8-return - "RUF", # Ruff-specific rules + "RET503", # Missing explicit `return` at the end of function able to return non-`None` value + "RET504", # Unnecessary assignment to `result` before `return` statement + "RET505", # [*] Unnecessary `else` after `return` statement + "RUF005", # Consider iterable unpacking instead of concatenation + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "RUF022", # `__all__` is not sorted + "RUF028", # This suppression comment is invalid + "RUF031", # [*] Avoid parentheses for tuples in subscripts "S", # flake8-bandit "SIM102", # Use a single `if` statement instead of nested `if` statements "SIM105", # Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass` - "SIM115", # Use context handler for opening files "SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements "SLF", # flake8-self "TD", # all TODOs, XXXs, etc. "TRY300", # Consider moving this statement to an `else` block "TRY301", # Abstract `raise` to an inner function + "UP032", # [*] Use f-string instead of `format` call +] + +[tool.ruff.lint.per-file-ignores] +# EM101 == raw-string-in-exception +# EM102 == f-string-in-exception +# EM103 == dot-format-in-exception +# T201 == print() +# T203 == pprint() +# TRY003 == raise-vanilla-args +# B904 == Use `raise from` to specify exception cause +# PLC1901 == `x == ""` can be simplified to `not x` as an empty string is falsey +".github/workflows/*" = ["B904", "EM101", "EM102", "EM103", "T201", "T203"] +"psutil/tests/*" = ["B904", "EM101", "EM102", "EM103", "PLC1901", "TRY003"] +"scripts/*" = ["B904", "EM101", "EM102", "EM103", "T201", "T203"] +"scripts/internal/*" = ["B904", "EM101", "EM102", "EM103", "T201", "T203", "TRY003"] +"setup.py" = [ + "B904", # Use ` raise from` to specify exception cause (PYTHON2.7 COMPAT) + "C4", # flake8-comprehensions (PYTHON2.7 COMPAT) + "FLY", # flynt (PYTHON2.7 COMPAT) + "FURB145", # [*] Prefer `copy` method over slicing (PYTHON2.7 COMPAT) + "T201", + "T203", "UP009", # [*] UTF-8 encoding declaration is unnecessary (PYTHON2.7 COMPAT) - "UP010", # [*] Unnecessary `__future__` import `print_function` for target Python version (PYTHON2.7 COMPAT) + "UP010", # [*] Unnecessary `__future__` import `print_function` (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) + "UP036", # Version block is outdated for minimum Python version ] -[tool.ruff.lint.per-file-ignores] -# T201 == print(), T203 == pprint() -# EM101 == raw-string-in-exception -# TRY003 == raise-vanilla-args -".github/workflows/*" = ["T201", "T203"] -"psutil/_compat.py" = ["PLW0127"] # self-assigning-variable -"psutil/tests/*" = ["EM101", "TRY003"] -"psutil/tests/runner.py" = ["T201", "T203"] -"scripts/*" = ["T201", "T203"] -"scripts/internal/*" = ["EM101", "T201", "T203", "TRY003"] -"setup.py" = ["T201", "T203"] - [tool.ruff.lint.isort] # https://beta.ruff.rs/docs/settings/#isort force-single-line = true # one import per line @@ -121,7 +132,6 @@ lines-after-imports = 2 [tool.coverage.report] exclude_lines = [ - "enum.IntEnum", "except ImportError:", "globals().update", "if BSD", @@ -131,22 +141,16 @@ exclude_lines = [ "if MACOS", "if NETBSD", "if OPENBSD", - "if PY3:", "if SUNOS", "if WINDOWS", "if _WINDOWS:", "if __name__ == .__main__.:", - "if enum is None:", - "if enum is not None:", - "if has_enums:", "if ppid_map is None:", "if sys.platform.startswith", - "import enum", "pragma: no cover", "raise NotImplementedError", ] omit = [ - "psutil/_compat.py", "psutil/tests/*", "setup.py", ] @@ -189,6 +193,18 @@ disable = [ "wrong-import-position", ] +[tool.vulture] +exclude = [ + "docs/conf.py", + "psutil/tests/", + "scripts/internal/winmake.py", +] +ignore_decorators = [ + "@_common.deprecated_method", + "@atexit.register", + "@pytest.fixture", +] + [tool.rstcheck] ignore_messages = [ "Duplicate explicit target name", @@ -208,17 +224,13 @@ trailing_comma_inline_array = true skip = [ "*-musllinux*", "cp313-win*", # pywin32 is not available on cp313 yet + "cp3{7,8,9,10,11,12}-*linux_{aarch64,ppc64le,s390x}", # Only test cp36/cp313 on qemu tested architectures "pp*", ] -test-command = [ - "env PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 PSUTIL_SCRIPTS_DIR={project}/scripts python {project}/psutil/tests/runner.py", - "env PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 PSUTIL_SCRIPTS_DIR={project}/scripts python {project}/psutil/tests/test_memleaks.py", -] -test-extras = "test" [tool.cibuildwheel.macos] archs = ["arm64", "x86_64"] [build-system] build-backend = "setuptools.build_meta" -requires = ["setuptools>=43", "wheel"] +requires = ["setuptools>=43"] diff --git a/scripts/battery.py b/scripts/battery.py index 0595d1ad1..b6d4679ea 100755 --- a/scripts/battery.py +++ b/scripts/battery.py @@ -13,7 +13,6 @@ plugged in: no """ -from __future__ import print_function import sys @@ -23,7 +22,7 @@ def secs2hours(secs): mm, ss = divmod(secs, 60) hh, mm = divmod(mm, 60) - return "%d:%02d:%02d" % (hh, mm, ss) + return f"{int(hh)}:{int(mm):02}:{int(ss):02}" def main(): @@ -33,16 +32,16 @@ def main(): if batt is None: return sys.exit("no battery is installed") - print("charge: %s%%" % round(batt.percent, 2)) + print(f"charge: {round(batt.percent, 2)}%") if batt.power_plugged: print( - "status: %s" - % ("charging" if batt.percent < 100 else "fully charged") + "status: " + f" {'charging' if batt.percent < 100 else 'fully charged'}" ) print("plugged in: yes") else: - print("left: %s" % secs2hours(batt.secsleft)) - print("status: %s" % "discharging") + print(f"left: {secs2hours(batt.secsleft)}") + print("status: discharging") print("plugged in: no") diff --git a/scripts/cpu_distribution.py b/scripts/cpu_distribution.py index bfbb14b6c..ecb0b9c0d 100755 --- a/scripts/cpu_distribution.py +++ b/scripts/cpu_distribution.py @@ -38,15 +38,14 @@ kwork """ -from __future__ import print_function import collections import os +import shutil import sys import time import psutil -from psutil._compat import get_terminal_size if not hasattr(psutil.Process, "cpu_num"): @@ -73,13 +72,13 @@ def main(): clean_screen() cpus_percent = psutil.cpu_percent(percpu=True) for i in range(num_cpus): - print("CPU %-6i" % i, end="") + print("CPU {:<6}".format(i), end="") if cpus_hidden: print(" (+ hidden)", end="") print() for _ in range(num_cpus): - print("%-10s" % cpus_percent.pop(0), end="") + print("{:<10}".format(cpus_percent.pop(0)), end="") print() # processes @@ -94,10 +93,10 @@ def main(): pname = procs[num].pop() except IndexError: pname = "" - print("%-10s" % pname[:10], end="") + print("{:<10}".format(pname[:10]), end="") print() curr_line += 1 - if curr_line >= get_terminal_size()[1]: + if curr_line >= shutil.get_terminal_size()[1]: break time.sleep(1) diff --git a/scripts/disk_usage.py b/scripts/disk_usage.py index c75c4f61d..be391e28a 100755 --- a/scripts/disk_usage.py +++ b/scripts/disk_usage.py @@ -23,8 +23,12 @@ def main(): - templ = "%-17s %8s %8s %8s %5s%% %9s %s" - print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type", "Mount")) + templ = "{:<17} {:>8} {:>8} {:>8} {:>5}% {:>9} {}" + print( + templ.format( + "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 not part.fstype: @@ -33,7 +37,7 @@ def main(): # partition or just hang. continue usage = psutil.disk_usage(part.mountpoint) - line = templ % ( + line = templ.format( part.device, bytes2human(usage.total), bytes2human(usage.used), diff --git a/scripts/fans.py b/scripts/fans.py index a9a8b8e67..950da7bd2 100755 --- a/scripts/fans.py +++ b/scripts/fans.py @@ -11,7 +11,6 @@ cpu_fan 3200 RPM """ -from __future__ import print_function import sys @@ -24,11 +23,13 @@ def main(): fans = psutil.sensors_fans() if not fans: print("no fans detected") - return + return None for name, entries in fans.items(): print(name) for entry in entries: - print(" %-20s %s RPM" % (entry.label or name, entry.current)) + print( + " {:<20} {} RPM".format(entry.label or name, entry.current) + ) print() diff --git a/scripts/free.py b/scripts/free.py index 332507284..c891b6fc9 100755 --- a/scripts/free.py +++ b/scripts/free.py @@ -18,9 +18,11 @@ def main(): virt = psutil.virtual_memory() swap = psutil.swap_memory() - templ = "%-7s %10s %10s %10s %10s %10s %10s" - print(templ % ('', 'total', 'used', 'free', 'shared', 'buffers', 'cache')) - sect = templ % ( + templ = "{:<7} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}" + print( + templ.format("", "total", "used", "free", "shared", "buffers", "cache") + ) + sect = templ.format( 'Mem:', int(virt.total / 1024), int(virt.used / 1024), @@ -30,7 +32,7 @@ def main(): int(getattr(virt, 'cached', 0) / 1024), ) print(sect) - sect = templ % ( + sect = templ.format( 'Swap:', int(swap.total / 1024), int(swap.used / 1024), diff --git a/scripts/ifconfig.py b/scripts/ifconfig.py index e23472ba9..3ab3c013b 100755 --- a/scripts/ifconfig.py +++ b/scripts/ifconfig.py @@ -42,7 +42,6 @@ broadcast : ff:ff:ff:ff:ff:ff """ -from __future__ import print_function import socket @@ -67,13 +66,12 @@ def main(): stats = psutil.net_if_stats() io_counters = psutil.net_io_counters(pernic=True) for nic, addrs in psutil.net_if_addrs().items(): - print("%s:" % (nic)) + print(f"{nic}:") if nic in stats: st = stats[nic] print(" stats : ", end='') print( - "speed=%sMB, duplex=%s, mtu=%s, up=%s" - % ( + "speed={}MB, duplex={}, mtu={}, up={}".format( st.speed, duplex_map[st.duplex], st.mtu, @@ -84,8 +82,7 @@ def main(): io = io_counters[nic] print(" incoming : ", end='') print( - "bytes=%s, pkts=%s, errs=%s, drops=%s" - % ( + "bytes={}, pkts={}, errs={}, drops={}".format( bytes2human(io.bytes_recv), io.packets_recv, io.errin, @@ -94,8 +91,7 @@ def main(): ) print(" outgoing : ", end='') print( - "bytes=%s, pkts=%s, errs=%s, drops=%s" - % ( + "bytes={}, pkts={}, errs={}, drops={}".format( bytes2human(io.bytes_sent), io.packets_sent, io.errout, @@ -103,14 +99,15 @@ def main(): ) ) for addr in addrs: - print(" %-4s" % af_map.get(addr.family, addr.family), end="") - print(" address : %s" % addr.address) + fam = " {:<4}".format(af_map.get(addr.family, addr.family)) + print(fam, end="") + print(f" address : {addr.address}") if addr.broadcast: - print(" broadcast : %s" % addr.broadcast) + print(f" broadcast : {addr.broadcast}") if addr.netmask: - print(" netmask : %s" % addr.netmask) + print(f" netmask : {addr.netmask}") if addr.ptp: - print(" p2p : %s" % addr.ptp) + print(f" p2p : {addr.ptp}") print() diff --git a/scripts/internal/appveyor_run_with_compiler.cmd b/scripts/internal/appveyor_run_with_compiler.cmd deleted file mode 100644 index 7965f865f..000000000 --- a/scripts/internal/appveyor_run_with_compiler.cmd +++ /dev/null @@ -1,89 +0,0 @@ -:: To build extensions for 64 bit Python 3, we need to configure environment -:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) -:: -:: To build extensions for 64 bit Python 2, we need to configure environment -:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) -:: -:: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific -:: environment configurations. -:: -:: Note: this script needs to be run with the /E:ON and /V:ON flags for the -:: cmd interpreter, at least for (SDK v7.0) -:: -:: More details at: -:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows -:: http://stackoverflow.com/a/13751649/163740 -:: -:: Author: Olivier Grisel -:: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ -:: -:: Notes about batch files for Python people: -:: -:: Quotes in values are literally part of the values: -:: SET FOO="bar" -:: FOO is now five characters long: " b a r " -:: If you don't want quotes, don't include them on the right-hand side. -:: -:: The CALL lines at the end of this file look redundant, but if you move them -:: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y -:: case, I don't know why. - -@ECHO OFF - -SET COMMAND_TO_RUN=%* -SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows -SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf - -:: Extract the major and minor versions, and allow for the minor version to be -:: more than 9. This requires the version number to have two dots in it. -SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% -IF "%PYTHON_VERSION:~3,1%" == "." ( - SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% -) ELSE ( - SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% -) - -:: Based on the Python version, determine what SDK version to use, and whether -:: to set the SDK for 64-bit. -IF %MAJOR_PYTHON_VERSION% == 2 ( - SET WINDOWS_SDK_VERSION="v7.0" - SET SET_SDK_64=Y -) ELSE ( - IF %MAJOR_PYTHON_VERSION% == 3 ( - SET WINDOWS_SDK_VERSION="v7.1" - IF %MINOR_PYTHON_VERSION% LEQ 4 ( - SET SET_SDK_64=Y - ) ELSE ( - SET SET_SDK_64=N - IF EXIST "%WIN_WDK%" ( - :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ - REN "%WIN_WDK%" 0wdf - ) - ) - ) ELSE ( - ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" - EXIT 1 - ) -) - -IF %PYTHON_ARCH% == 64 ( - IF %SET_SDK_64% == Y ( - ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture - SET DISTUTILS_USE_SDK=1 - SET MSSdk=1 - "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% - "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release - ECHO Executing: %COMMAND_TO_RUN% - call %COMMAND_TO_RUN% || EXIT 1 - ) ELSE ( - ECHO Using default MSVC build environment for 64 bit architecture - ECHO Executing: %COMMAND_TO_RUN% - call %COMMAND_TO_RUN% || EXIT 1 - ) -) ELSE ( - ECHO Using default MSVC build environment for 32 bit architecture - ECHO Executing: %COMMAND_TO_RUN% - call %COMMAND_TO_RUN% || EXIT 1 -) diff --git a/scripts/internal/bench_oneshot.py b/scripts/internal/bench_oneshot.py index b58224bb6..299f9cea6 100755 --- a/scripts/internal/bench_oneshot.py +++ b/scripts/internal/bench_oneshot.py @@ -9,8 +9,6 @@ See: https://github.com/giampaolo/psutil/issues/799. """ -from __future__ import division -from __future__ import print_function import sys import textwrap @@ -128,8 +126,9 @@ def call_oneshot(funs): def main(): print( - "%s methods involved on platform %r (%s iterations, psutil %s):" - % (len(names), sys.platform, ITERATIONS, psutil.__version__) + f"{len(names)} methods involved on platform" + f" {sys.platform!r} ({ITERATIONS} iterations, psutil" + f" {psutil.__version__}):" ) for name in sorted(names): print(" " + name) @@ -138,19 +137,19 @@ def main(): elapsed1 = timeit.timeit( "call_normal(funs)", setup=setup, number=ITERATIONS ) - print("normal: %.3f secs" % elapsed1) + print(f"normal: {elapsed1:.3f} secs") # "one shot" run elapsed2 = timeit.timeit( "call_oneshot(funs)", setup=setup, number=ITERATIONS ) - print("onshot: %.3f secs" % elapsed2) + print(f"onshot: {elapsed2:.3f} secs") # done if elapsed2 < elapsed1: - print("speedup: +%.2fx" % (elapsed1 / elapsed2)) + print(f"speedup: +{elapsed1 / elapsed2:.2f}x") elif elapsed2 > elapsed1: - print("slowdown: -%.2fx" % (elapsed2 / elapsed1)) + print(f"slowdown: -{elapsed2 / elapsed1:.2f}x") else: print("same speed") diff --git a/scripts/internal/bench_oneshot_2.py b/scripts/internal/bench_oneshot_2.py index fe5151d3e..aa3ca78d1 100755 --- a/scripts/internal/bench_oneshot_2.py +++ b/scripts/internal/bench_oneshot_2.py @@ -11,44 +11,42 @@ import sys import pyperf # requires "pip install pyperf" -from bench_oneshot import names import psutil p = psutil.Process() -funs = [getattr(p, n) for n in names] -def call_normal(): +def call_normal(funs): for fun in funs: fun() -def call_oneshot(): +def call_oneshot(funs): with p.oneshot(): for fun in funs: fun() -def add_cmdline_args(cmd, args): - cmd.append(args.benchmark) - - def main(): + from bench_oneshot import names + runner = pyperf.Runner() args = runner.parse_args() if not args.worker: print( - "%s methods involved on platform %r (psutil %s):" - % (len(names), sys.platform, psutil.__version__) + f"{len(names)} methods involved on platform" + f" {sys.platform!r} (psutil {psutil.__version__}):" ) for name in sorted(names): print(" " + name) - runner.bench_func("normal", call_normal) - runner.bench_func("oneshot", call_oneshot) + funs = [getattr(p, n) for n in names] + runner.bench_func("normal", call_normal, funs) + runner.bench_func("oneshot", call_oneshot, funs) -main() +if __name__ == "__main__": + main() diff --git a/scripts/internal/check_broken_links.py b/scripts/internal/check_broken_links.py index 90cb258c3..2c6852b9a 100755 --- a/scripts/internal/check_broken_links.py +++ b/scripts/internal/check_broken_links.py @@ -38,7 +38,6 @@ Author: Himanshu Shekhar (2017) """ -from __future__ import print_function import argparse import concurrent.futures @@ -93,7 +92,7 @@ def sanitize_url(url): def find_urls(s): matches = REGEX.findall(s) or [] - return list(set([sanitize_url(x) for x in matches])) + return list({sanitize_url(x) for x in matches}) def parse_rst(fname): @@ -123,7 +122,7 @@ def parse_py(fname): subidx = i + 1 while True: nextline = lines[subidx].strip() - if re.match('^# .+', nextline): + if re.match(r"^# .+", nextline): url += nextline[1:].strip() else: break @@ -144,7 +143,7 @@ def parse_c(fname): subidx = i + 1 while True: nextline = lines[subidx].strip() - if re.match('^// .+', nextline): + if re.match(r"^// .+", nextline): url += nextline[2:].strip() else: break @@ -213,7 +212,7 @@ def parallel_validator(urls): } for fut in concurrent.futures.as_completed(fut_to_url): current += 1 - sys.stdout.write("\r%s / %s" % (current, total)) + sys.stdout.write(f"\r{current} / {total}") sys.stdout.flush() fname, url = fut_to_url[fut] try: @@ -221,7 +220,7 @@ def parallel_validator(urls): except Exception: # noqa: BLE001 fails.append((fname, url)) print() - print("warn: error while validating %s" % url, file=sys.stderr) + print(f"warn: error while validating {url}", file=sys.stderr) traceback.print_exc() else: if not ok: @@ -243,7 +242,7 @@ def main(): for fname in args.files: urls = get_urls(fname) if urls: - print("%4s %s" % (len(urls), fname)) + print(f"{len(urls):4} {fname}") for url in urls: all_urls.append((fname, url)) @@ -253,9 +252,9 @@ def main(): else: for fail in fails: fname, url = fail - print("%-30s: %s " % (fname, url)) + print("{:<30}: {} ".format(fname, url)) print('-' * 20) - print("total: %s fails!" % len(fails)) + print(f"total: {len(fails)} fails!") sys.exit(1) diff --git a/scripts/internal/clinter.py b/scripts/internal/clinter.py index 516ca894e..4f0d99c31 100755 --- a/scripts/internal/clinter.py +++ b/scripts/internal/clinter.py @@ -6,7 +6,6 @@ """A super simple linter to check C syntax.""" -from __future__ import print_function import argparse import sys @@ -18,7 +17,7 @@ def warn(path, line, lineno, msg): global warned warned = True - print("%s:%s: %s" % (path, lineno, msg), file=sys.stderr) + print(f"{path}:{lineno}: {msg}", file=sys.stderr) def check_line(path, line, idx, lines): @@ -50,7 +49,7 @@ def check_line(path, line, idx, lines): keywords = ("if", "else", "while", "do", "enum", "for") for kw in keywords: if sls.startswith(kw + '('): - warn(path, line, lineno, "missing space between %r and '('" % kw) + warn(path, line, lineno, f"missing space between {kw!r} and '('") # eof if eof and not line.endswith('\n'): warn(path, line, lineno, "no blank line at EOF") diff --git a/scripts/internal/convert_readme.py b/scripts/internal/convert_readme.py index 0c4fade50..9aa75b907 100755 --- a/scripts/internal/convert_readme.py +++ b/scripts/internal/convert_readme.py @@ -36,7 +36,7 @@ `Add your logo `__. -Example usages""" # noqa +Example usages""" def main(): diff --git a/scripts/internal/download_wheels_github.py b/scripts/internal/download_wheels.py similarity index 68% rename from scripts/internal/download_wheels_github.py rename to scripts/internal/download_wheels.py index c6ecb5dda..73cda80f1 100755 --- a/scripts/internal/download_wheels_github.py +++ b/scripts/internal/download_wheels.py @@ -22,24 +22,22 @@ import requests -from psutil import __version__ from psutil._common import bytes2human from psutil.tests import safe_rmpath USER = "giampaolo" PROJECT = "psutil" -PROJECT_VERSION = __version__ OUTFILE = "wheels-github.zip" TOKEN = "" TIMEOUT = 30 def get_artifacts(): - base_url = "https://api.github.com/repos/%s/%s" % (USER, PROJECT) + base_url = f"https://api.github.com/repos/{USER}/{PROJECT}" url = base_url + "/actions/artifacts" res = requests.get( - url=url, headers={"Authorization": "token %s" % TOKEN}, timeout=TIMEOUT + url=url, headers={"Authorization": f"token {TOKEN}"}, timeout=TIMEOUT ) res.raise_for_status() data = json.loads(res.content) @@ -49,7 +47,7 @@ def get_artifacts(): def download_zip(url): print("downloading: " + url) res = requests.get( - url=url, headers={"Authorization": "token %s" % TOKEN}, timeout=TIMEOUT + url=url, headers={"Authorization": f"token {TOKEN}"}, timeout=TIMEOUT ) res.raise_for_status() totbytes = 0 @@ -57,21 +55,7 @@ def download_zip(url): for chunk in res.iter_content(chunk_size=16384): f.write(chunk) totbytes += len(chunk) - print("got %s, size %s)" % (OUTFILE, bytes2human(totbytes))) - - -def rename_win27_wheels(): - # See: https://github.com/giampaolo/psutil/issues/810 - src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PROJECT_VERSION - dst = 'dist/psutil-%s-cp27-none-win32.whl' % PROJECT_VERSION - if os.path.exists(src): - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) - src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PROJECT_VERSION - dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PROJECT_VERSION - if os.path.exists(src): - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) + print(f"got {OUTFILE}, size {bytes2human(totbytes)})") def run(): @@ -80,7 +64,6 @@ def run(): os.makedirs('dist', exist_ok=True) with zipfile.ZipFile(OUTFILE, 'r') as zf: zf.extractall('dist') - rename_win27_wheels() def main(): diff --git a/scripts/internal/download_wheels_appveyor.py b/scripts/internal/download_wheels_appveyor.py deleted file mode 100755 index 0e6490b39..000000000 --- a/scripts/internal/download_wheels_appveyor.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 - -# 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. - -"""Script which downloads wheel files hosted on AppVeyor: -https://ci.appveyor.com/project/giampaolo/psutil -Re-adapted from the original recipe of Ibarra Corretge' -: -http://code.saghul.net/index.php/2015/09/09/. -""" - -from __future__ import print_function - -import concurrent.futures -import os -import sys - -import requests - -from psutil import __version__ -from psutil._common import bytes2human -from psutil._common import print_color - - -USER = "giampaolo" -PROJECT = "psutil" -PROJECT_VERSION = __version__ -BASE_URL = 'https://ci.appveyor.com/api' -PY_VERSIONS = ['2.7'] -TIMEOUT = 30 - - -def download_file(url): - local_fname = url.split('/')[-1] - local_fname = os.path.join('dist', local_fname) - os.makedirs('dist', exist_ok=True) - r = requests.get(url, stream=True, timeout=TIMEOUT) - tot_bytes = 0 - with open(local_fname, 'wb') as f: - for chunk in r.iter_content(chunk_size=16384): - if chunk: # filter out keep-alive new chunks - f.write(chunk) - tot_bytes += len(chunk) - return local_fname - - -def get_file_urls(): - with requests.Session() as session: - data = session.get( - BASE_URL + '/projects/' + USER + '/' + PROJECT, timeout=TIMEOUT - ) - data = data.json() - - urls = [] - for job in (job['jobId'] for job in data['build']['jobs']): - job_url = BASE_URL + '/buildjobs/' + job + '/artifacts' - data = session.get(job_url, timeout=TIMEOUT) - data = data.json() - for item in data: - file_url = job_url + '/' + item['fileName'] - urls.append(file_url) - if not urls: - print_color("no artifacts found", 'red') - sys.exit(1) - else: - for url in sorted(urls, key=os.path.basename): - yield url - - -def rename_win27_wheels(): - # See: https://github.com/giampaolo/psutil/issues/810 - src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PROJECT_VERSION - dst = 'dist/psutil-%s-cp27-none-win32.whl' % PROJECT_VERSION - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) - src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PROJECT_VERSION - dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PROJECT_VERSION - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) - - -def run(): - urls = get_file_urls() - completed = 0 - exc = None - with concurrent.futures.ThreadPoolExecutor() as e: - fut_to_url = {e.submit(download_file, url): url for url in urls} - for fut in concurrent.futures.as_completed(fut_to_url): - url = fut_to_url[fut] - try: - local_fname = fut.result() - except Exception: - print_color("error while downloading %s" % (url), 'red') - raise - else: - completed += 1 - print( - "downloaded %-45s %s" - % (local_fname, bytes2human(os.path.getsize(local_fname))) - ) - # 2 wheels (32 and 64 bit) per supported python version - expected = len(PY_VERSIONS) * 2 - if expected != completed: - return sys.exit("expected %s files, got %s" % (expected, completed)) - if exc: - return sys.exit() - rename_win27_wheels() - - -def main(): - run() - - -if __name__ == '__main__': - main() diff --git a/scripts/internal/generate_manifest.py b/scripts/internal/generate_manifest.py index 9eeeb1a24..1e261e5a4 100755 --- a/scripts/internal/generate_manifest.py +++ b/scripts/internal/generate_manifest.py @@ -12,7 +12,7 @@ SKIP_EXTS = ('.png', '.jpg', '.jpeg', '.svg') -SKIP_FILES = 'appveyor.yml' +SKIP_FILES = () SKIP_PREFIXES = ('.ci/', '.github/') diff --git a/scripts/internal/git_pre_commit.py b/scripts/internal/git_pre_commit.py index 84c38cccd..a47d7987e 100755 --- a/scripts/internal/git_pre_commit.py +++ b/scripts/internal/git_pre_commit.py @@ -10,7 +10,6 @@ "make install-git-hooks". """ -from __future__ import print_function import os import shlex @@ -19,8 +18,6 @@ PYTHON = sys.executable -PY3 = sys.version_info[0] >= 3 -THIS_SCRIPT = os.path.realpath(__file__) def term_supports_colors(): @@ -48,7 +45,7 @@ def hilite(s, ok=True, bold=False): attr.append('31') if bold: attr.append('1') - return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s) + return f"\x1b[{';'.join(attr)}m{s}\x1b[0m" def exit(msg): @@ -75,11 +72,6 @@ def sh(cmd): return stdout -def open_text(path): - kw = {'encoding': 'utf8'} if PY3 else {} - return open(path, **kw) - - def git_commit_files(): out = sh(["git", "diff", "--cached", "--name-only"]) py_files = [ @@ -105,7 +97,7 @@ def git_commit_files(): def black(files): - print("running black (%s)" % len(files)) + print(f"running black ({len(files)})") cmd = [PYTHON, "-m", "black", "--check", "--safe"] + files if subprocess.call(cmd) != 0: return exit( @@ -115,8 +107,15 @@ def black(files): def ruff(files): - print("running ruff (%s)" % len(files)) - cmd = [PYTHON, "-m", "ruff", "check", "--no-cache"] + files + print(f"running ruff ({len(files)})") + cmd = [ + PYTHON, + "-m", + "ruff", + "check", + "--no-cache", + "--output-format=concise", + ] + files if subprocess.call(cmd) != 0: return exit( "Python code didn't pass 'ruff' style check." @@ -125,7 +124,7 @@ def ruff(files): def c_linter(files): - print("running clinter (%s)" % len(files)) + print(f"running clinter ({len(files)})") # XXX: we should escape spaces and possibly other amenities here cmd = [PYTHON, "scripts/internal/clinter.py"] + files if subprocess.call(cmd) != 0: @@ -133,14 +132,14 @@ def c_linter(files): def toml_sort(files): - print("running toml linter (%s)" % len(files)) + print(f"running toml linter ({len(files)})") cmd = ["toml-sort", "--check"] + files if subprocess.call(cmd) != 0: - return sys.exit("%s didn't pass style check" % ' '.join(files)) + return sys.exit(f"{' '.join(files)} didn't pass style check") def rstcheck(files): - print("running rst linter (%s)" % len(files)) + print(f"running rst linter ({len(files)})") cmd = ["rstcheck", "--config=pyproject.toml"] + files if subprocess.call(cmd) != 0: return sys.exit("RST code didn't pass style check") @@ -159,7 +158,7 @@ def main(): toml_sort(toml_files) if new_rm_mv: out = sh([PYTHON, "scripts/internal/generate_manifest.py"]) - with open_text('MANIFEST.in') as f: + with open("MANIFEST.in", encoding="utf8") as f: if out.strip() != f.read().strip(): sys.exit( "some files were added, deleted or renamed; " @@ -167,4 +166,5 @@ def main(): ) -main() +if __name__ == "__main__": + main() diff --git a/scripts/internal/install-sysdeps.sh b/scripts/internal/install-sysdeps.sh new file mode 100755 index 000000000..003a7d081 --- /dev/null +++ b/scripts/internal/install-sysdeps.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Depending on the UNIX platform, install the necessary system dependencies to: +# * compile psutil +# * run those unit tests that rely on CLI tools (netstat, ps, etc.) +# NOTE: this script MUST be kept compatible with the `sh` shell. + +set -e + +UNAME_S=$(uname -s) + +case "$UNAME_S" in + Linux) + LINUX=true + if command -v apt > /dev/null 2>&1; then + HAS_APT=true + elif command -v yum > /dev/null 2>&1; then + HAS_YUM=true + elif command -v apk > /dev/null 2>&1; then + HAS_APK=true # musl linux + fi + ;; + FreeBSD) + FREEBSD=true + ;; + NetBSD) + NETBSD=true + ;; + OpenBSD) + OPENBSD=true + ;; +esac + +# Check if running as root +if [ "$(id -u)" -ne 0 ]; then + SUDO=sudo +fi + +# Function to install system dependencies +main() { + if [ $HAS_APT ]; then + $SUDO apt-get install -y python3-dev gcc + $SUDO apt-get install -y net-tools coreutils util-linux # for tests + elif [ $HAS_YUM ]; then + $SUDO yum install -y python3-devel gcc + $SUDO yum install -y net-tools coreutils util-linux # for tests + elif [ $HAS_APK ]; then + $SUDO apk add python3-dev gcc musl-dev linux-headers coreutils procps + elif [ $FREEBSD ]; then + $SUDO pkg install -y python3 gcc + elif [ $NETBSD ]; then + $SUDO /usr/sbin/pkg_add -v pkgin + $SUDO pkgin update + $SUDO pkgin -y install python311-* gcc12-* + elif [ $OPENBSD ]; then + $SUDO pkg_add gcc python3 + else + echo "Unsupported platform: $UNAME_S" + fi +} + +main diff --git a/scripts/internal/install_pip.py b/scripts/internal/install_pip.py new file mode 100755 index 000000000..bca5d5fe7 --- /dev/null +++ b/scripts/internal/install_pip.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# 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. + +import sys + + +try: + import pip # noqa: F401 +except ImportError: + pass +else: + print("pip already installed") + sys.exit(0) + +import os +import ssl +import tempfile +from urllib.request import urlopen + + +URL = "https://bootstrap.pypa.io/get-pip.py" + + +def main(): + ssl_context = ( + ssl._create_unverified_context() + if hasattr(ssl, "_create_unverified_context") + else None + ) + with tempfile.NamedTemporaryFile(suffix=".py") as f: + print(f"downloading {URL} into {f.name}") + kwargs = dict(context=ssl_context) if ssl_context else {} + req = urlopen(URL, **kwargs) + data = req.read() + req.close() + + f.write(data) + f.flush() + print("download finished, installing pip") + + code = os.system(f"{sys.executable} {f.name} --user --upgrade") + + sys.exit(code) + + +if __name__ == "__main__": + main() diff --git a/scripts/internal/print_access_denied.py b/scripts/internal/print_access_denied.py index f7017b04d..b92e9bfc2 100755 --- a/scripts/internal/print_access_denied.py +++ b/scripts/internal/print_access_denied.py @@ -44,8 +44,6 @@ Totals: access-denied=1744, calls=10020, processes=334 """ -from __future__ import division -from __future__ import print_function import time from collections import defaultdict @@ -74,19 +72,19 @@ def main(): elapsed = time.time() - start # print - templ = "%-20s %-5s %-9s %s" - s = templ % ("API", "AD", "Percent", "Outcome") + templ = "{:<20} {:<5} {:<9} {}" + s = templ.format("API", "AD", "Percent", "Outcome") print_color(s, color=None, bold=True) for methname, ads in sorted(d.items(), key=lambda x: (x[1], x[0])): perc = (ads / tot_procs) * 100 outcome = "SUCCESS" if not ads else "ACCESS DENIED" - s = templ % (methname, ads, "%6.1f%%" % perc, outcome) + s = templ.format(methname, ads, f"{perc:6.1f}%", outcome) print_color(s, "red" if ads else None) tot_perc = round((tot_ads / tot_calls) * 100, 1) print("-" * 50) print( - "Totals: access-denied=%s (%s%%), calls=%s, processes=%s, elapsed=%ss" - % (tot_ads, tot_perc, tot_calls, tot_procs, round(elapsed, 2)) + "Totals: access-denied={} ({}%%), calls={}, processes={}, elapsed={}s" + .format(tot_ads, tot_perc, tot_calls, tot_procs, round(elapsed, 2)) ) diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py index d1a7d297f..8b62f2b71 100755 --- a/scripts/internal/print_announce.py +++ b/scripts/internal/print_announce.py @@ -49,8 +49,7 @@ line tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, \ nice, ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It \ currently supports Linux, Windows, macOS, Sun Solaris, FreeBSD, OpenBSD, \ -NetBSD and AIX, both 32-bit and 64-bit architectures. Supported Python \ -versions are 2.7 and 3.6+. PyPy is also known to work. +NetBSD and AIX. Supported Python versions are cPython 3.6+ and PyPy. What's new ========== diff --git a/scripts/internal/print_api_speed.py b/scripts/internal/print_api_speed.py index 786644b5a..1fdbc83d3 100755 --- a/scripts/internal/print_api_speed.py +++ b/scripts/internal/print_api_speed.py @@ -68,8 +68,6 @@ memory_maps 300 0.74281 """ -from __future__ import division -from __future__ import print_function import argparse import inspect @@ -83,11 +81,11 @@ TIMES = 300 timings = [] -templ = "%-25s %10s %10s" +templ = "{:<25} {:>10} {:>10}" def print_header(what): - s = templ % (what, "NUM CALLS", "SECONDS") + s = templ.format(what, "NUM CALLS", "SECONDS") print_color(s, color=None, bold=True) print("-" * len(s)) @@ -97,7 +95,7 @@ def print_timings(): i = 0 while timings[:]: title, times, elapsed = timings.pop(0) - s = templ % (title, str(times), "%.5f" % elapsed) + s = templ.format(title, str(times), f"{elapsed:.5f}") if i > len(timings) - 5: print_color(s, color="red") else: @@ -105,7 +103,7 @@ def print_timings(): def timecall(title, fun, *args, **kw): - print("%-50s" % title, end="") + print("{:<50}".format(title), end="") sys.stdout.flush() t = timer() for n in range(TIMES): @@ -193,7 +191,6 @@ def main(): 'as_dict', 'parent', 'parents', - 'memory_info_ex', 'oneshot', 'pid', 'rlimit', diff --git a/scripts/internal/print_dist.py b/scripts/internal/print_dist.py index 5b6531608..3ff399e22 100755 --- a/scripts/internal/print_dist.py +++ b/scripts/internal/print_dist.py @@ -20,7 +20,7 @@ def __init__(self, path): self._name = os.path.basename(path) def __repr__(self): - return "<%s(name=%s, plat=%s, arch=%s, pyver=%s)>" % ( + return "<{}(name={}, plat={}, arch={}, pyver={})>".format( self.__class__.__name__, self.name, self.platform(), @@ -54,15 +54,17 @@ def platform(self): else: return 'macos' else: - raise ValueError("unknown platform %r" % self.name) + raise ValueError(f"unknown platform {self.name!r}") def arch(self): if self.name.endswith(('x86_64.whl', 'amd64.whl')): - return '64' + return '64-bit' if self.name.endswith(("i686.whl", "win32.whl")): - return '32' + return '32-bit' if self.name.endswith("arm64.whl"): return 'arm64' + if self.name.endswith("aarch64.whl"): + return 'aarch64' return '?' def pyver(self): @@ -104,20 +106,20 @@ def main(): elif path.endswith(".tar.gz"): pkg = Tarball(path) else: - raise ValueError("invalid package %r" % path) + raise ValueError(f"invalid package {path!r}") groups[pkg.platform()].append(pkg) tot_files = 0 tot_size = 0 - templ = "%-120s %7s %7s %7s" + templ = "{:<120} {:>7} {:>8} {:>7}" for platf, pkgs in groups.items(): - ppn = "%s (%s)" % (platf, len(pkgs)) - s = templ % (ppn, "size", "arch", "pyver") + ppn = f"{platf} ({len(pkgs)})" + s = templ.format(ppn, "size", "arch", "pyver") print_color('\n' + s, color=None, bold=True) for pkg in sorted(pkgs, key=lambda x: x.name): tot_files += 1 tot_size += pkg.size() - s = templ % ( + s = templ.format( " " + pkg.name, bytes2human(pkg.size()), pkg.arch(), @@ -129,7 +131,7 @@ def main(): print_color(s, color='brown') print_color( - "\n\ntotals: files=%s, size=%s" % (tot_files, bytes2human(tot_size)), + f"\n\ntotals: files={tot_files}, size={bytes2human(tot_size)}", bold=True, ) diff --git a/scripts/internal/print_downloads.py b/scripts/internal/print_downloads.py index b453f15b9..610a20182 100755 --- a/scripts/internal/print_downloads.py +++ b/scripts/internal/print_downloads.py @@ -11,7 +11,6 @@ * https://hugovk.github.io/top-pypi-packages/. """ -from __future__ import print_function import json import os @@ -19,7 +18,7 @@ import subprocess import sys -import pypinfo # NOQA +import pypinfo # noqa: F401 from psutil._common import memoize @@ -49,6 +48,7 @@ def sh(cmd): stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, + env=env, ) stdout, stderr = p.communicate() if p.returncode != 0: @@ -68,7 +68,7 @@ def query(cmd): def top_packages(): global LAST_UPDATE ret = query( - "pypinfo --all --json --days %s --limit %s '' project" % (DAYS, LIMIT) + f"pypinfo --all --json --days {DAYS} --limit {LIMIT} '' project" ) LAST_UPDATE = ret['last_update'] return [(x['project'], x['download_count']) for x in ret['rows']] @@ -81,7 +81,7 @@ def ranking(): if name == PKGNAME: return i i += 1 - raise ValueError("can't find %s" % PKGNAME) + raise ValueError(f"can't find {PKGNAME}") def downloads(): @@ -89,40 +89,40 @@ def downloads(): for name, downloads in data: if name == PKGNAME: return downloads - raise ValueError("can't find %s" % PKGNAME) + raise ValueError(f"can't find {PKGNAME}") def downloads_pyver(): - return query("pypinfo --json --days %s %s pyversion" % (DAYS, PKGNAME)) + return query(f"pypinfo --json --days {DAYS} {PKGNAME} pyversion") def downloads_by_country(): - return query("pypinfo --json --days %s %s country" % (DAYS, PKGNAME)) + return query(f"pypinfo --json --days {DAYS} {PKGNAME} country") def downloads_by_system(): - return query("pypinfo --json --days %s %s system" % (DAYS, PKGNAME)) + return query(f"pypinfo --json --days {DAYS} {PKGNAME} system") def downloads_by_distro(): - return query("pypinfo --json --days %s %s distro" % (DAYS, PKGNAME)) + return query(f"pypinfo --json --days {DAYS} {PKGNAME} distro") # --- print -templ = "| %-30s | %15s |" +templ = "| {:<30} | {:>15} |" def print_row(left, right): if isinstance(right, int): - right = '{:,}'.format(right) - print(templ % (left, right)) + right = f"{right:,}" + print(templ.format(left, right)) def print_header(left, right="Downloads"): print_row(left, right) - s = templ % ("-" * 30, "-" * 15) + s = templ.format("-" * 30, "-" * 15) print("|:" + s[2:-2] + ":|") @@ -142,9 +142,9 @@ def main(): print("# Download stats") 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 + s = f"psutil download statistics of the last {DAYS} days (last update " + s += f"*{LAST_UPDATE}*).\n" + s += f"Generated via [pypistats.py]({GITHUB_SCRIPT_URL}) script.\n" print(s) data = [ @@ -171,4 +171,4 @@ def main(): try: main() finally: - print("bytes billed: %s" % bytes_billed, file=sys.stderr) + print(f"bytes billed: {bytes_billed}", file=sys.stderr) diff --git a/scripts/internal/print_hashes.py b/scripts/internal/print_hashes.py index 5b9cfb209..b8d8b365d 100755 --- a/scripts/internal/print_hashes.py +++ b/scripts/internal/print_hashes.py @@ -31,12 +31,9 @@ def main(): if os.path.isfile(file): md5 = csum(file, "md5") sha256 = csum(file, "sha256") - print( - "%s\nmd5: %s\nsha256: %s\n" - % (os.path.basename(file), md5, sha256) - ) + print(f"{os.path.basename(file)}\nmd5: {md5}\nsha256: {sha256}\n") else: - print("skipping %r (not a file)" % file) + print(f"skipping {file!r} (not a file)") if __name__ == "__main__": diff --git a/scripts/internal/print_timeline.py b/scripts/internal/print_timeline.py index 706601943..940724129 100755 --- a/scripts/internal/print_timeline.py +++ b/scripts/internal/print_timeline.py @@ -14,7 +14,7 @@ - {date}: `{ver} `__ - `what's new `__ - - `diff `__""" # NOQA + `diff `__""" # noqa: E501 def sh(cmd): @@ -24,7 +24,7 @@ def sh(cmd): def get_tag_date(tag): - out = sh(r"git log -1 --format=%ai {}".format(tag)) + out = sh(f"git log -1 --format=%ai {tag}") return out.split(' ')[0] diff --git a/scripts/internal/purge_installation.py b/scripts/internal/purge_installation.py index 55b2f5c50..254adabe0 100755 --- a/scripts/internal/purge_installation.py +++ b/scripts/internal/purge_installation.py @@ -38,4 +38,5 @@ def main(): rmpath(abspath) -main() +if __name__ == "__main__": + main() diff --git a/scripts/internal/test_python2_setup_py.py b/scripts/internal/test_python2_setup_py.py new file mode 100755 index 000000000..612184459 --- /dev/null +++ b/scripts/internal/test_python2_setup_py.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python2 + +# 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. + + +"""Invoke setup.py with Python 2.7, make sure it fails but not due to +SyntaxError, and that it prints a meaningful error message. +""" + +import os +import subprocess +import sys + + +ROOT_DIR = os.path.realpath( + os.path.join(os.path.dirname(__file__), "..", "..") +) + + +def main(): + if sys.version_info[:2] != (2, 7): + raise RuntimeError("this script is supposed to be run with python 2.7") + setup_py = os.path.join(ROOT_DIR, "setup.py") + p = subprocess.Popen( + [sys.executable, setup_py], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, stderr = p.communicate() + assert p.wait() == 1 + assert not stdout, stdout + assert "psutil no longer supports Python 2.7" in stderr, stderr + assert "Latest version supporting Python 2.7 is" in stderr, stderr + + +if __name__ == "__main__": + main() diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py index b789aa80d..41fb6e746 100755 --- a/scripts/internal/winmake.py +++ b/scripts/internal/winmake.py @@ -11,7 +11,6 @@ that they should be deemed illegal! """ -from __future__ import print_function import argparse import atexit @@ -21,43 +20,26 @@ import os import shutil import site -import ssl import subprocess import sys -import tempfile -APPVEYOR = bool(os.environ.get('APPVEYOR')) -PYTHON = sys.executable if APPVEYOR else os.getenv('PYTHON', sys.executable) -RUNNER_PY = 'psutil\\tests\\runner.py' -GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" -PY3 = sys.version_info[0] >= 3 +PYTHON = os.getenv('PYTHON', sys.executable) +PYTEST_ARGS = ["-v", "-s", "--tb=short"] HERE = os.path.abspath(os.path.dirname(__file__)) ROOT_DIR = os.path.realpath(os.path.join(HERE, "..", "..")) -PYPY = '__pypy__' in sys.builtin_module_names -DEPS = [ - "coverage", - "pdbpp", - "pip", - "pyperf", - "pyreadline", - "requests", - "setuptools", - "wheel", -] - -if sys.version_info[0] < 3: - DEPS.append('mock') - DEPS.append('ipaddress') - DEPS.append('enum34') - -if not PYPY: - DEPS.append("pywin32") - DEPS.append("wmi") +WINDOWS = os.name == "nt" + + +sys.path.insert(0, ROOT_DIR) # so that we can import setup.py + +import setup # noqa: E402 + + +TEST_DEPS = setup.TEST_DEPS +DEV_DEPS = setup.DEV_DEPS _cmds = {} -if PY3: - basestring = str GREEN = 2 LIGHTBLUE = 3 @@ -75,9 +57,8 @@ def safe_print(text, file=sys.stdout): """Prints a (unicode) string to the console, encoded depending on the stdout/file encoding (eg. cp437 on Windows). This is to avoid encoding errors in case of funky path names. - Works with Python 2 and 3. """ - if not isinstance(text, basestring): + if not isinstance(text, str): return print(text, file=file) try: file.write(text) @@ -101,6 +82,8 @@ def stderr_handle(): def win_colorprint(s, color=LIGHTBLUE): + if not WINDOWS: + return print(s) color += 8 # bold handle = stderr_handle() SetConsoleTextAttribute = ctypes.windll.Kernel32.SetConsoleTextAttribute @@ -112,39 +95,17 @@ def win_colorprint(s, color=LIGHTBLUE): def sh(cmd, nolog=False): + assert isinstance(cmd, list), repr(cmd) if not nolog: - safe_print("cmd: " + cmd) - p = subprocess.Popen( - cmd, shell=True, env=os.environ, cwd=os.getcwd() # noqa - ) - p.communicate() + safe_print(f"cmd: {cmd}") + p = subprocess.Popen(cmd, env=os.environ, universal_newlines=True) + p.communicate() # print stdout/stderr in real time if p.returncode != 0: sys.exit(p.returncode) def rm(pattern, directory=False): """Recursively remove a file or dir by pattern.""" - - def safe_remove(path): - try: - os.remove(path) - except OSError as err: - if err.errno != errno.ENOENT: - raise - else: - safe_print("rm %s" % path) - - def safe_rmtree(path): - def onerror(fun, path, excinfo): - exc = excinfo[1] - if exc.errno != errno.ENOENT: - raise # noqa: PLE0704 - - existed = os.path.isdir(path) - shutil.rmtree(path, onerror=onerror) - if existed: - safe_print("rmdir -f %s" % path) - if "*" not in pattern: if directory: safe_rmtree(pattern) @@ -160,10 +121,10 @@ def onerror(fun, path, excinfo): for name in found: path = os.path.join(root, name) if directory: - safe_print("rmdir -f %s" % path) + safe_print(f"rmdir -f {path}") safe_rmtree(path) else: - safe_print("rm %s" % path) + safe_print(f"rm {path}") safe_remove(path) @@ -174,19 +135,14 @@ def safe_remove(path): if err.errno != errno.ENOENT: raise else: - safe_print("rm %s" % path) + safe_print(f"rm {path}") def safe_rmtree(path): - def onerror(fun, path, excinfo): - exc = excinfo[1] - if exc.errno != errno.ENOENT: - raise # noqa: PLE0704 - existed = os.path.isdir(path) - shutil.rmtree(path, onerror=onerror) - if existed: - safe_print("rmdir -f %s" % path) + shutil.rmtree(path, ignore_errors=True) + if existed and not os.path.isdir(path): + safe_print(f"rmdir -f {path}") def recursive_rm(*patterns): @@ -214,21 +170,19 @@ def build(): """Build / compile.""" # Make sure setuptools is installed (needed for 'develop' / # edit mode). - sh('%s -c "import setuptools"' % PYTHON) + sh([PYTHON, "-c", "import setuptools"]) # "build_ext -i" copies compiled *.pyd files in ./psutil directory in # order to allow "import psutil" when using the interactive interpreter # from within psutil root directory. cmd = [PYTHON, "setup.py", "build_ext", "-i"] - if sys.version_info[:2] >= (3, 6) and (os.cpu_count() or 1) > 1: + if os.cpu_count() or 1 > 1: # noqa: PLR0133 cmd += ['--parallel', str(os.cpu_count())] # Print coloured warnings in real time. p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) try: for line in iter(p.stdout.readline, b''): - if PY3: - line = line.decode() - line = line.strip() + line = line.decode().strip() if 'warning' in line: win_colorprint(line, YELLOW) elif 'error' in line: @@ -245,55 +199,31 @@ def build(): p.wait() # Make sure it actually worked. - sh('%s -c "import psutil"' % PYTHON) + sh([PYTHON, "-c", "import psutil"]) win_colorprint("build + import successful", GREEN) def wheel(): """Create wheel file.""" build() - sh("%s setup.py bdist_wheel" % PYTHON) + sh([PYTHON, "setup.py", "bdist_wheel"]) def upload_wheels(): """Upload wheel files on PyPI.""" build() - sh("%s -m twine upload dist/*.whl" % PYTHON) + sh([PYTHON, "-m", "twine", "upload", "dist/*.whl"]) def install_pip(): """Install pip.""" - try: - sh('%s -c "import pip"' % PYTHON) - except SystemExit: - if PY3: - from urllib.request import urlopen - else: - from urllib2 import urlopen - - if hasattr(ssl, '_create_unverified_context'): - ctx = ssl._create_unverified_context() - else: - ctx = None - kw = dict(context=ctx) if ctx else {} - safe_print("downloading %s" % GET_PIP_URL) - req = urlopen(GET_PIP_URL, **kw) - data = req.read() - - tfile = os.path.join(tempfile.gettempdir(), 'get-pip.py') - with open(tfile, 'wb') as f: - f.write(data) - - try: - sh('%s %s --user' % (PYTHON, tfile)) - finally: - os.remove(tfile) + sh([PYTHON, os.path.join(HERE, "install_pip.py")]) def install(): """Install in develop / edit mode.""" build() - sh("%s setup.py develop" % PYTHON) + sh([PYTHON, "setup.py", "develop"]) def uninstall(): @@ -310,11 +240,11 @@ def uninstall(): os.chdir('C:\\') while True: try: - import psutil # NOQA + import psutil # noqa: F401 except ImportError: break else: - sh("%s -m pip uninstall -y psutil" % PYTHON) + sh([PYTHON, "-m", "pip", "uninstall", "-y", "psutil"]) finally: os.chdir(here) @@ -339,7 +269,7 @@ def uninstall(): if 'psutil' not in line: f.write(line) else: - print("removed line %r from %r" % (line, path)) + print(f"removed line {line!r} from {path!r}") def clean(): @@ -368,99 +298,115 @@ def clean(): safe_rmtree("tmp") -def setup_dev_env(): +def install_pydeps_test(): + """Install useful deps.""" + install_pip() + install_git_hooks() + sh([PYTHON, "-m", "pip", "install", "--user", "-U"] + TEST_DEPS) + + +def install_pydeps_dev(): """Install useful deps.""" install_pip() install_git_hooks() - sh("%s -m pip install -U %s" % (PYTHON, " ".join(DEPS))) + sh([PYTHON, "-m", "pip", "install", "--user", "-U"] + DEV_DEPS) -def test(name=RUNNER_PY): +def test(args=None): """Run tests.""" build() - sh("%s %s" % (PYTHON, name)) + args = args or [] + sh( + [PYTHON, "-m", "pytest", "--ignore=psutil/tests/test_memleaks.py"] + + PYTEST_ARGS + + args + ) + + +def test_parallel(): + test(["-n", "auto", "--dist", "loadgroup"]) def coverage(): """Run coverage tests.""" # Note: coverage options are controlled by .coveragerc file build() - sh("%s -m coverage run %s" % (PYTHON, RUNNER_PY)) - sh("%s -m coverage report" % PYTHON) - sh("%s -m coverage html" % PYTHON) - sh("%s -m webbrowser -t htmlcov/index.html" % PYTHON) + sh([PYTHON, "-m", "coverage", "run", "-m", "pytest"] + PYTEST_ARGS) + sh([PYTHON, "-m", "coverage", "report"]) + sh([PYTHON, "-m", "coverage", "html"]) + sh([PYTHON, "-m", "webbrowser", "-t", "htmlcov/index.html"]) def test_process(): """Run process tests.""" build() - sh("%s psutil\\tests\\test_process.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_process.py"]) def test_process_all(): """Run process all tests.""" build() - sh("%s psutil\\tests\\test_process_all.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_process_all.py"]) def test_system(): """Run system tests.""" build() - sh("%s psutil\\tests\\test_system.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_system.py"]) def test_platform(): """Run windows only tests.""" build() - sh("%s psutil\\tests\\test_windows.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_windows.py"]) def test_misc(): """Run misc tests.""" build() - sh("%s psutil\\tests\\test_misc.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_misc.py"]) + + +def test_scripts(): + """Run scripts tests.""" + build() + sh([PYTHON, "psutil\\tests\\test_scripts.py"]) def test_unicode(): """Run unicode tests.""" build() - sh("%s psutil\\tests\\test_unicode.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_unicode.py"]) def test_connections(): """Run connections tests.""" build() - sh("%s psutil\\tests\\test_connections.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_connections.py"]) def test_contracts(): """Run contracts tests.""" build() - sh("%s psutil\\tests\\test_contracts.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_contracts.py"]) def test_testutils(): """Run test utilities tests.""" build() - sh("%s psutil\\tests\\test_testutils.py" % PYTHON) - - -def test_by_name(name): - """Run test by name.""" - build() - sh("%s -m unittest -v %s" % (PYTHON, name)) + sh([PYTHON, "psutil\\tests\\test_testutils.py"]) def test_last_failed(): """Re-run tests which failed on last run.""" build() - sh("%s %s --last-failed" % (PYTHON, RUNNER_PY)) + test(["--last-failed"]) def test_memleaks(): """Run memory leaks tests.""" build() - sh("%s psutil\\tests\\test_memleaks.py" % PYTHON) + sh([PYTHON, "psutil\\tests\\test_memleaks.py"]) def install_git_hooks(): @@ -479,32 +425,32 @@ def install_git_hooks(): def bench_oneshot(): """Benchmarks for oneshot() ctx manager (see #799).""" - sh("%s -Wa scripts\\internal\\bench_oneshot.py" % PYTHON) + sh([PYTHON, "scripts\\internal\\bench_oneshot.py"]) def bench_oneshot_2(): """Same as above but using perf module (supposed to be more precise).""" - sh("%s -Wa scripts\\internal\\bench_oneshot_2.py" % PYTHON) + sh([PYTHON, "scripts\\internal\\bench_oneshot_2.py"]) def print_access_denied(): """Print AD exceptions raised by all Process methods.""" build() - sh("%s -Wa scripts\\internal\\print_access_denied.py" % PYTHON) + sh([PYTHON, "scripts\\internal\\print_access_denied.py"]) def print_api_speed(): """Benchmark all API calls.""" build() - sh("%s -Wa scripts\\internal\\print_api_speed.py" % PYTHON) + sh([PYTHON, "scripts\\internal\\print_api_speed.py"]) -def download_appveyor_wheels(): - """Download appveyor wheels.""" - sh( - "%s -Wa scripts\\internal\\download_wheels_appveyor.py " - "--user giampaolo --project psutil" % PYTHON - ) +def print_sysinfo(): + """Print system info.""" + build() + from psutil.tests import print_sysinfo + + print_sysinfo() def generate_manifest(): @@ -523,18 +469,12 @@ def get_python(path): # try to look for a python installation given a shortcut name path = path.replace('.', '') vers = ( - '27', - '27-32', - '27-64', - '310-32', '310-64', - '311-32', '311-64', - '312-32', '312-64', ) for v in vers: - pypath = r'C:\\python%s\python.exe' % v + pypath = rf"C:\\python{v}\python.exe" if path in pypath and os.path.isfile(pypath): return pypath @@ -549,15 +489,17 @@ def parse_args(): sp.add_parser('build', help="build") sp.add_parser('clean', help="deletes dev files") sp.add_parser('coverage', help="run coverage tests.") - sp.add_parser('download-appveyor-wheels', help="download wheels.") sp.add_parser('generate-manifest', help="generate MANIFEST.in file") sp.add_parser('help', help="print this help") sp.add_parser('install', help="build + install in develop/edit mode") sp.add_parser('install-git-hooks', help="install GIT pre-commit hook") sp.add_parser('install-pip', help="install pip") + sp.add_parser('install-pydeps-dev', help="install dev python deps") + sp.add_parser('install-pydeps-test', help="install python test deps") sp.add_parser('print-access-denied', help="print AD exceptions") sp.add_parser('print-api-speed', help="benchmark all API calls") - sp.add_parser('setup-dev-env', help="install deps") + sp.add_parser('print-sysinfo', help="print system info") + sp.add_parser('test-parallel', help="run tests in parallel") test = sp.add_parser('test', help="[ARG] run tests") test_by_name = sp.add_parser('test-by-name', help=" run test by name") sp.add_parser('test-connections', help="run connections tests") @@ -567,6 +509,7 @@ def parse_args(): ) sp.add_parser('test-memleaks', help="run memory leaks tests") sp.add_parser('test-misc', help="run misc tests") + sp.add_parser('test-scripts', help="run scripts tests") sp.add_parser('test-platform', help="run windows only tests") sp.add_parser('test-process', help="run process tests") sp.add_parser('test-process-all', help="run process all tests") @@ -596,23 +539,17 @@ def main(): PYTHON = get_python(args.python) if not PYTHON: return sys.exit( - "can't find any python installation matching %r" % args.python + f"can't find any python installation matching {args.python!r}" ) os.putenv('PYTHON', PYTHON) win_colorprint("using " + PYTHON) fname = args.command.replace('-', '_') fun = getattr(sys.modules[__name__], fname) # err if fun not defined - funargs = [] - # mandatory args - if args.command in ('test-by-name', 'test-script'): - if not args.arg: - sys.exit('command needs an argument') - funargs = [args.arg] - # optional args if args.command == 'test' and args.arg: - funargs = [args.arg] - fun(*funargs) + sh([PYTHON, args.arg]) # test a script + else: + fun() if __name__ == '__main__': diff --git a/scripts/iotop.py b/scripts/iotop.py index 23c3a376e..93f46d157 100755 --- a/scripts/iotop.py +++ b/scripts/iotop.py @@ -71,7 +71,7 @@ def poll(interval): """ # first get a list of all processes and disk io counters procs = list(psutil.process_iter()) - for p in procs[:]: + for p in procs.copy(): try: p._before = p.io_counters() except psutil.Error: @@ -83,7 +83,7 @@ def poll(interval): time.sleep(interval) # then retrieve the same info again - for p in procs[:]: + for p in procs.copy(): with p.oneshot(): try: p._after = p.io_counters() @@ -115,20 +115,20 @@ def poll(interval): def refresh_window(procs, disks_read, disks_write): """Print results on screen by using curses.""" curses.endwin() - templ = "%-5s %-7s %11s %11s %s" + templ = "{:<5} {:<7} {:>11} {:>11} {}" win.erase() - disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" % ( + disks_tot = "Total DISK READ: {} | Total DISK WRITE: {}".format( bytes2human(disks_read), bytes2human(disks_write), ) printl(disks_tot) - header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND") + header = templ.format("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND") printl(header, highlight=True) for p in procs: - line = templ % ( + line = templ.format( p.pid, p._username[:7], bytes2human(p._read_per_sec), diff --git a/scripts/killall.py b/scripts/killall.py index 592b8d6e3..532e8b15c 100755 --- a/scripts/killall.py +++ b/scripts/killall.py @@ -14,7 +14,7 @@ def main(): if len(sys.argv) != 2: - sys.exit('usage: %s name' % __file__) + sys.exit(f"usage: {__file__} name") else: name = sys.argv[1] @@ -24,7 +24,7 @@ def main(): proc.kill() killed.append(proc.pid) if not killed: - sys.exit('%s: no process found' % name) + sys.exit(f"{name}: no process found") else: sys.exit(0) diff --git a/scripts/meminfo.py b/scripts/meminfo.py index a13b7e00b..6bee96998 100755 --- a/scripts/meminfo.py +++ b/scripts/meminfo.py @@ -38,7 +38,7 @@ def pprint_ntuple(nt): value = getattr(nt, name) if name != 'percent': value = bytes2human(value) - print('%-10s : %7s' % (name.capitalize(), value)) + print('{:<10} : {:>7}'.format(name.capitalize(), value)) def main(): diff --git a/scripts/netstat.py b/scripts/netstat.py index b58900937..56924e6ae 100755 --- a/scripts/netstat.py +++ b/scripts/netstat.py @@ -37,8 +37,8 @@ def main(): - templ = "%-5s %-30s %-30s %-13s %-6s %s" - header = templ % ( + templ = "{:<5} {:<30} {:<30} {:<13} {:<6} {}" + header = templ.format( "Proto", "Local address", "Remote address", @@ -51,12 +51,12 @@ def main(): for p in psutil.process_iter(['pid', 'name']): proc_names[p.info['pid']] = p.info['name'] for c in psutil.net_connections(kind='inet'): - laddr = "%s:%s" % (c.laddr) + laddr = "{}:{}".format(*c.laddr) raddr = "" if c.raddr: - raddr = "%s:%s" % (c.raddr) + raddr = "{}:{}".format(*c.raddr) name = proc_names.get(c.pid, '?') or '' - line = templ % ( + line = templ.format( proto_map[(c.family, c.type)], laddr, raddr or AD, diff --git a/scripts/nettop.py b/scripts/nettop.py index eafcd0f5d..222771edc 100755 --- a/scripts/nettop.py +++ b/scripts/nettop.py @@ -81,8 +81,7 @@ def refresh_window(tot_before, tot_after, pnic_before, pnic_after): # totals printl( - "total bytes: sent: %-10s received: %s" - % ( + "total bytes: sent: {:<10} received: {}".format( bytes2human(tot_after.bytes_sent), bytes2human(tot_after.bytes_recv), ) @@ -96,27 +95,27 @@ def refresh_window(tot_before, tot_after, pnic_before, pnic_after): for name in nic_names: stats_before = pnic_before[name] stats_after = pnic_after[name] - templ = "%-15s %15s %15s" + templ = "{:<15s} {:>15} {:>15}" # fmt: off - printl(templ % (name, "TOTAL", "PER-SEC"), highlight=True) - printl(templ % ( + printl(templ.format(name, "TOTAL", "PER-SEC"), highlight=True) + printl(templ.format( "bytes-sent", bytes2human(stats_after.bytes_sent), bytes2human( stats_after.bytes_sent - stats_before.bytes_sent) + '/s', )) - printl(templ % ( + printl(templ.format( "bytes-recv", bytes2human(stats_after.bytes_recv), bytes2human( stats_after.bytes_recv - stats_before.bytes_recv) + '/s', )) - printl(templ % ( + printl(templ.format( "pkts-sent", stats_after.packets_sent, stats_after.packets_sent - stats_before.packets_sent, )) - printl(templ % ( + printl(templ.format( "pkts-recv", stats_after.packets_recv, stats_after.packets_recv - stats_before.packets_recv, diff --git a/scripts/pidof.py b/scripts/pidof.py index 662d5d657..c5af54c85 100755 --- a/scripts/pidof.py +++ b/scripts/pidof.py @@ -11,7 +11,6 @@ 1140 1138 1136 1134 1133 1129 1127 1125 1121 1120 1119 """ -from __future__ import print_function import sys @@ -22,10 +21,8 @@ def pidof(pgname): pids = [] for proc in psutil.process_iter(['name', 'cmdline']): # search for matches in the process name and cmdline - if ( - proc.info['name'] == pgname - or proc.info['cmdline'] - and proc.info['cmdline'][0] == pgname + if proc.info["name"] == pgname or ( + proc.info["cmdline"] and proc.info["cmdline"][0] == pgname ): pids.append(str(proc.pid)) return pids @@ -33,7 +30,7 @@ def pidof(pgname): def main(): if len(sys.argv) != 2: - sys.exit('usage: %s pgname' % __file__) + sys.exit(f"usage: {__file__} pgname") else: pgname = sys.argv[1] pids = pidof(pgname) diff --git a/scripts/pmap.py b/scripts/pmap.py index 577e4b294..54d53bebd 100755 --- a/scripts/pmap.py +++ b/scripts/pmap.py @@ -9,9 +9,9 @@ $ python3 scripts/pmap.py 32402 Address RSS Mode Mapping -0000000000400000 1200K r-xp /usr/bin/python2.7 -0000000000838000 4K r--p /usr/bin/python2.7 -0000000000839000 304K rw-p /usr/bin/python2.7 +0000000000400000 1200K r-xp /usr/bin/python3.7 +0000000000838000 4K r--p /usr/bin/python3.7 +0000000000839000 304K rw-p /usr/bin/python3.7 00000000008ae000 68K rw-p [anon] 000000000275e000 5396K rw-p [heap] 00002b29bb1e0000 124K r-xp /lib/x86_64-linux-gnu/ld-2.17.so @@ -28,15 +28,15 @@ ... """ +import shutil import sys import psutil from psutil._common import bytes2human -from psutil._compat import get_terminal_size def safe_print(s): - s = s[: get_terminal_size()[0]] + s = s[: shutil.get_terminal_size()[0]] try: print(s) except UnicodeEncodeError: @@ -47,12 +47,12 @@ def main(): if len(sys.argv) != 2: sys.exit('usage: pmap ') p = psutil.Process(int(sys.argv[1])) - templ = "%-20s %10s %-7s %s" - print(templ % ("Address", "RSS", "Mode", "Mapping")) + templ = "{:<20} {:>10} {:<7} {}" + print(templ.format("Address", "RSS", "Mode", "Mapping")) total_rss = 0 for m in p.memory_maps(grouped=False): total_rss += m.rss - line = templ % ( + line = templ.format( m.addr.split('-')[0].zfill(16), bytes2human(m.rss), m.perms, @@ -60,8 +60,8 @@ def main(): ) safe_print(line) print("-" * 31) - print(templ % ("Total", bytes2human(total_rss), '', '')) - safe_print("PID = %s, name = %s" % (p.pid, p.name())) + print(templ.format("Total", bytes2human(total_rss), "", "")) + safe_print(f"PID = {p.pid}, name = {p.name()}") if __name__ == '__main__': diff --git a/scripts/procinfo.py b/scripts/procinfo.py index 24004a960..4a328d506 100755 --- a/scripts/procinfo.py +++ b/scripts/procinfo.py @@ -120,9 +120,9 @@ def print_(a, b): if sys.stdout.isatty() and psutil.POSIX: - fmt = '\x1b[1;32m%-13s\x1b[0m %s' % (a, b) + fmt = "\x1b[1;32m{:<13}\x1b[0m {}".format(a, b) else: - fmt = '%-11s %s' % (a, b) + fmt = "{:<11} {}".format(a, b) print(fmt) @@ -130,10 +130,10 @@ def str_ntuple(nt, convert_bytes=False): if nt == ACCESS_DENIED: return "" if not convert_bytes: - return ", ".join(["%s=%s" % (x, getattr(nt, x)) for x in nt._fields]) + return ", ".join([f"{x}={getattr(nt, x)}" for x in nt._fields]) else: return ", ".join( - ["%s=%s" % (x, bytes2human(getattr(nt, x))) for x in nt._fields] + [f"{x}={bytes2human(getattr(nt, x))}" for x in nt._fields] ) @@ -148,7 +148,7 @@ def run(pid, verbose=False): with proc.oneshot(): try: parent = proc.parent() - parent = '(%s)' % parent.name() if parent else '' + parent = f"({parent.name()})" if parent else "" except psutil.Error: parent = '' try: @@ -165,14 +165,14 @@ def run(pid, verbose=False): # here we go print_('pid', pinfo['pid']) print_('name', pinfo['name']) - print_('parent', '%s %s' % (pinfo['ppid'], parent)) + print_('parent', f"{pinfo['ppid']} {parent}") print_('exe', pinfo['exe']) print_('cwd', pinfo['cwd']) print_('cmdline', ' '.join(pinfo['cmdline'])) print_('started', started) cpu_tot_time = datetime.timedelta(seconds=sum(pinfo['cpu_times'])) - cpu_tot_time = "%s:%s.%s" % ( + cpu_tot_time = "{}:{}.{}".format( cpu_tot_time.seconds // 60 % 60, str(cpu_tot_time.seconds % 60).zfill(2), str(cpu_tot_time.microseconds)[:2], @@ -207,7 +207,7 @@ def run(pid, verbose=False): else: print_( "ionice", - "class=%s, value=%s" % (str(ionice.ioclass), ionice.value), + f"class={ionice.ioclass}, value={ionice.value}", ) print_('num-threads', pinfo['num_threads']) @@ -221,13 +221,13 @@ def run(pid, verbose=False): if 'num_ctx_switches' in pinfo: print_("ctx-switches", str_ntuple(pinfo['num_ctx_switches'])) if pinfo['children']: - template = "%-6s %s" - print_("children", template % ("PID", "NAME")) + template = "{:<6} {}" + print_("children", template.format("PID", "NAME")) for child in pinfo['children']: try: - print_('', template % (child.pid, child.name())) + print_("", template.format(child.pid, child.name())) except psutil.AccessDenied: - print_('', template % (child.pid, "")) + print_("", template.format(child.pid, "")) except psutil.NoSuchProcess: pass @@ -242,10 +242,10 @@ def run(pid, verbose=False): print_('open-files', '') if pinfo['net_connections']: - template = '%-5s %-25s %-25s %s' + template = "{:<5} {:<25} {:<25} {}" print_( 'connections', - template % ('PROTO', 'LOCAL ADDR', 'REMOTE ADDR', 'STATUS'), + template.format("PROTO", "LOCAL ADDR", "REMOTE ADDR", "STATUS"), ) for conn in pinfo['net_connections']: if conn.type == socket.SOCK_STREAM: @@ -259,10 +259,10 @@ def run(pid, verbose=False): rip, rport = '*', '*' else: rip, rport = conn.raddr - line = template % ( + line = template.format( type, - "%s:%s" % (lip, lport), - "%s:%s" % (rip, rport), + f"{lip}:{lport}", + f"{rip}:{rport}", conn.status, ) print_('', line) @@ -270,14 +270,14 @@ def run(pid, verbose=False): print_('connections', '') if pinfo['threads'] and len(pinfo['threads']) > 1: - template = "%-5s %12s %12s" - print_('threads', template % ("TID", "USER", "SYSTEM")) + template = "{:<5} {:>12} {:>12}" + print_("threads", template.format("TID", "USER", "SYSTEM")) for i, thread in enumerate(pinfo['threads']): if not verbose and i >= NON_VERBOSE_ITERATIONS: print_("", "[...]") break - print_('', template % thread) - print_('', "total=%s" % len(pinfo['threads'])) + print_("", template.format(*thread)) + print_('', f"total={len(pinfo['threads'])}") else: print_('threads', '') @@ -292,8 +292,8 @@ def run(pid, verbose=False): else: resources.append((res_name, soft, hard)) if resources: - template = "%-12s %15s %15s" - print_("res-limits", template % ("RLIMIT", "SOFT", "HARD")) + template = "{:<12} {:>15} {:>15}" + print_("res-limits", template.format("RLIMIT", "SOFT", "HARD")) for res_name, soft, hard in resources: if soft == psutil.RLIM_INFINITY: soft = "infinity" @@ -301,28 +301,29 @@ def run(pid, verbose=False): hard = "infinity" print_( '', - template - % (RLIMITS_MAP.get(res_name, res_name), soft, hard), + template.format( + RLIMITS_MAP.get(res_name, res_name), soft, hard + ), ) if hasattr(proc, "environ") and pinfo['environ']: - template = "%-25s %s" - print_("environ", template % ("NAME", "VALUE")) + template = "{:<25} {}" + print_("environ", template.format("NAME", "VALUE")) for i, k in enumerate(sorted(pinfo['environ'])): if not verbose and i >= NON_VERBOSE_ITERATIONS: print_("", "[...]") break - print_("", template % (k, pinfo['environ'][k])) + print_("", template.format(k, pinfo["environ"][k])) if pinfo.get('memory_maps', None): - template = "%-8s %s" - print_("mem-maps", template % ("RSS", "PATH")) + template = "{:<8} {}" + print_("mem-maps", template.format("RSS", "PATH")) maps = sorted(pinfo['memory_maps'], key=lambda x: x.rss, reverse=True) for i, region in enumerate(maps): if not verbose and i >= NON_VERBOSE_ITERATIONS: print_("", "[...]") break - print_("", template % (bytes2human(region.rss), region.path)) + print_("", template.format(bytes2human(region.rss), region.path)) def main(): diff --git a/scripts/procsmem.py b/scripts/procsmem.py index eec5cd51a..bf58f203d 100755 --- a/scripts/procsmem.py +++ b/scripts/procsmem.py @@ -35,7 +35,6 @@ """ -from __future__ import print_function import sys @@ -54,8 +53,8 @@ def convert_bytes(n): for s in reversed(symbols): if n >= prefix[s]: value = float(n) / prefix[s] - return '%.1f%s' % (value, s) - return "%sB" % n + return f"{value:.1f}{s}" + return f"{n}B" def main(): @@ -81,12 +80,12 @@ def main(): procs.append(p) procs.sort(key=lambda p: p._uss) - templ = "%-7s %-7s %7s %7s %7s %7s %7s" - print(templ % ("PID", "User", "USS", "PSS", "Swap", "RSS", "Cmdline")) + templ = "{:<7} {:<7} {:>7} {:>7} {:>7} {:>7} {:>7}" + print(templ.format("PID", "User", "USS", "PSS", "Swap", "RSS", "Cmdline")) print("=" * 78) for p in procs[:86]: cmd = " ".join(p._info["cmdline"])[:50] if p._info["cmdline"] else "" - line = templ % ( + line = templ.format( p.pid, p._info["username"][:7] if p._info["username"] else "", convert_bytes(p._uss), @@ -98,7 +97,7 @@ def main(): print(line) if ad_pids: print( - "warning: access denied for %s pids" % (len(ad_pids)), + f"warning: access denied for {len(ad_pids)} pids", file=sys.stderr, ) diff --git a/scripts/ps.py b/scripts/ps.py index f07b56865..585eca2f4 100755 --- a/scripts/ps.py +++ b/scripts/ps.py @@ -32,21 +32,21 @@ """ import datetime +import shutil import time import psutil from psutil._common import bytes2human -from psutil._compat import get_terminal_size def main(): today_day = datetime.date.today() # fmt: off - templ = "%-10s %5s %5s %7s %7s %5s %6s %6s %6s %s" + templ = "{:<10} {:>5} {:>5} {:>7} {:>7} {:>5} {:>6} {:>6} {:>6} {}" attrs = ['pid', 'memory_percent', 'name', 'cmdline', 'cpu_times', 'create_time', 'memory_info', 'status', 'nice', 'username'] - print(templ % ("USER", "PID", "%MEM", "VSZ", "RSS", "NICE", - "STATUS", "START", "TIME", "CMDLINE")) + print(templ.format("USER", "PID", "%MEM", "VSZ", "RSS", "NICE", + "STATUS", "START", "TIME", "CMDLINE")) # fmt: on for p in psutil.process_iter(attrs, ad_value=None): if p.info['create_time']: @@ -97,7 +97,7 @@ def main(): cmdline = p.info['name'] status = p.info['status'][:5] if p.info['status'] else '' - line = templ % ( + line = templ.format( user, p.info['pid'], memp, @@ -109,7 +109,7 @@ def main(): cputime, cmdline, ) - print(line[: get_terminal_size()[0]]) + print(line[: shutil.get_terminal_size()[0]]) if __name__ == '__main__': diff --git a/scripts/pstree.py b/scripts/pstree.py index e873e467d..51e3ee6cb 100755 --- a/scripts/pstree.py +++ b/scripts/pstree.py @@ -27,7 +27,6 @@ ... """ -from __future__ import print_function import collections import sys diff --git a/scripts/sensors.py b/scripts/sensors.py index 668cca0a2..ba7a9edca 100755 --- a/scripts/sensors.py +++ b/scripts/sensors.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- 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 @@ -28,7 +27,6 @@ plugged in: yes """ -from __future__ import print_function import psutil @@ -36,7 +34,7 @@ def secs2hours(secs): mm, ss = divmod(secs, 60) hh, mm = divmod(mm, 60) - return "%d:%02d:%02d" % (hh, mm, ss) + return f"{int(hh)}:{int(mm):02}:{int(ss):02}" def main(): @@ -61,7 +59,7 @@ def main(): if name in temps: print(" Temperatures:") for entry in temps[name]: - s = " %-20s %s°C (high=%s°C, critical=%s°C)" % ( + s = " {:<20} {}°C (high={}°C, critical={}°C)".format( entry.label or name, entry.current, entry.high, @@ -73,23 +71,25 @@ def main(): print(" Fans:") for entry in fans[name]: print( - " %-20s %s RPM" - % (entry.label or name, entry.current) + " {:<20} {} RPM".format( + entry.label or name, entry.current + ) ) # Battery. if battery: print("Battery:") - print(" charge: %s%%" % round(battery.percent, 2)) + print(f" charge: {round(battery.percent, 2)}%") if battery.power_plugged: print( - " status: %s" - % ("charging" if battery.percent < 100 else "fully charged") + " status: {}".format( + "charging" if battery.percent < 100 else "fully charged" + ) ) print(" plugged in: yes") else: - print(" left: %s" % secs2hours(battery.secsleft)) - print(" status: %s" % "discharging") + print(f" left: {secs2hours(battery.secsleft)}") + print(" status: discharging") print(" plugged in: no") diff --git a/scripts/temperatures.py b/scripts/temperatures.py index 118ebc265..6bc078766 100755 --- a/scripts/temperatures.py +++ b/scripts/temperatures.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- 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 @@ -22,7 +21,6 @@ Core 3 54.0 °C (high = 100.0 °C, critical = 100.0 °C) """ -from __future__ import print_function import sys @@ -38,7 +36,7 @@ def main(): for name, entries in temps.items(): print(name) for entry in entries: - line = " %-20s %s °C (high = %s °C, critical = %s °C)" % ( + line = " {:<20} {} °C (high = {} °C, critical = %{} °C)".format( entry.label or name, entry.current, entry.high, diff --git a/scripts/top.py b/scripts/top.py index c0687ae1f..f772bdf00 100755 --- a/scripts/top.py +++ b/scripts/top.py @@ -129,13 +129,15 @@ def get_dashes(perc): percs = psutil.cpu_percent(interval=0, percpu=True) for cpu_num, perc in enumerate(percs): dashes, empty_dashes = get_dashes(perc) - line = " CPU%-2s [%s%s] %5s%%" % (cpu_num, dashes, empty_dashes, perc) + line = " CPU{:<2} [{}{}] {:>5}%".format( + cpu_num, dashes, empty_dashes, perc + ) printl(line, color=get_color(perc)) # memory usage mem = psutil.virtual_memory() dashes, empty_dashes = get_dashes(mem.percent) - line = " Mem [%s%s] %5s%% %6s / %s" % ( + line = " Mem [{}{}] {:>5}% {:>6} / {}".format( dashes, empty_dashes, mem.percent, @@ -147,7 +149,7 @@ def get_dashes(perc): # swap usage swap = psutil.swap_memory() dashes, empty_dashes = get_dashes(swap.percent) - line = " Swap [%s%s] %5s%% %6s / %s" % ( + line = " Swap [{}{}] {:>5}% {:>6} / {}".format( dashes, empty_dashes, swap.percent, @@ -160,15 +162,15 @@ def get_dashes(perc): st = [] for x, y in procs_status.items(): if y: - st.append("%s=%s" % (x, y)) - st.sort(key=lambda x: x[:3] in ('run', 'sle'), reverse=1) - printl(" Processes: %s (%s)" % (num_procs, ', '.join(st))) + st.append(f"{x}={y}") + st.sort(key=lambda x: x[:3] in {'run', 'sle'}, reverse=1) + printl(f" Processes: {num_procs} ({', '.join(st)})") # load average, uptime uptime = datetime.datetime.now() - datetime.datetime.fromtimestamp( psutil.boot_time() ) av1, av2, av3 = psutil.getloadavg() - line = " Load average: %.2f %.2f %.2f Uptime: %s" % ( + line = " Load average: {:.2f} {:.2f} {:.2f} Uptime: {}".format( av1, av2, av3, @@ -180,9 +182,9 @@ def get_dashes(perc): def refresh_window(procs, procs_status): """Print results on screen by using curses.""" curses.endwin() - templ = "%-6s %-8s %4s %6s %6s %5s %5s %9s %2s" + templ = "{:<6} {:<8} {:>4} {:>6} {:>6} {:>5} {:>5} {:>9} {:>2}" win.erase() - header = templ % ( + header = templ.format( "PID", "USER", "NI", @@ -201,7 +203,7 @@ def refresh_window(procs, procs_status): # is expressed as: "mm:ss.ms" if p.dict['cpu_times'] is not None: ctime = datetime.timedelta(seconds=sum(p.dict['cpu_times'])) - ctime = "%s:%s.%s" % ( + ctime = "{}:{}.{}".format( ctime.seconds // 60 % 60, str(ctime.seconds % 60).zfill(2), str(ctime.microseconds)[:2], @@ -215,7 +217,7 @@ def refresh_window(procs, procs_status): if p.dict['cpu_percent'] is None: p.dict['cpu_percent'] = '' username = p.dict['username'][:8] if p.dict['username'] else '' - line = templ % ( + line = templ.format( p.pid, username, p.dict['nice'], diff --git a/scripts/who.py b/scripts/who.py index 64a948107..d45954723 100755 --- a/scripts/who.py +++ b/scripts/who.py @@ -21,11 +21,11 @@ def main(): users = psutil.users() for user in users: proc_name = psutil.Process(user.pid).name() if user.pid else "" - line = "%-12s %-10s %-10s %-14s %s" % ( + line = "{:<12} {:<10} {:<10} {:<14} {}".format( user.name, user.terminal or '-', datetime.fromtimestamp(user.started).strftime("%Y-%m-%d %H:%M"), - "(%s)" % user.host if user.host else "", + f"({user.host or ''})", proc_name, ) print(line) diff --git a/scripts/winservices.py b/scripts/winservices.py index 216d0a652..7df589459 100755 --- a/scripts/winservices.py +++ b/scripts/winservices.py @@ -43,15 +43,15 @@ def main(): for service in psutil.win_service_iter(): info = service.as_dict() - print("%r (%r)" % (info['name'], info['display_name'])) - s = "status: %s, start: %s, username: %s, pid: %s" % ( + print(f"{info['name']!r} ({info['display_name']!r})") + s = "status: {}, start: {}, username: {}, pid: {}".format( info['status'], info['start_type'], info['username'], info['pid'], ) print(s) - print("binpath: %s" % info['binpath']) + print(f"binpath: {info['binpath']}") print() diff --git a/setup.py b/setup.py index 3c7900669..0014484a8 100755 --- a/setup.py +++ b/setup.py @@ -4,7 +4,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""Cross-platform lib for process and system monitoring in Python.""" +"""Cross-platform lib for process and system monitoring in Python. + +NOTE: the syntax of this script MUST be kept compatible with Python 2.7. +""" from __future__ import print_function @@ -21,9 +24,21 @@ import sys import sysconfig import tempfile +import textwrap import warnings +if sys.version_info[0] == 2: + sys.exit(textwrap.dedent("""\ + As of version 7.0.0 psutil no longer supports Python 2.7, see: + https://github.com/giampaolo/psutil/issues/2480 + Latest version supporting Python 2.7 is psutil 6.1.X. + Install it with: + + python2 -m pip install psutil==6.1.*\ + """)) + + with warnings.catch_warnings(): warnings.simplefilter("ignore") try: @@ -31,34 +46,29 @@ from setuptools import Extension from setuptools import setup except ImportError: + if "CIBUILDWHEEL" in os.environ: + raise setuptools = None from distutils.core import Extension from distutils.core import setup - try: - from wheel.bdist_wheel import bdist_wheel - except ImportError: - if "CIBUILDWHEEL" in os.environ: - raise - bdist_wheel = None + HERE = os.path.abspath(os.path.dirname(__file__)) -# ...so we can import _common.py and _compat.py +# ...so we can import _common.py sys.path.insert(0, os.path.join(HERE, "psutil")) -from _common import AIX # NOQA -from _common import BSD # NOQA -from _common import FREEBSD # NOQA -from _common import LINUX # NOQA -from _common import MACOS # NOQA -from _common import NETBSD # NOQA -from _common import OPENBSD # NOQA -from _common import POSIX # NOQA -from _common import SUNOS # NOQA -from _common import WINDOWS # NOQA -from _common import hilite # NOQA -from _compat import PY3 # NOQA -from _compat import which # NOQA +from _common import AIX # noqa: E402 +from _common import BSD # noqa: E402 +from _common import FREEBSD # noqa: E402 +from _common import LINUX # noqa: E402 +from _common import MACOS # noqa: E402 +from _common import NETBSD # noqa: E402 +from _common import OPENBSD # noqa: E402 +from _common import POSIX # noqa: E402 +from _common import SUNOS # noqa: E402 +from _common import WINDOWS # noqa: E402 +from _common import hilite # noqa: E402 PYPY = '__pypy__' in sys.builtin_module_names @@ -68,13 +78,54 @@ CP37_PLUS = PY37_PLUS and sys.implementation.name == "cpython" Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED") +# Test deps, installable via `pip install .[test]` or +# `make install-pydeps-test`. +TEST_DEPS = [ + "pytest", + "pytest-xdist", + "setuptools", +] + +if WINDOWS and not PYPY: + TEST_DEPS.append("pywin32") + TEST_DEPS.append("wheel") + TEST_DEPS.append("wmi") + +# Development deps, installable via `pip install .[dev]` or +# `make install-pydeps-dev`. +DEV_DEPS = TEST_DEPS + [ + "abi3audit", + "black", + "check-manifest", + "coverage", + "packaging", + "pylint", + "pyperf", + "pypinfo", + "pytest-cov", + "requests", + "rstcheck", + "ruff", + "sphinx", + "sphinx_rtd_theme", + "toml-sort", + "twine", + "virtualenv", + "vulture", + "wheel", +] + +if WINDOWS: + DEV_DEPS.append("pyreadline") + DEV_DEPS.append("pdbpp") + macros = [] if POSIX: macros.append(("PSUTIL_POSIX", 1)) if BSD: macros.append(("PSUTIL_BSD", 1)) -# Needed to determine _Py_PARSE_PID in case it's missing (Python 2, PyPy). +# Needed to determine _Py_PARSE_PID in case it's missing (PyPy). # Taken from Lib/test/test_fcntl.py. # XXX: not bullet proof as the (long long) case is missing. if struct.calcsize('l') <= 8: @@ -88,19 +139,6 @@ sources.append('psutil/_psutil_posix.c') -extras_require = { - "test": [ - "enum34; python_version <= '3.4'", - "ipaddress; python_version < '3.0'", - "mock; python_version < '3.0'", - ] -} -if not PYPY: - extras_require['test'].extend( - ["pywin32; sys.platform == 'win32'", "wmi; sys.platform == 'win32'"] - ) - - def get_version(): INIT = os.path.join(HERE, 'psutil/__init__.py') with open(INIT) as f: @@ -120,16 +158,19 @@ def get_version(): # Py_LIMITED_API lets us create a single wheel which works with multiple # python versions, including unreleased ones. -if bdist_wheel and CP36_PLUS and (MACOS or LINUX) and not Py_GIL_DISABLED: +if setuptools and CP36_PLUS and (MACOS or LINUX) and not Py_GIL_DISABLED: py_limited_api = {"py_limited_api": True} + options = {"bdist_wheel": {"py_limited_api": "cp36"}} macros.append(('Py_LIMITED_API', '0x03060000')) -elif bdist_wheel and CP37_PLUS and WINDOWS and not Py_GIL_DISABLED: +elif setuptools and CP37_PLUS and WINDOWS and not Py_GIL_DISABLED: # PyErr_SetFromWindowsErr / PyErr_SetFromWindowsErrWithFilename are # part of the stable API/ABI starting with CPython 3.7 py_limited_api = {"py_limited_api": True} + options = {"bdist_wheel": {"py_limited_api": "cp37"}} macros.append(('Py_LIMITED_API', '0x03070000')) else: py_limited_api = {} + options = {} def get_long_description(): @@ -166,12 +207,12 @@ def write(self, s): def missdeps(cmdline): s = "psutil could not be installed from sources" - if not SUNOS and not which("gcc"): + if not SUNOS and not shutil.which("gcc"): s += " because gcc is not installed. " else: s += ". Perhaps Python header files are not installed. " s += "Try running:\n" - s += " %s" % cmdline + s += " {}".format(cmdline) print(hilite(s, color="red", bold=True), file=sys.stderr) @@ -206,7 +247,7 @@ def unix_can_compile(c_code): def get_winver(): maj, min = sys.getwindowsversion()[0:2] - return '0x0%s' % ((maj * 100) + min) + return "0x0{}".format((maj * 100) + min) if sys.getwindowsversion()[0] < 6: msg = "this Windows version is too old (< Windows Vista); " @@ -225,6 +266,9 @@ def get_winver(): ('PSAPI_VERSION', 1), ]) + if Py_GIL_DISABLED: + macros.append(('Py_GIL_DISABLED', 1)) + ext = Extension( 'psutil._psutil_windows', sources=( @@ -385,7 +429,7 @@ def get_winver(): ) else: - sys.exit('platform %s is not supported' % sys.platform) + sys.exit("platform {} is not supported".format(sys.platform)) if POSIX: @@ -426,22 +470,11 @@ def get_sunos_update(): else: extensions = [ext] -cmdclass = {} -if py_limited_api: - - class bdist_wheel_abi3(bdist_wheel): - def get_tag(self): - python, abi, plat = bdist_wheel.get_tag(self) - return python, "abi3", plat - - cmdclass["bdist_wheel"] = bdist_wheel_abi3 - def main(): kwargs = dict( name='psutil', version=VERSION, - cmdclass=cmdclass, description=__doc__.replace('\n', ' ').strip() if __doc__ else '', long_description=get_long_description(), long_description_content_type='text/x-rst', @@ -461,6 +494,7 @@ def main(): license='BSD-3-Clause', packages=['psutil', 'psutil.tests'], ext_modules=extensions, + options=options, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -488,8 +522,6 @@ def main(): 'Operating System :: POSIX :: SunOS/Solaris', 'Operating System :: POSIX', 'Programming Language :: C', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', @@ -509,10 +541,12 @@ def main(): ], ) if setuptools is not None: + extras_require = { + "dev": DEV_DEPS, + "test": TEST_DEPS, + } kwargs.update( - python_requires=( - ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - ), + python_requires=">=3.6", extras_require=extras_require, zip_safe=False, ) @@ -529,19 +563,16 @@ def main(): ("build", "install", "sdist", "bdist", "develop") ) ): - py3 = "3" if PY3 else "" if LINUX: pyimpl = "pypy" if PYPY else "python" - if which('dpkg'): - missdeps( - "sudo apt-get install gcc %s%s-dev" % (pyimpl, py3) - ) - elif which('rpm'): - missdeps("sudo yum install gcc %s%s-devel" % (pyimpl, py3)) - elif which('apk'): + if shutil.which("dpkg"): + missdeps("sudo apt-get install gcc {}3-dev".format(pyimpl)) + elif shutil.which("rpm"): + missdeps("sudo yum install gcc {}3-devel".format(pyimpl)) + elif shutil.which("apk"): missdeps( - "sudo apk add gcc %s%s-dev musl-dev linux-headers" - % (pyimpl, py3) + "sudo apk add gcc {}3-dev musl-dev linux-headers" + .format(*pyimpl) ) elif MACOS: msg = ( @@ -550,14 +581,14 @@ def main(): ) print(hilite(msg, color="red"), file=sys.stderr) elif FREEBSD: - if which('pkg'): - missdeps("pkg install gcc python%s" % py3) - elif which('mport'): # MidnightBSD - missdeps("mport install gcc python%s" % py3) + if shutil.which("pkg"): + missdeps("pkg install gcc python3") + elif shutil.which("mport"): # MidnightBSD + missdeps("mport install gcc python3") elif OPENBSD: - missdeps("pkg_add -v gcc python%s" % py3) + missdeps("pkg_add -v gcc python3") elif NETBSD: - missdeps("pkgin install gcc python%s" % py3) + missdeps("pkgin install gcc python3") elif SUNOS: missdeps( "sudo ln -s /usr/bin/gcc /usr/local/bin/cc && "