v0.5.3
- Мелкие исправления увеличивающие работу конвертера
This commit is contained in:
@@ -16,11 +16,18 @@ from md2gost.diagram_includes import (
|
||||
)
|
||||
from md2gost.diagram_renderer import (
|
||||
DIAGRAM_LANGS,
|
||||
PLANTUML_CACHE_PREFIX,
|
||||
PLANTUML_VERSION,
|
||||
_cache_paths,
|
||||
_PumlJob,
|
||||
_plantuml_run,
|
||||
configure_diagrams,
|
||||
diagram_engine_status,
|
||||
is_diagram_lang,
|
||||
prepare_source,
|
||||
register_pending_diagram,
|
||||
render_diagram,
|
||||
reset_diagram_batch,
|
||||
resolve_plantuml_jar,
|
||||
)
|
||||
from md2gost.diagram_schemes import (
|
||||
@@ -558,6 +565,222 @@ def test_attach_svg_blip():
|
||||
assert len(image_rels) >= 2
|
||||
|
||||
|
||||
class _FakeProc:
|
||||
returncode = 0
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
|
||||
|
||||
def _fake_java(*, out_stem: str = "src", fail: bool = False):
|
||||
"""Stand-in for `java -jar plantuml.jar`: writes what the real jar would."""
|
||||
calls: list[list[str]] = []
|
||||
|
||||
def run(cmd, **kwargs):
|
||||
calls.append(list(cmd))
|
||||
if fail:
|
||||
proc = _FakeProc()
|
||||
proc.returncode = 1
|
||||
proc.stderr = "boom"
|
||||
return proc
|
||||
fmt = next(a[2:] for a in cmd if a.startswith("-t"))
|
||||
payload = (
|
||||
b"\x89PNG\r\n\x1a\n" + b"\x00" * 32 if fmt == "png"
|
||||
else b"<svg xmlns='http://www.w3.org/2000/svg'/>"
|
||||
)
|
||||
for arg in cmd:
|
||||
if not str(arg).endswith(".puml"):
|
||||
continue
|
||||
out_dir = Path(arg).parent / "out"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
(out_dir / f"{out_stem}.{fmt}").write_bytes(payload)
|
||||
return _FakeProc()
|
||||
|
||||
return run, calls
|
||||
|
||||
|
||||
def _dummy_jar(tmp_path: Path) -> Path:
|
||||
jar = tmp_path / "plantuml.jar"
|
||||
jar.write_bytes(b"PK" + b"\x00" * 2000)
|
||||
return jar
|
||||
|
||||
|
||||
def test_plantuml_run_batches_sources_into_one_jvm(tmp_path):
|
||||
jar = _dummy_jar(tmp_path)
|
||||
jobs = [_PumlJob(f"@startuml\nA{i} -> B{i}\n@enduml", tmp_path / f"{i}.png")
|
||||
for i in range(3)]
|
||||
run, calls = _fake_java()
|
||||
with patch("md2gost.diagram_renderer.subprocess.run", side_effect=run), \
|
||||
patch("md2gost.diagram_renderer._java_available", return_value=True):
|
||||
done = _plantuml_run(jobs, str(jar), "png")
|
||||
assert done == [True, True, True]
|
||||
assert len(calls) == 1
|
||||
assert sum(1 for a in calls[0] if str(a).endswith(".puml")) == 3
|
||||
for job in jobs:
|
||||
assert job.out_path.read_bytes().startswith(b"\x89PNG")
|
||||
|
||||
|
||||
def test_plantuml_run_keeps_named_diagrams_apart(tmp_path):
|
||||
"""`@startuml Name` renames the output file — each source needs its own -o dir."""
|
||||
jar = _dummy_jar(tmp_path)
|
||||
jobs = [_PumlJob("@startuml Name\nA -> B\n@enduml", tmp_path / f"{i}.png")
|
||||
for i in range(2)]
|
||||
run, calls = _fake_java(out_stem="Name")
|
||||
with patch("md2gost.diagram_renderer.subprocess.run", side_effect=run), \
|
||||
patch("md2gost.diagram_renderer._java_available", return_value=True):
|
||||
done = _plantuml_run(jobs, str(jar), "png")
|
||||
assert done == [True, True]
|
||||
assert calls[0].count("-o") == 1
|
||||
src_dirs = {Path(a).parent for a in calls[0] if str(a).endswith(".puml")}
|
||||
assert len(src_dirs) == 2
|
||||
assert all(job.out_path.is_file() for job in jobs)
|
||||
|
||||
|
||||
def test_plantuml_run_failure_reports_every_job(tmp_path):
|
||||
jar = _dummy_jar(tmp_path)
|
||||
jobs = [_PumlJob("@startuml\nA -> B\n@enduml", tmp_path / f"{i}.png")
|
||||
for i in range(2)]
|
||||
run, _calls = _fake_java(fail=True)
|
||||
with patch("md2gost.diagram_renderer.subprocess.run", side_effect=run), \
|
||||
patch("md2gost.diagram_renderer._java_available", return_value=True):
|
||||
done = _plantuml_run(jobs, str(jar), "png")
|
||||
assert done == [False, False]
|
||||
assert not any(job.out_path.exists() for job in jobs)
|
||||
|
||||
|
||||
def test_plantuml_run_without_java_never_spawns(tmp_path):
|
||||
jar = _dummy_jar(tmp_path)
|
||||
run, calls = _fake_java()
|
||||
with patch("md2gost.diagram_renderer.subprocess.run", side_effect=run), \
|
||||
patch("md2gost.diagram_renderer._java_available", return_value=False):
|
||||
done = _plantuml_run([_PumlJob("@startuml\nA -> B\n@enduml", tmp_path / "a.png")],
|
||||
str(jar), "png")
|
||||
assert done == [False]
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_batch_prepass_draws_whole_document_in_one_jvm(tmp_path):
|
||||
jar = _dummy_jar(tmp_path)
|
||||
configure_diagrams(
|
||||
fallback="off", cache_dir=str(tmp_path), plantuml_jar=str(jar), diagram_scale=1,
|
||||
)
|
||||
register_pending_diagram("uml", "A -> B")
|
||||
register_pending_diagram("uml", "C -> D")
|
||||
run, calls = _fake_java()
|
||||
with patch("md2gost.diagram_renderer.subprocess.run", side_effect=run), \
|
||||
patch("md2gost.diagram_renderer._java_available", return_value=True):
|
||||
first = render_diagram("uml", "A -> B")
|
||||
second = render_diagram("uml", "C -> D")
|
||||
assert len(calls) == 1
|
||||
assert first.png_path != second.png_path
|
||||
for result in (first, second):
|
||||
assert Path(result.png_path).read_bytes().startswith(b"\x89PNG")
|
||||
|
||||
|
||||
def test_batch_prepass_svg_mode_uses_two_jvms_not_four(tmp_path):
|
||||
jar = _dummy_jar(tmp_path)
|
||||
configure_diagrams(
|
||||
fallback="off", cache_dir=str(tmp_path), plantuml_jar=str(jar),
|
||||
diagram_scale=1, diagram_format="svg",
|
||||
)
|
||||
register_pending_diagram("uml", "A -> B")
|
||||
register_pending_diagram("uml", "C -> D")
|
||||
run, calls = _fake_java()
|
||||
with patch("md2gost.diagram_renderer.subprocess.run", side_effect=run), \
|
||||
patch("md2gost.diagram_renderer._java_available", return_value=True):
|
||||
first = render_diagram("uml", "A -> B")
|
||||
second = render_diagram("uml", "C -> D")
|
||||
assert sorted(next(a for a in c if a.startswith("-t")) for c in calls) == \
|
||||
["-tpng", "-tsvg"]
|
||||
assert first.svg_path and second.svg_path
|
||||
assert Path(first.svg_path).read_bytes().startswith(b"<svg")
|
||||
|
||||
|
||||
def test_batch_prepass_skips_cached_diagrams(tmp_path):
|
||||
jar = _dummy_jar(tmp_path)
|
||||
configure_diagrams(
|
||||
fallback="off", cache_dir=str(tmp_path), plantuml_jar=str(jar), diagram_scale=1,
|
||||
)
|
||||
cached_src, _ = prepare_source("uml", "A -> B")
|
||||
cached_png, _ = _cache_paths(
|
||||
cached_src, str(tmp_path), cache_prefix=PLANTUML_CACHE_PREFIX,
|
||||
)
|
||||
cached_png.write_bytes(b"\x89PNG\r\n\x1a\n" + b"cached")
|
||||
register_pending_diagram("uml", "A -> B")
|
||||
register_pending_diagram("uml", "C -> D")
|
||||
run, calls = _fake_java()
|
||||
with patch("md2gost.diagram_renderer.subprocess.run", side_effect=run), \
|
||||
patch("md2gost.diagram_renderer._java_available", return_value=True):
|
||||
hit = render_diagram("uml", "A -> B")
|
||||
render_diagram("uml", "C -> D")
|
||||
assert len(calls) == 1
|
||||
assert sum(1 for a in calls[0] if str(a).endswith(".puml")) == 1
|
||||
assert Path(hit.png_path).read_bytes().endswith(b"cached")
|
||||
|
||||
|
||||
def test_batch_prepass_not_triggered_for_explicit_overrides(tmp_path):
|
||||
configure_diagrams(fallback="remote", cache_dir=str(tmp_path))
|
||||
register_pending_diagram("uml", "A -> B")
|
||||
|
||||
def fake_kroki(source, diagram_type, base_url, out_path, fmt="png"):
|
||||
out_path.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 20)
|
||||
return True
|
||||
|
||||
with patch("md2gost.diagram_renderer._flush_plantuml_batch") as flush, \
|
||||
patch("md2gost.diagram_renderer._render_plantuml_jar", return_value=False), \
|
||||
patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki):
|
||||
render_diagram("uml", "A -> B", cache_dir=str(tmp_path))
|
||||
flush.assert_not_called()
|
||||
reset_diagram_batch()
|
||||
|
||||
|
||||
def test_diagram_figure_registers_for_batch():
|
||||
from docx import Document
|
||||
from md2gost import package_dir
|
||||
import md2gost.diagram_renderer as dr
|
||||
import os
|
||||
|
||||
reset_diagram_batch()
|
||||
doc = Document(os.path.join(package_dir(), "Template.docx"))
|
||||
DiagramFigure(doc._body, "uml", "A -> B")
|
||||
DiagramFigure(doc._body, "uml", "A -> B")
|
||||
DiagramFigure(doc._body, "mermaid", "flowchart LR\nA-->B")
|
||||
assert dr._PENDING == [("uml", "A -> B"), ("mermaid", "flowchart LR\nA-->B")]
|
||||
reset_diagram_batch()
|
||||
assert dr._PENDING == []
|
||||
|
||||
|
||||
def test_plantuml_cache_key_carries_jar_version(tmp_path):
|
||||
assert PLANTUML_VERSION in PLANTUML_CACHE_PREFIX
|
||||
prepared, dtype = prepare_source("uml", "A -> B")
|
||||
assert dtype == "plantuml"
|
||||
versioned, _ = _cache_paths(
|
||||
prepared, str(tmp_path), cache_prefix=PLANTUML_CACHE_PREFIX,
|
||||
)
|
||||
legacy, _ = _cache_paths(prepared, str(tmp_path))
|
||||
assert versioned != legacy
|
||||
|
||||
|
||||
def test_stale_unversioned_cache_is_a_miss_not_a_crash(tmp_path):
|
||||
"""Files cached before the jar version entered the key must be re-rendered."""
|
||||
configure_diagrams(fallback="remote", cache_dir=str(tmp_path), diagram_scale=1)
|
||||
prepared, _ = prepare_source("uml", "A -> B")
|
||||
legacy, _ = _cache_paths(prepared, str(tmp_path))
|
||||
legacy.write_bytes(b"\x89PNG\r\n\x1a\n" + b"stale")
|
||||
|
||||
def fake_kroki(source, diagram_type, base_url, out_path, fmt="png"):
|
||||
out_path.write_bytes(b"\x89PNG\r\n\x1a\n" + b"fresh")
|
||||
return True
|
||||
|
||||
with patch("md2gost.diagram_renderer._render_plantuml_jar", return_value=False), \
|
||||
patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki):
|
||||
result = render_diagram(
|
||||
"uml", "A -> B", cache_dir=str(tmp_path), diagram_scale=1,
|
||||
)
|
||||
assert Path(result.png_path) != legacy
|
||||
assert Path(result.png_path).read_bytes().endswith(b"fresh")
|
||||
assert legacy.read_bytes().endswith(b"stale")
|
||||
|
||||
|
||||
def test_resolve_plantuml_jar_explicit(tmp_path):
|
||||
jar = tmp_path / "plantuml.jar"
|
||||
jar.write_bytes(b"PK" + b"\x00" * 2000)
|
||||
|
||||
Reference in New Issue
Block a user