mirror of
https://jihulab.com/mr-one/openpilot.git
synced 2026-10-27 21:13:42 +08:00
108 lines
3.5 KiB
Python
Executable File
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))
|