diff --git a/pyproject.toml b/pyproject.toml index 668a4e52..be180190 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,10 @@ name = "retrobios" version = "1.0.0" description = "BIOS collection manager for retrogaming platforms" # install.py is the only entry point a user runs before cloning, and both -# bootstraps accept 3.8. Nothing here needs a newer runtime, so the floor -# stays where the bootstraps put it. +# bootstraps accept 3.8, so the floor stays where the bootstraps put it. That +# floor covers install.py alone: the build scripts under scripts/ use syntax +# CPython 3.12 introduced, which is the version CI pins and the version a +# contributor needs to run the pipeline or the test suite. requires-python = ">=3.8" dependencies = [ "pyyaml", @@ -16,6 +18,14 @@ dependencies = [ dev = [ "jsonschema>=4.23", ] +# The site build the repo generates and documents. Both were hard +# requirements of deploy-site.yml and of wiki/release-process.md while being +# declared nowhere, so a contributor following either had to read the +# workflow to learn what to install. +docs = [ + "mkdocs-material>=9.7.5,<10", + "pymdown-extensions>=10.14", +] [project.urls] Repository = "https://github.com/Abdess/retrobios" diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index f4a6ee35..56206114 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -1285,6 +1285,49 @@ class PipelineReportsWhatItDid(unittest.TestCase): stale = re.findall(r'results\["(\w+)"\] = True', source) self.assertEqual(stale, [], f"steps still claiming OK when skipped: {stale}") +class DeclaredDependenciesMatchTheDocumentedWorkflows(unittest.TestCase): + """What the workflows install has to be what the project declares. + + mkdocs-material and pymdown-extensions were hard requirements of the site + build in CI and in the release guide while pyproject declared neither, so + a contributor following either had to read the workflow to find out. + """ + + @staticmethod + def _pyproject() -> dict: + import tomllib + + with (ROOT / "pyproject.toml").open("rb") as handle: + return tomllib.load(handle) + + def test_every_package_ci_installs_is_declared(self): + extras = self._pyproject()["project"]["optional-dependencies"] + declared = { + name.split(">")[0].split("=")[0].split("<")[0].strip('"') + for group in extras.values() + for name in group + } + declared |= set(self._pyproject()["project"]["dependencies"]) + installed: set[str] = set() + for workflow in ("validate.yml", "deploy-site.yml"): + body = (ROOT / ".github" / "workflows" / workflow).read_text( + encoding="utf-8" + ) + for line in body.splitlines(): + if "pip install" not in line: + continue + for token in line.split("pip install", 1)[1].split(): + name = token.strip('"').split(">")[0].split("=")[0] + name = name.split("<")[0].strip() + if name: + installed.add(name) + self.assertEqual( + installed - declared, + set(), + "CI installs packages pyproject does not declare", + ) + + class ACheckThatCannotAnswerDoesNotPass(unittest.TestCase): """Exiting zero says the question was answered and the answer was yes.