Skip to content

Commit

Permalink
Update tests for new pyproject-parser error messages.
Browse files Browse the repository at this point in the history
Use re-assert for regex tests.
  • Loading branch information
domdfcoding committed May 18, 2022
1 parent 7bc67ec commit 0cdc9bb
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 34 deletions.
4 changes: 2 additions & 2 deletions __pkginfo__.py
Expand Up @@ -3,7 +3,7 @@
__all__ = ["extras_require"]

extras_require = {
"readme": ["docutils==0.16", "pyproject-parser[readme]>=0.3.0"],
"readme": ["docutils==0.16", "pyproject-parser[readme]>=0.6.0b2"],
"editable": ["editables>=0.2"],
"all": ["docutils==0.16", "editables>=0.2", "pyproject-parser[readme]>=0.3.0"]
"all": ["docutils==0.16", "editables>=0.2", "pyproject-parser[readme]>=0.6.0b2"]
}
1 change: 1 addition & 0 deletions formate.toml
Expand Up @@ -54,6 +54,7 @@ known_third_party = [
"pytest_cov",
"pytest_randomly",
"pytest_timeout",
"re_assert",
"readme_renderer",
"shippinglabel",
"toml",
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Expand Up @@ -49,9 +49,9 @@ Documentation = "https://whey.readthedocs.io/en/latest"
whey = "whey.__main__:main"

[project.optional-dependencies]
readme = [ "docutils==0.16", "pyproject-parser[readme]>=0.3.0",]
readme = [ "docutils==0.16", "pyproject-parser[readme]>=0.6.0b2",]
editable = [ "editables>=0.2",]
all = [ "docutils==0.16", "editables>=0.2", "pyproject-parser[readme]>=0.3.0",]
all = [ "docutils==0.16", "editables>=0.2", "pyproject-parser[readme]>=0.6.0b2",]

[tool.mkrecipe]
conda-channels = [ "conda-forge", "domdfcoding",]
Expand Down
2 changes: 1 addition & 1 deletion repo_helper.yml
Expand Up @@ -79,7 +79,7 @@ entry_points:

extras_require:
readme:
- pyproject-parser[readme]>=0.3.0
- pyproject-parser[readme]>=0.6.0b2
- docutils==0.16
editable:
- editables>=0.2
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Expand Up @@ -6,5 +6,5 @@ domdf-python-tools>=2.8.0
handy-archives>=0.1.0
natsort>=7.1.1
packaging>=20.9
pyproject-parser>=0.3.0
pyproject-parser>=0.6.0b2
shippinglabel>=0.16.0
3 changes: 2 additions & 1 deletion tests/requirements.txt
@@ -1,4 +1,4 @@
git+https://github.com/repo-helper/pyproject-examples
git+https://github.com/repo-helper/pyproject-examples@parserv6
coincidence>=0.2.0
coverage>=5.1
coverage-pyver-pragma>=0.2.1
Expand All @@ -9,5 +9,6 @@ pytest>=6.0.0
pytest-cov>=2.8.1
pytest-randomly>=3.7.0
pytest-timeout>=1.4.2
re-assert>=1.1.0
whey-conda>=0.1.0
whey-pth>=0.0.4
87 changes: 72 additions & 15 deletions tests/test_cli.py
@@ -1,5 +1,6 @@
# stdlib
import re
import textwrap
from typing import TYPE_CHECKING, Any, Dict, List, Type

# 3rd party
Expand All @@ -21,7 +22,7 @@
OPTIONAL_DEPENDENCIES,
URLS
)
from pyproject_examples.utils import file_not_found_regex
from re_assert import Matches # type: ignore[import]

