Personal tex compilation fixes
This commit is contained in:
1 parent
ec6ac9580b
commit
705db1068f
8 files changed
+217
-21
No files matched your search
+33
-2
@@ -127,8 +127,9 @@ class WorkspaceTests(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory) / "A"
|
||||
root.mkdir()
|
||||
for name in ("enonce.pdf", "A.pdf", "enonce.tex", "A.tex",
|
||||
"correction.tex", "Ac.tex"):
|
||||
for name in ("enonce.pdf", "énonce.pdf", "énoncé.pdf", "A.pdf",
|
||||
"enonce.tex", "énonce.tex", "énoncé.tex", "A.tex",
|
||||
"correction.tex", "corrige.tex", "corrigé.tex", "Ac.tex"):
|
||||
(root / name).touch()
|
||||
|
||||
workspace = EvaluationWorkspace(root)
|
||||
@@ -136,6 +137,30 @@ class WorkspaceTests(unittest.TestCase):
|
||||
self.assertEqual(workspace.statement_source_file, root / "enonce.tex")
|
||||
self.assertEqual(workspace.correction_source_file, root / "correction.tex")
|
||||
|
||||
def test_alternative_statement_inputs_are_detected_and_validated(self) -> None:
|
||||
for statement in ("énonce", "énoncé"):
|
||||
for correction in ("corrige", "corrigé"):
|
||||
with self.subTest(statement=statement, correction=correction), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory) / "Exam"
|
||||
root.mkdir()
|
||||
for name in (f"{statement}.pdf", f"{statement}.tex", f"{correction}.tex"):
|
||||
(root / name).touch()
|
||||
workspace = EvaluationWorkspace.discover(root)
|
||||
workspace.require_files("enonce.pdf", "enonce.tex", "correction.tex")
|
||||
self.assertEqual(workspace.statement_pdf_file, root / f"{statement}.pdf")
|
||||
self.assertEqual(workspace.statement_source_file, root / f"{statement}.tex")
|
||||
self.assertEqual(workspace.correction_source_file, root / f"{correction}.tex")
|
||||
self.assertTrue(workspace.is_statement_pdf(root / f"{statement}.pdf"))
|
||||
self.assertEqual(workspace.resolve("correction.tex"), root / f"{correction}.tex")
|
||||
|
||||
def test_discovery_recognizes_each_alternative_input_on_its_own(self) -> None:
|
||||
for name in ("énonce.pdf", "énonce.tex", "énoncé.pdf", "énoncé.tex", "corrige.tex", "corrigé.tex"):
|
||||
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory) / "Exam"
|
||||
root.mkdir()
|
||||
(root / name).touch()
|
||||
self.assertEqual(EvaluationWorkspace.discover(root).root, root)
|
||||
|
||||
def test_discover_and_require_report_clear_errors(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
@@ -2259,6 +2284,12 @@ class StandardCliTests(unittest.TestCase):
|
||||
|
||||
|
||||
class WorkflowTests(unittest.TestCase):
|
||||
def test_statement_compile_variant_avoids_gemini_and_restart(self) -> None:
|
||||
command = self.command('statement', 'compile',
|
||||
{'target': self.evaluation, 'restart': True})
|
||||
self.assertIn('--compile-only', command)
|
||||
self.assertNotIn('--restart', command)
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.steps = {step.id: step for step in build_workflow(True)}
|
||||
self.evaluation = evaluation_argument(REPOSITORY, REPOSITORY / "Example")
|
||||
|
||||
@@ -151,6 +151,33 @@ class FullStatementResumeTests(unittest.TestCase):
|
||||
response(contexts=[]),
|
||||
response(rubrics=[{'label': 'Ex 1', 'rubric_content': '4 points : correct'}])]
|
||||
|
||||
def test_compile_only_never_enters_analysis_and_preserves_sources(self):
|
||||
directory = self.root / 'Sol2'
|
||||
directory.mkdir()
|
||||
tex = directory / '1.tex'
|
||||
tex.write_text('manually edited solution')
|
||||
with patch.object(gemini, 'process_exam') as analysis, patch.object(
|
||||
gemini, 'compile_to_pdf', self.compiler):
|
||||
self.assertEqual(gemini.main([str(self.root), '--compile-only']), 0)
|
||||
analysis.assert_not_called()
|
||||
self.assertEqual(tex.read_text(), 'manually edited solution')
|
||||
self.assertEqual((directory / '1.pdf').read_bytes(), b'PDF')
|
||||
self.assertEqual(self.compiler.call_args.kwargs['source_path'], self.root / 'correction.tex')
|
||||
|
||||
def test_preamble_change_recompiles_previously_successful_pdfs(self):
|
||||
self.client.models.generate_content.side_effect = self.responses()
|
||||
self.assertEqual(self.run_exam(), ExitCode.SUCCESS)
|
||||
self.compiler.reset_mock()
|
||||
self.assertEqual(self.run_exam(), ExitCode.SUCCESS)
|
||||
self.compiler.assert_not_called()
|
||||
(self.root / 'correction.tex').write_text(
|
||||
r'\documentclass{article}\newcommand{\custom}{new}\begin{document}')
|
||||
self.client.models.generate_content.reset_mock()
|
||||
self.assertEqual(self.run_exam(), ExitCode.SUCCESS)
|
||||
self.client.models.generate_content.assert_not_called()
|
||||
self.assertEqual(self.compiler.call_count, 1)
|
||||
self.assertEqual(self.compiler.call_args.args[1], self.root / 'Sol2/Ex 1.pdf')
|
||||
|
||||
def test_rubric_failure_retry_keeps_grouping_edits_and_sources(self):
|
||||
self.client.models.generate_content.side_effect = [*self.responses()[:3], RuntimeError('offline')]
|
||||
self.assertEqual(self.run_exam(), ExitCode.PARTIAL)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import contextlib
|
||||
import io
|
||||
import subprocess
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
@@ -10,6 +11,61 @@ 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")
|
||||
|
||||
Reference in new issue
Block a user