134 lines
6.6 KiB
Python
134 lines
6.6 KiB
Python
import contextlib
|
|
import io
|
|
import subprocess
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from copienator import utils
|
|
|
|
|
|
class CompileToPdfTests(unittest.TestCase):
|
|
def test_source_preamble_keeps_macros_and_uses_fragment_layout(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
source = Path(directory) / 'source.tex'
|
|
source.write_text('% \\begin{document}\n\\documentclass{article}\n'
|
|
'\\usepackage{amsthm}\n\\newcommand{\\custom}{works}\n'
|
|
'\\begin{document}original body\\end{document}')
|
|
header = utils.fragment_latex_header(source)
|
|
self.assertIn('standalone', header)
|
|
self.assertIn(r'\usepackage{amsthm}', header)
|
|
self.assertIn(r'\newcommand{\custom}{works}', header)
|
|
self.assertNotIn('original body', header)
|
|
|
|
@unittest.skipUnless(shutil.which('pdflatex'), 'pdflatex is not installed')
|
|
def test_source_geometry_does_not_clip_fragment(self):
|
|
import pymupdf
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
source = Path(directory) / 'source.tex'
|
|
source.write_text(r'\documentclass{article}'
|
|
r'\usepackage[margin=1cm]{geometry}'
|
|
r'\geometry{top=0.5cm}'
|
|
r'\newcommand{\custom}{FIRSTLINE}'
|
|
r'\begin{document}unused body\end{document}')
|
|
output = Path(directory) / 'out.pdf'
|
|
utils.compile_to_pdf(r'\custom\par LASTLINE', output,
|
|
source_path=source, strict=True)
|
|
with pymupdf.open(output) as doc:
|
|
self.assertIn('FIRSTLINE', doc[0].get_text())
|
|
self.assertIn('LASTLINE', doc[0].get_text())
|
|
for word in doc[0].get_text('words'):
|
|
self.assertGreaterEqual(word[1], 0)
|
|
self.assertLessEqual(word[3], doc[0].rect.height)
|
|
|
|
def test_source_directory_is_available_to_tex(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
source = Path(directory) / 'source.tex'
|
|
source.write_text(r'\documentclass{article}\begin{document}')
|
|
result = subprocess.CompletedProcess(args=[], returncode=1, stdout='! error')
|
|
with patch.object(utils.subprocess, 'run', return_value=result) as run:
|
|
with self.assertRaises(RuntimeError):
|
|
utils.compile_to_pdf('broken', Path(directory) / 'out.pdf',
|
|
source_path=source, strict=True)
|
|
self.assertTrue(run.call_args.kwargs['env']['TEXINPUTS'].startswith(directory))
|
|
|
|
def test_strict_failure_preserves_previous_pdf(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
output = Path(directory) / 'out.pdf'
|
|
output.write_bytes(b'previous PDF')
|
|
def failed_run(*args, **kwargs):
|
|
(Path(kwargs['cwd']) / 'text.pdf').write_bytes(b'partial')
|
|
return subprocess.CompletedProcess(args=[], returncode=1, stdout='! error')
|
|
with patch.object(utils.subprocess, 'run', side_effect=failed_run):
|
|
with self.assertRaises(RuntimeError):
|
|
utils.compile_to_pdf('broken', output, strict=True)
|
|
self.assertEqual(output.read_bytes(), b'previous PDF')
|
|
|
|
def test_strict_compilation_propagates_failure_with_context(self):
|
|
result = subprocess.CompletedProcess(args=[], returncode=1,
|
|
stdout="! Undefined control sequence.\nl.13 broken command\n")
|
|
with tempfile.TemporaryDirectory() as directory, \
|
|
patch.object(utils.subprocess, "run", return_value=result) as run:
|
|
with self.assertRaisesRegex(RuntimeError, "LaTeX failed") as error:
|
|
utils.compile_to_pdf("broken", Path(directory) / "out.pdf", strict=True)
|
|
self.assertIn("l.13 broken command", str(error.exception))
|
|
self.assertEqual(run.call_args.kwargs["errors"], "replace")
|
|
|
|
def test_warns_when_latex_fails_even_if_partial_pdf_exists(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
output = Path(temporary) / "result.pdf"
|
|
|
|
def failed_run(*_args, **kwargs):
|
|
(Path(kwargs["cwd"]) / "text.pdf").write_bytes(b"partial")
|
|
return subprocess.CompletedProcess(
|
|
args=[], returncode=1,
|
|
stdout="! LaTeX Error: Something's wrong--perhaps a missing \\item.\n",
|
|
)
|
|
|
|
console = io.StringIO()
|
|
with patch.object(utils.subprocess, "run", side_effect=failed_run), \
|
|
contextlib.redirect_stdout(console):
|
|
utils.compile_to_pdf("broken", output)
|
|
|
|
self.assertEqual(output.read_bytes(), b"partial")
|
|
self.assertIn("Warning: LaTeX compilation reported errors", console.getvalue())
|
|
self.assertIn(utils.LATEX_WARNING_MARKER + str(output.resolve()), console.getvalue())
|
|
self.assertIn("a PDF was generated", console.getvalue())
|
|
self.assertIn("missing \\item", console.getvalue())
|
|
|
|
def test_says_no_pdf_was_generated_when_latex_fails(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
output = Path(temporary) / "result.pdf"
|
|
result = subprocess.CompletedProcess(
|
|
args=[], returncode=1, stdout="! Undefined control sequence.\n"
|
|
)
|
|
|
|
console = io.StringIO()
|
|
with patch.object(utils.subprocess, "run", return_value=result), \
|
|
contextlib.redirect_stdout(console):
|
|
utils.compile_to_pdf("broken", output)
|
|
|
|
self.assertFalse(output.exists())
|
|
self.assertIn("no PDF was generated", console.getvalue())
|
|
self.assertIn("Undefined control sequence", console.getvalue())
|
|
|
|
def test_warns_when_no_pdf_is_produced(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
output = Path(temporary) / "result.pdf"
|
|
result = subprocess.CompletedProcess(args=[], returncode=0, stdout="")
|
|
|
|
console = io.StringIO()
|
|
with patch.object(utils.subprocess, "run", return_value=result), \
|
|
contextlib.redirect_stdout(console):
|
|
utils.compile_to_pdf("valid", output)
|
|
|
|
self.assertFalse(output.exists())
|
|
self.assertIn("produced no PDF", console.getvalue())
|
|
self.assertIn(utils.LATEX_WARNING_MARKER + str(output.resolve()), console.getvalue())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|