Files
StarPilot/tools/ci/tests/test_prebuilt_release_receipt.py
T

108 lines
4.1 KiB
Python

import importlib.util
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
from tools.ci.check_prebuilt_receipt import ReceiptError, check_receipt
ROOT = Path(__file__).resolve().parents[3]
class TestPublishedReceipt(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.repo = Path(self.temp.name)
self.environment = {name: value for name, value in os.environ.items()
if name not in ('GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE')}
self.git('init', '-b', 'release')
self.git('config', 'user.name', 'Receipt Test')
self.git('config', 'user.email', 'receipt@example.invalid')
path = self.repo / 'openpilot/common/prebuilt_manifest.py'
path.parent.mkdir(parents=True)
shutil.copy2(ROOT / 'openpilot/common/prebuilt_manifest.py', path)
spec = importlib.util.spec_from_file_location('fixture_prebuilt_manifest', path)
assert spec is not None and spec.loader is not None
self.manifest = importlib.util.module_from_spec(spec)
spec.loader.exec_module(self.manifest)
(self.repo / 'prebuilt').touch()
(self.repo / 'native.so').write_bytes(b'original native artifact')
(self.repo / 'native.cc').write_text('int original_source;\n')
self.receipt()
def git(self, *args):
return subprocess.check_output(['git', '-c', 'gc.auto=0', '-c', 'maintenance.auto=false', '-C', str(self.repo), *args],
env=self.environment, text=True, stderr=subprocess.PIPE).strip()
def commit(self):
self.git('add', '.')
self.git('commit', '-m', 'fixture')
return self.git('rev-parse', 'HEAD')
def receipt(self):
self.git('add', '.')
entries = []
for record in self.git('ls-files', '--stage', '-z').split('\0'):
if record:
metadata, name = record.split('\t', 1)
mode, blob, stage = metadata.split()
assert stage == '0'
entries.append((name, mode, blob))
(self.repo / 'prebuilt.json').write_text(json.dumps({'schema_version': 1,
'protected_inventory_sha256': self.manifest.inventory_digest(entries)})+'\n')
return self.commit()
def test_matching_committed_release_passes(self):
self.assertEqual(check_receipt(self.repo), self.git('rev-parse', 'HEAD'))
def test_stale_source_and_artifact_each_fail(self):
for name in ('native.cc', 'native.so'):
with self.subTest(name=name):
(self.repo / name).write_bytes(b'changed')
self.commit()
with self.assertRaisesRegex(ReceiptError, 'no matching build receipt'):
check_receipt(self.repo)
self.receipt()
def test_ui_only_runtime_change_keeps_existing_contract(self):
path = self.repo / 'openpilot/starpilot/ui/onroad.py'
path.parent.mkdir(parents=True)
path.write_text('updated_ui = True\n')
commit = self.commit()
self.assertEqual(check_receipt(self.repo), commit)
def test_protected_test_change_also_requires_receipt(self):
path = self.repo / 'tools/ci/tests/example.py'
path.parent.mkdir(parents=True)
path.write_text('changed_fixture = True\n')
self.commit()
with self.assertRaises(ReceiptError):
check_receipt(self.repo)
def test_checks_committed_tree_not_dirty_working_bytes(self):
commit = self.git('rev-parse', 'HEAD')
(self.repo / 'native.so').write_bytes(b'uncommitted')
self.assertEqual(check_receipt(self.repo), commit)
def test_missing_marker_or_receipt_fails(self):
for name in ('prebuilt.json', 'prebuilt'):
with self.subTest(name=name):
(self.repo / name).unlink()
self.commit()
with self.assertRaises(ReceiptError):
check_receipt(self.repo)
(self.repo / name).touch()
self.receipt()
def test_old_valid_revision_remains_valid_after_bad_release(self):
previous = self.git('rev-parse', 'HEAD')
(self.repo / 'native.cc').write_text('new source without receipt\n')
self.commit()
self.assertEqual(check_receipt(self.repo, previous), previous)
with self.assertRaises(ReceiptError):
check_receipt(self.repo)