Skip to content

Commit eddf92e

Browse files
Copilotoscarlevin
andcommitted
Add error handling for failed image generation and tests to verify behavior
- In generate.py, each individual_* function now logs an error and raises an Exception when core does not produce the expected output file. This ensures the failure propagates up through generate_assets() so the asset type is not added to the successful_assets list and the cache is not incorrectly updated. - Add tests/test_generate.py with 8 tests covering all four individual_* functions, verifying both the failure case (raises) and success case (no raise). Co-authored-by: oscarlevin <6504596+oscarlevin@users.noreply.github.com> Agent-Logs-Url: https://github.com/PreTeXtBook/pretext-cli/sessions/7b12f474-b534-45aa-813c-9a2f75f4d8c3
1 parent ef5e4fc commit eddf92e

2 files changed

Lines changed: 238 additions & 0 deletions

File tree

pretext/project/generate.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ def individual_prefigure(
7474
f"Created prefigure diagram {annotations_output_file}; saving a copy to cache as {annotations_cache_file}"
7575
)
7676
shutil.copy2(annotations_output_file, annotations_cache_file)
77+
else:
78+
msg = f"Failed to generate prefigure diagram {Path(pfdiagram).name} in {format} format."
79+
log.error(msg)
80+
raise Exception(msg)
7781
log.debug("Finished individual_prefigure function")
7882

7983

@@ -109,6 +113,10 @@ def individual_asymptote(
109113
f"Created asymptote diagram {output_file}; saving a copy to cache as {cache_file}"
110114
)
111115
shutil.copy2(output_file, cache_file)
116+
else:
117+
msg = f"Failed to generate asymptote diagram {Path(asydiagram).name} in {outformat} format."
118+
log.error(msg)
119+
raise Exception(msg)
112120
log.debug("Finished individual_asymptote function")
113121

114122

@@ -146,6 +154,10 @@ def individual_sage(
146154
f"Created sageplot diagram {output_file}; saving a copy to cache as {cache_file}"
147155
)
148156
shutil.copy2(output_file, cache_file)
157+
else:
158+
msg = f"Failed to generate sageplot diagram {Path(sageplot).name} in {outformat} format."
159+
log.error(msg)
160+
raise Exception(msg)
149161
log.debug("Finished individual_sage function")
150162

151163

@@ -191,6 +203,10 @@ def individual_latex_image(
191203
f"Created latex-image {output_files[ext]}; saving a copy to cache as {cache_files[ext]}"
192204
)
193205
shutil.copy2(output_files[ext], cache_files[ext])
206+
else:
207+
msg = f"Failed to generate latex-image {Path(latex_image).name} in {ext} format."
208+
log.error(msg)
209+
raise Exception(msg)
194210
log.debug("Finished individual_latex function")
195211

196212

