Loading...
No commits yet
Not committed History
Blame
test_compile_tex_structure.py • 12.6 KB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Test file for: compile_tex_structure.py

import os
import re
import sys
from pathlib import Path

import pytest

# Add scripts/python to path for imports
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(ROOT_DIR / "scripts" / "python"))

from compile_tex_structure import (  # noqa: E402
    compile_tex_structure,
    expand_inputs,
    generate_signature,
)


class TestGenerateSignature:
    """Test signature generation."""

    def test_generate_signature_contains_scitex_writer(self):
        """Signature should contain 'SciTeX Writer'."""
        signature = generate_signature()
        assert "SciTeX Writer" in signature

    def test_generate_signature_contains_version(self):
        """Signature should contain version number or 'unknown'."""
        signature = generate_signature()
        # Should have version line
        assert "SciTeX Writer" in signature
        # Version is present (either from pyproject.toml or 'unknown')
        assert "SciTeX Writer unknown" in signature or re.search(
            r"SciTeX Writer \d+\.\d+", signature
        )

    def test_generate_signature_contains_engine(self):
        """Signature should contain engine information."""
        signature = generate_signature()
        assert "LaTeX compilation engine:" in signature
        # Default should be 'auto' if env vars not set
        assert "auto" in signature or "tectonic" in signature or "latexmk" in signature

    def test_generate_signature_with_env_engine(self, monkeypatch):
        """Signature should use SCITEX_WRITER_ENGINE if set."""
        monkeypatch.setenv("SCITEX_WRITER_ENGINE", "tectonic")
        signature = generate_signature()
        assert "engine: tectonic" in signature

    def test_generate_signature_with_source_file(self, tmp_path):
        """Signature should include source file path when provided."""
        source_file = tmp_path / "test.tex"
        signature = generate_signature(source_file=source_file)
        assert "Source:" in signature
        assert str(source_file) in signature

    def test_generate_signature_has_timestamp(self):
        """Signature should contain compilation timestamp."""
        signature = generate_signature()
        assert "Compiled:" in signature
        # Should match date format YYYY-MM-DD
        assert re.search(r"\d{4}-\d{2}-\d{2}", signature)

    def test_generate_signature_is_comment(self):
        """Signature should be LaTeX comments."""
        signature = generate_signature()
        lines = signature.split("\n")
        # Non-empty lines should start with %
        for line in lines:
            if line.strip():
                assert line.startswith("%")


