From 80bfea74c10dec30a0fa64e1379b80c897b060a9 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 13 Jul 2021 11:23:09 +0100 Subject: [PATCH] Support TOML v1.0.0 syntax in `pyproject.toml` (#1186) * Support TOML v1.0.0 syntax in `pyproject.toml` fixes #1180 Co-authored-by: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> * fix toml meta test * use pytest.mark.parametrize to narrow test failure * Update tests/test_config.py Co-authored-by: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> Co-authored-by: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> --- coverage/tomlconfig.py | 12 +++++----- setup.py | 2 +- tests/helpers.py | 2 +- tests/test_config.py | 53 ++++++++++++++++++++---------------------- tests/test_testing.py | 8 +++---- 5 files changed, 37 insertions(+), 40 deletions(-) diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py index 1e0b1241b..aa11a8a96 100644 --- a/coverage/tomlconfig.py +++ b/coverage/tomlconfig.py @@ -12,9 +12,9 @@ # TOML support is an install-time extra option. try: - import toml + import tomli except ImportError: # pragma: not covered - toml = None + tomli = None class TomlDecodeError(Exception): @@ -44,12 +44,12 @@ def read(self, filenames): toml_text = fp.read() except OSError: return [] - if toml: + if tomli is not None: toml_text = substitute_variables(toml_text, os.environ) try: - self.data = toml.loads(toml_text) - except toml.TomlDecodeError as err: - raise TomlDecodeError(*err.args) + self.data = tomli.loads(toml_text) + except tomli.TOMLDecodeError as err: + raise TomlDecodeError(str(err)) return [filename] else: has_toml = re.search(r"^\[tool\.coverage\.", toml_text, flags=re.MULTILINE) diff --git a/setup.py b/setup.py index da2df88fd..f6fb7b4eb 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,7 @@ def better_set_verbosity(v): extras_require={ # Enable pyproject.toml support. - 'toml': ['toml'], + 'toml': ['tomli'], }, # We need to get HTML assets from our htmlfiles directory. diff --git a/tests/helpers.py b/tests/helpers.py index 3b0e12834..28adf78c7 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -242,7 +242,7 @@ def without_module(using_module, missing_module_name): Use this in a test function to make an optional module unavailable during the test:: - with without_module(product.something, 'toml'): + with without_module(product.something, 'tomli'): use_toml_somehow() Arguments: diff --git a/tests/test_config.py b/tests/test_config.py index a8b0ecef5..2bef500e6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -186,31 +186,28 @@ def test_parse_errors(self): with pytest.raises(CoverageException, match=msg): coverage.Coverage() - def test_toml_parse_errors(self): + @pytest.mark.parametrize("bad_config,msg", [ + ("[tool.coverage.run]\ntimid = \"maybe?\"\n", r"maybe[?]"), + ("[tool.coverage.run\n", None), + ('[tool.coverage.report]\nexclude_lines = ["foo("]\n', + r"Invalid \[tool.coverage.report\].exclude_lines value u?'foo\(': " + r"(unbalanced parenthesis|missing \))"), + ('[tool.coverage.report]\npartial_branches = ["foo["]\n', + r"Invalid \[tool.coverage.report\].partial_branches value u?'foo\[': " + r"(unexpected end of regular expression|unterminated character set)"), + ('[tool.coverage.report]\npartial_branches_always = ["foo***"]\n', + r"Invalid \[tool.coverage.report\].partial_branches_always value " + r"u?'foo\*\*\*': " + r"multiple repeat"), + ('[tool.coverage.run]\nconcurrency="foo"', "not a list"), + ("[tool.coverage.report]\nprecision=1.23", "not an integer"), + ('[tool.coverage.report]\nfail_under="s"', "not a float"), + ]) + def test_toml_parse_errors(self, bad_config, msg): # Im-parsable values raise CoverageException, with details. - bad_configs_and_msgs = [ - ("[tool.coverage.run]\ntimid = \"maybe?\"\n", r"maybe[?]"), - ("[tool.coverage.run\n", r"Key group"), - ('[tool.coverage.report]\nexclude_lines = ["foo("]\n', - r"Invalid \[tool.coverage.report\].exclude_lines value u?'foo\(': " - r"(unbalanced parenthesis|missing \))"), - ('[tool.coverage.report]\npartial_branches = ["foo["]\n', - r"Invalid \[tool.coverage.report\].partial_branches value u?'foo\[': " - r"(unexpected end of regular expression|unterminated character set)"), - ('[tool.coverage.report]\npartial_branches_always = ["foo***"]\n', - r"Invalid \[tool.coverage.report\].partial_branches_always value " - r"u?'foo\*\*\*': " - r"multiple repeat"), - ('[tool.coverage.run]\nconcurrency="foo"', "not a list"), - ("[tool.coverage.report]\nprecision=1.23", "not an integer"), - ('[tool.coverage.report]\nfail_under="s"', "not a float"), - ] - - for bad_config, msg in bad_configs_and_msgs: - print("Trying %r" % bad_config) - self.make_file("pyproject.toml", bad_config) - with pytest.raises(CoverageException, match=msg): - coverage.Coverage() + self.make_file("pyproject.toml", bad_config) + with pytest.raises(CoverageException, match=msg): + coverage.Coverage() def test_environment_vars_in_config(self): # Config files can have $envvars in them. @@ -715,7 +712,7 @@ def test_note_is_obsolete(self): def test_no_toml_installed_no_toml(self): # Can't read a toml file that doesn't exist. - with without_module(coverage.tomlconfig, 'toml'): + with without_module(coverage.tomlconfig, 'tomli'): msg = "Couldn't read 'cov.toml' as a config file" with pytest.raises(CoverageException, match=msg): coverage.Coverage(config_file="cov.toml") @@ -723,7 +720,7 @@ def test_no_toml_installed_no_toml(self): def test_no_toml_installed_explicit_toml(self): # 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, 'toml'): + with without_module(coverage.tomlconfig, 'tomli'): msg = "Can't read 'cov.toml' without TOML support" with pytest.raises(CoverageException, match=msg): coverage.Coverage(config_file="cov.toml") @@ -735,7 +732,7 @@ def test_no_toml_installed_pyproject_toml(self): [tool.coverage.run] xyzzy = 17 """) - with without_module(coverage.tomlconfig, 'toml'): + with without_module(coverage.tomlconfig, 'tomli'): msg = "Can't read 'pyproject.toml' without TOML support" with pytest.raises(CoverageException, match=msg): coverage.Coverage() @@ -747,7 +744,7 @@ def test_no_toml_installed_pyproject_no_coverage(self): [tool.something] xyzzy = 17 """) - with without_module(coverage.tomlconfig, 'toml'): + with without_module(coverage.tomlconfig, 'tomli'): cov = coverage.Coverage() # We get default settings: assert not cov.config.timid diff --git a/tests/test_testing.py b/tests/test_testing.py index 7219ff0bc..4699799ec 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -347,10 +347,10 @@ def _same_python_executable(e1, e2): def test_without_module(): - toml1 = tomlconfig.toml - with without_module(tomlconfig, 'toml'): - toml2 = tomlconfig.toml - toml3 = tomlconfig.toml + toml1 = tomlconfig.tomli + with without_module(tomlconfig, 'tomli'): + toml2 = tomlconfig.tomli + toml3 = tomlconfig.tomli assert toml1 is toml3 is not None assert toml2 is None