diff --git a/coverage/misc.py b/coverage/misc.py index 1e4b4e749..bd1767cae 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -83,14 +83,16 @@ def import_third_party(modname): modname (str): the name of the module to import. Returns: - The imported module, or None if the module couldn't be imported. + The imported module, and a boolean indicating if the module could be imported. + + If the boolean is False, the module returned is not the one you want: don't use it. """ with sys_modules_saved(): try: - return importlib.import_module(modname) + return importlib.import_module(modname), True except ImportError: - return None + return sys, False def nice_pair(pair): diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py index 31cd0bb06..737c728cd 100644 --- a/coverage/tomlconfig.py +++ b/coverage/tomlconfig.py @@ -5,7 +5,6 @@ import os import re -import sys from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar @@ -17,9 +16,10 @@ if env.PYVERSION >= (3, 11, 0, "alpha", 7): import tomllib # pylint: disable=import-error + has_tomllib = True else: # TOML support on Python 3.10 and below is an install-time extra option. - tomllib = import_third_party("tomli") + tomllib, has_tomllib = import_third_party("tomli") class TomlDecodeError(Exception): @@ -51,7 +51,7 @@ def read(self, filenames: Iterable[str]) -> List[str]: toml_text = fp.read() except OSError: return [] - if sys.version_info >= (3, 11) or tomllib is not None: + if has_tomllib: try: self.data = tomllib.loads(toml_text) except tomllib.TOMLDecodeError as err: diff --git a/tests/helpers.py b/tests/helpers.py index 1645138c4..0503097e7 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -15,12 +15,10 @@ import textwrap import warnings -from types import ModuleType from typing import ( cast, Any, Callable, Generator, Iterable, List, Optional, Set, Tuple, Type, Union, ) -from unittest import mock import pytest @@ -284,24 +282,6 @@ def change_dir(new_dir: str) -> Generator[None, None, None]: os.chdir(old_dir) -def without_module(using_module: ModuleType, missing_module_name: str) -> mock._patch[Any]: - """ - Hide a module for testing. - - Use this in a test function to make an optional module unavailable during - the test:: - - with without_module(product.something, 'tomli'): - use_toml_somehow() - - Arguments: - using_module: a module in which to hide `missing_module_name`. - missing_module_name (str): the name of the module to hide. - - """ - return mock.patch.object(using_module, missing_module_name, None) - - def assert_count_equal(a: Iterable[Union[int, str]], b: Iterable[Union[int, str]]) -> None: """ A pytest-friendly implementation of assertCountEqual. diff --git a/tests/test_config.py b/tests/test_config.py index 66e71d233..08fdc1136 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -15,7 +15,6 @@ from coverage.tomlconfig import TomlConfigParser from tests.coveragetest import CoverageTest, UsingModulesMixin -from tests.helpers import without_module class ConfigTest(CoverageTest): @@ -713,7 +712,7 @@ def test_nocoveragerc_file_when_specified(self) -> None: def test_no_toml_installed_no_toml(self) -> None: # Can't read a toml file that doesn't exist. - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Couldn't read 'cov.toml' as a config file" with pytest.raises(ConfigError, match=msg): coverage.Coverage(config_file="cov.toml") @@ -722,7 +721,7 @@ def test_no_toml_installed_no_toml(self) -> None: def test_no_toml_installed_explicit_toml(self) -> None: # Can't specify a toml config file if toml isn't installed. self.make_file("cov.toml", "# A toml file!") - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Can't read 'cov.toml' without TOML support" with pytest.raises(ConfigError, match=msg): coverage.Coverage(config_file="cov.toml") @@ -735,7 +734,7 @@ def test_no_toml_installed_pyproject_toml(self) -> None: [tool.coverage.run] xyzzy = 17 """) - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Can't read 'pyproject.toml' without TOML support" with pytest.raises(ConfigError, match=msg): coverage.Coverage() @@ -748,7 +747,7 @@ def test_no_toml_installed_pyproject_toml_shorter_syntax(self) -> None: [tool.coverage] run.parallel = true """) - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Can't read 'pyproject.toml' without TOML support" with pytest.raises(ConfigError, match=msg): coverage.Coverage() @@ -761,7 +760,7 @@ def test_no_toml_installed_pyproject_no_coverage(self) -> None: [tool.something] xyzzy = 17 """) - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): cov = coverage.Coverage() # We get default settings: assert not cov.config.timid diff --git a/tests/test_misc.py b/tests/test_misc.py index 4fd3f7c78..745522b0e 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -118,7 +118,8 @@ def test_success(self): # Make sure we don't have pytest in sys.modules before we start. del sys.modules["pytest"] # Import pytest - mod = import_third_party("pytest") + mod, has = import_third_party("pytest") + assert has # Yes, it's really pytest: assert mod.__name__ == "pytest" print(dir(mod)) @@ -127,8 +128,8 @@ def test_success(self): assert "pytest" not in sys.modules def test_failure(self): - mod = import_third_party("xyzzy") - assert mod is None + _, has = import_third_party("xyzzy") + assert not has assert "xyzzy" not in sys.modules diff --git a/tests/test_testing.py b/tests/test_testing.py index 43c682cd3..d5447c45a 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -12,14 +12,13 @@ import pytest import coverage -from coverage import tomlconfig from coverage.exceptions import CoverageWarning from coverage.files import actual_path from tests.coveragetest import CoverageTest from tests.helpers import ( arcs_to_arcz_repr, arcz_to_arcs, assert_count_equal, assert_coverage_warnings, - CheckUniqueFilenames, re_lines, re_lines_text, re_line, without_module, + CheckUniqueFilenames, re_lines, re_lines_text, re_line, ) @@ -356,16 +355,6 @@ def _same_python_executable(e1, e2): return False # pragma: only failure -def test_without_module(): - toml1 = tomlconfig.tomllib - with without_module(tomlconfig, 'tomllib'): - toml2 = tomlconfig.tomllib - toml3 = tomlconfig.tomllib - - assert toml1 is toml3 is not None - assert toml2 is None - - class ArczTest(CoverageTest): """Tests of arcz/arcs helpers."""