Merge preview and CI fixes with Toyota filter support

This commit is contained in:
firestar5683
2026-10-22 20:00:23 -05:00
5 changed files with 72 additions and 11 deletions
@@ -340,8 +340,9 @@ class _Canvas:
for key in RUNTIME_PREVIEW_WIDGETS[str(self.profile)]:
logical_width, logical_height = widget_size(state.customization, self.profile, key)
# The restored compact US sign adds an offset row beyond its saved base size.
if (self.profile == Profile.COMPACT and key == 'speed_limit' and
not state.appearance.use_vienna_sign and resolve_unified_speed(state).offset_text):
compact_offset = (self.profile == Profile.COMPACT and key == 'speed_limit' and
not state.appearance.use_vienna_sign and bool(resolve_unified_speed(state).offset_text))
if compact_offset:
logical_height += 44
position = positions[key]
widget = PROFILES[str(self.profile)]['widgets'][key]
@@ -349,6 +350,8 @@ class _Canvas:
crop_y = max(0, math.floor(position['y'] - widget.get('visualInsetTop', 0)))
offset_x, offset_y = crop_x - position['x'], crop_y - position['y']
width, height = math.ceil(logical_width - offset_x), math.ceil(logical_height - offset_y)
if compact_offset and 0 <= crop_y < canvas_height:
height = min(height, canvas_height - crop_y)
if x + width > 1024:
x, y, row_height = 0, y + row_height, 0
sprites[key] = {'x': x, 'y': y, 'width': width, 'height': height, 'offsetX': offset_x, 'offsetY': offset_y}
@@ -238,9 +238,10 @@ def test_compact_preview_sprite_keeps_the_sign_offset_arrow_and_override_cross()
rl.init_window(128, 128, 'SLC preview crop fixture')
canvas = _Canvas(Profile.COMPACT)
try:
for y, scene in product((20, 0), ('slc_pending', 'slc_retained')):
for y, scene in product((20, 0, 64, 108), ('slc_pending', 'slc_retained')):
document = default_document()
document['layouts']['compact']['speed_limit']['y'] = y
assert validate_document(document)['layouts']['compact']['speed_limit']['y'] == y
with patch.dict('openpilot.starpilot.ui.layout_preview_renderer.RUNTIME_PREVIEW_WIDGETS', compact=('speed_limit',)):
png = canvas.render_widget_art(sample_state(scene, document), document['layouts']['compact'])
with Image.open(io.BytesIO(png)) as sheet:
@@ -256,7 +257,9 @@ def test_compact_preview_sprite_keeps_the_sign_offset_arrow_and_override_cross()
rl.unload_image(image)
assert np.array_equal(direct[..., 3], np.asarray(rebuilt)[..., 3])
assert slot['offsetX'] == slot['offsetY'] == 0
assert (slot['width'], slot['height']) == (118, 176)
assert (slot['width'], slot['height']) == (118, min(176, Profile.COMPACT.size[1] - y))
if scene == 'slc_retained':
assert np.any(direct[..., 3]), (y, scene)
finally:
canvas.close()
if owns_context:
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "1b232d611440fc932c2a7cc7abb80e2f32fa1dad49f470afbf2121771de57960", "schema_version": 1}
{"protected_inventory_sha256": "74afd213d9a908bdda84394f6eb7485f856ce018fb9e3c4aee9345851669a752", "schema_version": 1}
+8 -5
View File
@@ -238,6 +238,8 @@ def complete_mutation_artifact(path, summary, *, digest_out=None):
def check_import_origins(python, env, output, *, full_catalog=False):
sidecar = output.with_suffix(".origins.json")
sidecar.unlink(missing_ok=True)
script = """
import json
from pathlib import Path
@@ -278,14 +280,15 @@ print(json.dumps({'msgq_visionipc': str(Path(msgq.visionipc.visionipc_pyx.__file
"cereal_messaging": ROOT / "openpilot/cereal", "msgq_ipc": ROOT / "msgq_repo"}
if full_catalog:
expected["msgq_visionipc"] = ROOT / "msgq_repo"
return all(isinstance(origins.get(name), str) and Path(origins[name]).is_relative_to(path)
for name, path in expected.items())
valid = all(isinstance(origins.get(name), str) and Path(origins[name]).is_relative_to(path)
for name, path in expected.items())
if valid:
sidecar.write_text(json.dumps(origins, sort_keys=True) + "\n")
return valid
def imported_module_hashes(output):
origins = {}
for line in output.read_text().splitlines():
origins.update(json.loads(line))
origins = json.loads(output.with_suffix(".origins.json").read_text())
return {name: {"path": path, "sha256": hashlib.sha256(Path(path).read_bytes()).hexdigest()}
for name, path in origins.items()}
+53 -1
View File
@@ -56,8 +56,42 @@ class TestSafetyQualificationRunner(TestCase):
Path(path).write_bytes(b"native")
log = root / "imports.log"
log.write_text("\n".join(json.dumps({name: path}) for name, path in modules.items()) + "\n")
log.with_suffix(".origins.json").write_text(json.dumps(modules))
self.assertEqual(set(qualification.imported_module_hashes(log)), set(modules))
def test_validated_origins_sidecar_retains_multiline_stdout_and_stderr(self):
with tempfile.TemporaryDirectory() as td:
root = Path(td)
prefixes = {"opendbc": "opendbc_repo", "panda": "panda", "msgq": "msgq_repo",
"openpilot": "openpilot", "libsafety_py": "opendbc_repo",
"can_parser": "opendbc_repo", "can_packer": "opendbc_repo", "can_dbc": "opendbc_repo",
"cereal_log": "openpilot/cereal", "cereal_messaging": "openpilot/cereal", "msgq_ipc": "msgq_repo",
"msgq_visionipc": "msgq_repo"}
origins = {name: str(root / prefix / name) for name, prefix in prefixes.items()}
for path in origins.values():
Path(path).parent.mkdir(parents=True, exist_ok=True)
Path(path).write_bytes(b"actual provenance fixture")
first = {name: path for name, path in origins.items() if name != "msgq_visionipc"}
stdout = json.dumps(first) + "\n" + json.dumps({"msgq_visionipc": origins["msgq_visionipc"]}) + "\n"
warning = "kj/filesystem-disk-unix.c++:1734: warning: PWD environment variable doesn't match current directory\n"
log = root / "imports.log"
sidecar = log.with_suffix(".origins.json")
with patch.object(qualification, "ROOT", root), patch.object(qualification.subprocess, "run",
return_value=subprocess.CompletedProcess([], 0, stdout, warning)):
self.assertTrue(qualification.check_import_origins(sys.executable, {}, log, full_catalog=True))
self.assertEqual(log.read_text(), stdout + warning)
self.assertEqual(json.loads(sidecar.read_text()), origins)
self.assertEqual(set(qualification.imported_module_hashes(log)), set(origins))
foreign = dict(origins, msgq_visionipc="/foreign/visionipc.so")
for code, invalid in ((0, json.dumps(foreign)), (0, "bad stdout"), (1, stdout)):
sidecar.write_text(json.dumps(origins)) # Never reuse earlier success after a failed probe.
with self.subTest(code=code, stdout=invalid), patch.object(qualification, "ROOT", root), \
patch.object(qualification.subprocess, "run", return_value=subprocess.CompletedProcess([], code, invalid, warning)):
self.assertFalse(qualification.check_import_origins(sys.executable, {}, log, full_catalog=True))
self.assertFalse(sidecar.exists())
with self.assertRaises(FileNotFoundError):
qualification.imported_module_hashes(log)
def test_source_identity_includes_gate_and_workflow_inputs(self):
identity = qualification.source_identity()
self.assertFalse(any("/obj/" in path or "/gen/" in path for path in identity))
@@ -456,7 +490,25 @@ class TestSafetyQualificationRunner(TestCase):
with tempfile.TemporaryDirectory() as td:
output = Path(td)
gate = qualification.Gate("coverage", output, "/usr/bin/python3")
code, text = gate.run("timeout", [sys.executable, "-c", "import time; print('partial', flush=True); time.sleep(10)"], timeout=0.05)
ready = output / "child-ready"
script = "import pathlib,sys,time; print('partial', flush=True); pathlib.Path(sys.argv[1]).touch(); time.sleep(10)"
spawn = subprocess.Popen
def started_child(*args, **kwargs):
proc = spawn(*args, **kwargs)
deadline = time.monotonic() + 5
while not ready.exists():
if proc.poll() is not None or time.monotonic() >= deadline:
if proc.poll() is None:
os.killpg(proc.pid, signal.SIGKILL)
proc.communicate()
self.fail("Timeout fixture never became ready")
time.sleep(.005)
return proc
# Exercise the real timeout after the child's partial output is actually flushed.
with patch.object(qualification.subprocess, "Popen", side_effect=started_child):
code, text = gate.run("timeout", [sys.executable, "-c", script, str(ready)], timeout=0.05)
self.assertEqual(code, 124)
self.assertIn("Timed out", text)
self.assertIn("partial", (output / "timeout.log").read_text())