Recorrection GUI step ; LaTeX issues debugging

This commit is contained in:
sebastien committed 2026-10-10 20:40:48 +02:00
1 parent 2d0f7a219f
commit 48602c24f1
12 files changed
+306 -8

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
import tempfile
import unittest
from pathlib import Path
from subprocess import CompletedProcess
from unittest.mock import Mock, patch
from PIL import Image
from copienator.commands import annotating
class AnnotationLatexErrorTests(unittest.TestCase):
def test_failure_preserves_source_and_log_and_explains_recovery(self):
with tempfile.TemporaryDirectory() as directory:
diagnostics = Path(directory) / "diagnostics"
diagnostics.mkdir()
def compile_latex(command, **kwargs):
Path(kwargs["cwd"], "text.log").write_bytes(
b"preamble\n! Missing \\endcsname inserted.\nl.12 \\end{document}\n\xff"
)
return CompletedProcess(command, 1, stdout=b"compiler output\xff")
# Patch only the retained diagnostics directory, not TemporaryDirectory.
original_mkdtemp = tempfile.mkdtemp
def make_directory(*args, **kwargs):
if kwargs.get("prefix") == "copienator-latex-error-":
return str(diagnostics)
return original_mkdtemp(*args, **kwargs)
with patch.object(annotating.subprocess, "run", side_effect=compile_latex), patch.object(
annotating.tempfile, "mkdtemp", side_effect=make_directory
):
with self.assertRaises(annotating.AnnotationLatexError) as caught:
annotating.render_real_latex_text(r"Antislash \.", 400)
message = str(caught.exception)
for expected in ("code 1", r"Antislash \.", "Missing", "correction.json", "relancez cette étape", str(diagnostics)):
self.assertIn(expected, message)
self.assertIn(r"Antislash \.", (diagnostics / "text.tex").read_text())
self.assertTrue((diagnostics / "text.log").exists())
self.assertTrue((diagnostics / "compiler-output.txt").exists())
def test_comment_error_identifies_copy_label_and_correction_file(self):
for box in (None, [0, 0, 20, 20]):
with self.subTest(box=box), patch.object(
annotating, "render_score_text", return_value=Image.new("RGBA", (200, 40))
):
with self.assertRaises(annotating.AnnotationLatexError) as caught:
annotating.compose_label_image(
Image.new("RGB", (800, 200)), "I)7)",
{"feedback": [{"text": "Bad comment", "box_2d": box}]}, 0,
render_fn=Mock(side_effect=annotating.AnnotationLatexError("Bad comment")),
source_pdf=Path("/exam/Copies/Copie39/I)7).pdf"),
)
message = str(caught.exception)
for expected in ("Copie39", "I)7)", "/exam/correction.json", "Bad comment"):
self.assertIn(expected, message)
+62
View File
@@ -214,6 +214,49 @@ class GuiConvenienceTests(unittest.TestCase):
self.assertNotIn("overwrite", self.app.arg_vars)
self.assertIn("--refaire", self.app._make_command())
def test_optional_recorrection_opens_examples_and_returns_to_correction(self):
from copienator import read_json
ids = [step.id for step in self.app.steps]
self.assertEqual(ids[ids.index("manual_resolution") + 1], "prepare_recorrection")
copies = self.evaluation / "Copies"
copies.mkdir()
(copies / "Copie46.pdf").touch()
(self.evaluation / "labels").write_text("II)9)\n")
self.app.tree.selection_set("prepare_recorrection")
self.app.update()
with patch("copienator_gui.app.open_path") as opened:
self.app.edit_recorrection_button.invoke()
path = self.evaluation / "refaire.json"
opened.assert_called_once_with(path)
self.assertEqual(read_json(path), [])
path.write_text(path.read_text().replace("\n[]\n", '\n[["Copie46", []]]\n'))
with patch.object(self.app.runner, "start") as start:
self.app.run_button.invoke()
self.app.update()
start.assert_not_called()
self.assertEqual(self.app.state_store.step("prepare_recorrection")["status"], "success")
self.assertEqual(self.app.current_step.id, "correction")
self.assertEqual(self.app.variant_var.get(), "refaire")
self.assertIn("--refaire", self.app._make_command())
self.assertNotIn("--overwrite", self.app._make_command())
def test_ignoring_optional_recorrection_continues_without_creating_file(self):
self.app.tree.selection_set("prepare_recorrection")
self.app.update()
self.app._skip_step()
self.app.update()
self.assertEqual(self.app.current_step.id, "post_correction")
self.assertEqual(self.app.state_store.step("prepare_recorrection")["status"], "skipped")
self.assertFalse((self.evaluation / "refaire.json").exists())
def test_optional_recorrection_requires_a_saved_selection(self):
self.app.tree.selection_set("prepare_recorrection")
self.app.update()
with patch("copienator_gui.app.messagebox.showerror") as error:
self.app.run_button.invoke()
error.assert_called_once()
self.assertEqual(self.app.current_step.id, "prepare_recorrection")
def test_giving_names_waits_for_explicit_manual_completion(self):
return_dir = self.evaluation / "A Rendre" / "Student (01)"
return_dir.mkdir(parents=True)
@@ -560,6 +603,25 @@ class GuiConvenienceTests(unittest.TestCase):
self.assertIn("Cibler la copie sélectionnée", controls)
self.assertIn("Cibler tout le dossier", controls)
def test_answer_splitting_waits_for_target_and_can_select_one_copy(self):
copies = self.evaluation / "Copies"
copies.mkdir()
for name in ("Copie01.pdf", "Copie46.pdf"):
(copies / name).touch()
with patch.object(self.app, "_run_current_step") as run:
self.app.tree.selection_set("splitting")
self.app.update()
run.assert_not_called()
self.assertIn("Copie46.pdf", self.app.copy_paths)
self.app.copy_var.set("Copie46.pdf")
self.app._target_selected_copy()
command = self.app._make_command()
self.assertIn("split-answers", command)
self.assertEqual(Path(command[5]), copies / "Copie46.pdf")
self.app._target_all_pages()
command = self.app._make_command()
self.assertEqual((self.repository / command[5]).resolve(), self.evaluation)
def test_label_review_can_show_and_target_one_copy(self):
copies = self.evaluation / "Copies"
copies.mkdir()
+1 -1
View File
@@ -2378,7 +2378,7 @@ class WorkflowTests(unittest.TestCase):
self.assertEqual(
auto_start,
{"crop_blank_margins", "splitting", "crop_exercise_bottoms", "grouping"},
{"crop_blank_margins", "crop_exercise_bottoms", "grouping"},
)
self.assertEqual(
skip_for_live, {"submit_batches", "batch_status", "fetch_batches"}
+34
View File
@@ -0,0 +1,34 @@
import json
import tempfile
import unittest
from pathlib import Path
from copienator import read_json
from copienator_gui.recorrection import prepare_selection_file
class RecorrectionFileTests(unittest.TestCase):
def test_examples_are_inactive_and_opening_preserves_selection(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
path = prepare_selection_file(root)
self.assertEqual(read_json(path), [])
self.assertIn('// Une copie entière', path.read_text())
selection = [["Copie46", ["II)9)"]]]
path.write_text(json.dumps(selection))
prepare_selection_file(root)
self.assertEqual(read_json(path), selection)
before = path.read_bytes()
prepare_selection_file(root)
self.assertEqual(path.read_bytes(), before)
def test_comments_do_not_affect_strings_or_other_json_files(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
path = root / "refaire.json"
path.write_text('// Example\n[["Copie01", ["a//b"]]]\n')
self.assertEqual(read_json(path), [["Copie01", ["a//b"]]])
other = root / "correction.json"
other.write_text(path.read_text())
with self.assertRaises(json.JSONDecodeError):
read_json(other)