diff options
-rw-r--r-- | coverage/backward.py | 7 | ||||
-rw-r--r-- | coverage/config.py | 5 | ||||
-rw-r--r-- | coverage/optional.py | 68 | ||||
-rw-r--r-- | coverage/tomlconfig.py | 3 | ||||
-rw-r--r-- | tests/test_testing.py | 12 |
5 files changed, 86 insertions, 9 deletions
diff --git a/coverage/backward.py b/coverage/backward.py index e051fa55..17f04219 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -3,7 +3,7 @@ """Add things to old Pythons so I can pretend they are newer.""" -# This file does tricky stuff, so disable a pylint warning. +# This file's purpose is to provide modules to be imported from here. # pylint: disable=unused-import import os @@ -27,11 +27,6 @@ try: except ImportError: import configparser -try: - import toml -except ImportError: - toml = None - # What's a string called? try: string_class = basestring diff --git a/coverage/config.py b/coverage/config.py index 89a0321e..ca3de3bd 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -9,7 +9,7 @@ import os import re from coverage import env -from coverage.backward import configparser, iitems, string_class, toml +from coverage.backward import configparser, iitems, string_class from coverage.misc import contract, CoverageException, isolate_module from coverage.misc import substitute_variables @@ -260,6 +260,7 @@ class CoverageConfig(object): """ _, ext = os.path.splitext(filename) if ext == '.toml': + from coverage.optional import toml if toml is None: return False cp = TomlConfigParser(our_file) @@ -482,9 +483,9 @@ def config_files_to_try(config_file): config_file = ".coveragerc" files_to_try = [ (config_file, True, specified_file), - ("pyproject.toml", False, False), ("setup.cfg", False, False), ("tox.ini", False, False), + ("pyproject.toml", False, False), ] return files_to_try diff --git a/coverage/optional.py b/coverage/optional.py new file mode 100644 index 00000000..ee617b62 --- /dev/null +++ b/coverage/optional.py @@ -0,0 +1,68 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +""" +Imports that we need at runtime, but might not be present. + +When importing one of these modules, always do it in the function where you +need the module. Some tests will need to remove the module. If you import +it at the top level of your module, then the test won't be able to simulate +the module being unimportable. + +The import will always succeed, but the value will be None if the module is +unavailable. + +Bad:: + + # MyModule.py + from coverage.optional import unsure + + def use_unsure(): + unsure.something() + +Good:: + + # MyModule.py + + def use_unsure(): + from coverage.optional import unsure + if unsure is None: + raise Exception("Module unsure isn't available!") + + unsure.something() + +""" + +import contextlib + +# This file's purpose is to provide modules to be imported from here. +# pylint: disable=unused-import + +# TOML support is an install-time extra option. +try: + import toml +except ImportError: # pragma: not covered + toml = None + + +@contextlib.contextmanager +def without(modname): + """Hide a module for testing. + + Use this in a test function to make an optional module unavailable during + the test:: + + with coverage.optional.without('toml'): + use_toml_somehow() + + Arguments: + modname (str): the name of a module importable from + `coverage.optional`. + + """ + real_module = globals()[modname] + try: + globals()[modname] = None + yield + finally: + globals()[modname] = real_module diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py index 144b13ca..33647309 100644 --- a/coverage/tomlconfig.py +++ b/coverage/tomlconfig.py @@ -8,7 +8,7 @@ import os import re from coverage import env -from coverage.backward import configparser, path_types, toml +from coverage.backward import configparser, path_types from coverage.misc import CoverageException, substitute_variables @@ -32,6 +32,7 @@ class TomlConfigParser: self._data = [] def read(self, filenames): + from coverage.optional import toml if toml is None: raise RuntimeError('toml module is not installed.') diff --git a/tests/test_testing.py b/tests/test_testing.py index 4a4a013c..15e6f84f 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -15,6 +15,7 @@ import coverage from coverage.backunittest import TestCase, unittest from coverage.files import actual_path from coverage.misc import StopEverything +import coverage.optional from tests.coveragetest import CoverageTest, convert_skip_exceptions from tests.helpers import CheckUniqueFilenames, re_lines, re_line @@ -308,3 +309,14 @@ def _same_python_executable(e1, e2): return True return False # pragma: only failure + + +def test_optional_without(): + # pylint: disable=reimported + from coverage.optional import toml as toml1 + with coverage.optional.without('toml'): + from coverage.optional import toml as toml2 + from coverage.optional import toml as toml3 + + assert toml1 is toml3 is not None + assert toml2 is None |