# this package
from tests.example_configs import COMPLETE_A, COMPLETE_B
Expand Down Expand Up @@ -368,67 +369,90 @@ def test_build_additional_files(
[
pytest.param(
'[project]\nname = "spam"',
"BadConfigError: The 'project.version' field must be provided.\nAborted!",
textwrap.dedent("""\
BadConfigError: The 'project.version' field must be provided.
Use '--traceback' to view the full traceback.
Aborted!"""),
id="no_version"
),
pytest.param(
'[project]\n\nversion = "2020.0.0"',
"BadConfigError: The 'project.name' field must be provided.\nAborted!",
textwrap.dedent("""\
BadConfigError: The 'project.name' field must be provided.
Use '--traceback' to view the full traceback.
Aborted!"""),
id="no_name"
),
pytest.param(
'[project]\ndynamic = ["name"]',
"BadConfigError: The 'project.name' field may not be dynamic.\nAborted!",
textwrap.dedent("""\
BadConfigError: The 'project.name' field may not be dynamic.
Use '--traceback' to view the full traceback.
Aborted!"""),
id="dynamic_name"
),
pytest.param(
'[project]\nname = "???????12345=============☃"\nversion = "2020.0.0"',
"BadConfigError: The value for 'project.name' is invalid.\nAborted!",
re.escape(textwrap.dedent("""\
BadConfigError: The value '???????12345=============☃' for 'project.name' is invalid.
Documentation: https://whey.readthedocs.io/en/latest/configuration.html#tconf-project.name
Use '--traceback' to view the full traceback.
Aborted!""")),
id="bad_name"
),
pytest.param(
'[project]\nname = "spam"\nversion = "???????12345=============☃"',
re.escape("BadConfigError: Invalid version: '???????12345=============☃'\nAborted!"),
re.escape(textwrap.dedent("""\
InvalidVersion: '???????12345=============☃'
Note: versions must follow PEP 440
Documentation: https://peps.python.org/pep-0440/
Use '--traceback' to view the full traceback.
Aborted!""")),
id="bad_version"
),
pytest.param(
f'{MINIMAL_CONFIG}\nrequires-python = "???????12345=============☃"',
re.escape("BadConfigError: Invalid specifier: '???????12345=============☃'\nAborted!"),
re.escape(textwrap.dedent("""\
InvalidSpecifier: '???????12345=============☃'
Note: specifiers must follow PEP 508
Documentation: https://peps.python.org/pep-0508/
Use '--traceback' to view the full traceback.
Aborted!""")),
id="bad_requires_python"
),
pytest.param(
f'{MINIMAL_CONFIG}\nauthors = [{{name = "Bob, Alice"}}]',
r"BadConfigError: The 'project.authors\[0\].name' key cannot contain commas.\nAborted!",
r"BadConfigError: The 'project.authors\[0\].name' key cannot contain commas.\n Documentation: https://whey.readthedocs.io/en/latest/configuration.html#tconf-project.authors\n Use '--traceback' to view the full traceback.\nAborted!",
id="author_comma"
),
pytest.param(
f'{MINIMAL_CONFIG}\nmaintainers = [{{name = "Bob, Alice"}}]',
r"BadConfigError: The 'project.maintainers\[0\].name' key cannot contain commas.\nAborted!",
r"BadConfigError: The 'project.maintainers\[0\].name' key cannot contain commas.\n Documentation: https://whey.readthedocs.io/en/latest/configuration.html#tconf-project.maintainers\n Use '--traceback' to view the full traceback.\nAborted!",
id="maintainer_comma"
),
pytest.param(
f'{MINIMAL_CONFIG}\nkeywords = [1, 2, 3, 4, 5]',
r"TypeError: Invalid type for 'project.keywords\[0\]': expected <class 'str'>, got <class 'int'>\nAborted!",
r"TypeError: Invalid type for 'project.keywords\[0\]': expected <class 'str'>, got <class 'int'>\n Documentation: https://whey.readthedocs.io/en/latest/configuration.html#tconf-project.keywords\n Use '--traceback' to view the full traceback.\nAborted!",
id="keywords_wrong_type"
),
pytest.param(
f'{MINIMAL_CONFIG}\nclassifiers = [1, 2, 3, 4, 5]',
r"TypeError: Invalid type for 'project.classifiers\[0\]': expected <class 'str'>, got <class 'int'>\nAborted!",
r"TypeError: Invalid type for 'project.classifiers\[0\]': expected <class 'str'>, got <class 'int'>\n Documentation: https://whey.readthedocs.io/en/latest/configuration.html#tconf-project.classifiers\n Use '--traceback' to view the full traceback.\nAborted!",
id="classifiers_wrong_type"
),
pytest.param(
f'{MINIMAL_CONFIG}\ndependencies = [1, 2, 3, 4, 5]',
r"TypeError: Invalid type for 'project.dependencies\[0\]': expected <class 'str'>, got <class 'int'>\nAborted!",
r"TypeError: Invalid type for 'project.dependencies\[0\]': expected <class 'str'>, got <class 'int'>\n Documentation: https://whey.readthedocs.io/en/latest/configuration.html#tconf-project.dependencies\n Use '--traceback' to view the full traceback.\nAborted!",
id="dependencies_wrong_type"
),
pytest.param(
f'{MINIMAL_CONFIG}\nreadme = "README.rst"',
f"File Not Found: {file_not_found_regex('README.rst')}\nAborted!",
"No such file or directory: 'README.rst'\nAborted!",
id="missing_readme_file",
),
pytest.param(
f'{MINIMAL_CONFIG}\nlicense = {{file = "LICENSE.txt"}}',
f"File Not Found: {file_not_found_regex('LICENSE.txt')}\nAborted!",
"No such file or directory: 'LICENSE.txt'\nAborted!",
id="missing_license_file",
),
]
Expand All @@ -448,7 +472,40 @@ def test_bad_config(
)
assert result.exit_code == 1

assert re.match(match, result.stdout.rstrip())
_Matches(match).assert_matches(result.stdout.rstrip())
# assert re.match(match, result.stdout.rstrip())


# Based on https://pypi.org/project/re-assert/
# Copyright (c) 2019 Anthony Sottile
# MIT Licensed
class _Matches(Matches):

def _fail_message(self, fail: str) -> str:
# binary search to find the longest substring match
pos, bound = 0, len(fail)
while pos < bound:
pivot = pos + (bound - pos + 1) // 2
match = self._pattern.match(fail[:pivot], partial=True)
if match:
pos = pivot
else:
bound = pivot - 1

retv = [f' regex: {self._pattern.pattern}', " failed to match at:", '']
for line in fail.splitlines(True):
line_noeol = line.rstrip('\r\n')
retv.append(f'> {line_noeol}')
if 0 <= pos <= len(line_noeol):
indent = ''.join(c if c.isspace() else ' ' for c in line[:pos])
retv.append(f' {indent}^')
pos = -1
else:
pos -= len(line)
if pos >= 0:
retv.append('>')
retv.append(" ^")
return '\n'.join(retv)


@pytest.mark.parametrize(
Expand Down
14 changes: 2 additions & 12 deletions whey/utils.py
Expand Up @@ -49,18 +49,8 @@

class WheyTracebackHandler(ConfigTracebackHandler):
"""
Custom :class:`consolekit.tracebacks.TracebackHandler` which handles :exc:`dom_toml.parser.BadConfigError`
and :exc:`packaging.requirements.InvalidRequirement`.
""" # noqa: D400

def handle_InvalidRequirement(self, e: InvalidRequirement) -> bool: # noqa: D102
raise abort(f"{e.__class__.__name__}: {e}", colour=False)

def handle_TypeError(self, e: TypeError) -> bool: # noqa: D102
raise abort(f"{e.__class__.__name__}: {e}", colour=False)

def handle_BadConfigError(self, e: BadConfigError) -> bool: # noqa: D102
raise abort(f"{e.__class__.__name__}: {e}", colour=False)
Custom :class:`consolekit.tracebacks.TracebackHandler`.
"""


def parse_custom_builders(builders: Optional[Iterable[str]]) -> Dict[str, Type[AbstractBuilder]]:
Expand Down

0 comments on commit 0cdc9bb

Please sign in to comment.