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
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ build-backend = "hatchling.build"

[project]
name = "docproof"
version = "0.1.2"
# DYNAMIC, and that is the fix rather than a preference. This read `version = "0.1.2"`
# while `src/docproof/__init__.py` said "0.1.0", so `docproof --version` and every
# report header printed 0.1.0 on an install pinned to v0.1.2. Two sources of truth for
# one number, disagreeing - which is the exact defect this tool exists to find, in this
# tool. Hatchling reads the package attribute now, so there is one literal and the
# build follows it. Correcting the number would have left the second copy in place.
dynamic = ["version"]
description = "Prove your documentation against your code. Deterministically, with receipts."
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -34,6 +40,9 @@ dev = [
[project.scripts]
docproof = "docproof.cli:main"

[tool.hatch.version]
path = "src/docproof/__init__.py"

[tool.hatch.build.targets.wheel]
packages = ["src/docproof"]

Expand Down
2 changes: 1 addition & 1 deletion src/docproof/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""docproof — prove your documentation against your code."""

__version__ = "0.1.0"
__version__ = "0.1.3"
83 changes: 83 additions & 0 deletions tests/test_version_has_one_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""The version is declared once, and the build reads it from there.

THE DEFECT, MEASURED 2026-08-18
-------------------------------
`pyproject.toml` carried `version = "0.1.2"` while `src/docproof/__init__.py` carried
`__version__ = "0.1.0"`. Three releases had shipped and the README pinned v0.1.2, so a user
who followed the README got a tool whose every report header read:

docproof 0.1.0 - their-repo, N document(s)

`--version` said the same. The packaging metadata was right and the number the tool SAYS
ABOUT ITSELF was two releases stale, which is a documented claim contradicted by the
repository - the exact thing docproof is for, inside docproof.

Found while checking whether the tool was fit to offer into another project's CI, after a
gsd-core maintainer had been pointed at it.

WHY THIS IS A STRUCTURAL TEST AND NOT A VALUE TEST
--------------------------------------------------
Asserting the two numbers are EQUAL would pass today and rot the moment someone bumps one
of them, which is precisely how this arose. The fix was to delete the second copy: hatchling
reads the package attribute, so there is nothing left to disagree. This test asserts THAT,
because a rule that says "keep these in sync" is a rule somebody has to remember, and the
whole point of this repository is that those are the rules that fail.
"""

from __future__ import annotations

import re
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PYPROJECT = ROOT / "pyproject.toml"
INIT = ROOT / "src" / "docproof" / "__init__.py"


def _pyproject() -> str:
return PYPROJECT.read_text(encoding="utf-8")


def test_pyproject_declares_no_version_of_its_own() -> None:
"""A static `version = "..."` under [project] is the second copy that caused this."""
project_block = _pyproject().split("[project]", 1)[1].split("\n[", 1)[0]
static = re.findall(r"^version\s*=", project_block, re.M)

assert static == [], (
"pyproject declares a static version again. That is the second source of truth "
"that printed 0.1.0 on a v0.1.2 install. Use the package attribute."
)


def test_pyproject_marks_version_dynamic_and_points_at_the_package() -> None:
text = _pyproject()

assert 'dynamic = ["version"]' in text
assert "[tool.hatch.version]" in text
assert 'path = "src/docproof/__init__.py"' in text


def test_the_package_carries_exactly_one_version_literal() -> None:
literals = re.findall(r'__version__\s*=\s*"([^"]+)"', INIT.read_text(encoding="utf-8"))

assert len(literals) == 1, f"expected one version literal, found {literals}"


def test_the_reported_version_is_the_declared_one() -> None:
"""What `--version` and every report header print comes from that literal.

The header is the user-visible surface that was wrong, so it is asserted directly
rather than trusting that importing the name is enough.
"""
from docproof import __version__

declared = re.search(r'__version__\s*=\s*"([^"]+)"', INIT.read_text(encoding="utf-8"))
assert declared is not None
assert __version__ == declared.group(1)


def test_the_version_is_a_plain_release_number() -> None:
"""Guards the bump itself: a stray suffix or an empty string would ship silently."""
from docproof import __version__

assert re.fullmatch(r"\d+\.\d+\.\d+", __version__), __version__
Loading