mirror of
https://github.com/commaai/panda.git
synced 2026-06-08 13:17:17 +08:00
The new misra_failfast.py wrapper patches misra.py's reportError to sys.exit(1) on the first real violation, handling cppcheck's inline and macro suppressions to avoid false positives on clean code. Also removes sampling (all 12 tests run in ~38s with xdist) and adds board/crypto and board/certs to ignored mutation paths since they're only included from bootstub.c. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
3.0 KiB
Python
Executable File
95 lines
3.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import os
|
|
import glob
|
|
import pytest
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import random
|
|
|
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
|
ROOT = os.path.join(HERE, "../../")
|
|
|
|
# skip mutating these paths
|
|
IGNORED_PATHS = (
|
|
'board/obj',
|
|
'board/jungle',
|
|
'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',
|
|
'board/crypto',
|
|
'board/certs',
|
|
)
|
|
|
|
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 so every xdist worker collects the same test params
|
|
rng = random.Random(len(files))
|
|
for p in patterns:
|
|
mutations.append((rng.choice(files), p, True))
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("fn, patch, should_fail", mutations)
|
|
def test_misra_mutation(fn, patch, should_fail):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
SKIP = {'.venv', '.git', '__pycache__', '.mypy_cache', '.ruff_cache', '.pytest_cache', 'pandacan.egg-info'}
|
|
shutil.copytree(ROOT, tmp + "/panda", dirs_exist_ok=True,
|
|
ignore=lambda d, files: [f for f in files if f in SKIP])
|
|
|
|
# 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)
|
|
|
|
# run test (SKIP_BUILD: cppcheck doesn't need firmware binaries,
|
|
# MISRA_ONLY: skip non-misra checkers for speed)
|
|
env = "SKIP_TABLES_DIFF=1 SKIP_BUILD=1 MISRA_ONLY=1"
|
|
r = subprocess.run(f"{env} panda/tests/misra/test_misra.sh", cwd=tmp, shell=True)
|
|
failed = r.returncode != 0
|
|
assert failed == should_fail
|