class TestExpandInputs:
    """Test TeX input expansion."""

    def test_expand_inputs_simple_file(self, tmp_path):
        """Should read and return simple file content."""
        test_file = tmp_path / "test.tex"
        test_file.write_text("Hello, world!")

        result = expand_inputs(test_file)
        assert "Hello, world!" in result

    def test_expand_inputs_with_input_command(self, tmp_path):
        """Should expand \\input{} commands."""
        # Create child file
        child_file = tmp_path / "child.tex"
        child_file.write_text("Child content here")

        # Create parent file that references child
        parent_file = tmp_path / "parent.tex"
        parent_file.write_text("Start\n\\input{child}\nEnd")

        result = expand_inputs(parent_file)

        # Should contain both parent and child content
        assert "Start" in result
        assert "Child content here" in result
        assert "End" in result
        # Should have header comment for expanded file
        assert "File: child" in result

    def test_expand_inputs_missing_file(self, tmp_path):
        """Should handle missing input file gracefully."""
        parent_file = tmp_path / "parent.tex"
        parent_file.write_text("\\input{nonexistent}")

        result = expand_inputs(parent_file)

        # Should contain SKIPPED comment
        assert "SKIPPED" in result
        assert "file not found" in result

    def test_expand_inputs_circular_reference(self, tmp_path):
        """Should detect and prevent circular references."""
        # Create file that references itself
        self_ref_file = tmp_path / "circular.tex"
        self_ref_file.write_text("Start\n\\input{circular}\nEnd")

        result = expand_inputs(self_ref_file)

        # Should contain circular reference warning
        assert "SKIPPED" in result
        assert "circular reference" in result or "already processed" in result

    def test_expand_inputs_max_depth(self, tmp_path):
        """Should stop at max recursion depth."""
        # Create deeply nested structure
        files = []
        for i in range(15):
            f = tmp_path / f"level_{i}.tex"
            if i < 14:
                f.write_text(f"Level {i}\n\\input{{level_{i + 1}}}")
            else:
                f.write_text(f"Level {i}")
            files.append(f)

        result = expand_inputs(files[0], max_depth=5)

        # Should stop before reaching the deepest level
        assert "Level 0" in result
        assert "Level 5" in result or "Level 6" in result
        # Should hit depth limit
        assert "ERROR" in result or "Max recursion depth" in result

    def test_expand_inputs_commented_line_skipped(self, tmp_path):
        """Should skip \\input{} in commented lines."""
        child_file = tmp_path / "child.tex"
        child_file.write_text("This should not appear")

        parent_file = tmp_path / "parent.tex"
        parent_file.write_text("Start\n% \\input{child}\nEnd")

        result = expand_inputs(parent_file)

        # Should NOT expand commented input
        assert "This should not appear" not in result
        # Original comment should be preserved
        assert "% \\input{child}" in result

    def test_expand_inputs_adds_tex_extension(self, tmp_path):
        """Should add .tex extension if not present."""
        child_file = tmp_path / "child.tex"
        child_file.write_text("Child content")

        parent_file = tmp_path / "parent.tex"
        # Reference without .tex extension
        parent_file.write_text("\\input{child}")

        result = expand_inputs(parent_file)

        # Should successfully expand even without .tex
        assert "Child content" in result

    def test_expand_inputs_nested_multiple_levels(self, tmp_path):
        """Should handle multiple levels of nesting."""
        level2 = tmp_path / "level2.tex"
        level2.write_text("Level 2 content")

        level1 = tmp_path / "level1.tex"
        level1.write_text("Level 1 start\n\\input{level2}\nLevel 1 end")

        level0 = tmp_path / "level0.tex"
        level0.write_text("Level 0 start\n\\input{level1}\nLevel 0 end")

        result = expand_inputs(level0)

        # All levels should be present
        assert "Level 0 start" in result
        assert "Level 1 start" in result
        assert "Level 2 content" in result
        assert "Level 1 end" in result
        assert "Level 0 end" in result

    def test_expand_inputs_nonexistent_file(self, tmp_path):
        """Should handle nonexistent file at top level."""
        nonexistent = tmp_path / "does_not_exist.tex"

        result = expand_inputs(nonexistent)

        # Should return SKIPPED message
        assert "SKIPPED" in result
        assert "file not found" in result


