diff --git a/docs/examples/report.md b/docs/examples/report.md index db94c9f..a43add5 100644 --- a/docs/examples/report.md +++ b/docs/examples/report.md @@ -11,16 +11,16 @@ The example is rebuilt from the same tree that produces the published documentation, so the HTML, canonical JSON, and SARIF artifacts stay aligned.

- + Open interactive HTML report - + Open canonical JSON - + Open SARIF - + View generation manifest

diff --git a/scripts/build_docs_example_report.py b/scripts/build_docs_example_report.py index 3819143..4bb9627 100644 --- a/scripts/build_docs_example_report.py +++ b/scripts/build_docs_example_report.py @@ -32,7 +32,7 @@ "report.sarif", "manifest.json", ) -_RELATIVE_LIVE_HREF = re.compile(r'href="live/([a-zA-Z0-9_.-]+)"') +_RELATIVE_LIVE_HREF = re.compile(r'href=(["\'])(?:\./)?live/([a-zA-Z0-9_.-]+)\1') @dataclass(frozen=True) @@ -81,6 +81,9 @@ def _run_codeclone(scan_root: Path, artifacts: ReportArtifacts) -> None: str(artifacts.json), "--sarif", str(artifacts.sarif), + # Docs preview is report generation, not the structural CI gate. + "--no-fail-on-new", + "--no-fail-on-new-metrics", "--no-progress", "--quiet", ] @@ -160,8 +163,30 @@ def _published_artifact_href(site_url: str, artifact_name: str) -> str: return f"{parsed.scheme}://{parsed.netloc}{artifact_path}" -def _sample_report_page_path(output_dir: Path) -> Path: - return output_dir.parent / "index.html" +def _sample_report_page_paths(output_dir: Path) -> list[Path]: + site_root = output_dir.parent.parent.parent + candidates = ( + output_dir.parent / "index.html", + site_root / "examples" / "report.html", + ) + return [path for path in candidates if path.is_file()] + + +def _patch_page_links(page: Path, site_url: str) -> None: + text = page.read_text(encoding="utf-8") + + def _replace(match: re.Match[str]) -> str: + quote = match.group(1) + artifact_name = match.group(2) + href = _published_artifact_href(site_url, artifact_name) + return f"href={quote}{href}{quote}" + + patched = _RELATIVE_LIVE_HREF.sub(_replace, text) + if _RELATIVE_LIVE_HREF.search(patched): + msg = f"relative live/* artifact links remain in {page}" + raise RuntimeError(msg) + if patched != text: + page.write_text(patched, encoding="utf-8") def _patch_sample_report_links(*, output_dir: Path, site_url: str) -> None: @@ -171,19 +196,18 @@ def _patch_sample_report_links(*, output_dir: Path, site_url: str) -> None: trailing slash (common with navigation.instant), resolving to ``/examples/live/...`` instead of ``/examples/report/live/...``. """ - report_page = _sample_report_page_path(output_dir) - if not report_page.is_file(): - return - text = report_page.read_text(encoding="utf-8") - - def _replace(match: re.Match[str]) -> str: - artifact_name = match.group(1) - href = _published_artifact_href(site_url, artifact_name) - return f'href="{href}"' - - patched = _RELATIVE_LIVE_HREF.sub(_replace, text) - if patched != text: - report_page.write_text(patched, encoding="utf-8") + pages = _sample_report_page_paths(output_dir) + if not pages: + site_root = output_dir.parent.parent.parent + expected_index = output_dir.parent / "index.html" + expected_report = site_root / "examples" / "report.html" + msg = ( + "sample report HTML page not found under built site; expected " + f"{expected_index} or {expected_report}" + ) + raise FileNotFoundError(msg) + for page in pages: + _patch_page_links(page, site_url) def _verify_report_artifacts(destination: ReportArtifacts) -> None: diff --git a/tests/test_docs_build_contract.py b/tests/test_docs_build_contract.py index d8cd1f2..4d5a951 100644 --- a/tests/test_docs_build_contract.py +++ b/tests/test_docs_build_contract.py @@ -46,3 +46,34 @@ def test_docs_build_strict() -> None: timeout=120, ) assert result.returncode == 0, result.stderr or result.stdout + + +def test_sample_report_built_page_has_absolute_artifact_links() -> None: + site_root = _REPO_ROOT / "site" + build = subprocess.run( + [ + "uv", + "run", + "--with", + "zensical==0.0.46", + "zensical", + "build", + "--clean", + "--strict", + ], + cwd=_REPO_ROOT, + capture_output=True, + text=True, + timeout=120, + ) + assert build.returncode == 0, build.stderr or build.stdout + candidates = ( + site_root / "examples" / "report" / "index.html", + site_root / "examples" / "report.html", + ) + page = next((path for path in candidates if path.is_file()), None) + assert page is not None, "expected built sample report HTML page" + text = page.read_text(encoding="utf-8") + assert 'href="live/' not in text + assert 'href="./live/' not in text + assert "examples/report/live/index.html" in text diff --git a/tests/test_docs_example_report.py b/tests/test_docs_example_report.py index bba5122..3235e10 100644 --- a/tests/test_docs_example_report.py +++ b/tests/test_docs_example_report.py @@ -30,6 +30,23 @@ def test_published_artifact_href_uses_site_url_path_prefix() -> None: assert href == "https://orenlab.github.io/codeclone/examples/report/live/index.html" +def test_sample_report_markdown_links_match_zensical_site_url() -> None: + module = _load_docs_report_namespace() + read_site_url = module["_read_site_url"] + published_artifact_href = module["_published_artifact_href"] + assert callable(read_site_url) + assert callable(published_artifact_href) + repo_root = Path(__file__).resolve().parents[1] + site_url = read_site_url(repo_root) + report_md = (repo_root / "docs" / "examples" / "report.md").read_text( + encoding="utf-8" + ) + artifact_names = module["_ARTIFACT_NAMES"] + assert isinstance(artifact_names, tuple) + for artifact in artifact_names: + assert published_artifact_href(site_url, str(artifact)) in report_md + + def test_patch_sample_report_links_rewrites_relative_live_hrefs( tmp_path: Path, ) -> None: @@ -67,6 +84,23 @@ def test_patch_sample_report_links_rewrites_relative_live_hrefs( ) +def test_patch_sample_report_links_requires_built_page(tmp_path: Path) -> None: + module = _load_docs_report_namespace() + patch_sample_report_links = module["_patch_sample_report_links"] + assert callable(patch_sample_report_links) + output_dir = tmp_path / "examples" / "report" / "live" + output_dir.mkdir(parents=True) + try: + patch_sample_report_links( + output_dir=output_dir, + site_url="https://orenlab.github.io/codeclone/", + ) + except FileNotFoundError as exc: + assert "sample report HTML page not found" in str(exc) + else: + raise AssertionError("expected FileNotFoundError when page is missing") + + def test_docs_example_report_uses_main_entrypoint( tmp_path: Path, ) -> None: @@ -109,6 +143,8 @@ def _fake_run( str(artifacts.json), "--sarif", str(artifacts.sarif), + "--no-fail-on-new", + "--no-fail-on-new-metrics", "--no-progress", "--quiet", ],