From 8e7c7806d179fc0aafa0575c7aaa5016699d8925 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Fri, 18 Sep 2026 22:16:43 +0530 Subject: [PATCH 1/2] ci(readme): fail closed when command discovery is empty --- tests/test_readme_examples.py | 51 ++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/tests/test_readme_examples.py b/tests/test_readme_examples.py index 195db05..6946ffd 100644 --- a/tests/test_readme_examples.py +++ b/tests/test_readme_examples.py @@ -2,6 +2,7 @@ import json import os +import re import shlex import shutil import subprocess @@ -11,15 +12,38 @@ import pytest -def readme_commands() -> list[list[str]]: +README_PATH = Path(__file__).parents[1] / "README.md" +EXAMPLES_PATH = Path(__file__).parents[1] / "examples" + + +def parse_readme_commands(markdown: str) -> list[list[str]]: commands: list[list[str]] = [] in_shell_block = False - for line in Path("README.md").read_text(encoding="utf-8").splitlines(): - if line.strip().startswith("```"): - in_shell_block = not in_shell_block + for line in markdown.splitlines(): + fence = re.match(r"^\s*```([^`]*)", line) + if fence: + language = fence.group(1).strip().lower() + if in_shell_block: + in_shell_block = False + else: + in_shell_block = language in {"bash", "sh", "shell", "console"} + continue + if not in_shell_block: continue - if in_shell_block and line.strip().startswith("northstar"): - commands.append(shlex.split(line.strip())) + command_line = line.strip() + if command_line.startswith("$ "): + command_line = command_line[2:].lstrip() + if re.match(r"^northstar(?:-[a-z0-9-]+)?(?:\s|$)", command_line): + commands.append(shlex.split(command_line)) + return commands + + +def readme_commands() -> list[list[str]]: + commands = parse_readme_commands(README_PATH.read_text(encoding="utf-8")) + if len(commands) < 10: + raise AssertionError( + f"README command discovery found only {len(commands)} Northstar commands." + ) return commands @@ -40,6 +64,7 @@ def run_installed_command( "LOCALAPPDATA": str(home / "home" / "AppData" / "Local"), } ) + shutil.copytree(EXAMPLES_PATH, home / "examples") return subprocess.run( [executable, *args[1:]], capture_output=True, @@ -61,6 +86,20 @@ def test_readme_northstar_commands_run_from_the_installed_wheel( ) +def test_readme_command_parser_fails_closed_on_an_invalid_documented_command( + tmp_path: Path, +) -> None: + markdown = README_PATH.read_text(encoding="utf-8").replace( + "$ northstar --help", "$ northstar --definitely-invalid" + ) + commands = parse_readme_commands(markdown) + invalid = next(args for args in commands if "--definitely-invalid" in args) + result = run_installed_command(invalid, tmp_path) + + assert result.returncode != 0 + assert commands + + def test_readme_json_output_is_machine_readable(tmp_path: Path) -> None: result = run_installed_command( ["northstar", "--quiet", "--environment", "dev", "status", "--format", "json"], From 098b5088e8c6b72188550fe70c162289b8accc29 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Sat, 19 Sep 2026 16:26:02 +0530 Subject: [PATCH 2/2] test: share robust README command discovery --- tests/documentation_commands.py | 29 ++++++++++++++++++++++++++ tests/test_documentation.py | 10 +++------ tests/test_readme_examples.py | 37 ++++++++++----------------------- 3 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 tests/documentation_commands.py diff --git a/tests/documentation_commands.py b/tests/documentation_commands.py new file mode 100644 index 0000000..53312b1 --- /dev/null +++ b/tests/documentation_commands.py @@ -0,0 +1,29 @@ +"""Shared extraction of executable shell examples from Markdown documents.""" + +from __future__ import annotations + +import re +import shlex + + +_FENCE_PATTERN = re.compile(r"^\s*```") +_COMMAND_PATTERN = re.compile(r"^northstar(?:-[a-z0-9-]+)?(?:\s|$)") + + +def parse_documented_commands(markdown: str) -> list[list[str]]: + """Return Northstar commands from any fenced Markdown code block.""" + + commands: list[list[str]] = [] + in_code_block = False + for line in markdown.splitlines(): + if _FENCE_PATTERN.match(line): + in_code_block = not in_code_block + continue + if not in_code_block: + continue + command_line = line.strip() + if command_line.startswith("$ "): + command_line = command_line[2:].lstrip() + if _COMMAND_PATTERN.match(command_line): + commands.append(shlex.split(command_line)) + return commands diff --git a/tests/test_documentation.py b/tests/test_documentation.py index 1eca93a..758cce9 100644 --- a/tests/test_documentation.py +++ b/tests/test_documentation.py @@ -1,7 +1,5 @@ from __future__ import annotations -import re -import shlex import tempfile from pathlib import Path @@ -9,19 +7,17 @@ import pytest from base_cli_demo.cli import command +from documentation_commands import parse_documented_commands -COMMAND_PATTERN = re.compile(r"^\$\s+(northstar(?:\s+.*)?)$") DOCUMENTS = (Path("README.md"), Path("docs/learning-path.md")) def documented_commands() -> list[tuple[str, list[str]]]: examples: list[tuple[str, list[str]]] = [] for document in DOCUMENTS: - for line in document.read_text(encoding="utf-8").splitlines(): - match = COMMAND_PATTERN.match(line) - if match: - examples.append((str(document), shlex.split(match.group(1)))) + commands = parse_documented_commands(document.read_text(encoding="utf-8")) + examples.extend((str(document), command) for command in commands) return examples diff --git a/tests/test_readme_examples.py b/tests/test_readme_examples.py index 6946ffd..8e34a5a 100644 --- a/tests/test_readme_examples.py +++ b/tests/test_readme_examples.py @@ -2,8 +2,6 @@ import json import os -import re -import shlex import shutil import subprocess import sys @@ -11,38 +9,22 @@ import pytest +from documentation_commands import parse_documented_commands + README_PATH = Path(__file__).parents[1] / "README.md" EXAMPLES_PATH = Path(__file__).parents[1] / "examples" def parse_readme_commands(markdown: str) -> list[list[str]]: - commands: list[list[str]] = [] - in_shell_block = False - for line in markdown.splitlines(): - fence = re.match(r"^\s*```([^`]*)", line) - if fence: - language = fence.group(1).strip().lower() - if in_shell_block: - in_shell_block = False - else: - in_shell_block = language in {"bash", "sh", "shell", "console"} - continue - if not in_shell_block: - continue - command_line = line.strip() - if command_line.startswith("$ "): - command_line = command_line[2:].lstrip() - if re.match(r"^northstar(?:-[a-z0-9-]+)?(?:\s|$)", command_line): - commands.append(shlex.split(command_line)) - return commands + return parse_documented_commands(markdown) def readme_commands() -> list[list[str]]: commands = parse_readme_commands(README_PATH.read_text(encoding="utf-8")) - if len(commands) < 10: + if not commands: raise AssertionError( - f"README command discovery found only {len(commands)} Northstar commands." + "README command discovery found no Northstar commands." ) return commands @@ -64,7 +46,8 @@ def run_installed_command( "LOCALAPPDATA": str(home / "home" / "AppData" / "Local"), } ) - shutil.copytree(EXAMPLES_PATH, home / "examples") + if any(argument.startswith("examples/") for argument in args): + shutil.copytree(EXAMPLES_PATH, home / "examples") return subprocess.run( [executable, *args[1:]], capture_output=True, @@ -89,8 +72,10 @@ def test_readme_northstar_commands_run_from_the_installed_wheel( def test_readme_command_parser_fails_closed_on_an_invalid_documented_command( tmp_path: Path, ) -> None: - markdown = README_PATH.read_text(encoding="utf-8").replace( - "$ northstar --help", "$ northstar --definitely-invalid" + markdown = README_PATH.read_text(encoding="utf-8") + assert "$ northstar --help" in markdown + markdown = markdown.replace( + "$ northstar --help", "$ northstar --definitely-invalid", 1 ) commands = parse_readme_commands(markdown) invalid = next(args for args in commands if "--definitely-invalid" in args)