Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ classifiers = [
"Topic :: Software Development :: Code Generators",
]
dependencies = [
"plain-parser==0.1.4",
"plain-parser==0.2.0",

# Both transitive via plain-parser; pinned so an automatic mistletoe upgrade doesn't change spec hashes.
"mistletoe==1.6.0",
Expand Down
52 changes: 52 additions & 0 deletions tests/test_dependency_pins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""The pins of requirements.txt and pyproject.toml must say the same thing.

requirements.txt builds the development environment and the CI jobs. pyproject.toml is what the
wheel carries, so it is what an installed client gets. A pin changed in one file and not the other
passes every test here and still ships a client that cannot start.
"""

import os
import re
import tomllib

REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def _name(requirement: str) -> str:
return re.split(r"[=<>@\[]", requirement, maxsplit=1)[0].strip().lower()


def _requirements() -> dict[str, str]:
with open(os.path.join(REPO, "requirements.txt"), encoding="utf-8") as f:
lines = [line.strip() for line in f]
return {_name(line): line for line in lines if line and not line.startswith("#")}


def _pyproject() -> tuple[dict[str, str], dict[str, str]]:
with open(os.path.join(REPO, "pyproject.toml"), "rb") as f:
pyproject = tomllib.load(f)["project"]
runtime = {_name(item): item for item in pyproject["dependencies"]}
dev = {_name(item): item for item in pyproject["optional-dependencies"]["dev"]}
return runtime, dev


def test_every_pin_of_the_wheel_matches_requirements():
requirements = _requirements()
runtime, dev = _pyproject()

mismatched = {
name: (requirement, requirements.get(name))
for name, requirement in {**runtime, **dev}.items()
if requirements.get(name) != requirement
}

assert mismatched == {}, f"pyproject.toml and requirements.txt disagree: {mismatched}"


def test_requirements_names_nothing_the_wheel_leaves_out():
requirements = _requirements()
runtime, dev = _pyproject()

missing = sorted(set(requirements) - set(runtime) - set(dev))

assert missing == [], f"requirements.txt pins what pyproject.toml does not declare: {missing}"
Loading