Record bounded wrapped-label trials and independent conservation review
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
from pathlib import Path
|
||||
import copy, hashlib, json, sys
|
||||
|
||||
assert len(sys.argv) == 4
|
||||
original, saved, output = map(Path, sys.argv[1:])
|
||||
assert not output.exists()
|
||||
sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
assert sha(original) == '9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8'
|
||||
before, after = [json.loads(p.read_bytes()) for p in [original, saved]]
|
||||
diagram = '00000000-0038-4000-8000-000000000001'
|
||||
edge = '63009b5f-539e-5914-b630-ec6b7fefb4d5'
|
||||
old_routes = before['diagrams'][diagram]['routes']
|
||||
routes = after['diagrams'][diagram]['routes']
|
||||
assert not any(r['edge'] == edge for r in old_routes)
|
||||
changed = [r for r in routes if r['edge'] == edge]
|
||||
assert len(changed) == 1
|
||||
route = changed[0]
|
||||
assert route['waypoints'] == []
|
||||
assert route.get('source_attachment') is None and route.get('target_attachment') is None
|
||||
assert set(route) <= {'edge','waypoints','label','source_attachment','target_attachment'}
|
||||
label = route['label']
|
||||
assert label['fraction'] == 0.55
|
||||
assert set(label) == {'fraction','offset'} and set(label['offset']) == {'x','y'}
|
||||
# Declared native delta30/20px at rounded Fit52%, not a geometry-derived oracle.
|
||||
assert 0.515 <= 0.525
|
||||
assert 62.591065 + 30/0.525 - 1 <= label['offset']['x'] <= 62.591065 + 30/0.515 + 1
|
||||
assert 20/0.525 - 1 <= label['offset']['y'] <= 20/0.515 + 1
|
||||
restored = copy.deepcopy(after)
|
||||
restored['diagrams'][diagram]['routes'] = [r for r in routes if r['edge'] != edge]
|
||||
assert restored == before, 'Complete Project must differ only by declared target route label'
|
||||
receipt = {'status':'PASS', 'original_sha256':sha(original), 'saved_sha256':sha(saved),
|
||||
'saved_bytes':saved.stat().st_size, 'declared_edit':route,
|
||||
'complete_project_equal_after_removing_only_declared_route':True,
|
||||
'scope':'F30px right/20px down at displayed52%; no extra formatting field persisted'}
|
||||
with output.open('x',encoding='utf-8') as f: json.dump(receipt,f,indent=2);f.write('\n')
|
||||
print(json.dumps(receipt,indent=2))
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
"""Read-only native SVG conservation check. Python 3 stdlib; never overwrite a receipt.
|
||||
|
||||
Usage: check-wrapped-svg.py BASELINE.svg CANDIDATE.svg E|F NEW-OUTPUT.json
|
||||
Both native export directories must retain their original eight-card.json input.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
TARGET = "63009b5f-539e-5914-b630-ec6b7fefb4d5"
|
||||
NAME = "Technician service conversation"
|
||||
FIXTURE_SHA = "9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8"
|
||||
NS = "{http://www.w3.org/2000/svg}"
|
||||
# Independent expected logical metrics printed by the pinned Rust/Iced tests at
|
||||
# ebb6a9980b11c4d4df2f7e413c6b135a337f6f6d. Never infer these from candidate SVG.
|
||||
# Lines: canonical display text, left x, top y, shaped baseline y, glyph advance.
|
||||
METRICS = {
|
||||
"E": {"rect": [1102.4126, 254.5, 119.174805, 40.0], "lines": [
|
||||
["Technician service", 1110.4126, 258.5, 271.8828, 103.174805],
|
||||
["conversation", 1125.4089, 274.5, 287.8828, 73.18213],
|
||||
]},
|
||||
"F": {"rect": [1180.0, 226.2, 89.18213, 56.0], "lines": [
|
||||
["Technician", 1194.2905, 230.2, 243.58281, 60.601074],
|
||||
["service", 1205.0847, 246.2, 259.58282, 39.012695],
|
||||
["conversation", 1188.0, 262.2, 275.58282, 73.18213],
|
||||
]},
|
||||
}
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
def digest(data):
|
||||
return {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
|
||||
|
||||
def close(actual, expected, label):
|
||||
value = float(actual)
|
||||
require(math.isfinite(value) and abs(value - expected) <= 0.0002,
|
||||
f"{label}: expected {expected}, got {actual}")
|
||||
|
||||
def element_attrs(element, tag, fixed, numeric):
|
||||
require(element.tag == NS + tag, f"Expected {tag}, got {element.tag}")
|
||||
require(set(element.attrib) == set(fixed) | set(numeric), f"Unexpected attributes on {tag}")
|
||||
for key, value in fixed.items():
|
||||
require(element.get(key) == value, f"{tag}.{key} changed")
|
||||
for key, value in numeric.items():
|
||||
close(element.get(key), value, tag + "." + key)
|
||||
|
||||
def strip_immediate_label(raw, candidate):
|
||||
edge = re.compile(rb'<path\b[^>]*\bdata-edge="' + TARGET.encode() + rb'"[^>]*>.*?</path>', re.DOTALL)
|
||||
matches = list(edge.finditer(raw))
|
||||
require(len(matches) == 1, "Target edge must occur exactly once")
|
||||
start = matches[0].end()
|
||||
pattern = (rb'<g data-label-experiment="wrapped">.*?</g>' if candidate
|
||||
else rb'<rect\b[^>]*/><text\b[^>]*>[^<]*</text>')
|
||||
label = re.compile(pattern, re.DOTALL).match(raw, start)
|
||||
require(label is not None, "Target label must immediately follow its edge")
|
||||
return raw[:start] + raw[label.end():], raw[start:label.end()]
|
||||
|
||||
def check(args, report):
|
||||
baseline, candidate = Path(args.baseline), Path(args.candidate)
|
||||
before, after = baseline.read_bytes(), candidate.read_bytes()
|
||||
report["inputs"] = {"baseline_svg": {"path": str(baseline), **digest(before)},
|
||||
"candidate_svg": {"path": str(candidate), **digest(after)}}
|
||||
fixture_bytes = []
|
||||
for key, svg in [("baseline_project", baseline), ("candidate_project", candidate)]:
|
||||
path = svg.parent / "eight-card.json"
|
||||
data = path.read_bytes()
|
||||
report["inputs"][key] = {"path": str(path), **digest(data)}
|
||||
require(digest(data)["sha256"] == FIXTURE_SHA, key + " differs from pinned full Project")
|
||||
fixture_bytes.append(data)
|
||||
require(fixture_bytes[0] == fixture_bytes[1], "Full input Projects differ")
|
||||
roots = [ET.fromstring(data) for data in [before, after]]
|
||||
for root in roots:
|
||||
require(root.tag == NS + "svg", "Expected SVG root")
|
||||
require(roots[0].attrib == roots[1].attrib, "Root attributes/viewBox changed")
|
||||
require(roots[0].get("viewBox") == "24 -12 1566 874", "Pinned full scene viewBox changed")
|
||||
edges = [[e for e in root.iter() if e.get("data-edge") is not None] for root in roots]
|
||||
for found in edges:
|
||||
require(len(found) == 12 and len({e.get("data-edge") for e in found}) == 12,
|
||||
"Expected all 12 distinct edge UUIDs")
|
||||
require([ET.tostring(e) for e in edges[0]] == [ET.tostring(e) for e in edges[1]],
|
||||
"Edge order, UUID, path, orientation/marker, drawing attributes or title changed")
|
||||
for root in roots:
|
||||
children = list(root)
|
||||
target_index = next(i for i, e in enumerate(children) if e.get("data-edge") == TARGET)
|
||||
require(children[target_index].find(NS + "title").text == NAME, "Canonical edge title changed")
|
||||
bchildren = list(roots[0])
|
||||
bi = next(i for i, e in enumerate(bchildren) if e.get("data-edge") == TARGET)
|
||||
element_attrs(bchildren[bi + 1], "rect", {"fill": "white"},
|
||||
{"x": 1180., "y": 262.5, "width": 233., "height": 24.})
|
||||
element_attrs(bchildren[bi + 2], "text", {"text-anchor": "middle", "dominant-baseline": "central",
|
||||
"font-family": "sans-serif", "fill": "#637487"}, {"x": 1296.5, "y": 274.5, "font-size": 13.})
|
||||
require(bchildren[bi + 2].text == NAME, "Baseline target is not canonical one-line label")
|
||||
groups = [e for e in roots[1].iter() if e.get("data-label-experiment") is not None]
|
||||
require(len(groups) == 1, "Exactly one experimental label group is allowed")
|
||||
group = groups[0]
|
||||
element_attrs(group, "g", {"data-label-experiment": "wrapped"}, {})
|
||||
cchildren = list(roots[1])
|
||||
ci = next(i for i, e in enumerate(cchildren) if e.get("data-edge") == TARGET)
|
||||
require(cchildren[ci + 1] is group, "Wrapped group belongs immediately to target edge")
|
||||
expected = METRICS[args.profile]
|
||||
parts = list(group)
|
||||
require(len(parts) == 2 + len(expected["lines"]), "Unexpected wrapped group contents")
|
||||
element_attrs(parts[0], "title", {}, {})
|
||||
require(parts[0].text == NAME and not list(parts[0]), "Canonical group title changed")
|
||||
element_attrs(parts[1], "rect", {"fill": "white"}, dict(zip(["x", "y", "width", "height"], expected["rect"])))
|
||||
for actual, (name, x, top, baseline_y, width) in zip(parts[2:], expected["lines"]):
|
||||
element_attrs(actual, "text", {"font-family": "Segoe UI", "fill": "#637487"},
|
||||
{"x": x, "y": baseline_y, "font-size": 13.})
|
||||
require(actual.text == name and not list(actual), "Ordered complete display line changed")
|
||||
require(" ".join(e.text for e in parts[2:]) == NAME, "Wrapping altered the complete canonical text")
|
||||
close(expected["rect"][2], max(line[4] for line in expected["lines"]) + 16., "Expected advance plus padding")
|
||||
close(expected["rect"][3], 8. + 16. * len(expected["lines"]), "Expected line height plus padding")
|
||||
bremaining, bremoved = strip_immediate_label(before, False)
|
||||
cremaining, cremoved = strip_immediate_label(after, True)
|
||||
require(bremaining == cremaining,
|
||||
"Raw SVG bytes outside ONLY target's immediate label presentation changed")
|
||||
report.update({
|
||||
"status": "PASS", "full_project_bytes_equal": True,
|
||||
"edge_uuids": [e.get("data-edge") for e in edges[0]],
|
||||
"all_12_edge_records_exact": True, "root_and_viewBox_exact": True,
|
||||
"all_remaining_svg_bytes_exact": True, "remaining_svg": digest(bremaining),
|
||||
"removed_baseline_presentation": digest(bremoved), "removed_candidate_presentation": digest(cremoved),
|
||||
"expected_logical_metrics": expected, "numeric_absolute_tolerance": 0.0002,
|
||||
"conservation_scope": "All edge/path/orientation/drawing, card, port and unrelated label bytes preserved; only target immediate label presentation removed for comparison.",
|
||||
"limits": "Logical SVG conservation and declared typography coordinates only. No browser/font availability, glyph/raster parity, screenshot reading, native input-event history, save/reopen or usability claim.",
|
||||
})
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("baseline")
|
||||
parser.add_argument("candidate")
|
||||
parser.add_argument("profile", choices=["E", "F"])
|
||||
parser.add_argument("output")
|
||||
args = parser.parse_args()
|
||||
report = {"status": "FAIL", "profile": args.profile,
|
||||
"script": digest(Path(__file__).read_bytes()),
|
||||
"metrics_source": "ebb6a9980b11c4d4df2f7e413c6b135a337f6f6d Rust/Iced test stdout; wrapped-label-regression-first.log"}
|
||||
# Exclusive creation precedes reads; a failed prior run is never overwritten.
|
||||
with Path(args.output).open("x", encoding="utf-8") as output:
|
||||
try:
|
||||
check(args, report)
|
||||
except Exception as error:
|
||||
report["failure"] = {"type": type(error).__name__, "message": str(error)}
|
||||
json.dump(report, output, indent=2)
|
||||
output.write("\n")
|
||||
print(json.dumps(report, indent=2))
|
||||
return 0 if report["status"] == "PASS" else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,160 @@
|
||||
"""Read-only native SVG conservation check. Python 3 stdlib; never overwrite a receipt.
|
||||
|
||||
Usage: check-wrapped-svg.py BASELINE.svg CANDIDATE.svg E|F NEW-OUTPUT.json
|
||||
Both native export directories must retain their original eight-card.json input.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
TARGET = "63009b5f-539e-5914-b630-ec6b7fefb4d5"
|
||||
NAME = "Technician service conversation"
|
||||
FIXTURE_SHA = "9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8"
|
||||
NS = "{http://www.w3.org/2000/svg}"
|
||||
# Independent expected logical metrics printed by the pinned Rust/Iced tests at
|
||||
# ebb6a9980b11c4d4df2f7e413c6b135a337f6f6d. Never infer these from candidate SVG.
|
||||
# Lines: canonical display text, left x, top y, shaped baseline y, glyph advance.
|
||||
METRICS = {
|
||||
"E": {"rect": [1102.4126, 254.5, 119.174805, 40.0], "lines": [
|
||||
["Technician service", 1110.4126, 258.5, 271.8828, 103.174805],
|
||||
["conversation", 1125.4089, 274.5, 287.8828, 73.18213],
|
||||
]},
|
||||
"F": {"rect": [1180.0, 226.2, 89.18213, 56.0], "lines": [
|
||||
["Technician", 1194.2905, 230.2, 243.58281, 60.601074],
|
||||
["service", 1205.0847, 246.2, 259.58282, 39.012695],
|
||||
["conversation", 1188.0, 262.2, 275.58282, 73.18213],
|
||||
]},
|
||||
}
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
def digest(data):
|
||||
return {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
|
||||
|
||||
def close(actual, expected, label):
|
||||
value = float(actual)
|
||||
require(math.isfinite(value) and abs(value - expected) <= 0.0002,
|
||||
f"{label}: expected {expected}, got {actual}")
|
||||
|
||||
def element_attrs(element, tag, fixed, numeric):
|
||||
require(element.tag == NS + tag, f"Expected {tag}, got {element.tag}")
|
||||
require(set(element.attrib) == set(fixed) | set(numeric), f"Unexpected attributes on {tag}")
|
||||
for key, value in fixed.items():
|
||||
require(element.get(key) == value, f"{tag}.{key} changed")
|
||||
for key, value in numeric.items():
|
||||
close(element.get(key), value, tag + "." + key)
|
||||
|
||||
def strip_immediate_label(raw, candidate):
|
||||
edge = re.compile(rb'<path\b[^>]*\bdata-edge="' + TARGET.encode() + rb'"[^>]*>.*?</path>', re.DOTALL)
|
||||
matches = list(edge.finditer(raw))
|
||||
require(len(matches) == 1, "Target edge must occur exactly once")
|
||||
start = matches[0].end()
|
||||
pattern = (rb'<g data-label-experiment="wrapped">.*?</g>' if candidate
|
||||
else rb'<rect\b[^>]*/><text\b[^>]*>[^<]*</text>')
|
||||
label = re.compile(pattern, re.DOTALL).match(raw, start)
|
||||
require(label is not None, "Target label must immediately follow its edge")
|
||||
return raw[:start] + raw[label.end():], raw[start:label.end()]
|
||||
|
||||
def check(args, report):
|
||||
baseline, candidate = Path(args.baseline), Path(args.candidate)
|
||||
before, after = baseline.read_bytes(), candidate.read_bytes()
|
||||
report["inputs"] = {"baseline_svg": {"path": str(baseline), **digest(before)},
|
||||
"candidate_svg": {"path": str(candidate), **digest(after)}}
|
||||
fixture_bytes = []
|
||||
for key, svg in [("baseline_project", baseline), ("candidate_project", candidate)]:
|
||||
path = svg.parent / "eight-card.json"
|
||||
data = path.read_bytes()
|
||||
report["inputs"][key] = {"path": str(path), **digest(data)}
|
||||
require(digest(data)["sha256"] == FIXTURE_SHA, key + " differs from pinned full Project")
|
||||
fixture_bytes.append(data)
|
||||
require(fixture_bytes[0] == fixture_bytes[1], "Full input Projects differ")
|
||||
roots = [ET.fromstring(data) for data in [before, after]]
|
||||
for root in roots:
|
||||
require(root.tag == NS + "svg", "Expected SVG root")
|
||||
require(roots[0].attrib == roots[1].attrib, "Root attributes/viewBox changed")
|
||||
require(roots[0].get("viewBox") == "24 -12 1566 874", "Pinned full scene viewBox changed")
|
||||
edges = [[e for e in root.iter() if e.get("data-edge") is not None] for root in roots]
|
||||
for found in edges:
|
||||
require(len(found) == 12 and len({e.get("data-edge") for e in found}) == 12,
|
||||
"Expected all 12 distinct edge UUIDs")
|
||||
require([ET.tostring(e) for e in edges[0]] == [ET.tostring(e) for e in edges[1]],
|
||||
"Edge order, UUID, path, orientation/marker, drawing attributes or title changed")
|
||||
for root in roots:
|
||||
children = list(root)
|
||||
target_index = next(i for i, e in enumerate(children) if e.get("data-edge") == TARGET)
|
||||
require(children[target_index].find(NS + "title").text == NAME, "Canonical edge title changed")
|
||||
bchildren = list(roots[0])
|
||||
bi = next(i for i, e in enumerate(bchildren) if e.get("data-edge") == TARGET)
|
||||
element_attrs(bchildren[bi + 1], "rect", {"fill": "white"},
|
||||
{"x": 1180., "y": 262.5, "width": 233., "height": 24.})
|
||||
element_attrs(bchildren[bi + 2], "text", {"text-anchor": "middle", "dominant-baseline": "central",
|
||||
"font-family": "sans-serif", "fill": "#637487"}, {"x": 1296.5, "y": 274.5, "font-size": 13.})
|
||||
require(bchildren[bi + 2].text == NAME, "Baseline target is not canonical one-line label")
|
||||
groups = [e for e in roots[1].iter() if e.get("data-label-experiment") is not None]
|
||||
require(len(groups) == 1, "Exactly one experimental label group is allowed")
|
||||
group = groups[0]
|
||||
element_attrs(group, "g", {"data-label-experiment": "wrapped"}, {})
|
||||
cchildren = list(roots[1])
|
||||
ci = next(i for i, e in enumerate(cchildren) if e.get("data-edge") == TARGET)
|
||||
require(cchildren[ci + 1] is group, "Wrapped group belongs immediately to target edge")
|
||||
expected = METRICS[args.profile]
|
||||
parts = list(group)
|
||||
require(len(parts) == 2 + len(expected["lines"]), "Unexpected wrapped group contents")
|
||||
require(not group.text and all(not list(part) and not part.tail for part in parts),
|
||||
"Wrapped presentation contains unexpected nested elements or stray text")
|
||||
element_attrs(parts[0], "title", {}, {})
|
||||
require(parts[0].text == NAME and not list(parts[0]), "Canonical group title changed")
|
||||
element_attrs(parts[1], "rect", {"fill": "white"}, dict(zip(["x", "y", "width", "height"], expected["rect"])))
|
||||
require(not parts[1].text, "Backing rectangle contains unexpected text")
|
||||
for actual, (name, x, top, baseline_y, width) in zip(parts[2:], expected["lines"]):
|
||||
element_attrs(actual, "text", {"font-family": "Segoe UI", "fill": "#637487"},
|
||||
{"x": x, "y": baseline_y, "font-size": 13.})
|
||||
require(actual.text == name and not list(actual), "Ordered complete display line changed")
|
||||
require(" ".join(e.text for e in parts[2:]) == NAME, "Wrapping altered the complete canonical text")
|
||||
close(expected["rect"][2], max(line[4] for line in expected["lines"]) + 16., "Expected advance plus padding")
|
||||
close(expected["rect"][3], 8. + 16. * len(expected["lines"]), "Expected line height plus padding")
|
||||
bremaining, bremoved = strip_immediate_label(before, False)
|
||||
cremaining, cremoved = strip_immediate_label(after, True)
|
||||
require(bremaining == cremaining,
|
||||
"Raw SVG bytes outside ONLY target's immediate label presentation changed")
|
||||
report.update({
|
||||
"status": "PASS", "full_project_bytes_equal": True,
|
||||
"edge_uuids": [e.get("data-edge") for e in edges[0]],
|
||||
"all_12_edge_records_exact": True, "root_and_viewBox_exact": True,
|
||||
"all_remaining_svg_bytes_exact": True, "remaining_svg": digest(bremaining),
|
||||
"removed_baseline_presentation": digest(bremoved), "removed_candidate_presentation": digest(cremoved),
|
||||
"expected_logical_metrics": expected, "numeric_absolute_tolerance": 0.0002,
|
||||
"conservation_scope": "All edge/path/orientation/drawing, card, port and unrelated label bytes preserved; only target immediate label presentation removed for comparison.",
|
||||
"limits": "Logical SVG conservation and declared typography coordinates only. No browser/font availability, glyph/raster parity, screenshot reading, native input-event history, save/reopen or usability claim.",
|
||||
})
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("baseline")
|
||||
parser.add_argument("candidate")
|
||||
parser.add_argument("profile", choices=["E", "F"])
|
||||
parser.add_argument("output")
|
||||
args = parser.parse_args()
|
||||
report = {"status": "FAIL", "profile": args.profile,
|
||||
"script": digest(Path(__file__).read_bytes()),
|
||||
"metrics_source": "ebb6a9980b11c4d4df2f7e413c6b135a337f6f6d Rust/Iced test stdout; wrapped-label-regression-first.log"}
|
||||
# Exclusive creation precedes reads; a failed prior run is never overwritten.
|
||||
with Path(args.output).open("x", encoding="utf-8") as output:
|
||||
try:
|
||||
check(args, report)
|
||||
except Exception as error:
|
||||
report["failure"] = {"type": type(error).__name__, "message": str(error)}
|
||||
json.dump(report, output, indent=2)
|
||||
output.write("\n")
|
||||
print(json.dumps(report, indent=2))
|
||||
return 0 if report["status"] == "PASS" else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,36 @@
|
||||
from pathlib import Path
|
||||
import datetime, hashlib, json, os, subprocess, sys
|
||||
|
||||
assert len(sys.argv) == 2 and sys.argv[1] in ['E', 'F']
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
run = root/('.local/native-wrapped-'+sys.argv[1])
|
||||
record = json.loads((run/'baseline.json').read_text(encoding='utf-8'))
|
||||
assert record['label_profile'] == sys.argv[1]
|
||||
sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
for relative, entry in record['files'].items():
|
||||
assert sha(run/relative) == entry['sha256']
|
||||
system = os.environ['SystemRoot']
|
||||
env = os.environ.copy()
|
||||
env['PATH'] = str(Path(system)/'System32')+';'+system
|
||||
for key in list(env):
|
||||
if key.startswith(('CARGO_', 'RUST', 'LLVM', 'WGPU', 'ARCADIA_')):
|
||||
del env[key]
|
||||
env['ARCADIA_LABEL_EXPERIMENT'] = sys.argv[1]
|
||||
receipt = {'source': record['source'], 'start': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
'label_profile': sys.argv[1], 'binary_sha256': record['files']['bin/arcadia-studio.exe']['sha256'],
|
||||
'runtime_sha256': record['files']['bin/libunwind.dll']['sha256'],
|
||||
'fixture_sha256': record['files']['working/eight-card.json']['sha256'],
|
||||
'cwd': str(run/'working'), 'path': env['PATH']}
|
||||
with (run/'stdout.log').open('xb') as out, (run/'stderr.log').open('xb') as err:
|
||||
child = subprocess.Popen([str(run/'bin/arcadia-studio.exe')], cwd=run/'working', env=env,
|
||||
stdout=out, stderr=err, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
receipt['pid'] = child.pid
|
||||
(run/'started.json').write_text(json.dumps(receipt, indent=2)+'\n', encoding='utf-8')
|
||||
print(json.dumps(receipt, indent=2), flush=True)
|
||||
code = child.wait()
|
||||
receipt.update({'exit_code': code, 'finished': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
'stdout_bytes': (run/'stdout.log').stat().st_size, 'stderr_bytes': (run/'stderr.log').stat().st_size,
|
||||
'all_pinned_files_unchanged': all(sha(run/r)==v['sha256'] for r,v in record['files'].items())})
|
||||
(run/'finished.json').write_text(json.dumps(receipt, indent=2)+'\n', encoding='utf-8')
|
||||
print(json.dumps(receipt, indent=2), flush=True)
|
||||
assert code == 0 and receipt['stdout_bytes'] == receipt['stderr_bytes'] == 0 and receipt['all_pinned_files_unchanged']
|
||||
@@ -0,0 +1,50 @@
|
||||
from pathlib import Path
|
||||
import datetime, hashlib, json, os, shutil, subprocess, sys
|
||||
|
||||
assert len(sys.argv) == 2 and sys.argv[1] in ['F', 'Baseline']
|
||||
profile = sys.argv[1]
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
donor = root / '.local/native-wrapped-F'
|
||||
run = root / ('.local/native-wrapped-reopen-' + profile)
|
||||
assert not run.exists(), 'Never overwrite a previous native run'
|
||||
run.mkdir()
|
||||
(run / 'bin').mkdir()
|
||||
(run / 'working').mkdir()
|
||||
(run / 'captures').mkdir()
|
||||
sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
pins = {
|
||||
'bin/arcadia-studio.exe': '2b6d76d744d7422fc826f7f7ca0009b296a0efb751fe0b3bbc1516b8a624c201',
|
||||
'bin/libunwind.dll': 'f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4',
|
||||
'working/moved-F.json': '0ad4015d06cf513cd8f9cfe3a155c07590ba09585002277d6eed7a78dd3b6a57',
|
||||
}
|
||||
for relative, expected in pins.items():
|
||||
source = donor / relative
|
||||
assert sha(source) == expected
|
||||
shutil.copyfile(source, run / relative)
|
||||
assert sha(run / relative) == expected
|
||||
system = os.environ['SystemRoot']
|
||||
env = os.environ.copy()
|
||||
env['PATH'] = str(Path(system) / 'System32') + ';' + system
|
||||
for key in list(env):
|
||||
if key.startswith(('CARGO_', 'RUST', 'LLVM', 'WGPU', 'ARCADIA_')):
|
||||
del env[key]
|
||||
if profile == 'F':
|
||||
env['ARCADIA_LABEL_EXPERIMENT'] = 'F'
|
||||
receipt = {'source': 'd0896ced75fea73bcda6bd14f259a34503840704',
|
||||
'profile': profile, 'start': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
'files': pins, 'cwd': str(run / 'working'), 'path': env['PATH'],
|
||||
'external_profile': env.get('ARCADIA_LABEL_EXPERIMENT')}
|
||||
with (run / 'stdout.log').open('xb') as out, (run / 'stderr.log').open('xb') as err:
|
||||
child = subprocess.Popen([str(run / 'bin/arcadia-studio.exe')], cwd=run / 'working', env=env,
|
||||
stdout=out, stderr=err, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
receipt['pid'] = child.pid
|
||||
(run / 'started.json').write_text(json.dumps(receipt, indent=2) + '\n', encoding='utf-8')
|
||||
print(json.dumps(receipt, indent=2), flush=True)
|
||||
code = child.wait()
|
||||
receipt.update({'exit_code': code, 'finished': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
'stdout_bytes': (run / 'stdout.log').stat().st_size,
|
||||
'stderr_bytes': (run / 'stderr.log').stat().st_size,
|
||||
'all_pinned_files_unchanged': all(sha(run / p) == h for p, h in pins.items())})
|
||||
(run / 'finished.json').write_text(json.dumps(receipt, indent=2) + '\n', encoding='utf-8')
|
||||
print(json.dumps(receipt, indent=2), flush=True)
|
||||
assert code == 0 and receipt['stdout_bytes'] == receipt['stderr_bytes'] == 0 and receipt['all_pinned_files_unchanged']
|
||||
@@ -0,0 +1,26 @@
|
||||
from pathlib import Path
|
||||
import hashlib, json, shutil, subprocess
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
source = 'd0896ced75fea73bcda6bd14f259a34503840704'
|
||||
assert subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=root, text=True).strip() == source
|
||||
subprocess.run(['git', 'diff', '--exit-code', source, '--', 'crates', 'Cargo.toml', 'Cargo.lock'], cwd=root, check=True)
|
||||
sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
target = Path('C:/Users/User/Documents/Git/ARCADIA-Studio/.local/target-gnullvm/debug/arcadia-studio.exe')
|
||||
runtime = Path('C:/ar-save-01a0d717/.local/native46/bin/libunwind.dll')
|
||||
fixture = root/'harness/experiments/wrapped-label-20261001/fixture/eight-card.json'
|
||||
assert sha(runtime) == 'f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4'
|
||||
assert sha(fixture) == '9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8'
|
||||
for profile in ['F', 'E']:
|
||||
run = root/('.local/native-wrapped-'+profile)
|
||||
run.mkdir(exist_ok=False)
|
||||
(run/'bin').mkdir(); (run/'working').mkdir(); (run/'captures').mkdir()
|
||||
for original, destination in [(target, run/'bin/arcadia-studio.exe'), (runtime, run/'bin/libunwind.dll'), (fixture, run/'working/eight-card.json')]:
|
||||
shutil.copyfile(original, destination)
|
||||
assert sha(original) == sha(destination)
|
||||
record = {'source': source, 'experiment': 'bounded measured multiline labels; not production acceptance',
|
||||
'label_profile': profile, 'compiler_profile': 'dev/unoptimized', 'target': 'x86_64-pc-windows-gnullvm',
|
||||
'files': {p.relative_to(run).as_posix(): {'bytes': p.stat().st_size, 'sha256': sha(p)}
|
||||
for p in [run/'bin/arcadia-studio.exe', run/'bin/libunwind.dll', run/'working/eight-card.json']}}
|
||||
(run/'baseline.json').write_text(json.dumps(record, indent=2)+'\n', encoding='utf-8')
|
||||
print(json.dumps(record, indent=2))
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
"""Independent read-only saved-F Project/SVG oracle; writes a new receipt only.
|
||||
Run from any cwd: python wrapped-reopen-independent-check.py NEW-RECEIPT.json
|
||||
"""
|
||||
from pathlib import Path
|
||||
import copy, hashlib, json, math, re, struct, sys
|
||||
import xml.etree.ElementTree as ET
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EDGE = '63009b5f-539e-5914-b630-ec6b7fefb4d5'
|
||||
DIAGRAM = '00000000-0038-4000-8000-000000000001'
|
||||
NAME = 'Technician service conversation'
|
||||
NS = '{http://www.w3.org/2000/svg}'
|
||||
TOL = 0.0002
|
||||
P = ROOT/'.local/native-wrapped-F/working'
|
||||
Q = ROOT/'.local/native-wrapped-reopen-F/working'
|
||||
FILES = {'pristine_project':P/'eight-card.json','saved_project':Q/'moved-F.json','pristine_svg':P/'pristine-F.svg','reopened_svg':Q/'reopened-F.svg'}
|
||||
def sha(b): return hashlib.sha256(b).hexdigest()
|
||||
def require(c,m):
|
||||
if not c: raise ValueError(m)
|
||||
def f(x): return struct.unpack('<f',struct.pack('<f',x))[0]
|
||||
def add(a,b): return f(f(a)+f(b))
|
||||
def sub(a,b): return f(f(a)-f(b))
|
||||
def mul(a,b): return f(f(a)*f(b))
|
||||
def div(a,b): return f(f(a)/f(b))
|
||||
def close(a,b,m):
|
||||
require(math.isfinite(float(a)) and abs(float(a)-b)<=TOL, f'{m}: {a} versus expected {b}')
|
||||
def group_and_rest(raw):
|
||||
path = re.compile(rb'<path\b[^>]*\bdata-edge="'+EDGE.encode()+rb'"[^>]*>.*?</path>',re.S)
|
||||
hits=list(path.finditer(raw));require(len(hits)==1,'Exactly one target path required')
|
||||
at=hits[0].end()
|
||||
match=re.compile(rb'<g data-label-experiment="wrapped">.*?</g>',re.S).match(raw,at)
|
||||
require(match is not None,'Immediate target wrapped label group missing')
|
||||
return ET.fromstring(match.group()),raw[:at]+raw[match.end():]
|
||||
def expected(points,label):
|
||||
lengths=[f(math.hypot(sub(b[0],a[0]),sub(b[1],a[1]))) for a,b in zip(points,points[1:])]
|
||||
total=0.
|
||||
for n in lengths: total=add(total,n)
|
||||
remaining=mul(total,label['fraction'])
|
||||
for a,b,n in zip(points,points[1:],lengths):
|
||||
if n>0 and remaining<=n:
|
||||
t=div(remaining,n);anchor=[add(a[i],mul(sub(b[i],a[i]),t)) for i in range(2)];break
|
||||
remaining=sub(remaining,n)
|
||||
else: raise ValueError('Anchor outside path')
|
||||
# Independent prior Iced shaping evidence, not candidate-derived metrics.
|
||||
widths=[60.601074,39.012695,73.18213]
|
||||
width=add(max(widths),16.);height=56.
|
||||
center=[add(anchor[0],label['offset']['x']),add(anchor[1],label['offset']['y'])]
|
||||
rect=[sub(center[0],div(width,2)),sub(center[1],28.),width,height]
|
||||
lines=[]
|
||||
for i,(name,w) in enumerate(zip(['Technician','service','conversation'],widths)):
|
||||
top=add(add(rect[1],4.),mul(16.,i))
|
||||
lines.append([name,sub(center[0],div(w,2)),add(top,13.3828125)])
|
||||
return {'path_length':total,'anchor':anchor,'center':center,'rect':rect,'lines':lines}
|
||||
def label_check(group,e):
|
||||
require(group.tag=='g' and group.attrib=={'data-label-experiment':'wrapped'},'Unexpected group')
|
||||
parts=list(group);require(len(parts)==5,'Exactly title, rect and three lines required')
|
||||
require(group.text is None and all(not list(x) and x.tail is None for x in parts),'Unexpected nested/stray content')
|
||||
require(parts[0].tag=='title' and parts[0].attrib=={} and parts[0].text==NAME,'Title changed')
|
||||
rect=parts[1];require(rect.tag=='rect' and set(rect.attrib)=={'x','y','width','height','fill'} and rect.get('fill')=='white' and rect.text is None,'Rect shape changed')
|
||||
for k,v in zip(['x','y','width','height'],e['rect']):close(rect.get(k),v,'rect.'+k)
|
||||
for actual,(name,x,y) in zip(parts[2:],e['lines']):
|
||||
require(actual.tag=='text' and set(actual.attrib)=={'x','y','font-family','font-size','fill'},'Text shape changed')
|
||||
require(actual.text==name and actual.get('font-family')=='Segoe UI' and actual.get('fill')=='#637487','Text content/font/fill changed')
|
||||
close(actual.get('font-size'),13.,'font-size');close(actual.get('x'),x,name+'.x');close(actual.get('y'),y,name+'.baseline')
|
||||
require(' '.join(x.text for x in parts[2:])==NAME,'Canonical name changed')
|
||||
def check(data):
|
||||
require(sha(data['pristine_project'])=='9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8','Pristine bytes changed')
|
||||
require(sha(data['saved_project'])=='0ad4015d06cf513cd8f9cfe3a155c07590ba09585002277d6eed7a78dd3b6a57','Saved bytes changed')
|
||||
old,new=[json.loads(data[x]) for x in ['pristine_project','saved_project']]
|
||||
require(old['diagrams'][DIAGRAM]['routes']==[],'Unexpected original routes')
|
||||
routes=new['diagrams'][DIAGRAM]['routes'];require(len(routes)==1,'Unexpected added routes')
|
||||
route=routes[0]
|
||||
require(route=={'edge':EDGE,'waypoints':[],'label':{'fraction':0.55,'offset':{'x':120.812744,'y':38.814423}}},'Unexpected saved route fields/values')
|
||||
restored=copy.deepcopy(new);restored['diagrams'][DIAGRAM]['routes']=[]
|
||||
require(restored==old,'Complete Project contains another delta')
|
||||
require(old['relations'][EDGE]['name']==new['relations'][EDGE]['name']==NAME,'Canonical model Name changed')
|
||||
before,after=[ET.fromstring(data[x]) for x in ['pristine_svg','reopened_svg']]
|
||||
require(before.tag==after.tag==NS+'svg','SVG namespace/root changed')
|
||||
require(before.attrib==after.attrib=={'viewBox':'24 -12 1566 874'},'Root bounds changed')
|
||||
edges=[[e for e in root.iter() if e.get('data-edge') is not None] for root in [before,after]]
|
||||
require(len(edges[0])==len(edges[1])==12 and len({e.get('data-edge') for e in edges[0]})==12,'Distinct 12-edge inventory changed')
|
||||
require([ET.tostring(e) for e in edges[0]]==[ET.tostring(e) for e in edges[1]],'Path, marker, title, style or edge order changed')
|
||||
target=next(e for e in edges[0] if e.get('data-edge')==EDGE)
|
||||
require(target.get('d')=='M 1001 316.5 L 1019 316.5 L 1162 316.5 L 1162 89.5 L 1180 89.5','Pinned original target route changed')
|
||||
points=[(1001.,316.5),(1019.,316.5),(1162.,316.5),(1162.,89.5),(1180.,89.5)]
|
||||
groups=[];rests=[]
|
||||
for key,root in [('pristine_svg',before),('reopened_svg',after)]:
|
||||
require(len([e for e in root.iter() if e.get('data-label-experiment') is not None])==1,'Extra experimental groups')
|
||||
g,r=group_and_rest(data[key]);groups.append(g);rests.append(r)
|
||||
require(rests[0]==rests[1],'Bytes outside ONLY target label presentation changed')
|
||||
pristine=expected(points,{'fraction':.55,'offset':{'x':add(18.,div(add(73.18213,16.),2.)),'y':0.}})
|
||||
reopened=expected(points,route['label'])
|
||||
label_check(groups[0],pristine);label_check(groups[1],reopened)
|
||||
require(groups[0][1].get('width')==groups[1][1].get('width') and groups[0][1].get('height')==groups[1][1].get('height'),'Dimensions differ textually')
|
||||
return {'complete_project_only_target_label_route':True,'original_bytes_unchanged':True,'all_12_edges_exact':True,'all_svg_bytes_outside_target_label_exact':True,'remaining_svg_sha256':sha(rests[0]),'expected_pristine':pristine,'expected_reopened':reopened,'numeric_absolute_tolerance':TOL}
|
||||
def main():
|
||||
require(len(sys.argv)==2,'Specify a new receipt path')
|
||||
report={'status':'FAIL','script_sha256':sha(Path(__file__).read_bytes()),'invocation':sys.argv,'scope':'Saved-F full Project and logical SVG; no native input events, raster/font portability, baseline-profile reopen or usability acceptance'}
|
||||
with Path(sys.argv[1]).open('x',encoding='utf-8') as out:
|
||||
try:
|
||||
data={k:p.read_bytes() for k,p in FILES.items()}
|
||||
report['inputs']={k:{'path':str(FILES[k]),'bytes':len(v),'sha256':sha(v)} for k,v in data.items()}
|
||||
report.update(check(data))
|
||||
report['status']='PASS'
|
||||
except Exception as e: report['failure']=str(e)
|
||||
json.dump(report,out,indent=2);out.write('\n')
|
||||
print(json.dumps(report,indent=2));return 0 if report['status']=='PASS' else 1
|
||||
if __name__=='__main__': sys.exit(main())
|
||||
Reference in New Issue
Block a user