From a712f79bf1cb96390bf7e4eaaa5bac23a9a595fd Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Thu, 9 Apr 2026 17:03:34 -0700 Subject: [PATCH] ansible-test - Upgrade coverage for Python 3.10+ (#86811) * Upgrade coverage for Python 3.10+. * Fix unit tests. * Use ctrace to improve performance. It's unclear why this improves performance on Python 3.10 and 3.14. --- .../fragments/ansible-test-coverage.yml | 2 + .../_data/requirements/ansible-test.txt | 3 +- .../ansible_test/_internal/coverage_util.py | 5 +- test/units/module_utils/basic/test_imports.py | 73 ++++++++++++------- test/units/plugins/test_plugins.py | 9 ++- 5 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 changelogs/fragments/ansible-test-coverage.yml diff --git a/changelogs/fragments/ansible-test-coverage.yml b/changelogs/fragments/ansible-test-coverage.yml new file mode 100644 index 00000000000..07fe8306300 --- /dev/null +++ b/changelogs/fragments/ansible-test-coverage.yml @@ -0,0 +1,2 @@ +minor_changes: + - ansible-test - Upgrade ``coverage`` for Python 3.10 and later. diff --git a/test/lib/ansible_test/_data/requirements/ansible-test.txt b/test/lib/ansible_test/_data/requirements/ansible-test.txt index 7f5e7d267af..d8f5be5e38e 100644 --- a/test/lib/ansible_test/_data/requirements/ansible-test.txt +++ b/test/lib/ansible_test/_data/requirements/ansible-test.txt @@ -1,2 +1,3 @@ # The test-constraints sanity test verifies this file, but changes must be made manually to keep it in up-to-date. -coverage == 7.10.7 ; python_version >= '3.9' and python_version <= '3.14' +coverage == 7.13.5 ; python_version >= '3.10' and python_version <= '3.15' +coverage == 7.10.7 ; python_version >= '3.9' and python_version <= '3.9' diff --git a/test/lib/ansible_test/_internal/coverage_util.py b/test/lib/ansible_test/_internal/coverage_util.py index c92aee476dd..ae39585a5bd 100644 --- a/test/lib/ansible_test/_internal/coverage_util.py +++ b/test/lib/ansible_test/_internal/coverage_util.py @@ -69,7 +69,8 @@ class CoverageVersion: COVERAGE_VERSIONS = ( # IMPORTANT: Keep this in sync with the ansible-test.txt requirements file. - CoverageVersion('7.10.7', 7, (3, 9), (3, 14)), + CoverageVersion('7.13.5', 7, (3, 10), (3, 15)), + CoverageVersion('7.10.7', 7, (3, 9), (3, 9)), ) """ This tuple specifies the coverage version to use for Python version ranges. @@ -248,6 +249,7 @@ def generate_ansible_coverage_config() -> str: """Generate code coverage configuration for Ansible tests.""" coverage_config = """ [run] +core = ctrace branch = True concurrency = multiprocessing @@ -288,6 +290,7 @@ def generate_collection_coverage_config() -> str: coverage_config = f""" [run] +core = ctrace branch = True concurrency = multiprocessing diff --git a/test/units/module_utils/basic/test_imports.py b/test/units/module_utils/basic/test_imports.py index c55d8403e4f..d0cfa1d53f3 100644 --- a/test/units/module_utils/basic/test_imports.py +++ b/test/units/module_utils/basic/test_imports.py @@ -22,41 +22,51 @@ class TestImports(unittest.TestCase): if mod in sys.modules: del sys.modules[mod] - @patch.object(builtins, '__import__') - def test_module_utils_basic_import_syslog(self, mock_import): + def test_module_utils_basic_import_syslog(self): + present = True + def _mock_import(name, *args, **kwargs): if name == 'syslog': + if present: + return unittest.mock.MagicMock() raise ImportError return realimport(name, *args, **kwargs) - self.clear_modules(['syslog', 'ansible.module_utils.basic']) - mod = builtins.__import__('ansible.module_utils.basic') - self.assertTrue(mod.module_utils.basic.HAS_SYSLOG) + with patch.object(builtins, '__import__', _mock_import): + self.clear_modules(['syslog', 'ansible.module_utils.basic']) + mod = builtins.__import__('ansible.module_utils.basic') + self.assertTrue(mod.module_utils.basic.HAS_SYSLOG) + + present = False + + self.clear_modules(['syslog', 'ansible.module_utils.basic']) + mod = builtins.__import__('ansible.module_utils.basic') + self.assertFalse(mod.module_utils.basic.HAS_SYSLOG) + + self.clear_modules(['syslog', 'ansible.module_utils.basic']) - self.clear_modules(['syslog', 'ansible.module_utils.basic']) - mock_import.side_effect = _mock_import - mod = builtins.__import__('ansible.module_utils.basic') - self.assertFalse(mod.module_utils.basic.HAS_SYSLOG) + def test_module_utils_basic_import_selinux(self): + present = True - @patch.object(builtins, '__import__') - def test_module_utils_basic_import_selinux(self, mock_import): def _mock_import(name, globals=None, locals=None, fromlist=tuple(), level=0, **kwargs): if name == 'ansible.module_utils.compat' and fromlist == ('selinux',): + if present: + return unittest.mock.MagicMock() raise ImportError return realimport(name, globals=globals, locals=locals, fromlist=fromlist, level=level, **kwargs) - try: + with patch.object(builtins, '__import__', _mock_import): self.clear_modules(['ansible.module_utils.compat.selinux', 'ansible.module_utils.basic']) mod = builtins.__import__('ansible.module_utils.basic') self.assertTrue(mod.module_utils.basic.HAVE_SELINUX) - except ImportError: - # no selinux on test system, so skip - pass - self.clear_modules(['ansible.module_utils.compat.selinux', 'ansible.module_utils.basic']) - mock_import.side_effect = _mock_import - mod = builtins.__import__('ansible.module_utils.basic') - self.assertFalse(mod.module_utils.basic.HAVE_SELINUX) + present = False + + self.clear_modules(['ansible.module_utils.compat.selinux', 'ansible.module_utils.basic']) + mod = builtins.__import__('ansible.module_utils.basic') + self.assertFalse(mod.module_utils.basic.HAVE_SELINUX) + + self.clear_modules(['ansible.module_utils.compat.selinux', 'ansible.module_utils.basic']) # FIXME: doesn't work yet # @patch.object(builtins, 'bytes') @@ -64,22 +74,29 @@ class TestImports(unittest.TestCase): # mock_bytes.side_effect = NameError() # from ansible.module_utils import basic - @patch.object(builtins, '__import__') - def test_module_utils_basic_import_systemd_journal(self, mock_import): + def test_module_utils_basic_import_systemd_journal(self): + present = True + def _mock_import(name, *args, **kwargs): try: fromlist = kwargs.get('fromlist', args[2]) except IndexError: fromlist = [] if name == 'systemd' and 'journal' in fromlist: + if present: + return unittest.mock.MagicMock() raise ImportError return realimport(name, *args, **kwargs) - self.clear_modules(['systemd', 'ansible.module_utils.basic']) - mod = builtins.__import__('ansible.module_utils.basic') - self.assertTrue(mod.module_utils.basic.has_journal) + with patch.object(builtins, '__import__', _mock_import): + self.clear_modules(['systemd', 'ansible.module_utils.basic']) + mod = builtins.__import__('ansible.module_utils.basic') + self.assertTrue(mod.module_utils.basic.has_journal) + + present = False + + self.clear_modules(['systemd', 'ansible.module_utils.basic']) + mod = builtins.__import__('ansible.module_utils.basic') + self.assertFalse(mod.module_utils.basic.has_journal) - self.clear_modules(['systemd', 'ansible.module_utils.basic']) - mock_import.side_effect = _mock_import - mod = builtins.__import__('ansible.module_utils.basic') - self.assertFalse(mod.module_utils.basic.has_journal) + self.clear_modules(['systemd', 'ansible.module_utils.basic']) diff --git a/test/units/plugins/test_plugins.py b/test/units/plugins/test_plugins.py index b86e1c13f84..47fc33c4a7f 100644 --- a/test/units/plugins/test_plugins.py +++ b/test/units/plugins/test_plugins.py @@ -19,6 +19,7 @@ from __future__ import annotations import os +import sys import unittest from unittest.mock import patch, MagicMock @@ -49,10 +50,12 @@ class TestErrors(unittest.TestCase): bam = MagicMock() bam.__file__ = '/path/to/my/foo/bar/bam/__init__.py' bar_pkg.bam = bam - foo_pkg.return_value.bar = bar_pkg + foo_pkg.bar = bar_pkg pl = PluginLoader('test', 'foo.bar.bam', 'test', 'test_plugin') - with patch('builtins.__import__', foo_pkg): - self.assertEqual(pl._get_package_paths(), ['/path/to/my/foo/bar/bam']) + sys.modules['foo'] = foo_pkg + sys.modules['foo.bar'] = bar_pkg + sys.modules['foo.bar.bam'] = bam + self.assertEqual(pl._get_package_paths(), ['/path/to/my/foo/bar/bam']) def test_plugins__get_paths(self): pl = PluginLoader('test', '', 'test', 'test_plugin')