From c6764cd634db5628a2a2b1e2fe5f58772e47d84a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 03:18:28 +0000 Subject: [PATCH 1/2] Initial plan From b63817100ccf7ce46da8900fc042ecef5d7008bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 03:25:35 +0000 Subject: [PATCH 2/2] Add tests for utility functions: working_directory, format_docstring_as_help_str, is_earlier_version, hash_path, xml_syntax_is_valid Co-authored-by: oscarlevin <6504596+oscarlevin@users.noreply.github.com> --- tests/test_utils.py | 96 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 489812d5..28bfa343 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,5 @@ import os +import pytest from pathlib import Path from pretext import utils @@ -10,7 +11,8 @@ def test_working_directory(tmp_path: Path) -> None: assert Path().resolve() == tmp_path.resolve() with utils.working_directory(subdir): assert Path().resolve().parent == tmp_path.resolve() - # TODO check path returns afterward + # After exiting context manager, the directory should have returned to the original + assert Path().resolve() == tmp_path.resolve() def test_project_path(tmp_path: Path) -> None: @@ -73,3 +75,95 @@ def test_requirements_version(tmp_path: Path) -> None: assert ( utils.requirements_version(tmp_path) is None ), f"Should not match: {line!r}" + + +def test_format_docstring_as_help_str() -> None: + # Leading/trailing whitespace on each line is stripped, and single newlines + # within a paragraph are collapsed to a single space. + docstring = """ + First line of text + that continues here. + + Second paragraph. + """ + result = utils.format_docstring_as_help_str(docstring) + assert "First line of text that continues here." in result + assert "Second paragraph." in result + # Double newlines (paragraph breaks) are preserved as "\n\n". + assert "\n\n" in result + + # A single-line docstring has no newlines. + assert "\n" not in utils.format_docstring_as_help_str("Single line.") + assert utils.format_docstring_as_help_str(" spaced ") == "spaced" + + # An empty string produces an empty string. + assert utils.format_docstring_as_help_str("") == "" + + +def test_is_earlier_version() -> None: + assert utils.is_earlier_version("1.0.0", "2.0.0") + assert utils.is_earlier_version("2.0.0", "2.1.0") + assert utils.is_earlier_version("2.1.0", "2.1.1") + assert not utils.is_earlier_version("2.0.0", "1.0.0") + assert not utils.is_earlier_version("2.1.0", "2.0.0") + assert not utils.is_earlier_version("2.1.1", "2.1.0") + # Equal versions are not earlier. + assert not utils.is_earlier_version("1.2.3", "1.2.3") + # When the primary digits are equal but the strings differ only in length, + # the shorter version string is treated as earlier. In pretext-cli, dev + # builds (e.g. "2.11.5.dev0") are nightly POST-release builds produced + # *after* the stable release, so "2.11.5" is earlier than "2.11.5.dev0". + assert utils.is_earlier_version("2.11.5", "2.11.5.dev0") + assert not utils.is_earlier_version("2.11.5.dev0", "2.11.5") + + +def test_hash_path(tmp_path: Path) -> None: + # hash_path should return a 10-character hex string. + result = utils.hash_path(tmp_path) + assert isinstance(result, str) + assert len(result) == 10 + assert all(c in "0123456789abcdef" for c in result) + # The same path should always produce the same hash. + assert utils.hash_path(tmp_path) == utils.hash_path(tmp_path) + # Different paths should (almost certainly) produce different hashes. + other_path = tmp_path / "subdir" + assert utils.hash_path(tmp_path) != utils.hash_path(other_path) + + +def test_xml_syntax_is_valid(tmp_path: Path) -> None: + # A well-formed PreTeXt file should pass validation. + valid_xml = tmp_path / "valid.ptx" + valid_xml.write_text( + "\n" + "\n" + "
\n" + " Hello\n" + "

Content.

\n" + "
\n" + "
\n" + ) + assert utils.xml_syntax_is_valid(valid_xml) + + # A file whose root tag is not should fail. + wrong_root = tmp_path / "wrong_root.ptx" + wrong_root.write_text( + "\n" + "\n" + "

Content.

\n" + "
\n" + ) + assert not utils.xml_syntax_is_valid(wrong_root) + + # A file with malformed XML should fail. + bad_xml = tmp_path / "bad.ptx" + bad_xml.write_text( + "\n" + "\n" + " \n" + "\n" + ) + assert not utils.xml_syntax_is_valid(bad_xml) + + # A nonexistent file should raise IOError. + with pytest.raises(IOError): + utils.xml_syntax_is_valid(tmp_path / "nonexistent.ptx")