Files
StarPilot/tools/ci/run_local_tests.py
T
firestar5683 6d742eb343 CI
Add control trace and platform qualification tools that exercise explicit safety modes and vehicle coverage.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: YassineYousfi <yyousfi1@binghamton.edu>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>

Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 19:56:17 -05:00

146 lines
8.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run pre-push regression checks in the existing isolated developer runtime."""
import argparse
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import tempfile
import time
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from tools.ci.run_predeploy import commands, run
from tools.ci.run_host_tests import UI_PYTEST_FILES
QUICK_TARGETS = (
'openpilot/common/tests/test_params.py',
'openpilot/common/tests/test_params_registry_usage.py',
'openpilot/selfdrive/selfdrived/tests',
'openpilot/starpilot/tests',
'openpilot/starpilot/aol/tests',
'openpilot/starpilot/analytics/tests',
'openpilot/starpilot/gps/tests',
'opendbc_repo/opendbc/car/tests/test_gps.py',
'openpilot/starpilot/bluetooth/tests/test_owner.py',
'openpilot/starpilot/flm/tests/test_controller_replay.py',
'openpilot/starpilot/controllers/tests',
'openpilot/starpilot/car/tesla/tests',
'openpilot/starpilot/lateral/tests',
'openpilot/starpilot/longitudinal/tests',
'openpilot/starpilot/speed_limits/tests/test_runtime_replay.py',
'openpilot/starpilot/drive_state/tests',
'openpilot/starpilot/models/tests',
'openpilot/starpilot/software/tests/test_fast_update.py',
'openpilot/starpilot/software/tests/test_update_process.py',
'openpilot/starpilot/software/tests/test_history.py',
'openpilot/starpilot/ui/tests',
'openpilot/starpilot/galaxy/tests/test_access.py',
'openpilot/starpilot/galaxy/tests/test_pairing_migration.py',
'openpilot/starpilot/galaxy/tests/test_settings_defaults.py',
'openpilot/starpilot/galaxy/tests/test_vehicle_configuration.py',
'openpilot/starpilot/galaxy/tests/test_tesla_screen_settings.py',
'opendbc_repo/opendbc/car/gm/tests/test_bolt_pedal.py',
'opendbc_repo/opendbc/car/gm/tests/test_camera_acc_pedal.py',
'opendbc_repo/opendbc/car/gm/tests/test_volt_transitions.py',
'opendbc_repo/opendbc/car/gm/tests/test_ordinary_cc.py::TestMalibuHybridCc',
'opendbc_repo/opendbc/safety/tests/test_gm_bolt_pedal.py',
'opendbc_repo/opendbc/safety/tests/test_gm_camera_acc_pedal.py',
'opendbc_repo/opendbc/safety/tests/test_gm_cc_pedal.py',
'opendbc_repo/opendbc/safety/tests/test_gm_hybrid_cc.py',
'opendbc_repo/opendbc/car/tesla/tests/test_screen_button.py',
'opendbc_repo/opendbc/safety/tests/test_tesla_screen_button.py',
'opendbc_repo/opendbc/safety/tests/test_toyota.py',
'opendbc_repo/opendbc/car/toyota/tests/test_retrofit.py',
'opendbc_repo/opendbc/car/honda/tests/test_stock_aol.py',
'openpilot/selfdrive/car/tests/test_alpha_long_saved_intent.py',
'openpilot/selfdrive/car/tests/test_bolt_cc_integration.py',
'openpilot/selfdrive/car/tests/test_bolt_cruise_event.py',
'openpilot/selfdrive/car/tests/test_carparams_first_publish.py',
'openpilot/selfdrive/car/tests/test_conditional_manual_event.py',
'openpilot/selfdrive/car/tests/test_cruise_intervals.py',
'openpilot/selfdrive/car/tests/test_cruise_speed.py',
'openpilot/selfdrive/car/tests/test_curve_press_event.py',
'openpilot/selfdrive/car/tests/test_distance_personality_claim.py',
'openpilot/selfdrive/car/tests/test_honda_aol.py',
'openpilot/selfdrive/car/tests/test_hyundai_aol.py',
'openpilot/selfdrive/car/tests/test_init_lifecycle.py',
'openpilot/selfdrive/car/tests/test_lkas_long_only.py',
'opendbc_repo/opendbc/car/gm/tests/test_suburban_gateway.py',
'opendbc_repo/opendbc/car/gm/tests/test_volt_alternate_brake.py',
'opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_handoff.py',
'opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_stock_parser.py',
'opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_engagement_messages.py',
'opendbc_repo/opendbc/safety/tests/test_hyundai_ioniq6_long.py',
) + UI_PYTEST_FILES
def main(arguments=None):
parser = argparse.ArgumentParser(description=__doc__, epilog=(
'Default: lint, packaging/CI/updater checks, startup/migration, Params, controls, models, UI, and Bolt/Ioniq regressions. '
+ 'First use prepares host dependencies; later runs reuse them. No device build, flash, push, or road qualification.'))
parser.add_argument('--full', action='store_true', help='Run the existing full Linux pre-deployment suite, including recorded routes and replay')
parser.add_argument('--download', action='store_true', help='Allow pinned recorded-route downloads with --full (replay may also fetch its logs)')
parser.add_argument('--output', type=Path, help='New directory for per-check logs; defaults to a temporary report directory')
args = parser.parse_args(arguments)
if args.download and not args.full:
parser.error('--download requires --full; the default checks do not fetch routes')
if args.full and platform.system() != 'Linux':
parser.error('--full requires Linux for native transport and map IPC; run ./test for local checks on macOS')
output = args.output.expanduser().resolve() if args.output else Path(tempfile.mkdtemp(prefix='starpilot-tests-')) / 'results'
if output.exists():
parser.error('--output must be new so previous reports are preserved')
if output.is_relative_to(ROOT):
parser.error('--output must be outside the source checkout')
if os.environ.get('SP_HOST_RUNTIME') != '1':
options = ['--output', str(output), *(['--full'] if args.full else []), *(['--download'] if args.download else [])]
print(f'Preparing isolated host tests. Reports: {output}', flush=True)
started = time.monotonic()
result = subprocess.run([str(ROOT / 'dev'), 'python', 'tools/ci/run_local_tests.py', *options], cwd=ROOT, check=False)
print(f'Total including host preparation: {time.monotonic() - started:.1f}s', flush=True)
return result.returncode if result.returncode >= 0 else 128 - result.returncode
environment = dict(os.environ, SCALE='1', RAYLIB_BACKEND='desktop' if platform.system() == 'Darwin' else 'headless',
FUZZ_SEED='0', PYTEST_DISABLE_PLUGIN_AUTOLOAD='1')
for key in ('FAST', 'RUN', 'SKIP', 'PYTEST_ADDOPTS', 'PYTEST_PLUGINS'):
environment.pop(key, None)
if args.full:
return subprocess.call([sys.executable, 'tools/ci/run_predeploy.py', '--output', str(output),
*(['--download'] if args.download else [])], cwd=ROOT, env=environment)
if platform.system() == 'Darwin':
for formula in ('coreutils', 'gnu-sed'):
for prefix in ('/opt/homebrew', '/usr/local'):
directory = Path(prefix) / f'opt/{formula}/libexec/gnubin'
if directory.is_dir():
environment['PATH'] = str(directory) + os.pathsep + environment.get('PATH', '')
break
sed = shutil.which('sed', path=environment.get('PATH'))
if sed is None or 'GNU sed' not in subprocess.run(
[sed, '--version'], env=environment, capture_output=True, text=True, check=False).stdout:
parser.error('GNU sed is required for the updater checks; install gnu-sed (brew install gnu-sed on macOS)')
if shutil.which('timeout', path=environment.get('PATH')) is None:
parser.error('GNU timeout is required for the updater checks; install coreutils (brew install coreutils on macOS)')
shared_memory = '/tmp' if platform.system() == 'Darwin' else '/dev/shm'
with tempfile.TemporaryDirectory(prefix='starpilot-test-params-') as params, \
tempfile.TemporaryDirectory(prefix='msgq_starpilot-test-', dir=shared_memory) as messaging:
environment.update(PARAMS_ROOT=params, OPENPILOT_PREFIX=Path(messaging).name.removeprefix('msgq_'))
plan = commands(output, Path('/unused'))['source']
aol_protocol = output / 'test_aol_protocol'
plan.extend((('aol-protocol-build', ['c++', '-std=c++17', '-I.', '-Iopenpilot', '-Iopendbc_repo',
'openpilot/selfdrive/pandad/tests/test_aol_protocol.cc', '-o', str(aol_protocol)]),
('aol-protocol', [str(aol_protocol)])))
plan.append(('regressions', [sys.executable, '-m', 'pytest', '-q', '--import-mode=importlib',
'--junitxml', str(output / 'regressions.xml'), *QUICK_TARGETS]))
result = run(plan, output, environment=environment)
print(f'{"PASS" if result == 0 else "FAIL"}: local pre-push checks. Reports: {output}', flush=True)
return result
if __name__ == '__main__':
raise SystemExit(main())