class TestCompileTexStructure:
    """Test full TeX structure compilation."""

    def test_compile_tex_structure_creates_output(self, tmp_path):
        """Should create output file."""
        base_file = tmp_path / "base.tex"
        base_file.write_text(
            "\\documentclass{article}\n\\begin{document}\nHello\n\\end{document}"
        )

        output_file = tmp_path / "output.tex"

        success = compile_tex_structure(
            base_tex=base_file, output_tex=output_file, verbose=False
        )

        assert success is True
        assert output_file.exists()

    def test_compile_tex_structure_contains_signature(self, tmp_path):
        """Output should contain compilation signature."""
        base_file = tmp_path / "base.tex"
        base_file.write_text("Content")

        output_file = tmp_path / "output.tex"

        compile_tex_structure(base_tex=base_file, output_tex=output_file, verbose=False)

        output_content = output_file.read_text()
        assert "SciTeX Writer" in output_content
        assert "Compiled:" in output_content

    def test_compile_tex_structure_expands_inputs(self, tmp_path):
        """Should expand \\input{} commands in base file."""
        child_file = tmp_path / "child.tex"
        child_file.write_text("Child content")

        base_file = tmp_path / "base.tex"
        base_file.write_text("Start\n\\input{child}\nEnd")

        output_file = tmp_path / "output.tex"

        compile_tex_structure(base_tex=base_file, output_tex=output_file, verbose=False)

        output_content = output_file.read_text()
        assert "Child content" in output_content

    def test_compile_tex_structure_missing_base_file(self, tmp_path):
        """Should return False for missing base file."""
        base_file = tmp_path / "nonexistent.tex"
        output_file = tmp_path / "output.tex"

        success = compile_tex_structure(
            base_tex=base_file, output_tex=output_file, verbose=False
        )

        assert success is False
        assert not output_file.exists()

    def test_compile_tex_structure_creates_output_dir(self, tmp_path):
        """Should create output directory if it doesn't exist."""
        base_file = tmp_path / "base.tex"
        base_file.write_text("Content")

        output_dir = tmp_path / "nested" / "dir"
        output_file = output_dir / "output.tex"

        success = compile_tex_structure(
            base_tex=base_file, output_tex=output_file, verbose=False
        )

        assert success is True
        assert output_dir.exists()
        assert output_file.exists()

    def test_tectonic_mode_comments_out_lineno(self, tmp_path):
        """Tectonic mode should comment out lineno package."""
        base_file = tmp_path / "base.tex"
        base_file.write_text(
            "\\usepackage{lineno}\n\\linenumbers\n\\begin{document}\nContent\n\\end{document}"
        )

        output_file = tmp_path / "output.tex"

        compile_tex_structure(
            base_tex=base_file,
            output_tex=output_file,
            verbose=False,
            tectonic_mode=True,
        )

        output_content = output_file.read_text()
        # lineno package should be commented out
        assert "% \\usepackage{lineno}" in output_content
        # linenumbers command should be commented out
        assert "% \\linenumbers" in output_content

    def test_tectonic_mode_comments_out_bashful(self, tmp_path):
        """Tectonic mode should comment out bashful package."""
        base_file = tmp_path / "base.tex"
        base_file.write_text(
            "\\usepackage{bashful}\n\\begin{document}\nContent\n\\end{document}"
        )

        output_file = tmp_path / "output.tex"

        compile_tex_structure(
            base_tex=base_file,
            output_tex=output_file,
            verbose=False,
            tectonic_mode=True,
        )

        output_content = output_file.read_text()
        # bashful package should be commented out
        assert "% \\usepackage{bashful}" in output_content

    def test_dark_mode_injection(self, tmp_path):
        """Dark mode should inject styling before document."""
        # Create the dark_mode.tex file structure
        dark_mode_dir = tmp_path / "00_shared" / "latex_styles"
        dark_mode_dir.mkdir(parents=True)
        dark_mode_file = dark_mode_dir / "dark_mode.tex"
        dark_mode_file.write_text(
            "% Dark mode test content\n\\pagecolor{black}\n\\color{white}"
        )

        base_file = tmp_path / "01_manuscript" / "base.tex"
        base_file.parent.mkdir(parents=True)
        base_file.write_text("\\begin{document}\nContent\n\\end{document}")

        output_file = tmp_path / "01_manuscript" / "output.tex"

        compile_tex_structure(
            base_tex=base_file, output_tex=output_file, verbose=False, dark_mode=True
        )

        output_content = output_file.read_text()
        # Should have dark mode comment
        assert "Dark mode styling" in output_content
        # Should have dark mode content
        assert "pagecolor{black}" in output_content
        # Dark mode should come before \begin{document}
        dark_pos = output_content.find("Dark mode")
        doc_pos = output_content.find("\\begin{document}")
        assert dark_pos < doc_pos


if __name__ == "__main__":
    pytest.main([os.path.abspath(__file__), "-v"])