mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
694b460f80
Provide laptop and device build entry points, cache compatibility checks and developer tooling. Keep distribution assets and model packaging available before runtime consumers. Co-authored-by: Danny <dannyirwin31@yahoo.com> Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu> Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Co-authored-by: prabhaavp <prabhaav@msu.edu>
362 lines
14 KiB
Python
362 lines
14 KiB
Python
"""Source-manifest validation with standard-library and temporary Git fixtures."""
|
|
|
|
import copy
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from openpilot.common.vendor_manifest import DEPENDENCIES, parse_manifest, validate_revision, validate_worktree
|
|
|
|
|
|
def make_manifest():
|
|
return {
|
|
"schema_version": 1,
|
|
"upstream": {"url": "https://github.com/commaai/openpilot.git", "commit": "1" * 40},
|
|
"dependencies": [
|
|
{"path": name, "url": f"https://github.com/commaai/{name}.git", "commit": "2" * 40, "tree": "3" * 40, "exclude": []}
|
|
for name in sorted(DEPENDENCIES)
|
|
],
|
|
}
|
|
|
|
|
|
def extension_manifest():
|
|
manifest = make_manifest()
|
|
entry = next(item for item in manifest['dependencies'] if item['path'] == 'jetlink_repo')
|
|
manifest['dependencies'].remove(entry)
|
|
manifest['vendored_extensions'] = [entry]
|
|
return manifest
|
|
|
|
|
|
class TestManifestSchema(unittest.TestCase):
|
|
def assert_invalid(self, manifest):
|
|
with self.assertRaises(ValueError):
|
|
parse_manifest(json.dumps(manifest))
|
|
|
|
def test_valid_manifest(self):
|
|
manifest = make_manifest()
|
|
manifest["dependencies"][-1]["exclude"] = ["AGENTS.md", "docs/generated"]
|
|
self.assertEqual(parse_manifest(json.dumps(manifest)), manifest)
|
|
|
|
def test_extension_and_legacy_manifest_forms(self):
|
|
for manifest in (make_manifest(), extension_manifest()):
|
|
self.assertEqual(parse_manifest(json.dumps(manifest)), manifest)
|
|
legacy = extension_manifest()
|
|
del legacy['vendored_extensions']
|
|
self.assertEqual(parse_manifest(json.dumps(legacy)), legacy)
|
|
|
|
def test_extensions_are_known_unique_and_fully_validated(self):
|
|
for field, value in (('path', 'unknown_repo'), ('path', 'panda'), ('commit', 'short'),
|
|
('tree', 'short'), ('url', 'http://example.com'), ('exclude', ['../escape'])):
|
|
with self.subTest(field=field, value=value):
|
|
manifest = extension_manifest()
|
|
manifest['vendored_extensions'][0][field] = value
|
|
self.assert_invalid(manifest)
|
|
for entries in (None, {}, 'jetlink_repo'):
|
|
manifest = extension_manifest()
|
|
manifest['vendored_extensions'] = entries
|
|
self.assert_invalid(manifest)
|
|
manifest = extension_manifest()
|
|
manifest['vendored_extensions'] *= 2
|
|
self.assert_invalid(manifest)
|
|
manifest = extension_manifest()
|
|
manifest['dependencies'].append(copy.deepcopy(manifest['vendored_extensions'][0]))
|
|
self.assert_invalid(manifest)
|
|
|
|
def test_malformed_json_and_non_objects(self):
|
|
for raw in ("", "{", b"\xff", "null", "true", "[]", '"manifest"', "1"):
|
|
with self.subTest(raw=raw), self.assertRaises(ValueError):
|
|
parse_manifest(raw)
|
|
|
|
def test_schema_is_integer_version_one(self):
|
|
for version in (None, True, False, 1.0, 0, 2, "1", [], {}):
|
|
with self.subTest(version=version):
|
|
manifest = make_manifest()
|
|
manifest["schema_version"] = version
|
|
self.assert_invalid(manifest)
|
|
|
|
def test_invalid_source_urls(self):
|
|
for url in (None, [], "", "https://", "https:///repo", "http://example.com/repo", "file:///repo", "git@example.com:repo"):
|
|
for field in ("upstream", "dependency"):
|
|
with self.subTest(url=url, field=field):
|
|
manifest = make_manifest()
|
|
entry = manifest["upstream"] if field == "upstream" else manifest["dependencies"][0]
|
|
entry["url"] = url
|
|
self.assert_invalid(manifest)
|
|
|
|
def test_invalid_revision_and_tree_identifiers(self):
|
|
for sha in (None, [], "", "a" * 39, "a" * 41, "G" * 40, "A" * 40, "master"):
|
|
for field in ("upstream", "dependency", "tree"):
|
|
with self.subTest(sha=sha, field=field):
|
|
manifest = make_manifest()
|
|
entry = manifest["upstream"] if field == "upstream" else manifest["dependencies"][0]
|
|
entry["tree" if field == "tree" else "commit"] = sha
|
|
self.assert_invalid(manifest)
|
|
|
|
def test_dependencies_are_complete_unique_and_known(self):
|
|
manifest = make_manifest()
|
|
variants = [None, {}, [], manifest["dependencies"][:-1], manifest["dependencies"] + [manifest["dependencies"][0]]]
|
|
for entries in variants:
|
|
with self.subTest(entries=entries):
|
|
candidate = copy.deepcopy(manifest)
|
|
candidate["dependencies"] = entries
|
|
self.assert_invalid(candidate)
|
|
for path in (None, [], {}, "../panda", "/panda", "panda/child", "unknown"):
|
|
with self.subTest(path=path):
|
|
candidate = make_manifest()
|
|
candidate["dependencies"][0]["path"] = path
|
|
self.assert_invalid(candidate)
|
|
for entry in (None, [], "dependency"):
|
|
with self.subTest(entry=entry):
|
|
candidate = make_manifest()
|
|
candidate["dependencies"][0] = entry
|
|
self.assert_invalid(candidate)
|
|
|
|
def test_exclusions_are_safe_relative_paths(self):
|
|
for exclusion in ("", ".", "..", "../escape", "/absolute", "dir/../../escape", "./file", "dir//file", ".git", "dir/.git", "bad\0path"):
|
|
with self.subTest(exclusion=exclusion):
|
|
manifest = make_manifest()
|
|
manifest["dependencies"][0]["exclude"] = [exclusion]
|
|
self.assert_invalid(manifest)
|
|
for excluded in (None, "AGENTS.md", [None], [[]], [{}], ["file", "file"]):
|
|
with self.subTest(excluded=excluded):
|
|
manifest = make_manifest()
|
|
manifest["dependencies"][0]["exclude"] = excluded
|
|
self.assert_invalid(manifest)
|
|
|
|
|
|
class TestManifestSourceTrees(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temporary.cleanup)
|
|
self.root = Path(self.temporary.name)
|
|
self.repo = self.root / "source"
|
|
self.repo.mkdir()
|
|
env_patch = patch.dict(os.environ, {
|
|
"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull,
|
|
"GIT_AUTHOR_NAME": "Source manifest test", "GIT_COMMITTER_NAME": "Source manifest test",
|
|
"GIT_AUTHOR_EMAIL": "source@example.invalid", "GIT_COMMITTER_EMAIL": "source@example.invalid",
|
|
})
|
|
env_patch.start()
|
|
self.addCleanup(env_patch.stop)
|
|
self.git("init", "-b", "test-manifest")
|
|
self.manifest = make_manifest()
|
|
self.manifest_path = self.repo / "upstream-sync.json"
|
|
self.write_manifest()
|
|
for name in DEPENDENCIES:
|
|
folder = self.repo / name
|
|
folder.mkdir()
|
|
(folder / "README.md").write_text("Ordinary dependency source\n")
|
|
self.commit()
|
|
|
|
def git(self, *args, input_text=None):
|
|
return subprocess.run(
|
|
["git", "-C", str(self.repo), *args], input=input_text, check=True,
|
|
text=True, capture_output=True,
|
|
).stdout.strip()
|
|
|
|
def write_manifest(self):
|
|
self.manifest_path.write_text(json.dumps(self.manifest))
|
|
|
|
def commit(self):
|
|
self.git("add", "--all")
|
|
self.git("commit", "-m", "Source layout fixture")
|
|
|
|
def assert_invalid_revision_and_worktree(self):
|
|
with self.assertRaises(ValueError):
|
|
validate_revision(self.repo, "HEAD")
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def use_extension(self):
|
|
self.manifest = extension_manifest()
|
|
self.write_manifest()
|
|
self.commit()
|
|
|
|
def test_extension_tree_and_missing_provenance(self):
|
|
self.use_extension()
|
|
self.assertEqual(validate_revision(self.repo, 'HEAD'), self.manifest)
|
|
self.assertEqual(validate_worktree(self.repo), self.manifest)
|
|
del self.manifest['vendored_extensions']
|
|
self.write_manifest()
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
shutil.rmtree(self.repo / 'jetlink_repo')
|
|
self.commit()
|
|
self.assertEqual(validate_revision(self.repo, 'HEAD'), self.manifest)
|
|
self.assertEqual(validate_worktree(self.repo), self.manifest)
|
|
|
|
def test_extension_missing_folder_and_symlink_rejected(self):
|
|
self.use_extension()
|
|
folder = self.repo / 'jetlink_repo'
|
|
external = self.root / 'external-jetlink'
|
|
folder.rename(external)
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
folder.symlink_to(external, target_is_directory=True)
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
|
|
def test_extension_excluded_metadata_and_nested_git_rejected(self):
|
|
self.use_extension()
|
|
folder = self.repo / 'jetlink_repo'
|
|
(folder / '.git').mkdir()
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
(folder / '.git').rmdir()
|
|
(folder / 'AGENTS.md').write_text('Excluded metadata')
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
|
|
def test_valid_tree_permits_local_source_edits(self):
|
|
self.assertEqual(validate_revision(self.repo, "HEAD"), self.manifest)
|
|
(self.repo / "panda" / "README.md").write_text("Intentional local customization\n")
|
|
self.assertEqual(validate_worktree(self.repo), self.manifest)
|
|
|
|
def test_missing_tracked_manifest(self):
|
|
self.manifest_path.unlink()
|
|
self.commit()
|
|
with self.assertRaises(ValueError):
|
|
validate_revision(self.repo, "HEAD")
|
|
|
|
def test_manifest_worktree_symlink(self):
|
|
target = self.root / "external-manifest.json"
|
|
self.manifest_path.rename(target)
|
|
self.manifest_path.symlink_to(target)
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def test_tracked_manifest_symlink(self):
|
|
target = self.repo / "source-manifest.json"
|
|
self.manifest_path.rename(target)
|
|
self.manifest_path.symlink_to(target.name)
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
|
|
def test_missing_dependency_folder(self):
|
|
shutil.rmtree(self.repo / "panda")
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
|
|
def test_dependency_root_symlink(self):
|
|
folder = self.repo / "panda"
|
|
target = self.root / "panda-outside"
|
|
folder.rename(target)
|
|
folder.symlink_to(target, target_is_directory=True)
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
|
|
def test_gitlink_anywhere_is_rejected(self):
|
|
commit = self.git("rev-parse", "HEAD")
|
|
self.git("update-index", "--add", "--cacheinfo", f"160000,{commit},panda/embedded")
|
|
self.git("commit", "-m", "Nested gitlink fixture")
|
|
self.assert_invalid_revision_and_worktree()
|
|
|
|
def test_tracked_metadata_is_rejected(self):
|
|
for name in (".gitmodules", "AGENTS.md", "nested/agents.md"):
|
|
with self.subTest(name=name):
|
|
path = self.repo / "panda" / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("Excluded metadata fixture\n")
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
path.unlink()
|
|
self.commit()
|
|
|
|
def test_nested_git_metadata_is_rejected(self):
|
|
for relative in (".git", "nested/.git"):
|
|
for kind in ("file", "directory", "broken_symlink"):
|
|
with self.subTest(relative=relative, kind=kind):
|
|
path = self.repo / "panda" / relative
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
if kind == "file":
|
|
path.write_text("gitdir: /does-not-exist\n")
|
|
elif kind == "directory":
|
|
path.mkdir()
|
|
else:
|
|
path.symlink_to("does-not-exist")
|
|
try:
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
finally:
|
|
if path.is_dir() and not path.is_symlink():
|
|
path.rmdir()
|
|
else:
|
|
path.unlink()
|
|
|
|
def test_missing_tracked_source(self):
|
|
(self.repo / "panda" / "README.md").unlink()
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def test_regular_file_replaced_by_directory(self):
|
|
file = self.repo / "panda" / "README.md"
|
|
file.unlink()
|
|
file.mkdir()
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def test_regular_file_replaced_by_symlink(self):
|
|
file = self.repo / "panda" / "README.md"
|
|
file.unlink()
|
|
file.symlink_to("../msgq_repo/README.md")
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def test_intermediate_directory_symlink_is_rejected(self):
|
|
folder = self.repo / "panda" / "nested"
|
|
folder.mkdir()
|
|
(folder / "source.py").write_text("SOURCE = 1\n")
|
|
self.commit()
|
|
target = self.root / "external-source"
|
|
folder.rename(target)
|
|
folder.symlink_to(target, target_is_directory=True)
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def test_tracked_leaf_symlink_is_preserved(self):
|
|
link = self.repo / "panda" / "linked-readme"
|
|
link.symlink_to("README.md")
|
|
self.commit()
|
|
self.assertEqual(validate_revision(self.repo, "HEAD"), self.manifest)
|
|
self.assertEqual(validate_worktree(self.repo), self.manifest)
|
|
link.unlink()
|
|
link.write_text("Unexpected regular file\n")
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def test_unresolved_index_is_rejected(self):
|
|
blob = self.git("rev-parse", "HEAD:panda/README.md")
|
|
self.git("update-index", "--force-remove", "panda/README.md")
|
|
self.git("update-index", "--index-info", input_text=f"100644 {blob} 2\tpanda/README.md\n100644 {blob} 3\tpanda/README.md\n")
|
|
with self.assertRaises(ValueError):
|
|
validate_worktree(self.repo)
|
|
|
|
def test_excluded_file_and_directory_are_not_tracked(self):
|
|
entry = next(entry for entry in self.manifest["dependencies"] if entry["path"] == "panda")
|
|
entry["exclude"] = ["private.txt", "generated"]
|
|
self.write_manifest()
|
|
self.commit()
|
|
self.assertEqual(validate_revision(self.repo, "HEAD"), self.manifest)
|
|
for name in ("private.txt", "generated/source.py"):
|
|
with self.subTest(name=name):
|
|
file = self.repo / "panda" / name
|
|
file.parent.mkdir(parents=True, exist_ok=True)
|
|
file.write_text("Excluded content\n")
|
|
self.commit()
|
|
self.assert_invalid_revision_and_worktree()
|
|
file.unlink()
|
|
self.commit()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|