Public Access
DEVX-98: feat: add lint_docs tool, fix doc_coverage/check_translations for any repo
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 8s
Post-merge / vikunja (push) Successful in 13s
Post-merge / configure-repo (push) Successful in 11s
Post-merge / sync-wiki (push) Successful in 21s
Post-merge / release (push) Successful in 25s
Build Images / detect-type (push) Successful in 38s
Post-merge / badges (push) Successful in 34s
Post-merge / publish (push) Successful in 33s
Build Images / build-and-push (push) Successful in 3m20s
Build Images / cleanup (push) Successful in 1m57s
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 8s
Post-merge / vikunja (push) Successful in 13s
Post-merge / configure-repo (push) Successful in 11s
Post-merge / sync-wiki (push) Successful in 21s
Post-merge / release (push) Successful in 25s
Build Images / detect-type (push) Successful in 38s
Post-merge / badges (push) Successful in 34s
Post-merge / publish (push) Successful in 33s
Build Images / build-and-push (push) Successful in 3m20s
Build Images / cleanup (push) Successful in 1m57s
This commit was merged in pull request #154.
This commit is contained in:
@@ -12,10 +12,13 @@ from devx.ci.doc_coverage import (
|
||||
main,
|
||||
)
|
||||
|
||||
# Path to devx's own source directory (for testing)
|
||||
DEVX_SRC_DIR = Path(__file__).resolve().parent.parent.parent / "src" / "devx"
|
||||
|
||||
|
||||
class TestExtractCliCommands:
|
||||
def test_extracts_commands(self) -> None:
|
||||
commands = extract_cli_commands()
|
||||
commands = extract_cli_commands(DEVX_SRC_DIR)
|
||||
# devx CLI has commands under ci, tools, and molecule groups
|
||||
assert "auto-merge" in commands
|
||||
assert "release" in commands
|
||||
@@ -24,39 +27,30 @@ class TestExtractCliCommands:
|
||||
assert "install-tools" in commands
|
||||
|
||||
def test_returns_list(self) -> None:
|
||||
commands = extract_cli_commands()
|
||||
commands = extract_cli_commands(DEVX_SRC_DIR)
|
||||
assert isinstance(commands, list)
|
||||
assert len(commands) > 0
|
||||
|
||||
def test_no_cli_file(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def test_no_cli_file(self, tmp_path: Path) -> None:
|
||||
"""Returns empty list when CLI file doesn't exist."""
|
||||
from devx.ci import doc_coverage
|
||||
|
||||
monkeypatch.setattr(doc_coverage, "CLI_FILE", Path("/nonexistent/cli.py"))
|
||||
commands = extract_cli_commands()
|
||||
commands = extract_cli_commands(tmp_path)
|
||||
assert commands == []
|
||||
|
||||
def test_def_fallback_no_explicit_name(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def test_def_fallback_no_explicit_name(self, tmp_path: Path) -> None:
|
||||
"""When a command decorator has no explicit name, falls back to the def name."""
|
||||
from devx.ci import doc_coverage
|
||||
|
||||
fake_cli = tmp_path / "cli.py"
|
||||
fake_cli.write_text("@click.group()\ndef cli():\n pass\n@cli.command()\ndef my_command():\n pass\n")
|
||||
monkeypatch.setattr(doc_coverage, "CLI_FILE", fake_cli)
|
||||
commands = extract_cli_commands()
|
||||
commands = extract_cli_commands(tmp_path)
|
||||
assert "my_command" in commands
|
||||
|
||||
def test_command_decorator_no_def_fallback(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def test_command_decorator_no_def_fallback(self, tmp_path: Path) -> None:
|
||||
"""When a command decorator has no name and no following def, it is skipped."""
|
||||
from devx.ci import doc_coverage
|
||||
|
||||
fake_cli = tmp_path / "cli.py"
|
||||
# The last @cli.command() has no explicit name and no def statement after it
|
||||
fake_cli.write_text(
|
||||
"@click.group()\ndef cli():\n pass\n@cli.command()\ndef real_cmd():\n pass\n@cli.command()\npass\n"
|
||||
)
|
||||
monkeypatch.setattr(doc_coverage, "CLI_FILE", fake_cli)
|
||||
commands = extract_cli_commands()
|
||||
commands = extract_cli_commands(tmp_path)
|
||||
# real_cmd should be found via def fallback; the bare @cli.command() is skipped
|
||||
assert "real_cmd" in commands
|
||||
assert "pass" not in commands
|
||||
@@ -96,19 +90,29 @@ class TestMain:
|
||||
docs = tmp_path / "docs"
|
||||
(docs / "user").mkdir(parents=True)
|
||||
(docs / "tech").mkdir(parents=True)
|
||||
# Get actual commands from the CLI
|
||||
commands = extract_cli_commands()
|
||||
src = tmp_path / "src" / "devx"
|
||||
src.mkdir(parents=True)
|
||||
(src / "ci").mkdir()
|
||||
(src / "__init__.py").write_text("")
|
||||
(src / "ci" / "__init__.py").write_text("")
|
||||
# Create a fake cli.py with some commands
|
||||
(src / "cli.py").write_text(
|
||||
"@click.group()\ndef cli():\n pass\n"
|
||||
"@cli.command('release')\ndef release():\n pass\n"
|
||||
"@cli.command('setup')\ndef setup():\n pass\n"
|
||||
)
|
||||
# Create a fake module and CI script
|
||||
(src / "config.py").write_text("# config module")
|
||||
(src / "ci" / "auto_merge.py").write_text("# auto_merge script")
|
||||
# Write cli-commands.md with all commands
|
||||
cli_content = "\n".join(f"## {cmd}" for cmd in commands)
|
||||
cli_content = "## release\n\n## setup\n"
|
||||
(docs / "user" / "cli-commands.md").write_text(cli_content)
|
||||
# Write architecture.md with all modules
|
||||
from devx.ci.doc_coverage import REQUIRED_MODULES, REQUIRED_SCRIPTS
|
||||
|
||||
(docs / "tech" / "architecture.md").write_text(" ".join(REQUIRED_MODULES))
|
||||
(docs / "tech" / "architecture.md").write_text("config.py")
|
||||
# Write ci-cd-workflow.md with all scripts
|
||||
(docs / "tech" / "ci-cd-workflow.md").write_text(" ".join(REQUIRED_SCRIPTS))
|
||||
(docs / "tech" / "ci-cd-workflow.md").write_text("auto_merge.py")
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs)])
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs), "--source-dir", str(src)])
|
||||
assert result.exit_code == 0
|
||||
assert "100%" in result.output
|
||||
|
||||
@@ -117,11 +121,21 @@ class TestMain:
|
||||
docs = tmp_path / "docs"
|
||||
(docs / "user").mkdir(parents=True)
|
||||
(docs / "tech").mkdir(parents=True)
|
||||
src = tmp_path / "src" / "devx"
|
||||
src.mkdir(parents=True)
|
||||
(src / "ci").mkdir()
|
||||
(src / "__init__.py").write_text("")
|
||||
(src / "ci" / "__init__.py").write_text("")
|
||||
(src / "cli.py").write_text(
|
||||
"@click.group()\ndef cli():\n pass\n@cli.command('release')\ndef release():\n pass\n"
|
||||
)
|
||||
(src / "config.py").write_text("# config")
|
||||
(src / "ci" / "auto_merge.py").write_text("# auto_merge")
|
||||
(docs / "user" / "cli-commands.md").write_text("No commands here.")
|
||||
(docs / "tech" / "architecture.md").write_text("No modules here.")
|
||||
(docs / "tech" / "ci-cd-workflow.md").write_text("No scripts here.")
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs), "--fail-on-missing"])
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs), "--source-dir", str(src), "--fail-on-missing"])
|
||||
assert result.exit_code == 1
|
||||
|
||||
def test_missing_docs_warn_only(self, tmp_path: Path) -> None:
|
||||
@@ -129,10 +143,55 @@ class TestMain:
|
||||
docs = tmp_path / "docs"
|
||||
(docs / "user").mkdir(parents=True)
|
||||
(docs / "tech").mkdir(parents=True)
|
||||
src = tmp_path / "src" / "devx"
|
||||
src.mkdir(parents=True)
|
||||
(src / "ci").mkdir()
|
||||
(src / "__init__.py").write_text("")
|
||||
(src / "ci" / "__init__.py").write_text("")
|
||||
(src / "cli.py").write_text(
|
||||
"@click.group()\ndef cli():\n pass\n@cli.command('release')\ndef release():\n pass\n"
|
||||
)
|
||||
(src / "config.py").write_text("# config")
|
||||
(src / "ci" / "auto_merge.py").write_text("# auto_merge")
|
||||
(docs / "user" / "cli-commands.md").write_text("No commands here.")
|
||||
(docs / "tech" / "architecture.md").write_text("No modules here.")
|
||||
(docs / "tech" / "ci-cd-workflow.md").write_text("No scripts here.")
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs)])
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs), "--source-dir", str(src)])
|
||||
assert result.exit_code == 0
|
||||
assert "MISSING" in result.output
|
||||
|
||||
def test_auto_detect_scripts_dir(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""When src/ doesn't exist but scripts/ does, auto-detect it."""
|
||||
monkeypatch.chdir(tmp_path)
|
||||
docs = tmp_path / "docs"
|
||||
(docs / "user").mkdir(parents=True)
|
||||
(docs / "tech").mkdir(parents=True)
|
||||
scripts = tmp_path / "scripts"
|
||||
scripts.mkdir()
|
||||
(scripts / "cli.py").write_text(
|
||||
"@click.group()\ndef cli():\n pass\n@cli.command('release')\ndef release():\n pass\n"
|
||||
)
|
||||
(scripts / "config.py").write_text("# config")
|
||||
(docs / "user" / "cli-commands.md").write_text("## release\n")
|
||||
(docs / "tech" / "architecture.md").write_text("config.py")
|
||||
(docs / "tech" / "ci-cd-workflow.md").write_text("")
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs)])
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_no_source_dir_falls_back_to_required(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""When no source dir exists, falls back to REQUIRED_MODULES/SCRIPTS."""
|
||||
monkeypatch.chdir(tmp_path)
|
||||
docs = tmp_path / "docs"
|
||||
(docs / "user").mkdir(parents=True)
|
||||
(docs / "tech").mkdir(parents=True)
|
||||
(docs / "user" / "cli-commands.md").write_text("")
|
||||
from devx.ci.doc_coverage import REQUIRED_MODULES, REQUIRED_SCRIPTS
|
||||
|
||||
(docs / "tech" / "architecture.md").write_text(" ".join(REQUIRED_MODULES))
|
||||
(docs / "tech" / "ci-cd-workflow.md").write_text(" ".join(REQUIRED_SCRIPTS))
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--docs-dir", str(docs)])
|
||||
# No source dir found, so no CLI commands, but modules/scripts from REQUIRED lists
|
||||
assert result.exit_code == 0
|
||||
|
||||
Reference in New Issue
Block a user