Source code for tests.test_readme

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Tests for readme.md documentation sync with code."""

from __future__ import annotations

import re
from pathlib import Path

from click.testing import CliRunner

from repomatic.cli import repomatic
from repomatic.tool_runner import TOOL_REGISTRY, NativeFormat

REPO_ROOT = Path(__file__).parent.parent
README = REPO_ROOT / "readme.md"

# The --config default path includes a platform-specific directory
# (~/Library/Application Support/ on macOS, ~/.config/ on Linux, etc.).
# Different path lengths also cause different line-wrapping in the help output.
# Strip the entire --config block (from "--config" to "--no-config") so the
# README (written on macOS) matches CI (Linux/Windows).
_CONFIG_OPTION_RE = re.compile(r"  --config CONFIG_PATH.+?(?=  --no-config)", re.DOTALL)


def _readme_text() -> str:
    """Read readme.md once and cache for the module."""
    return README.read_text(encoding="UTF-8")


def _normalize_config_path(text: str) -> str:
    """Strip the platform-specific --config option block."""
    return _CONFIG_OPTION_RE.sub("", text)


[docs] def test_readme_cli_help_matches() -> None: """CLI help output in readme.md must match actual ``repomatic --help``.""" runner = CliRunner() result = runner.invoke(repomatic, ["--no-color", "--help"]) assert result.exit_code == 0 assert result.output, "No output from `repomatic --help`" assert _normalize_config_path(result.output) in _normalize_config_path( _readme_text() ), ( "CLI help output in readme.md is out of sync. " "Re-run `repomatic --no-color --help` and paste the output." )
[docs] def test_readme_config_table_matches() -> None: """Config table in readme.md must match ``show-config`` output.""" runner = CliRunner() result = runner.invoke( repomatic, ["--no-color", "--table-format", "github", "show-config"] ) assert result.exit_code == 0 assert result.output, "No output from `repomatic show-config`" assert result.output in _readme_text(), ( "Config table in readme.md is out of sync. " "Re-run `repomatic --table-format github show-config` and paste the output." )
[docs] def test_readme_bridge_table_covers_registry() -> None: """The bridge table must list every registry tool that supports translation. A tool supports ``[tool.X]`` translation when it does not natively read ``pyproject.toml`` (``reads_pyproject=False``) and can receive a translated config via either a ``config_flag`` or ``native_config_files`` in a non-editorconfig format (editorconfig files are shared across tools and not suitable as single-tool bridge targets). """ readme = _readme_text() match = re.search( r"### `\[tool\.X\]` bridge for third-party tools.*?" r"\| Tool\s+\|.*?\n\| :-+.*?\n(.*?)\n\n", readme, re.DOTALL, ) assert match, "Bridge table not found in readme.md" documented = set() for line in match.group(1).strip().splitlines(): # Extract tool name from markdown link: | [name](url) | m = re.match(r"\|\s*\[([^\]]+)\]", line) if m: documented.add(m.group(1)) bridgeable = { name for name, spec in TOOL_REGISTRY.items() if not spec.reads_pyproject and ( spec.config_flag or ( spec.native_config_files and spec.native_format is not NativeFormat.EDITORCONFIG ) ) } missing = bridgeable - documented assert not missing, ( f"Tools with [tool.X] bridge support missing from readme bridge table: " f"{sorted(missing)}. Add them to the '### `[tool.X]` bridge' section." ) extra = documented - bridgeable assert not extra, ( f"Tools listed in readme bridge table but not bridgeable in registry: " f"{sorted(extra)}. Remove them or update the registry." )
[docs] def test_readme_tip_table_covers_registry() -> None: """The TIP table must list every registry tool that natively reads pyproject.toml. Tools with ``reads_pyproject=True`` in the registry should appear in the TIP admonition table. The table may also list non-registry tools (e.g., coverage.py, pytest, uv) that the workflows use and that natively read ``[tool.*]`` sections. """ readme = _readme_text() match = re.search( r"> \[!TIP\].*?" r"> \| Tool\s+\|.*?\n> \| :-+.*?\n(.*?)\n>\n", readme, re.DOTALL, ) assert match, "TIP table not found in readme.md" documented = set() for line in match.group(1).strip().splitlines(): # Extract tool name from: > | [name](url) | m = re.match(r">\s*\|\s*\[([^\]]+)\]", line) if m: documented.add(m.group(1)) native_readers = { name for name, spec in TOOL_REGISTRY.items() if spec.reads_pyproject } # Registry tool names may differ from display names (e.g., "bump-my-version" # is displayed as "bump-my-version" in the registry but the table shows it # by its PyPI/branded name). Map registry names to the names used in the # table. display_names = set() for name in native_readers: spec = TOOL_REGISTRY[name] display_names.add(spec.package or spec.name) missing = display_names - documented assert not missing, ( f"Tools with reads_pyproject=True missing from readme TIP table: " f"{sorted(missing)}. Add them to the '[tool.*] sections' TIP." )