tests/test_generate.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
"""
2+
Tests to ensure that failed image generation is not reported as success.
3+
4+
When a core conversion function fails to produce an output file, the
5+
individual_* functions in pretext/project/generate.py should raise an
6+
exception so that the asset type is not added to the generated asset cache.
7+
"""
8+
from pathlib import Path
9+
from unittest.mock import patch
10+
11+
import pytest
12+
13+
from pretext.project import generate
14+
15+
16+
def _make_asset_file(tmp_path: Path, name: str = "test_asset.tex") -> Path:
17+
"""Create a minimal asset file in tmp_path for use in tests."""
18+
asset_file = tmp_path / name
19+
asset_file.write_text("some asset content")
20+
return asset_file
21+
22+
23+
def test_individual_latex_image_raises_on_missing_output(tmp_path: Path) -> None:
24+
"""individual_latex_image should raise when core does not produce the output file."""
25+
asset_file = _make_asset_file(tmp_path, "image.tex")
26+
cache_dir = tmp_path / "cache" / "latex-image"
27+
cache_dir.mkdir(parents=True)
28+
dest_dir = tmp_path / "dest"
29+
dest_dir.mkdir()
30+
31+
# Patch core so it does NOT create the output file.
32+
with patch("pretext.project.generate.core.individual_latex_image_conversion"):
33+
with pytest.raises(Exception, match="Failed to generate latex-image"):
34+
generate.individual_latex_image(
35+
latex_image=str(asset_file),
36+
outformat="svg",
37+
dest_dir=dest_dir,
38+
method="xelatex",
39+
cache_dir=tmp_path / "cache",
40+
skip_cache=True,
41+
)
42+
43+
44+
def test_individual_latex_image_succeeds_when_output_exists(tmp_path: Path) -> None:
45+
"""individual_latex_image should not raise when core produces the output file."""
46+
asset_file = _make_asset_file(tmp_path, "image.tex")
47+
cache_dir = tmp_path / "cache" / "latex-image"
48+
cache_dir.mkdir(parents=True)
49+
dest_dir = tmp_path / "dest"
50+
dest_dir.mkdir()
51+
52+
def fake_conversion(latex_image: str, outformat: str, dest_dir: Path, method: str) -> None:
53+
# Simulate a successful conversion by creating the expected output file.
54+
(dest_dir / Path(latex_image).with_suffix(".svg").name).touch()
55+
56+
with patch(
57+
"pretext.project.generate.core.individual_latex_image_conversion",
58+
side_effect=fake_conversion,
59+
):
60+
# Should not raise.
61+
generate.individual_latex_image(
62+
latex_image=str(asset_file),
63+
outformat="svg",
64+
dest_dir=dest_dir,
65+
method="xelatex",
66+
cache_dir=tmp_path / "cache",
67+
skip_cache=True,
68+
)
69+
70+
71+
def test_individual_asymptote_raises_on_missing_output(tmp_path: Path) -> None:
72+
"""individual_asymptote should raise when core does not produce the output file."""
73+
asset_file = _make_asset_file(tmp_path, "diagram.asy")
74+
cache_dir = tmp_path / "cache" / "asymptote"
75+
cache_dir.mkdir(parents=True)
76+
dest_dir = tmp_path / "dest"
77+
dest_dir.mkdir()
78+
79+
with patch("pretext.project.generate.core.individual_asymptote_conversion"):
80+
with pytest.raises(Exception, match="Failed to generate asymptote diagram"):
81+
generate.individual_asymptote(
82+
asydiagram=str(asset_file),
83+
outformat="svg",
84+
method="server",
85+
asy_cli=[],
86+
asyversion="",
87+
alberta="",
88+
dest_dir=dest_dir,
89+
cache_dir=tmp_path / "cache",
90+
skip_cache=True,
91+
)
92+
93+
94+
def test_individual_asymptote_succeeds_when_output_exists(tmp_path: Path) -> None:
95+
"""individual_asymptote should not raise when core produces the output file."""
96+
asset_file = _make_asset_file(tmp_path, "diagram.asy")
97+
cache_dir = tmp_path / "cache" / "asymptote"
98+
cache_dir.mkdir(parents=True)
99+
dest_dir = tmp_path / "dest"
100+
dest_dir.mkdir()
101+
102+
def fake_conversion(
103+
asydiagram: str,
104+
outformat: str,
105+
method: str,
106+
asy_cli: list,
107+
asyversion: str,
108+
alberta: str,
109+
dest_dir: Path,
110+
) -> None:
111+
(dest_dir / Path(asydiagram).with_suffix(".svg").name).touch()
112+
113+
with patch(
114+
"pretext.project.generate.core.individual_asymptote_conversion",
115+
side_effect=fake_conversion,
116+
):
117+
generate.individual_asymptote(
118+
asydiagram=str(asset_file),
119+
outformat="svg",
120+
method="server",
121+
asy_cli=[],
122+
asyversion="",
123+
alberta="",
124+
dest_dir=dest_dir,
125+
cache_dir=tmp_path / "cache",
126+
skip_cache=True,
127+
)
128+
129+
130+
def test_individual_sage_raises_on_missing_output(tmp_path: Path) -> None:
131+
"""individual_sage should raise when core does not produce the output file."""
132+
asset_file = _make_asset_file(tmp_path, "plot.sage")
133+
cache_dir = tmp_path / "cache" / "sageplot"
134+
cache_dir.mkdir(parents=True)
135+
dest_dir = tmp_path / "dest"
136+
dest_dir.mkdir()
137+
138+
with patch("pretext.project.generate.core.individual_sage_conversion"):
139+
with pytest.raises(Exception, match="Failed to generate sageplot diagram"):
140+
generate.individual_sage(
141+
sageplot=str(asset_file),
142+
outformat="svg",
143+
dest_dir=dest_dir,
144+
sage_executable_cmd=["sage"],
145+
cache_dir=tmp_path / "cache",
146+
skip_cache=True,
147+
)
148+
149+
150+
def test_individual_sage_succeeds_when_output_exists(tmp_path: Path) -> None:
151+
"""individual_sage should not raise when core produces the output file."""
152+
asset_file = _make_asset_file(tmp_path, "plot.sage")
153+
cache_dir = tmp_path / "cache" / "sageplot"
154+
cache_dir.mkdir(parents=True)
155+
dest_dir = tmp_path / "dest"
156+
dest_dir.mkdir()
157+
158+
def fake_conversion(
159+
sageplot: str,
160+
outformat: str,
161+
dest_dir: Path,
162+
sage_executable_cmd: list,
163+
) -> None:
164+
(dest_dir / Path(sageplot).with_suffix(".svg").name).touch()
165+
166+
with patch(
167+
"pretext.project.generate.core.individual_sage_conversion",
168+
side_effect=fake_conversion,
169+
):
170+
generate.individual_sage(
171+
sageplot=str(asset_file),
172+
outformat="svg",
173+
dest_dir=dest_dir,
174+
sage_executable_cmd=["sage"],
175+
cache_dir=tmp_path / "cache",
176+
skip_cache=True,
177+
)
178+
179+
180+
def test_individual_prefigure_raises_on_missing_output(tmp_path: Path) -> None:
181+
"""individual_prefigure should raise when core does not produce the output file."""
182+
asset_file = _make_asset_file(tmp_path, "figure.xml")
183+
cache_dir = tmp_path / "cache" / "prefigure"
184+
cache_dir.mkdir(parents=True)
185+
tmp_dir = tmp_path / "tmp_work"
186+
tmp_dir.mkdir()
187+
188+
with patch("pretext.project.generate.core.individual_prefigure_conversion"):
189+
with pytest.raises(Exception, match="Failed to generate prefigure diagram"):
190+
generate.individual_prefigure(
191+
pfdiagram=str(asset_file),
192+
outformat="svg",
193+
tmp_dir=str(tmp_dir),
194+
cache_dir=tmp_path / "cache",
195+
skip_cache=True,
196+
)
197+
198+
199+
def test_individual_prefigure_succeeds_when_output_exists(tmp_path: Path) -> None:
200+
"""individual_prefigure should not raise when core produces the output file."""
201+
asset_file = _make_asset_file(tmp_path, "figure.xml")
202+
cache_dir = tmp_path / "cache" / "prefigure"
203+
cache_dir.mkdir(parents=True)
204+
tmp_dir = tmp_path / "tmp_work"
205+
tmp_dir.mkdir()
206+
207+
def fake_conversion(pfdiagram: str, outformat: str) -> None:
208+
output_dir = tmp_dir / "output"
209+
output_dir.mkdir(parents=True, exist_ok=True)
210+
(output_dir / Path(pfdiagram).with_suffix(".svg").name).touch()
211+
212+
with patch(
213+
"pretext.project.generate.core.individual_prefigure_conversion",
214+
side_effect=fake_conversion,
215+
):
216+
generate.individual_prefigure(
217+
pfdiagram=str(asset_file),
218+
outformat="svg",
219+
tmp_dir=str(tmp_dir),
220+
cache_dir=tmp_path / "cache",
221+
skip_cache=True,
222+
)

0 commit comments

Comments
 (0)