Remaster fifteen original pictures and bundle portable review receipts
This commit is contained in:
1 parent
05a2eae96b
commit
6e4008c62f
250 files changed
+619129
-61
No files matched your search
@@ -11,8 +11,9 @@ import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
from hd_candidate_review import binding_digest, proposal_frames
|
||||
from hd_art_audit import checked_palette, validate_hd_frame
|
||||
@@ -23,6 +24,10 @@ from hd_frame_policy import validate_frame_policy, retained_review_version_ok, o
|
||||
|
||||
CARRIED_TERRAIN_V2 = "standalone-terrain-carried-identity-v2"
|
||||
|
||||
# Exact historical canonical HD root recorded in accepted carried-QA receipts.
|
||||
# Its metadata is bundled under the supplied HD root; private audit roots are not mapped.
|
||||
LEGACY_HD_RECEIPT_ROOTS = (PurePosixPath("/home/quintine/Projects/RaptorReplica/data/hd"),)
|
||||
|
||||
|
||||
def asset_list(value, label):
|
||||
if not isinstance(value, list) or any(not isinstance(asset, str) or
|
||||
@@ -284,13 +289,45 @@ def validate_carried_registered_identity_v2(root, candidate, job, rows, review):
|
||||
raise ValueError("carried v2 unused record or complete carried digest differs")
|
||||
|
||||
|
||||
def legacy_local_receipt_bytes(root, relative):
|
||||
"""Read an exact legacy-root receipt from the bundle without touching W."""
|
||||
if relative.is_absolute() or not relative.parts or ".." in relative.parts:
|
||||
raise ValueError("legacy receipt path escapes HD root")
|
||||
local = Path(root).resolve() / relative
|
||||
path = contained(root, relative)
|
||||
if local != path or local.is_symlink():
|
||||
raise ValueError("legacy receipt must be a regular nonsymlink bundle file")
|
||||
before = path.lstat()
|
||||
if not stat.S_ISREG(before.st_mode):
|
||||
raise ValueError("legacy receipt must be a regular nonsymlink bundle file")
|
||||
def identity(value):
|
||||
return (value.st_dev, value.st_ino, value.st_mode, value.st_size, value.st_mtime_ns)
|
||||
flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
||||
with os.fdopen(os.open(path, flags), "rb") as stream:
|
||||
opened = os.fstat(stream.fileno())
|
||||
payload = stream.read()
|
||||
finished = os.fstat(stream.fileno())
|
||||
after = path.lstat()
|
||||
if (identity(before) != identity(opened) or identity(before) != identity(finished)
|
||||
or identity(before) != identity(after) or local.resolve() != path
|
||||
or len(payload) != after.st_size):
|
||||
raise ValueError("independent receipt changed during read")
|
||||
return payload
|
||||
|
||||
|
||||
def receipt_json(root, receipt):
|
||||
if (not isinstance(receipt, dict) or not isinstance(receipt.get("path"), str) or
|
||||
re.fullmatch(r"[0-9a-f]{64}", str(receipt.get("sha256", ""))) is None):
|
||||
raise ValueError("missing hash-bound independent receipt")
|
||||
relative = Path(receipt["path"])
|
||||
path = relative if relative.is_absolute() else contained(root, relative)
|
||||
payload = path.read_bytes()
|
||||
historical = PurePosixPath(receipt["path"])
|
||||
legacy_root = next((legacy for legacy in LEGACY_HD_RECEIPT_ROOTS
|
||||
if historical.is_absolute() and historical.is_relative_to(legacy)), None)
|
||||
if legacy_root is not None:
|
||||
payload = legacy_local_receipt_bytes(root, Path(*historical.relative_to(legacy_root).parts))
|
||||
else:
|
||||
path = relative if relative.is_absolute() else contained(root, relative)
|
||||
payload = path.read_bytes()
|
||||
if hashlib.sha256(payload).hexdigest() != receipt["sha256"]:
|
||||
raise ValueError("independent receipt changed")
|
||||
return json.loads(payload)
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
"""Reconstruct Original UI geometry around authenticated generated material.
|
||||
|
||||
This creates DERIVED artwork, not untouched generation and not art approval.
|
||||
Original owns all occupied/empty pixels. Generated RGB is registered in complete
|
||||
declared regions, shifted once per Original palette class, and bounded around
|
||||
that class. Unsupported strokes receive disclosed flat Original-color backing.
|
||||
No text is inferred, no random detail is made, and no new runtime policy is used.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, __version__ as PILLOW_VERSION
|
||||
|
||||
import hd_frame_policy
|
||||
|
||||
METHOD = "derived-generated-ui-original-stencil-class-tone-visible-parent-variance-v3"
|
||||
PARENT_VISIBLE_ALPHA = 128
|
||||
MAX_PIXELS = 4_194_304
|
||||
MAX_INPUT_BYTES = 32 * 1024 * 1024
|
||||
|
||||
|
||||
def sha(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def checked_input(binding, base, byte_limit=MAX_INPUT_BYTES):
|
||||
if type(binding) is not dict or set(binding) != {"path", "sha256"}:
|
||||
raise ValueError("input needs exactly path/sha256")
|
||||
if type(binding["path"]) is not str or not binding["path"]:
|
||||
raise ValueError("input path must be nonempty text")
|
||||
digest = binding["sha256"]
|
||||
if (type(digest) is not str or len(digest) != 64
|
||||
or any(c not in "0123456789abcdef" for c in digest)):
|
||||
raise ValueError("input SHA256 must be lowercase hexadecimal")
|
||||
path = Path(binding["path"])
|
||||
if not path.is_absolute():
|
||||
path = Path(base) / path
|
||||
if path.stat().st_size > byte_limit:
|
||||
raise ValueError("input byte limit exceeded")
|
||||
data = path.read_bytes()
|
||||
if len(data) > byte_limit or sha(data) != digest:
|
||||
raise ValueError(f"input identity mismatch: {path}")
|
||||
return path.resolve(), data
|
||||
|
||||
|
||||
def bounds(value, width, height, label):
|
||||
if (type(value) is not list or len(value) != 4
|
||||
or any(type(n) is not int for n in value)):
|
||||
raise ValueError(f"{label} needs four integer bounds")
|
||||
l, t, r, b = value
|
||||
if not (0 <= l < r <= width and 0 <= t < b <= height):
|
||||
raise ValueError(f"{label} exceeds its canvas")
|
||||
return l, t, r, b
|
||||
|
||||
|
||||
def parent_material_witnesses(parent, original_classes):
|
||||
"""Certify local RGB variation before alpha/resampling can manufacture it.
|
||||
|
||||
All nine pixels must be visible (alpha>=128) and belong to the same mapped Original
|
||||
palette class. Hidden RGB, low-alpha feather pixels, Source gaps and class boundaries
|
||||
cannot certify material. This is a conservative numeric witness, not an
|
||||
artistic verdict about whether the texture is useful.
|
||||
"""
|
||||
height, width = original_classes.shape
|
||||
witnesses = np.zeros((height, width), dtype=bool)
|
||||
if height < 3 or width < 3:
|
||||
return witnesses
|
||||
center = original_classes[1:-1, 1:-1]
|
||||
eligible = (parent[1:-1, 1:-1, 3] >= PARENT_VISIBLE_ALPHA) & (center < 240)
|
||||
varying = np.zeros_like(eligible)
|
||||
center_rgb = parent[1:-1, 1:-1, :3]
|
||||
for dy, dx in [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]:
|
||||
neighbor = parent[1+dy:height-1+dy, 1+dx:width-1+dx]
|
||||
neighbor_class = original_classes[1+dy:height-1+dy, 1+dx:width-1+dx]
|
||||
eligible &= (neighbor[:, :, 3] >= PARENT_VISIBLE_ALPHA) & (neighbor_class == center)
|
||||
varying |= np.any(neighbor[:, :, :3] != center_rgb, axis=2)
|
||||
witnesses[1:-1, 1:-1] = eligible & varying
|
||||
return witnesses
|
||||
|
||||
|
||||
def render(recipe, base):
|
||||
allowed = {"format_version", "label", "asset", "source", "palette", "parent_raw",
|
||||
"logical_canvas", "origin", "output_scale", "regions", "classes"}
|
||||
required = allowed - {"label"}
|
||||
if (type(recipe) is not dict or set(recipe) - allowed or not required <= set(recipe)
|
||||
or type(recipe["format_version"]) is not int or recipe["format_version"] != 1):
|
||||
raise ValueError("unknown/missing recipe fields or version")
|
||||
asset = recipe["asset"]
|
||||
if (type(asset) is not str or not asset or len(asset) > 8
|
||||
or any(c not in "0123456789abcdef" for c in asset)):
|
||||
raise ValueError("asset needs lowercase hexadecimal identity")
|
||||
source_path, source_bytes = checked_input(recipe["source"], base)
|
||||
raw_path, raw_bytes = checked_input(recipe["parent_raw"], base)
|
||||
palette_path, palette_bytes = checked_input(recipe["palette"], base, 768)
|
||||
if len(palette_bytes) != 768:
|
||||
raise ValueError("palette must have768 six-bit components")
|
||||
width, height, indices = hd_frame_policy._indices(source_bytes)
|
||||
if (type(recipe["logical_canvas"]) is not list or type(recipe["origin"]) is not list
|
||||
or any(type(n) is not int for n in recipe["logical_canvas"] + recipe["origin"])
|
||||
or recipe["logical_canvas"] != [width, height] or recipe["origin"] != [0, 0]):
|
||||
raise ValueError("Original canvas and top-left origin must remain exact")
|
||||
scale = recipe["output_scale"]
|
||||
if type(scale) is not int or not 4 <= scale <= 16:
|
||||
raise ValueError("output_scale needs integer4..16")
|
||||
if width * height * scale * scale > MAX_PIXELS:
|
||||
raise ValueError("derived image exceeds pixel ceiling")
|
||||
if isinstance(indices, (bytes, bytearray, memoryview)):
|
||||
decoded = np.frombuffer(indices, dtype=np.uint8).astype(np.uint16)
|
||||
else:
|
||||
decoded = np.asarray(indices, dtype=np.uint16)
|
||||
source = decoded.reshape(height, width)
|
||||
painted = source < 256
|
||||
if not painted.any():
|
||||
raise ValueError("Original has no painted UI")
|
||||
source_classes = set(int(x) for x in np.unique(source[painted]))
|
||||
if any(x >= 240 for x in source_classes):
|
||||
raise ValueError("UI cleanup supports ordinary Original palette classes only")
|
||||
classes = recipe["classes"]
|
||||
if type(classes) is not list or not 1 <= len(classes) <= 16:
|
||||
raise ValueError("classes needs one to16 explicit Original palette classes")
|
||||
limits = {}
|
||||
for cls in classes:
|
||||
if (type(cls) is not dict or set(cls) != {"index", "max_channel_delta"}
|
||||
or type(cls["index"]) is not int or type(cls["max_channel_delta"]) is not int
|
||||
or not 0 <= cls["index"] < 240 or not 0 <= cls["max_channel_delta"] <= 32
|
||||
or cls["index"] in limits):
|
||||
raise ValueError("invalid/duplicate Original material class")
|
||||
limits[cls["index"]] = cls["max_channel_delta"]
|
||||
if set(limits) != source_classes:
|
||||
raise ValueError("material classes must exactly cover painted Original indices")
|
||||
palette = np.minimum(np.frombuffer(palette_bytes, dtype=np.uint8), 63)
|
||||
palette = ((palette << 2) | (palette >> 4)).reshape(256, 3)
|
||||
with Image.open(io.BytesIO(raw_bytes)) as opened:
|
||||
if opened.format != "PNG" or opened.width * opened.height > MAX_PIXELS:
|
||||
raise ValueError("parent must be a bounded PNG")
|
||||
raw = opened.convert("RGBA")
|
||||
regions = recipe["regions"]
|
||||
if type(regions) is not list or not 1 <= len(regions) <= 16:
|
||||
raise ValueError("regions needs one to16 explicit material registrations")
|
||||
assigned = np.zeros((height, width), dtype=bool)
|
||||
plan = []
|
||||
for region in regions:
|
||||
if (type(region) is not dict or set(region) != {"name", "source_bounds", "raw_bounds"}
|
||||
or type(region["name"]) is not str or not region["name"]):
|
||||
raise ValueError("region needs name/source_bounds/raw_bounds")
|
||||
l, t, r, b = bounds(region["source_bounds"], width, height, "Original region")
|
||||
rb = bounds(region["raw_bounds"], *raw.size, "raw material region")
|
||||
if assigned[t:b, l:r].any():
|
||||
raise ValueError("material registration regions overlap")
|
||||
selected = painted[t:b, l:r]
|
||||
if not selected.any():
|
||||
raise ValueError("material region contains no painted Original cells")
|
||||
assigned[t:b, l:r] = True
|
||||
plan.append((region, (l, t, r, b), rb))
|
||||
if not np.all(assigned[painted]):
|
||||
raise ValueError("material regions do not cover every painted Original cell")
|
||||
# Inspect untouched parent pixels before performing any RGBA resize. Map the
|
||||
# logical stencil into each parent region by exact pixel-center arithmetic.
|
||||
authenticated_material = []
|
||||
parent_witness_count = 0
|
||||
for region, (l, t, r, b), rb in plan:
|
||||
parent = np.asarray(raw.crop(rb))
|
||||
ph, pw = parent.shape[:2]
|
||||
ox = ((np.arange(pw, dtype=np.int64)*2+1)*(r-l)) // (pw*2)
|
||||
oy = ((np.arange(ph, dtype=np.int64)*2+1)*(b-t)) // (ph*2)
|
||||
original_classes = source[t+oy[:,None], l+ox[None,:]]
|
||||
witnesses = parent_material_witnesses(parent, original_classes)
|
||||
parent_witness_count += int(witnesses.sum())
|
||||
authenticated_material.append((region, (l,t,r,b), rb, witnesses, original_classes))
|
||||
if parent_witness_count == 0:
|
||||
raise ValueError("no generated fine RGB: no pre-resample visible parent material variance")
|
||||
# Each registered material field remains continuous across its complete region.
|
||||
source_nx = source.repeat(scale, axis=0).repeat(scale, axis=1)
|
||||
occupied = source_nx < 256
|
||||
rgb = np.zeros((height * scale, width * scale, 3), dtype=np.uint8)
|
||||
trusted = np.zeros_like(occupied)
|
||||
certified_class = np.full(source_nx.shape, 256, dtype=np.uint16)
|
||||
region_reports = []
|
||||
for region, (l, t, r, b), rb, witnesses, original_classes in authenticated_material:
|
||||
size = ((r-l)*scale, (b-t)*scale)
|
||||
sampled = np.asarray(raw.crop(rb).resize(size, Image.Resampling.LANCZOS))
|
||||
yy, xx = slice(t*scale, b*scale), slice(l*scale, r*scale)
|
||||
rgb[yy, xx] = sampled[:, :, :3]
|
||||
trusted[yy, xx] = sampled[:, :, 3] >= 128
|
||||
# Nearest registration carries evidence only; it never paints RGB.
|
||||
witness_classes = np.where(witnesses, original_classes, 256).astype(np.uint16)
|
||||
certified_class[yy, xx] = np.asarray(Image.fromarray(witness_classes).resize(size, Image.Resampling.NEAREST))
|
||||
region_reports.append(dict(name=region["name"], source_bounds=[l,t,r,b],
|
||||
raw_material_sampling_bounds=list(rb), registered_canvas=list(size),
|
||||
registered_RGBA_sha256=sha(sampled.tobytes()),
|
||||
pre_resample_visible_parent_material_witnesses=int(witnesses.sum()),
|
||||
parent_witness_class_map_sha256=sha(witness_classes.tobytes())))
|
||||
direct = occupied & trusted
|
||||
restored = occupied & ~trusted
|
||||
certified_direct = direct & (certified_class == source_nx)
|
||||
direct_fraction = float(direct.sum() / occupied.sum())
|
||||
if direct_fraction < 0.5:
|
||||
raise ValueError("most generated UI support is absent; refusing Source-fill relabel")
|
||||
registered_rgb = rgb.copy()
|
||||
class_reports = []
|
||||
for index in sorted(source_classes):
|
||||
selected = source_nx == index
|
||||
supported = selected & direct
|
||||
missing = selected & restored
|
||||
target = palette[index].astype(np.int16)
|
||||
if supported.any():
|
||||
# A single tone anchor per entire palette class, never per source cell.
|
||||
median = np.median(registered_rgb[supported], axis=0)
|
||||
shift = target.astype(np.float64) - median
|
||||
before_limit = np.rint(registered_rgb[supported].astype(np.float64) + shift)
|
||||
delta = np.clip(before_limit-target, -limits[index], limits[index])
|
||||
rgb[supported] = np.clip(target + delta, 0, 255).astype(np.uint8)
|
||||
clipped = int(np.count_nonzero(before_limit-target != delta))
|
||||
else:
|
||||
median, shift, clipped = None, None, 0
|
||||
rgb[missing] = target.astype(np.uint8)
|
||||
class_reports.append(dict(index=index, Original_RGB=target.tolist(),
|
||||
max_channel_delta=limits[index],
|
||||
generated_supported_pixels=int(supported.sum()),
|
||||
restored_flat_Original_functional_pixels=int(missing.sum()),
|
||||
registered_RGB_median=None if median is None else median.tolist(),
|
||||
consistent_RGB_shift=None if shift is None else shift.tolist(),
|
||||
channel_samples_clipped_to_declared_tone_bound=clipped))
|
||||
detail_cells = []
|
||||
detail_pixels = 0
|
||||
for y, x in np.argwhere(painted):
|
||||
yy, xx = slice(y*scale, (y+1)*scale), slice(x*scale, (x+1)*scale)
|
||||
values = rgb[yy, xx][certified_direct[yy, xx]]
|
||||
if len(values) > 1:
|
||||
differing = int(np.any(values != values[0], axis=1).sum())
|
||||
if differing:
|
||||
detail_cells.append([int(x),int(y)])
|
||||
detail_pixels += differing
|
||||
if not detail_cells:
|
||||
raise ValueError("no generated fine RGB remains; refusing an Original NN relabel")
|
||||
rgba = np.zeros((height*scale, width*scale, 4), dtype=np.uint8)
|
||||
rgba[occupied, :3] = rgb[occupied]
|
||||
rgba[occupied, 3] = 255
|
||||
report = dict(method=METHOD,
|
||||
output_class="DERIVED_ORIGINAL_UI_GEOMETRY_WITH_REGISTERED_GENERATED_RGB_NOT_ART_APPROVAL",
|
||||
input_paths=dict(source=str(source_path),parent_raw=str(raw_path),palette=str(palette_path)),
|
||||
logical_canvas=[width,height], origin=[0,0], output_scale=scale,
|
||||
full_output_canvas=[width*scale,height*scale], runtime_crop=None,
|
||||
Original_painted_cells=int(painted.sum()), painted_output_pixels=int(occupied.sum()),
|
||||
exact_Original_Nx_alpha=True, direct_generated_support_pixels=int(direct.sum()),
|
||||
direct_generated_support_fraction=direct_fraction,
|
||||
pre_resample_visible_parent_material_witnesses=parent_witness_count,
|
||||
registered_certified_parent_material_pixels=int(certified_direct.sum()),
|
||||
fine_RGB_credit_requires_same_class_parent_material_witness=True,
|
||||
parent_material_visibility=dict(alpha_min=PARENT_VISIBLE_ALPHA,neighborhood=[3,3],
|
||||
RGB_variance_before_resampling=True,Original_class_must_match_all_neighbors=True),
|
||||
restored_flat_Original_functional_pixels=int(restored.sum()),
|
||||
restored_Original_cells=[[int(x),int(y)] for y,x in np.argwhere(restored.reshape(height,scale,width,scale).any(axis=(1,3)))],
|
||||
material_regions=region_reports, class_tone_controls=class_reports,
|
||||
generated_fine_RGB_cells=detail_cells, generated_fine_RGB_pixels=detail_pixels,
|
||||
material_metrics_are_not_artistic_approval=True,
|
||||
raw_RGB_is_shifted_and_bounded_not_claimed_byte_exact=True,
|
||||
no_random_or_tiled_synthetic_detail=True,
|
||||
Source_decoder_sha256=sha(Path(hd_frame_policy.__file__).read_bytes()),
|
||||
reader_environment=dict(Python=sys.version,Python_executable_sha256=sha(Path(sys.executable).read_bytes()),
|
||||
NumPy=np.__version__,Pillow=PILLOW_VERSION))
|
||||
return rgba, report
|
||||
|
||||
|
||||
def run_recipe(recipe_path, output):
|
||||
recipe_path, output = Path(recipe_path), Path(output)
|
||||
if output.exists():
|
||||
raise ValueError("output exists; never overwrite old artwork or evidence")
|
||||
recipe_bytes = recipe_path.read_bytes()
|
||||
recipe = json.loads(recipe_bytes)
|
||||
rgba, report = render(recipe, recipe_path.resolve().parent)
|
||||
buffer = io.BytesIO()
|
||||
Image.fromarray(rgba).save(buffer, format="PNG", compress_level=9)
|
||||
png = buffer.getvalue()
|
||||
name = f"{recipe['asset']}-derived.png"
|
||||
report["execution"] = dict(status="success",method=METHOD,
|
||||
recipe_sha256=sha(recipe_bytes),tool_sha256=sha(Path(__file__).read_bytes()),
|
||||
source_sha256={recipe["asset"]:recipe["source"]["sha256"]},
|
||||
parent_raw_sha256=[recipe["parent_raw"]["sha256"]],
|
||||
output_sha256={recipe["asset"]:sha(png)})
|
||||
report["output"] = dict(file=name,bytes=len(png),RGBA_sha256=sha(rgba.tobytes()))
|
||||
output.mkdir(parents=True,exist_ok=False)
|
||||
(output/name).write_bytes(png)
|
||||
(output/"derived-provenance.json").write_text(json.dumps(report,indent=2,sort_keys=True)+'\n')
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("recipe",type=Path)
|
||||
parser.add_argument("output_new_directory",type=Path)
|
||||
args=parser.parse_args()
|
||||
report=run_recipe(args.recipe,args.output_new_directory)
|
||||
print(json.dumps(dict(execution=report["execution"],generated_support_fraction=report["direct_generated_support_fraction"],restored_Original_pixels=report["restored_flat_Original_functional_pixels"])))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Canonical bundle copies resolve historical carried-QA receipts without W."""
|
||||
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
import hashlib, json, os, tempfile, types, unittest
|
||||
from unittest.mock import patch
|
||||
import hd_register_reviewed as m
|
||||
|
||||
LEGACY=PurePosixPath('/home/quintine/Projects/RaptorReplica/data/hd')
|
||||
REL=Path('registered-class-review/batch-4742-final-successor-metadata/terrain-203fd-2040c/runtime-qa-current7b-exact-component-alias.json')
|
||||
|
||||
class LegacyReceipt(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp=tempfile.TemporaryDirectory(prefix='receipt-portability-')
|
||||
self.work=Path(self.temp.name);self.root=self.work/'Renamed 游戏 bundle/data/hd';self.root.mkdir(parents=True)
|
||||
self.payload=b'{"diagnostics":[],"probe":"local-copy"}\n'
|
||||
self.digest=hashlib.sha256(self.payload).hexdigest();self.local=self.root/REL
|
||||
self.local.parent.mkdir(parents=True);self.local.write_bytes(self.payload)
|
||||
self.receipt=dict(path=str(LEGACY/REL.as_posix()),sha256=self.digest)
|
||||
def tearDown(self):self.temp.cleanup()
|
||||
def deny_legacy_open(self):
|
||||
original=os.open;seen=[]
|
||||
def guarded(path,*a,**kw):
|
||||
seen.append(str(path))
|
||||
if Path(path).is_relative_to(LEGACY):raise AssertionError('Must never inspect or open W original')
|
||||
return original(path,*a,**kw)
|
||||
return patch.object(m.os,'open',new=guarded),seen
|
||||
def test_renamed_unicode_bundle_uses_local_when_legacy_unreadable(self):
|
||||
guard,seen=self.deny_legacy_open()
|
||||
with guard:result=m.receipt_json(self.root,self.receipt)
|
||||
self.assertEqual(result,json.loads(self.payload));self.assertEqual(seen,[str(self.local)])
|
||||
def test_local_preferred_even_if_legacy_would_be_valid(self):
|
||||
original=Path.read_bytes;seen=[]
|
||||
def logical_w(self):
|
||||
if self==LEGACY/REL:seen.append(str(self));return b'{"probe":"old-W-would-exist"}'
|
||||
return original(self)
|
||||
with patch.object(Path,'read_bytes',new=logical_w):self.assertEqual(m.receipt_json(self.root,self.receipt),json.loads(self.payload))
|
||||
self.assertEqual(seen,[])
|
||||
def test_posix_historical_receipt_maps_when_native_windows_absolute_rule_is_false(self):
|
||||
self.assertFalse(PureWindowsPath(self.receipt['path']).is_absolute())
|
||||
original=Path
|
||||
def windows_like_native(*values):
|
||||
if len(values)==1 and values[0]==self.receipt['path']:
|
||||
return PureWindowsPath(values[0])
|
||||
return original(*values)
|
||||
with patch.object(m,'Path',new=windows_like_native):
|
||||
self.assertEqual(m.receipt_json(self.root,self.receipt),json.loads(self.payload))
|
||||
|
||||
def test_wrong_local_sha_fails_without_external_fallback(self):
|
||||
self.local.write_bytes(b'{"probe":"wrong"}')
|
||||
guard,seen=self.deny_legacy_open()
|
||||
with guard,self.assertRaisesRegex(ValueError,'independent receipt changed'):m.receipt_json(self.root,self.receipt)
|
||||
self.assertEqual(seen,[str(self.local)])
|
||||
def test_missing_local_fails_without_external_fallback(self):
|
||||
self.local.unlink();guard,seen=self.deny_legacy_open()
|
||||
with guard,self.assertRaises(FileNotFoundError):m.receipt_json(self.root,self.receipt)
|
||||
self.assertEqual(seen,[])
|
||||
def test_legacy_parent_traversal_rejects_before_read(self):
|
||||
value=dict(self.receipt,path=str(LEGACY/'registered-class-review/../escape.json'))
|
||||
with self.assertRaisesRegex(ValueError,'legacy receipt path escapes HD root'):m.receipt_json(self.root,value)
|
||||
def test_exact_prefix_without_file_rejects(self):
|
||||
with self.assertRaisesRegex(ValueError,'legacy receipt path escapes HD root'):m.receipt_json(self.root,dict(self.receipt,path=str(LEGACY)))
|
||||
def test_legacy_leaf_symlink_inside_root_rejects(self):
|
||||
target=self.local.with_name('target.json');target.write_bytes(self.payload);self.local.unlink()
|
||||
try:self.local.symlink_to(target)
|
||||
except OSError as e:self.skipTest(str(e))
|
||||
with self.assertRaisesRegex(ValueError,'regular nonsymlink'):m.receipt_json(self.root,self.receipt)
|
||||
def test_legacy_parent_symlink_escape_rejects(self):
|
||||
out=self.work/'outside';out.mkdir();(out/'qa.json').write_bytes(self.payload)
|
||||
link=self.root/'linked'
|
||||
try:link.symlink_to(out,target_is_directory=True)
|
||||
except OSError as e:self.skipTest(str(e))
|
||||
value=dict(self.receipt,path=str(LEGACY/'linked/qa.json'))
|
||||
with self.assertRaisesRegex(ValueError,'path escapes HD root'):m.receipt_json(self.root,value)
|
||||
def test_legacy_parent_symlink_inside_root_rejects(self):
|
||||
actual=self.root/'real';actual.mkdir();(actual/'qa.json').write_bytes(self.payload)
|
||||
link=self.root/'linked'
|
||||
try:link.symlink_to(actual,target_is_directory=True)
|
||||
except OSError as e:self.skipTest(str(e))
|
||||
with self.assertRaisesRegex(ValueError,'regular nonsymlink'):m.receipt_json(self.root,dict(self.receipt,path=str(LEGACY/'linked/qa.json')))
|
||||
def test_directory_is_not_regular_receipt(self):
|
||||
self.local.unlink();self.local.mkdir()
|
||||
with self.assertRaisesRegex(ValueError,'regular nonsymlink'):m.receipt_json(self.root,self.receipt)
|
||||
def test_metadata_changes_during_fd_read_reject(self):
|
||||
original=os.fstat;calls=[]
|
||||
def changed(fd):
|
||||
s=original(fd);calls.append(fd)
|
||||
if len(calls)==2:return types.SimpleNamespace(st_dev=s.st_dev,st_ino=s.st_ino,st_mode=s.st_mode,st_size=s.st_size,st_mtime_ns=s.st_mtime_ns+1)
|
||||
return s
|
||||
with patch.object(m.os,'fstat',new=changed),self.assertRaisesRegex(ValueError,'changed during read'):m.receipt_json(self.root,self.receipt)
|
||||
def test_path_replacement_before_open_rejects_same_bytes(self):
|
||||
original=os.open
|
||||
def replaced(path,*a,**kw):
|
||||
other=self.local.with_suffix('.replacement');other.write_bytes(self.payload);os.replace(other,self.local)
|
||||
return original(path,*a,**kw)
|
||||
with patch.object(m.os,'open',new=replaced),self.assertRaisesRegex(ValueError,'changed during read'):m.receipt_json(self.root,self.receipt)
|
||||
def test_relative_control_and_legacy_errors_unchanged(self):
|
||||
good=dict(path=str(REL),sha256=self.digest)
|
||||
self.assertEqual(m.receipt_json(self.root,good),json.loads(self.payload))
|
||||
for bad in [{},dict(path=str(REL),sha256='BAD'),dict(path=str(REL),sha256='0'*64),dict(path='../escape.json',sha256=self.digest)]:
|
||||
with self.subTest(bad=bad):
|
||||
expected = ('missing hash-bound independent receipt' if not bad or bad.get('sha256') == 'BAD'
|
||||
else 'independent receipt changed' if bad.get('sha256') == '0'*64
|
||||
else 'path escapes HD root')
|
||||
with self.assertRaisesRegex(ValueError,expected):m.receipt_json(self.root,bad)
|
||||
def test_unrecognized_absolute_private_proof_is_not_remapped(self):
|
||||
original=self.work/'.reference/proof.json';original.parent.mkdir();original.write_bytes(self.payload)
|
||||
(self.root/'proof.json').write_bytes(b'{"wrong":"basename-fallback"}')
|
||||
ref=dict(path=str(original),sha256=self.digest)
|
||||
self.assertEqual(m.receipt_json(self.root,ref),json.loads(self.payload))
|
||||
def test_similar_prefix_is_not_remapped(self):
|
||||
absolute=Path(str(LEGACY)+'-spoof')/'qa.json';original=Path.read_bytes;seen=[]
|
||||
def logical(self):
|
||||
if self==absolute:seen.append(str(self));return self_case.payload
|
||||
return original(self)
|
||||
self_case=self
|
||||
with patch.object(Path,'read_bytes',new=logical):
|
||||
if absolute.is_absolute():
|
||||
self.assertEqual(m.receipt_json(self.root,dict(path=str(absolute),sha256=self.digest)),json.loads(self.payload))
|
||||
else:
|
||||
# Windows treats this unrecognized POSIX-looking path as rooted,
|
||||
# not absolute; preserve the preexisting containment rejection.
|
||||
with self.assertRaisesRegex(ValueError,'path escapes HD root'):
|
||||
m.receipt_json(self.root,dict(path=str(absolute),sha256=self.digest))
|
||||
self.assertEqual(seen,[str(absolute)] if absolute.is_absolute() else [])
|
||||
|
||||
if __name__=='__main__':unittest.main(verbosity=2)
|
||||
@@ -0,0 +1,259 @@
|
||||
"""Synthetic behavioral checks only: no Original catalog or generated-parent art."""
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
import hd_ui_cleanup as cleanup
|
||||
|
||||
|
||||
class UICleanup(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp=tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root=Path(self.temp.name)
|
||||
|
||||
def fixture(self,cells,raw=None,regions=None,classes=None,scale=4):
|
||||
h,w=len(cells),len(cells[0])
|
||||
source=bytearray(struct.pack('<5i',0,0,11,w,h))
|
||||
for y,row in enumerate(cells):
|
||||
for x,value in enumerate(row):
|
||||
if value<256:
|
||||
source+=struct.pack('<4i',x,y,y*w+x,1)+bytes([value])
|
||||
source+=struct.pack('<4i',0,0,-1,0)
|
||||
(self.root/'Source').write_bytes(source)
|
||||
palette=bytearray(768)
|
||||
for i,value in [(10,[30,20,10]),(11,[45,30,15]),(31,[1,1,1]),(146,[21,37,39])]:
|
||||
palette[3*i:3*i+3]=bytes(value)
|
||||
(self.root/'palette').write_bytes(palette)
|
||||
if raw is None:
|
||||
yy,xx=np.indices((h*scale,w*scale))
|
||||
raw=np.stack([80+xx*3,60+yy*2,40+xx+yy,np.full_like(xx,255)],axis=2).astype(np.uint8)
|
||||
Image.fromarray(raw).save(self.root/'raw.png')
|
||||
bind=lambda name:dict(path=name,sha256=cleanup.sha((self.root/name).read_bytes()))
|
||||
if regions is None:
|
||||
regions=[dict(name='whole synthetic UI',source_bounds=[0,0,w,h],raw_bounds=[0,0,raw.shape[1],raw.shape[0]])]
|
||||
if classes is None:
|
||||
classes=[dict(index=i,max_channel_delta=16) for i in sorted({x for row in cells for x in row if x<256})]
|
||||
return dict(format_version=1,label='Synthetic fixture only',asset='1010f',
|
||||
source=bind('Source'),palette=bind('palette'),parent_raw=bind('raw.png'),
|
||||
logical_canvas=[w,h],origin=[0,0],output_scale=scale,
|
||||
regions=regions,classes=classes)
|
||||
|
||||
def test_counter_gaps_and_missing_filled_slot_keep_exact_original_semantics(self):
|
||||
# Literal ring/counter plus a separate filled slot and one empty slot.
|
||||
cells=[[10,10,10,256,10,256],
|
||||
[10,256,10,256,256,256],
|
||||
[10,10,10,256,10,256]]
|
||||
yy,xx=np.indices((12,24))
|
||||
raw=np.stack([80+xx*3,60+yy*2,40+xx+yy,np.full_like(xx,255)],axis=2).astype(np.uint8)
|
||||
raw[0:4,16:20]=[255,0,255,0] # Genuine Source-filled slot absent from raw.
|
||||
recipe=self.fixture(cells,raw)
|
||||
rgba,report=cleanup.render(recipe,self.root)
|
||||
expected=np.zeros((12,24),dtype=bool)
|
||||
expected[0:4,0:12]=True;expected[4:8,0:4]=True;expected[4:8,8:12]=True
|
||||
expected[8:12,0:12]=True;expected[0:4,16:20]=True;expected[8:12,16:20]=True
|
||||
self.assertTrue(np.array_equal(rgba[:,:,3]==255,expected))
|
||||
self.assertTrue(np.all(rgba[~expected]==0),'raw ghost widgets and counters stay empty')
|
||||
self.assertTrue(np.all(rgba[0:4,16:20,:3]==[121,81,40]))
|
||||
self.assertEqual(report['restored_flat_Original_functional_pixels'],16)
|
||||
self.assertEqual(report['restored_Original_cells'],[[4,0]])
|
||||
self.assertEqual(report['direct_generated_support_fraction'],0.9)
|
||||
self.assertGreater(report['generated_fine_RGB_pixels'],0)
|
||||
|
||||
def test_two_rails_keep_canvas_edge_and_full_lower_right_endpoint(self):
|
||||
cells=[[146]*8,[256]*8,[256]*8,[256,31,31,31,31,31,31,256],[256]*8]
|
||||
yy,xx=np.indices((12,40))
|
||||
raw=np.stack([80+xx,70+yy*2,60+xx//2,np.full_like(xx,255)],axis=2).astype(np.uint8)
|
||||
recipe=self.fixture(cells,raw,
|
||||
regions=[dict(name='upper rail',source_bounds=[0,0,8,1],raw_bounds=[2,0,38,4]),
|
||||
dict(name='lower rail',source_bounds=[1,3,7,4],raw_bounds=[6,8,34,12])],
|
||||
classes=[dict(index=31,max_channel_delta=2),dict(index=146,max_channel_delta=16)])
|
||||
rgba,report=cleanup.render(recipe,self.root)
|
||||
self.assertTrue(np.all(rgba[0:4,:,3]==255),'top rail includes both canvas edges')
|
||||
self.assertTrue(np.all(rgba[12:16,4:28,3]==255),'last lower endpoint remains present')
|
||||
self.assertTrue(np.all(rgba[12:16,:4]==0))
|
||||
self.assertTrue(np.all(rgba[12:16,28:]==0))
|
||||
self.assertTrue(np.all(rgba[4:12]==0));self.assertTrue(np.all(rgba[16:]==0))
|
||||
self.assertLessEqual(int(rgba[12:16,4:28,:3].max()),6,'nearblack rail cannot become gray')
|
||||
self.assertEqual(report['painted_output_pixels'],224)
|
||||
self.assertEqual(report['runtime_crop'],None)
|
||||
|
||||
def test_distinct_light_dark_classes_retain_generated_chroma_and_spatial_detail(self):
|
||||
recipe=self.fixture([[10,10,11,11],[10,10,11,11]])
|
||||
rgba,report=cleanup.render(recipe,self.root)
|
||||
light=rgba[:,8:,:3];dark=rgba[:,:8,:3]
|
||||
self.assertGreater(float(light.mean()),float(dark.mean())+15)
|
||||
self.assertGreater(int(dark[0,-1,0]),int(dark[0,0,0]),'real horizontal red variation survives')
|
||||
self.assertEqual(int(dark[0,-1,1]),int(dark[0,0,1]),'no invented horizontal green variation')
|
||||
self.assertGreater(int(dark[-1,0,1]),int(dark[0,0,1]),'real vertical green variation survives')
|
||||
self.assertTrue(all(c['generated_supported_pixels']>0 for c in report['class_tone_controls']))
|
||||
self.assertTrue(report['raw_RGB_is_shifted_and_bounded_not_claimed_byte_exact'])
|
||||
|
||||
def test_hidden_transparent_rgb_is_not_used_to_paint_missing_strokes(self):
|
||||
yy,xx=np.indices((8,8));raw=np.stack([80+xx*3,60+yy*2,40+xx+yy,np.full_like(xx,255)],axis=2).astype(np.uint8)
|
||||
raw[:4,:4]=[0,255,255,0]
|
||||
recipe=self.fixture([[10,10],[10,10]],raw)
|
||||
rgba,report=cleanup.render(recipe,self.root)
|
||||
self.assertTrue(np.all(rgba[:4,:4,:3]==[121,81,40]))
|
||||
self.assertEqual(report['restored_flat_Original_functional_pixels'],16)
|
||||
|
||||
def test_flat_raw_cannot_borrow_source_class_contrast_as_generated_hd_credit(self):
|
||||
raw=np.full((8,16,4),[110,90,70,255],dtype=np.uint8)
|
||||
recipe=self.fixture([[10,10,11,11],[10,10,11,11]],raw)
|
||||
with self.assertRaisesRegex(ValueError,'no generated fine RGB'):
|
||||
cleanup.render(recipe,self.root)
|
||||
|
||||
def test_flat_visible_parent_feather_and_hidden_rgb_cannot_gain_detail_from_resampling(self):
|
||||
# Literal independent-review geometry:17x15 parent onto16x8 output.
|
||||
for style in ['feather boundary','hidden transparent RGB','constant semitransparency']:
|
||||
with self.subTest(style=style):
|
||||
raw=np.full((15,17,4),[110,90,70,255],dtype=np.uint8)
|
||||
if style=='feather boundary':raw[:3,:,3]=80;raw[:,:2,3]=0
|
||||
elif style=='hidden transparent RGB':raw[:3]=[0,255,255,0]
|
||||
else:raw[:,:,3]=150
|
||||
recipe=self.fixture([[10,10,11,11],[10,10,11,11]],raw)
|
||||
self.assertEqual(len(np.unique(raw[raw[:,:,3]>=128,:3],axis=0)),1)
|
||||
with patch.object(Image.Image,'resize',side_effect=AssertionError('flat material must fail before any resize')):
|
||||
with self.assertRaisesRegex(ValueError,'no pre-resample visible parent material variance'):
|
||||
cleanup.render(recipe,self.root)
|
||||
|
||||
def test_nearopaque252_253_textured_parent_is_visible_material_not_artificially_required255(self):
|
||||
for alpha in [252,253]:
|
||||
with self.subTest(alpha=alpha):
|
||||
yy,xx=np.indices((15,17))
|
||||
raw=np.stack([70+xx*3,60+yy*2,30+xx+yy,np.full_like(xx,alpha)],axis=2).astype(np.uint8)
|
||||
recipe=self.fixture([[10,10,11,11],[10,10,11,11]],raw)
|
||||
rgba,report=cleanup.render(recipe,self.root)
|
||||
self.assertTrue(np.all(rgba[:,:,3]==255),'Original controls output occupancy independently of parent opacity')
|
||||
self.assertGreater(report['pre_resample_visible_parent_material_witnesses'],0)
|
||||
self.assertGreater(report['registered_certified_parent_material_pixels'],0)
|
||||
self.assertGreater(report['generated_fine_RGB_pixels'],0)
|
||||
self.assertEqual(report['parent_material_visibility']['alpha_min'],128)
|
||||
|
||||
def test_visibility_boundary_128_is_eligible_and_127_is_excluded_before_resampling(self):
|
||||
yy,xx=np.indices((12,16))
|
||||
for alpha in [127,128]:
|
||||
raw=np.stack([70+xx*3,60+yy*2,30+xx+yy,np.full_like(xx,alpha)],axis=2).astype(np.uint8)
|
||||
recipe=self.fixture([[10,10,11,11],[10,10,11,11]],raw)
|
||||
if alpha==127:
|
||||
with patch.object(Image.Image,'resize',side_effect=AssertionError('low-alpha material must fail before any resize')):
|
||||
with self.assertRaisesRegex(ValueError,'no pre-resample visible parent material variance'):
|
||||
cleanup.render(recipe,self.root)
|
||||
else:
|
||||
rgba,report=cleanup.render(recipe,self.root)
|
||||
self.assertGreater(report['pre_resample_visible_parent_material_witnesses'],0)
|
||||
|
||||
def test_rgb_variance_only_below_visible_alpha_or_across_classes_cannot_certify_parent(self):
|
||||
classes=np.full((7,7),10,dtype=np.uint16)
|
||||
raw=np.full((7,7,4),[110,90,70,253],dtype=np.uint8)
|
||||
raw[3,4]=[210,120,90,127]
|
||||
self.assertFalse(cleanup.parent_material_witnesses(raw,classes).any())
|
||||
raw[3,4,3]=128
|
||||
self.assertTrue(cleanup.parent_material_witnesses(raw,classes)[3,3])
|
||||
classes[3,4]=11
|
||||
self.assertFalse(cleanup.parent_material_witnesses(raw,classes)[3,3])
|
||||
|
||||
def test_material_only_in_source_empty_gaps_cannot_authorize_flat_painted_glyphs(self):
|
||||
raw=np.full((16,24,4),[110,90,70,255],dtype=np.uint8)
|
||||
yy,xx=np.indices((16,8))
|
||||
raw[:,8:16,:3]=np.stack([100+xx*3,90+yy*2,70+xx+yy],axis=2)
|
||||
recipe=self.fixture([[10,256,10],[10,256,10]],raw)
|
||||
with self.assertRaisesRegex(ValueError,'no pre-resample visible parent material variance'):
|
||||
cleanup.render(recipe,self.root)
|
||||
|
||||
def test_flat_palette_class_boundaries_do_not_count_as_material_detail(self):
|
||||
raw=np.full((12,24,4),[110,90,70,255],dtype=np.uint8)
|
||||
raw[:,12:,:3]=[150,100,80]
|
||||
recipe=self.fixture([[10,10,11,11],[10,10,11,11]],raw)
|
||||
with self.assertRaisesRegex(ValueError,'no pre-resample visible parent material variance'):
|
||||
cleanup.render(recipe,self.root)
|
||||
|
||||
def test_valid_uncompressed_kind1_source_keeps_byte_palette_values_and_renders(self):
|
||||
recipe=self.fixture([[10,10],[10,10]])
|
||||
source=struct.pack('<5i',1,1,0,2,2)+bytes([10,10,10,10])
|
||||
(self.root/'Source').write_bytes(source)
|
||||
recipe['source']['sha256']=cleanup.sha(source)
|
||||
rgba,report=cleanup.render(recipe,self.root)
|
||||
self.assertEqual(rgba.shape,(8,8,4))
|
||||
self.assertTrue(np.all(rgba[:,:,3]==255))
|
||||
self.assertEqual(report['Original_painted_cells'],4)
|
||||
self.assertGreater(report['pre_resample_visible_parent_material_witnesses'],0)
|
||||
self.assertGreater(report['registered_certified_parent_material_pixels'],0)
|
||||
self.assertGreater(report['generated_fine_RGB_pixels'],0)
|
||||
|
||||
def test_majority_missing_support_is_rejected(self):
|
||||
yy,xx=np.indices((8,8));raw=np.stack([80+xx*3,60+yy*2,40+xx+yy,np.zeros_like(xx)],axis=2).astype(np.uint8)
|
||||
raw[:4,:4,3]=255
|
||||
recipe=self.fixture([[10,10],[10,10]],raw)
|
||||
with self.assertRaisesRegex(ValueError,'most generated UI support'):
|
||||
cleanup.render(recipe,self.root)
|
||||
|
||||
def test_overlapping_or_incomplete_registrations_fail_closed(self):
|
||||
recipe=self.fixture([[10,10],[10,10]])
|
||||
duplicate=copy.deepcopy(recipe);duplicate['regions'].append(copy.deepcopy(duplicate['regions'][0]))
|
||||
with self.assertRaisesRegex(ValueError,'overlap'):cleanup.render(duplicate,self.root)
|
||||
missing=copy.deepcopy(recipe);missing['regions'][0]['source_bounds']=[0,0,1,2]
|
||||
with self.assertRaisesRegex(ValueError,'do not cover'):cleanup.render(missing,self.root)
|
||||
|
||||
def test_identity_canvas_origin_classes_and_numeric_types_fail_closed(self):
|
||||
recipe=self.fixture([[10,10],[10,10]])
|
||||
mutations=[lambda r:r['source'].update(sha256='0'*64),
|
||||
lambda r:r.update(logical_canvas=[3,2]),
|
||||
lambda r:r.update(origin=[1,0]),
|
||||
lambda r:r.update(origin=[False,0]),
|
||||
lambda r:r.update(output_scale=True),
|
||||
lambda r:r['classes'][0].update(index=True),
|
||||
lambda r:r['classes'][0].update(max_channel_delta=float('nan')),
|
||||
lambda r:r['classes'][0].update(max_channel_delta=33),
|
||||
lambda r:r['classes'].append(dict(index=11,max_channel_delta=16)),
|
||||
lambda r:r['regions'][0].update(raw_bounds=[0,0,9,8]),
|
||||
lambda r:r.update(unrecognized_policy=True)]
|
||||
for mutate in mutations:
|
||||
with self.subTest(mutate=mutate):
|
||||
bad=copy.deepcopy(recipe);mutate(bad)
|
||||
with self.assertRaises(ValueError):cleanup.render(bad,self.root)
|
||||
|
||||
def test_empty_or_reserved_original_is_not_silently_repaired(self):
|
||||
empty=self.fixture([[256,256],[256,256]])
|
||||
with self.assertRaisesRegex(ValueError,'no painted UI'):cleanup.render(empty,self.root)
|
||||
reserved=self.fixture([[240,10],[10,10]])
|
||||
with self.assertRaisesRegex(ValueError,'ordinary Original palette'):cleanup.render(reserved,self.root)
|
||||
|
||||
def test_resource_bounds_stop_before_output_allocation_or_decoding_large_inputs(self):
|
||||
recipe=self.fixture([[10,10],[10,10]])
|
||||
with patch.object(cleanup,'MAX_PIXELS',63):
|
||||
with self.assertRaisesRegex(ValueError,'derived image exceeds pixel ceiling'):
|
||||
cleanup.render(recipe,self.root)
|
||||
with self.assertRaisesRegex(ValueError,'input byte limit exceeded'):
|
||||
cleanup.checked_input(recipe['parent_raw'],self.root,byte_limit=1)
|
||||
|
||||
def test_repeatable_new_output_full_lineage_and_inputs_unchanged(self):
|
||||
recipe=self.fixture([[10,10,11],[10,256,11]])
|
||||
p=self.root/'recipe.json';p.write_text(json.dumps(recipe,sort_keys=True))
|
||||
before={n:(self.root/n).read_bytes() for n in ['Source','palette','raw.png']}
|
||||
reports=[cleanup.run_recipe(p,self.root/f'output{i}') for i in range(2)]
|
||||
images=[(self.root/f'output{i}/1010f-derived.png').read_bytes() for i in range(2)]
|
||||
self.assertEqual(images[0],images[1]);self.assertEqual(reports[0],reports[1])
|
||||
execution=reports[0]['execution']
|
||||
self.assertEqual(execution['status'],'success')
|
||||
self.assertEqual(execution['method'],cleanup.METHOD)
|
||||
self.assertEqual(execution['source_sha256'],{'1010f':recipe['source']['sha256']})
|
||||
self.assertEqual(execution['parent_raw_sha256'],[recipe['parent_raw']['sha256']])
|
||||
self.assertEqual(execution['recipe_sha256'],cleanup.sha(p.read_bytes()))
|
||||
self.assertEqual(execution['tool_sha256'],cleanup.sha(Path(cleanup.__file__).read_bytes()))
|
||||
self.assertEqual(execution['output_sha256'],{'1010f':cleanup.sha(images[0])})
|
||||
self.assertEqual(reports[0]['Source_decoder_sha256'],cleanup.sha(Path(cleanup.hd_frame_policy.__file__).read_bytes()))
|
||||
self.assertEqual(before,{n:(self.root/n).read_bytes() for n in before})
|
||||
with self.assertRaisesRegex(ValueError,'output exists'):
|
||||
cleanup.run_recipe(p,self.root/'output0')
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user