From df744f8cbcad7ea7dca893be5017920afa4ce32f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 3 Nov 2019 18:32:01 -0500 Subject: [PATCH] Give warnings about not being able to parse TOML files if toml isn't installed --- coverage/config.py | 3 --- coverage/tomlconfig.py | 47 +++++++++++++++++++++++++----------------- tests/test_config.py | 34 ++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/coverage/config.py b/coverage/config.py index ca3de3bd5..62f281add 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -260,9 +260,6 @@ def from_file(self, filename, our_file): """ _, ext = os.path.splitext(filename) if ext == '.toml': - from coverage.optional import toml - if toml is None: - return False cp = TomlConfigParser(our_file) else: cp = HandyConfigParser(our_file) diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py index 336473098..f5978820c 100644 --- a/coverage/tomlconfig.py +++ b/coverage/tomlconfig.py @@ -25,34 +25,43 @@ class TomlConfigParser: # pylint: disable=missing-function-docstring def __init__(self, our_file): + self.our_file = our_file self.getters = [lambda obj: obj['tool']['coverage']] - if our_file: + if self.our_file: self.getters.append(lambda obj: obj) self._data = [] def read(self, filenames): + # RawConfigParser takes a filename or list of filenames, but we only + # ever call this with a single filename. + assert isinstance(filenames, path_types) + filename = filenames + if env.PYVERSION >= (3, 6): + filename = os.fspath(filename) + from coverage.optional import toml if toml is None: - raise RuntimeError('toml module is not installed.') - - if isinstance(filenames, path_types): - filenames = [filenames] - read_ok = [] - for filename in filenames: - try: - with io.open(filename, encoding='utf-8') as fp: - toml_data = fp.read() - toml_data = substitute_variables(toml_data, os.environ) + if self.our_file: + raise CoverageException("Can't read {!r} without TOML support".format(filename)) + + try: + with io.open(filename, encoding='utf-8') as fp: + toml_data = fp.read() + toml_data = substitute_variables(toml_data, os.environ) + if toml: + try: self._data.append(toml.loads(toml_data)) - except IOError: - continue - except toml.TomlDecodeError as err: - raise TomlDecodeError(*err.args) - if env.PYVERSION >= (3, 6): - filename = os.fspath(filename) - read_ok.append(filename) - return read_ok + except toml.TomlDecodeError as err: + raise TomlDecodeError(*err.args) + elif re.search(r"^\[tool\.coverage\.", toml_data, flags=re.MULTILINE): + # Looks like they meant to read TOML, but we can't. + raise CoverageException("Can't read {!r} without TOML support".format(filename)) + else: + return [] + except IOError: + return [] + return [filename] def has_option(self, section, option): for data in self._data: diff --git a/tests/test_config.py b/tests/test_config.py index 3bd2fd2b1..74ff5f009 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,6 +8,7 @@ import coverage from coverage.misc import CoverageException +import coverage.optional from tests.coveragetest import CoverageTest, UsingModulesMixin @@ -651,3 +652,36 @@ def test_nocoveragerc_file_when_specified(self): self.assertFalse(cov.config.timid) self.assertFalse(cov.config.branch) self.assertEqual(cov.config.data_file, ".coverage") + + def test_no_toml_installed_explicit_toml(self): + # Can't specify a toml config file if toml isn't installed. + with coverage.optional.without('toml'): + msg = "Can't read 'cov.toml' without TOML support" + with self.assertRaisesRegex(CoverageException, msg): + coverage.Coverage(config_file="cov.toml") + + def test_no_toml_installed_pyproject_toml(self): + # Can't have coverage config in pyproject.toml without toml installed. + self.make_file("pyproject.toml", """\ + # A toml file! + [tool.coverage.run] + xyzzy = 17 + """) + with coverage.optional.without('toml'): + msg = "Can't read 'pyproject.toml' without TOML support" + with self.assertRaisesRegex(CoverageException, msg): + coverage.Coverage() + + def test_no_toml_installed_pyproject_no_coverage(self): + # It's ok to have non-coverage pyproject.toml without toml installed. + self.make_file("pyproject.toml", """\ + # A toml file! + [tool.something] + xyzzy = 17 + """) + with coverage.optional.without('toml'): + cov = coverage.Coverage() + # We get default settings: + self.assertFalse(cov.config.timid) + self.assertFalse(cov.config.branch) + self.assertEqual(cov.config.data_file, ".coverage")