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")