Skip to content

Commit 6f2901b

Browse files
committed
Reject binary linked resources during --dry-run
Dry-run only ran the parse-time link checks (missing target, directory, absolute path, .plain), so a linked PNG/PDF/ZIP passed validation and the error only surfaced at render time. Dry-run now loads every linked resource through the same collect_linked_resources + load_linked_resources path that RenderContext uses for rendering, so a binary target fails fast with the identical UnsupportedResourceType message (and missing-file / oversized base64 errors surface at dry-run too). Regression tests cover binary rejection, unusual-extension text acceptance, and the empty-file case.
1 parent f3a1344 commit 6f2901b

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

‎plain2code.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ def warn_if_acceptance_tests_without_conformance_script(plain_module, args) -> N
203203
)
204204

205205

206+
def validate_linked_resources(plain_module) -> None:
207+
"""Load every linked resource exactly the way rendering does, so dry-run
208+
surfaces the same errors (binary file, missing file, oversized base64 blob)."""
209+
for module in plain_module.all_required_modules + [plain_module]:
210+
resources_list: list[dict] = []
211+
plain_spec.collect_linked_resources(module.plain_source, resources_list, None, True)
212+
file_utils.load_linked_resources(module.template_dirs, resources_list, module.module_name)
213+
214+
206215
def render( # noqa: C901
207216
plain_module: plain_modules.PlainModule,
208217
args,
@@ -393,6 +402,12 @@ def main(): # noqa: C901
393402
return
394403

395404
if args.dry_run:
405+
try:
406+
validate_linked_resources(plain_module)
407+
except Exception as e:
408+
console.error(f"Error: {str(e)}")
409+
return
410+
396411
console.info("Printing dry run output...\n")
397412
render_range = plain_spec.compute_render_range(args, plain_module.plain_source)
398413
print_dry_run_output(plain_module.plain_source, render_range)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Regression tests for linked-resource validation during --dry-run.
2+
3+
A linked resource must be a single text-based file. Rendering rejects binary
4+
targets in ``file_utils.load_linked_resources`` (a failed UTF-8 decode raises
5+
``UnsupportedResourceType``); dry-run must surface the same errors through
6+
``plain2code.validate_linked_resources``, which reuses that exact mechanism.
7+
"""
8+
9+
import pytest
10+
11+
from plain2code import validate_linked_resources
12+
from plain2code_exceptions import UnsupportedResourceType
13+
from plain_modules import PlainModule
14+
15+
PLAIN_TEMPLATE = """***definitions***
16+
17+
- :Widget: is an item managed by the application.
18+
- Its icon is [the widget resource](resources/{resource_name}).
19+
20+
***implementation reqs***
21+
22+
- Implementation should be in Python.
23+
24+
***functional specs***
25+
26+
- A :Widget: can be created.
27+
"""
28+
29+
30+
def _make_module(tmp_path, monkeypatch, resource_name, resource_bytes):
31+
resources_dir = tmp_path / "resources"
32+
resources_dir.mkdir()
33+
(resources_dir / resource_name).write_bytes(resource_bytes)
34+
(tmp_path / "binlink.plain").write_text(PLAIN_TEMPLATE.format(resource_name=resource_name))
35+
36+
# Link targets are resolved relative to the current working directory at parse time.
37+
monkeypatch.chdir(tmp_path)
38+
return PlainModule("binlink.plain", str(tmp_path / "build"), [str(tmp_path)])
39+
40+
41+
def test_dry_run_validation_rejects_binary_linked_resource(tmp_path, monkeypatch):
42+
module = _make_module(tmp_path, monkeypatch, "image.png", b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\xff\xfe\xfd")
43+
44+
with pytest.raises(UnsupportedResourceType) as exc_info:
45+
validate_linked_resources(module)
46+
47+
assert "resources/image.png" in str(exc_info.value)
48+
assert "binary file" in str(exc_info.value)
49+
50+
51+
def test_dry_run_validation_accepts_text_file_with_unusual_extension(tmp_path, monkeypatch):
52+
module = _make_module(tmp_path, monkeypatch, "notes.xyz123", b"plain text content\n")
53+
54+
validate_linked_resources(module)
55+
56+
57+
def test_dry_run_validation_accepts_empty_file(tmp_path, monkeypatch):
58+
module = _make_module(tmp_path, monkeypatch, "empty.txt", b"")
59+
60+
validate_linked_resources(module)

0 commit comments

Comments
 (0)