Files
firestar5683 22a8575199 Vendor dependencies and resources for standalone builds
Keep official openpilot ancestry and dependency licenses. Store resources directly in Git, remove LFS setup requirements, and leave the downloadable big model outside the source tree.
2026-10-03 21:01:18 +00:00

108 lines
3.5 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import glob
import unittest
import shutil
import subprocess
import tempfile
import random
from concurrent.futures import ProcessPoolExecutor
HERE = os.path.abspath(os.path.dirname(__file__))
ROOT = os.path.join(HERE, "../../")
# skip mutating these paths
IGNORED_PATHS = (
'board/obj',
'board/body',
'board/stm32h7/inc',
'board/fake_stm.h',
# bootstub only files
'board/flasher.h',
'board/bootstub.c',
'board/bootstub_declarations.h',
'board/stm32h7/llflash.h',
)
mutations = [
(None, None, False), # no mods, should pass
("board/stm32h7/llfdcan.h", "s/return ret;/if (true) { return ret; } else { return false; }/g", True),
]
patterns = [
# misra-c2012-13.3
"$a void test(int tmp) { int tmp2 = tmp++ + 2; if (tmp2) {;}}",
# misra-c2012-13.4
"$a int test(int x, int y) { return (x=2) && (y=2); }",
# misra-c2012-13.5
"$a void test(int tmp) { if (true && tmp++) {;} }",
# misra-c2012-13.6
"$a void test(int tmp) { if (sizeof(tmp++)) {;} }",
# misra-c2012-14.1
"$a void test(float len) { for (float j = 0; j < len; j++) {;} }",
# misra-c2012-14.4
"$a void test(int len) { if (len - 8) {;} }",
# misra-c2012-16.4
r"$a void test(int temp) {switch (temp) { case 1: ; }}\n",
# misra-c2012-17.8
"$a void test(int cnt) { for (cnt=0;;cnt++) {;} }",
# misra-c2012-20.4
r"$a #define auto 1\n",
# misra-c2012-20.5
r"$a #define TEST 1\n#undef TEST\n",
]
all_files = glob.glob('board/**', root_dir=ROOT, recursive=True)
files = sorted(f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS))
assert len(files) > 50, all(d in files for d in ('board/main.c', 'board/stm32h7/llfdcan.h'))
# fixed seed for reproducible mutation selection
rng = random.Random(len(files))
for p in patterns:
mutations.append((rng.choice(files), p, True))
# sample to keep CI fast, but always include the no-mutation case
mutations = [mutations[0]] + rng.sample(mutations[1:], min(2, len(mutations) - 1))
def run_mutation(fn, patch, should_fail):
with tempfile.TemporaryDirectory() as tmp:
shutil.copytree(ROOT, tmp + "/panda", ignore=shutil.ignore_patterns(".git", ".venv", "__pycache__"))
# apply patch
if fn is not None:
fpath = os.path.join(tmp, "panda", fn)
with open(fpath) as f:
content = f.read()
if patch.startswith("s/"):
old, new = patch[2:].rsplit("/g", 1)[0].split("/", 1)
content = content.replace(old, new)
elif patch.startswith("$a "):
content += patch[3:].replace(r"\n", "\n")
with open(fpath, "w") as f:
f.write(content)
return subprocess.run("SKIP_TABLES_DIFF=1 panda/tests/misra/test_misra.sh", cwd=tmp, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
class TestMisraMutation(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Each mutation has its own checkout and subprocess; keep the expensive checks parallel.
executor = ProcessPoolExecutor(max_workers=len(mutations))
cls.addClassCleanup(executor.shutdown)
cls.results = [executor.submit(run_mutation, *mutation) for mutation in mutations]
def mutation_test(index):
def test(self):
result = self.results[index].result()
self.assertEqual(result.returncode != 0, mutations[index][2], result.stdout)
test.__doc__ = f"MISRA mutation: {mutations[index]}"
return test
for index in range(len(mutations)):
setattr(TestMisraMutation, f"test_misra_mutation_{index}", mutation_test(index))