summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-10-05 19:43:00 -0400
committerNed Batchelder <ned@nedbatchelder.com>2021-10-06 07:20:22 -0400
commit613446ca9da592c6925329b869b9ef785d83f76e (patch)
treec522f410454e4fc61519598d4cda0f91843703d2
parent62116801c3ae2f7bfc6302836e46bdfac681c1a5 (diff)
downloadpython-coveragepy-git-613446ca9da592c6925329b869b9ef785d83f76e.tar.gz
fix: pretend we didn't import third-party packages we use. #1228
tomli couldn't use coverage themselves because we imported it early. Cleaning sys.modules means their own imports will actually execute after coverage has started, so their files will be properly measured.
-rw-r--r--CHANGES.rst5
-rw-r--r--coverage/misc.py27
-rw-r--r--coverage/tomlconfig.py7
-rw-r--r--tests/test_misc.py20
4 files changed, 52 insertions, 7 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 50a81aaf..937d2d0f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -22,7 +22,10 @@ This list is detailed and covers changes in each pre-release version.
Unreleased
----------
-Nothing yet.
+- Changed an internal detail of how tomli is imported, so that tomli can use
+ coverage.py for their own test suite (`issue 1228`_).
+
+.. _issue 1228: https://github.com/nedbat/coveragepy/issues/1228
.. _changes_60:
diff --git a/coverage/misc.py b/coverage/misc.py
index 11dad23e..cd4a7740 100644
--- a/coverage/misc.py
+++ b/coverage/misc.py
@@ -5,6 +5,7 @@
import errno
import hashlib
+import importlib
import importlib.util
import inspect
import locale
@@ -43,6 +44,32 @@ def isolate_module(mod):
os = isolate_module(os)
+def import_third_party(modname):
+ """Import a third-party module we need, but might not be installed.
+
+ This also cleans out the module after the import, so that coverage won't
+ appear to have imported it. This lets the third party use coverage for
+ their own tests.
+
+ Arguments:
+ modname (str): the name of the module to import.
+
+ Returns:
+ The imported module, or None if the module couldn't be imported.
+
+ """
+ try:
+ mod = importlib.import_module(modname)
+ except ImportError:
+ mod = None
+
+ imported = [m for m in sys.modules if m.startswith(modname)]
+ for name in imported:
+ del sys.modules[name]
+
+ return mod
+
+
def dummy_decorator_with_args(*args_unused, **kwargs_unused):
"""Dummy no-op implementation of a decorator with arguments."""
def _decorator(func):
diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py
index 203192c9..3301acc8 100644
--- a/coverage/tomlconfig.py
+++ b/coverage/tomlconfig.py
@@ -8,13 +8,10 @@ import os
import re
from coverage.exceptions import CoverageException
-from coverage.misc import substitute_variables
+from coverage.misc import import_third_party, substitute_variables
# TOML support is an install-time extra option.
-try:
- import tomli
-except ImportError: # pragma: not covered
- tomli = None
+tomli = import_third_party("tomli")
class TomlDecodeError(Exception):
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 3858c4f8..077c2434 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -3,11 +3,13 @@
"""Tests of miscellaneous stuff."""
+import sys
+
import pytest
from coverage.exceptions import CoverageException
from coverage.misc import contract, dummy_decorator_with_args, file_be_gone
-from coverage.misc import Hasher, one_of, substitute_variables
+from coverage.misc import Hasher, one_of, substitute_variables, import_third_party
from coverage.misc import USE_CONTRACTS
from tests.coveragetest import CoverageTest
@@ -155,3 +157,19 @@ def test_substitute_variables_errors(text):
substitute_variables(text, VARS)
assert text in str(exc_info.value)
assert "Variable NOTHING is undefined" in str(exc_info.value)
+
+
+class ImportThirdPartyTest(CoverageTest):
+ """Test import_third_party."""
+
+ run_in_temp_dir = False
+
+ def test_success(self):
+ mod = import_third_party("pytest")
+ assert mod.__name__ == "pytest"
+ assert "pytest" not in sys.modules
+
+ def test_failure(self):
+ mod = import_third_party("xyzzy")
+ assert mod is None
+ assert "xyzzy" not in sys.modules