From c3effb7a7a12cbde6977d21b32de718e74dbe2ef Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 20 Dec 2024 20:46:37 +0100 Subject: [PATCH] Test internal scripts (#2484) --- MANIFEST.in | 1 + Makefile | 4 + psutil/tests/test_misc.py | 161 ---------------- psutil/tests/test_scripts.py | 242 +++++++++++++++++++++++++ scripts/internal/bench_oneshot_2.py | 16 +- scripts/internal/git_pre_commit.py | 3 +- scripts/internal/purge_installation.py | 3 +- scripts/internal/winmake.py | 7 + 8 files changed, 267 insertions(+), 170 deletions(-) create mode 100755 psutil/tests/test_scripts.py diff --git a/MANIFEST.in b/MANIFEST.in index 5ec1cdd9e..b60794b91 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -163,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 diff --git a/Makefile b/Makefile index 2c7d050ce..3e74e8c7d 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,10 @@ test-misc: ## Run miscellaneous tests. ${MAKE} build $(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) -m pytest $(PYTEST_ARGS) $(ARGS) psutil/tests/test_testutils.py diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 9d24bb32c..1c42b6022 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -6,7 +6,6 @@ """Miscellaneous tests.""" -import ast import collections import contextlib import io @@ -14,13 +13,11 @@ import os import pickle import socket -import stat import sys 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,22 +28,12 @@ from psutil._common import parse_environ_block from psutil._common import supports_ipv6 from psutil._common import wrap_numbers -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 QEMU_USER -from psutil.tests import SCRIPTS_DIR from psutil.tests import PsutilTestCase 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 @@ -334,14 +321,6 @@ def check(ret): assert b.pid == 4567 assert b.name == 'name' - # 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 pytest.skip("can't find setup.py") - # module = import_module_by_path(setup_py) - # self.assertRaises(SystemExit, module.setup) - # self.assertEqual(module.get_version(), psutil.__version__) - 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. @@ -896,143 +875,3 @@ def test_cache_clear_public_apis(self): psutil.net_io_counters.cache_clear() caches = wrap_numbers.cache_info() assert caches == ({}, {}, {}) - - -# =================================================================== -# --- Example script tests -# =================================================================== - - -@pytest.mark.skipif( - not os.path.exists(SCRIPTS_DIR), reason="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 = 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') 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/scripts/internal/bench_oneshot_2.py b/scripts/internal/bench_oneshot_2.py index 1076dffc8..aa3ca78d1 100755 --- a/scripts/internal/bench_oneshot_2.py +++ b/scripts/internal/bench_oneshot_2.py @@ -11,27 +11,27 @@ 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 main(): + from bench_oneshot import names + runner = pyperf.Runner() args = runner.parse_args() @@ -43,8 +43,10 @@ def main(): 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/git_pre_commit.py b/scripts/internal/git_pre_commit.py index 10f6368da..a47d7987e 100755 --- a/scripts/internal/git_pre_commit.py +++ b/scripts/internal/git_pre_commit.py @@ -166,4 +166,5 @@ def main(): ) -main() +if __name__ == "__main__": + main() 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/winmake.py b/scripts/internal/winmake.py index 5413b827b..1692c1253 100755 --- a/scripts/internal/winmake.py +++ b/scripts/internal/winmake.py @@ -366,6 +366,12 @@ def test_misc(): 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() @@ -502,6 +508,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")