import hashlib import io import os import struct import tarfile from typing import Any from datetime import UTC, datetime, timedelta from pathlib import Path import pytest from openpilot.starpilot.soloist import package from openpilot.starpilot.soloist.paths import Layout BUILD = 1792173722 # 2026-10-16, from a real `soloist --version` NOW = datetime.fromtimestamp(BUILD, UTC) + timedelta(days=1) VERSION_LINE = f'soloist 1.3.9.32 build {BUILD} (20261016) (g440165f200) (linux/aarch64)' def elf(machine=183, elf_class=2, interpreter=b'/lib/ld-linux-aarch64.so.1', extra=b'GLIBC_2.38\x00') -> bytes: header = bytearray(64) header[:4] = b'\x7fELF' header[4], header[5] = elf_class, 1 phoff, phentsize = 64, 56 struct.pack_into(' bytes: members = members if members is not None else {'soloist': elf(), 'CHANGELOG.md': b'# Soloist', 'THIRD_PARTY_LICENSES.txt': b'MIT'} buffer = io.BytesIO() with tarfile.open(fileobj=buffer, mode='w:gz') as tar: for name, data in members.items(): info = tarfile.TarInfo(prefix + name) info.size = len(data) info.mode = 0o555 tar.addfile(info, io.BytesIO(data)) if links: info = tarfile.TarInfo('evil') info.type = tarfile.SYMTYPE info.linkname = '/etc/passwd' tar.addfile(info) return buffer.getvalue() def write(tmp_path: Path, data: bytes) -> Path: path = tmp_path / 'upload.tar.gz' path.write_bytes(data) return path def official(data: bytes): return lambda: (hashlib.md5(data).hexdigest(), len(data)) def run_import(layout, path, data=None, **kwargs): data = path.read_bytes() if data is None else data options: dict[str, Any] = {'digest': official(data), 'probe': lambda _exe: VERSION_LINE, 'now': NOW, **kwargs} return package.import_archive(layout, path, **options) def test_read_archive_keeps_known_files_and_accepts_one_folder(tmp_path): files = package.read_archive(write(tmp_path, archive(prefix='soloist_release_arm64/'))) assert set(files) == {'soloist', 'CHANGELOG.md', 'THIRD_PARTY_LICENSES.txt'} @pytest.mark.parametrize('data,code', [ (b'PK\x03\x04 not gzip', 'NOT_ARCHIVE'), (archive({'README': b'x'}), 'NO_PLAYER'), (archive(links=True), 'UNSAFE'), (archive({'../soloist': elf()}), 'UNSAFE'), (archive({'a/b/c/soloist': elf()}), 'NO_PLAYER'), (archive()[:200], 'CORRUPT'), ], ids=['not_gzip', 'no_player', 'links', 'parent_path', 'nested', 'truncated']) def test_read_archive_rejects_unsafe_or_wrong_archives(tmp_path, data, code): with pytest.raises(package.ImportFailed) as error: package.read_archive(write(tmp_path, data)) assert error.value.code == code def test_read_archive_bounds_decompressed_bytes(tmp_path): with pytest.raises(package.ImportFailed) as error: package.read_archive(write(tmp_path, archive({'filler.bin': b'\x00' * (3 * 1024 * 1024), 'soloist': elf()})), max_unpacked=1024 * 1024) assert error.value.code == 'TOO_LARGE' @pytest.mark.parametrize('binary,code', [ (elf(machine=62), 'WRONG_ARCH'), (elf(elf_class=1), 'WRONG_ARCH'), (elf(interpreter=b'/lib/ld-linux-armhf.so.3'), 'INCOMPATIBLE'), (b'#!/bin/sh\necho hi\n' + b'\x00' * 64, 'NO_PLAYER'), ], ids=['x86_64', 'elf32', 'armhf', 'script']) def test_check_executable(binary, code): with pytest.raises(package.ImportFailed) as error: package.check_executable(binary) assert error.value.code == code def test_check_executable_glibc_requirement(): package.check_executable(elf(), max_glibc_minor=39) with pytest.raises(package.ImportFailed) as error: package.check_executable(elf(), max_glibc_minor=35) assert error.value.code == 'INCOMPATIBLE' def test_parse_version_reads_build_timestamp(): parsed = package.parse_version(VERSION_LINE) assert parsed['version'] == '1.3.9.32' assert parsed['built'].startswith('2026-10-16') assert parsed['expires'].startswith('2027-01-14') with pytest.raises(package.ImportFailed): package.parse_version('soloist dev build') def test_import_installs_and_reports_status(tmp_path): layout = Layout(tmp_path / 'root') data = archive() stages = [] metadata = run_import(layout, write(tmp_path, data), progress=lambda **v: stages.append(v.get('stage'))) assert stages == ['reading', 'unpacking', 'verifying', 'testing', 'installing'] assert os.access(layout.current / 'soloist', os.X_OK) status = package.release_status(layout, now=NOW) assert status['installed'] and status['version'] == '1.3.9.32' and status['level'] == 'ok' and status['daysLeft'] == 89 assert package.release_status(layout, now=NOW + timedelta(days=80))['level'] == 'soon' assert package.release_status(layout, now=NOW + timedelta(days=95))['expired'] is True assert metadata['sha256'] == hashlib.sha256(data).hexdigest() def test_import_refuses_files_that_do_not_match_spotify(tmp_path): layout = Layout(tmp_path / 'root') path = write(tmp_path, archive()) with pytest.raises(package.ImportFailed) as error: run_import(layout, path, digest=lambda: ('0' * 32, path.stat().st_size)) assert error.value.code == 'NOT_OFFICIAL' assert not package.release_status(layout)['installed'] assert not any(layout.releases.iterdir()) if layout.releases.exists() else True def test_import_without_internet_needs_a_previously_verified_build(tmp_path): layout = Layout(tmp_path / 'root') data = archive() def offline(): raise package.ImportFailed('offline', 'OFFLINE') with pytest.raises(package.ImportFailed) as error: run_import(layout, write(tmp_path, data), digest=offline) assert error.value.code == 'OFFLINE' run_import(layout, write(tmp_path, data)) run_import(layout, write(tmp_path, data), digest=offline) # reinstalling the same verified build works offline def test_probe_is_only_run_after_verification_and_expiry_is_enforced(tmp_path): layout = Layout(tmp_path / 'root') probed = [] path = write(tmp_path, archive()) with pytest.raises(package.ImportFailed): run_import(layout, path, digest=lambda: ('0' * 32, 1), probe=lambda exe: probed.append(exe) or VERSION_LINE) assert probed == [] with pytest.raises(package.ImportFailed) as error: run_import(layout, write(tmp_path, archive()), now=NOW + timedelta(days=100)) assert error.value.code == 'EXPIRED' assert not package.release_status(layout)['installed'] def test_update_keeps_previous_and_rollback_restores_it(tmp_path): layout = Layout(tmp_path / 'root') run_import(layout, write(tmp_path, archive())) first = os.path.realpath(layout.current) run_import(layout, write(tmp_path, archive({'soloist': elf(extra=b'GLIBC_2.38\x00new build')}))) assert os.path.realpath(layout.current) != first assert os.path.realpath(layout.previous) == first assert len(list(layout.releases.iterdir())) == 2 assert package.rollback(layout) assert os.path.realpath(layout.current) == first assert not layout.previous.exists() def test_activation_requires_park(tmp_path): layout = Layout(tmp_path / 'root') with pytest.raises(package.ImportFailed) as error: run_import(layout, write(tmp_path, archive()), may_activate=lambda: False) assert error.value.code == 'DRIVING' assert not package.release_status(layout)['installed'] def test_import_job_reports_failure_and_removes_upload(tmp_path): layout = Layout(tmp_path / 'root') job = package.ImportJob(layout, digest=lambda: ('0' * 32, 1), probe=lambda _exe: VERSION_LINE, now=NOW) path = write(tmp_path, archive()) job.start(path=path, enabled=lambda: True) job.thread.join(timeout=5) assert job.status()['state'] == 'failed' and job.status()['code'] == 'NOT_OFFICIAL' assert not path.exists()