diff --git a/.gitattributes b/.gitattributes index 2f77e91..9ad170f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,6 @@ *.ipynb linguist-documentation + +# Force LF checkout for text files regardless of host OS (#21): Windows CI +# checked fixtures/templates out with CRLF, inflating every reported byte +# size opsward computes from disk (a 37-byte stub measured 40). +* text=auto eol=lf diff --git a/opsward/cli.py b/opsward/cli.py index 90fa6ae..a66d869 100644 --- a/opsward/cli.py +++ b/opsward/cli.py @@ -190,9 +190,14 @@ def maintain( def _relative_path(path: Path, base: Path) -> str: - """Return path relative to base, or absolute if not under base.""" + """Return path relative to base, or absolute if not under base. + + Rendered with forward slashes regardless of host OS (#21): this is display + text, not a filesystem path, and a Windows-separated path leaking into + printed output (or into generated doc content) is not portable. + """ try: - return str(path.relative_to(base)) + return path.relative_to(base).as_posix() except ValueError: return str(path) diff --git a/opsward/generate.py b/opsward/generate.py index f47d7ea..8162d2b 100644 --- a/opsward/generate.py +++ b/opsward/generate.py @@ -488,6 +488,10 @@ def _find_conftest(sr: ScanResult) -> str | None: Checks the detected test dir and the root first, then a bounded search, skipping hidden/vendored trees. Returns None when none exists so generated docs never assert a conftest that isn't there. + + Rendered with forward slashes regardless of host OS (#21): this path is + embedded in generated doc content that ships to readers on every platform, + so it must not carry the host's separator. """ root = sr.project_root if not root.is_dir(): @@ -495,12 +499,12 @@ def _find_conftest(sr: ScanResult) -> str | None: test_dir = _detect_test_dir(sr) for candidate in (root / test_dir / "conftest.py", root / "conftest.py"): if candidate.is_file(): - return str(candidate.relative_to(root)) + return candidate.relative_to(root).as_posix() for found in sorted(root.rglob("conftest.py")): parts = found.relative_to(root).parts if any(p.startswith(".") or p in _MAP_SKIP_NAMES for p in parts): continue - return str(found.relative_to(root)) + return found.relative_to(root).as_posix() return None diff --git a/tests/test_cli_parity.py b/tests/test_cli_parity.py index 612f7a0..c64591b 100644 --- a/tests/test_cli_parity.py +++ b/tests/test_cli_parity.py @@ -21,14 +21,12 @@ unrecorded version fails loudly rather than silently skipping: a parity test that quietly does nothing is worse than no parity test. -**Windows** is asserted too, minus the six cases in :data:`WINDOWS_CONTENT_DIFFS`. Those -six differ for reasons that have nothing to do with the command line and everything to do -with opsward's own output: it prints ``misc\\docs\\...`` where POSIX prints -``misc/docs/...``, and it reports file sizes inflated by CRLF checkout (a 37-byte stub -measures 40). Both are the pre-existing Windows bugs tracked in issue #21 -- argh printed -exactly the same thing -- and they are listed rather than skipped so that everything else -on Windows, the whole grammar included, stays asserted. That matters: the ``.exe`` defect -this migration found in cw lived precisely there. +**Windows** is asserted in full, with no content exceptions. It used to need six -- +opsward printed ``misc\\docs\\...`` where POSIX printed ``misc/docs/...``, and reported +file sizes inflated by CRLF checkout (a 37-byte stub measured 40) -- but both were fixed +in #21 (display paths now render via ``PurePath.as_posix()``; ``.gitattributes`` pins +``eol=lf`` so a Windows checkout no longer inflates byte counts), so nothing is exempted +any more. """ import shutil @@ -40,19 +38,10 @@ REPO_ROOT = Path(__file__).resolve().parent.parent GOLDEN_DIR = REPO_ROOT / "misc" -#: Cases whose *content* differs on Windows because of opsward's own output, not because of -#: anything about the command line: printed paths use the OS separator, and reported file -#: sizes count CRLF line endings. Tracked in issue #21; unchanged by the migration to cw. -#: If one of these starts matching, the test fails with `unexpected-match` -- which is the -#: correct outcome, and means #21 was fixed and the entry should be deleted. -WINDOWS_CONTENT_DIFFS = [ - ["generate", "tests/fixtures/bare_project"], - ["generate", "tests/fixtures/bare_project", "--agents-md", "--hooks"], - ["generate", "tests/fixtures/bare_project", "-a"], - ["maintain", "tests/fixtures/stale_project"], - ["diagnose", "--verbose", "tests/fixtures/python_project"], - ["diagnose", "-v", "tests/fixtures/python_project"], -] +#: Previously the six cases whose *content* differed on Windows because of opsward's own +#: output (OS-separated paths, CRLF-inflated byte counts) -- fixed in #21. Kept as an empty +#: tuple, rather than deleted, so `expect_diff=` below still documents what it is for. +WINDOWS_CONTENT_DIFFS: list[list[str]] = [] def _golden_for_this_python() -> Path: diff --git a/tests/test_generate.py b/tests/test_generate.py index 87dfb85..4e6f619 100644 --- a/tests/test_generate.py +++ b/tests/test_generate.py @@ -379,7 +379,10 @@ def test_python_docs_path(): ) doc_files = [f for f in files if "docs_guide.md" == f.target_path.name] assert doc_files - assert "misc/docs" in str(doc_files[0].target_path) + # .as_posix(), not str(): target_path is a real filesystem Path (a WindowsPath + # renders with backslashes on Windows, correctly, since it is used to write the + # file to disk) -- the assertion is about the logical path, not the OS rendering. + assert "misc/docs" in doc_files[0].target_path.as_posix() def test_jsts_docs_path(): @@ -393,6 +396,7 @@ def test_jsts_docs_path(): ) doc_files = [f for f in files if "docs_guide.md" == f.target_path.name] assert doc_files - path_str = str(doc_files[0].target_path) + # .as_posix(), not str() -- see test_python_docs_path. + path_str = doc_files[0].target_path.as_posix() assert "misc/docs" not in path_str assert "/docs/" in path_str