Skip to content

Commit

Permalink
Support TOML v1.0.0 syntax in pyproject.toml (#1186)
Browse files Browse the repository at this point in the history
* Support TOML v1.0.0 syntax in `pyproject.toml`

fixes #1180

Co-authored-by: Taneli Hukkinen <[email protected]>

* fix toml meta test

* use pytest.mark.parametrize to narrow test failure

* Update tests/test_config.py

Co-authored-by: Taneli Hukkinen <[email protected]>

Co-authored-by: Taneli Hukkinen <[email protected]>
  • Loading branch information
graingert and hukkin authored Jul 13, 2021
1 parent 8cb3215 commit 80bfea7
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 40 deletions.
12 changes: 6 additions & 6 deletions coverage/tomlconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
53 changes: 25 additions & 28 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -715,15 +712,15 @@ 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")

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")
Expand All @@ -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()
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions tests/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 80bfea7

Please sign in to comment.