hypothesis fuzz testing (#20818)
* add hypothesis testing
* cleanup
* some cleanup
* check for infinity too
* one-liner
* add more fields that are used
* no print
* add locationd testing
* sensor timestamp is signed
* recursive finite checking
* keep locationd inputs finite for now
* specify proces name on command line
* increase timeout and add raw speed
* abstract runner in function
* add unittest class
old-commit-hash: 33edb62967
commatwo_master
parent
5d82a760db
commit
b3838fb91e
5 changed files with 218 additions and 29 deletions
@ -1,3 +1,3 @@ |
|||||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||||
oid sha256:690153f35d93395bae301cfcd607d141203117839855754381973636a0b8a846 |
oid sha256:803c2edc32d1e8fbfb92207f5d8f073d3c89da025e3658e6c2f1c9ec649f5e0d |
||||||
size 1887 |
size 1904 |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||||
oid sha256:2965278134cfd52e027ef646eaf43b92e856fa895cfa67c81fac2ffe7887d32f |
oid sha256:050349245ca49edc60182ef21c32281623b5a5bd7c16ab1b1a8e48aecd99cc08 |
||||||
size 202729 |
size 203871 |
||||||
|
@ -0,0 +1,180 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import sys |
||||||
|
import unittest |
||||||
|
from collections import Counter |
||||||
|
|
||||||
|
import hypothesis.strategies as st |
||||||
|
import numpy as np |
||||||
|
from hypothesis import assume, given, settings |
||||||
|
|
||||||
|
from cereal import log |
||||||
|
from selfdrive.car.toyota.values import CAR as TOYOTA |
||||||
|
from selfdrive.test.process_replay.process_replay import (CONFIGS, |
||||||
|
replay_process) |
||||||
|
|
||||||
|
|
||||||
|
def get_process_config(process): |
||||||
|
return [cfg for cfg in CONFIGS if cfg.proc_name == process][0] |
||||||
|
|
||||||
|
|
||||||
|
def get_event_union_strategy(r, name): |
||||||
|
return st.fixed_dictionaries({ |
||||||
|
'valid': st.booleans(), |
||||||
|
'logMonoTime': st.integers(min_value=0, max_value=2**64-1), |
||||||
|
name: r[name[0].upper() + name[1:]], |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
def get_strategy_for_events(event_types, finite=False): |
||||||
|
# TODO: generate automatically based on capnp definitions |
||||||
|
def floats(**kwargs): |
||||||
|
allow_nan = False if finite else None |
||||||
|
allow_infinity = False if finite else None |
||||||
|
return st.floats(**kwargs, allow_nan=allow_nan, allow_infinity=allow_infinity) |
||||||
|
|
||||||
|
r = {} |
||||||
|
r['liveLocationKalman.Measurement'] = st.fixed_dictionaries({ |
||||||
|
'value': st.lists(floats(), min_size=3, max_size=3), |
||||||
|
'std': st.lists(floats(), min_size=3, max_size=3), |
||||||
|
'valid': st.booleans(), |
||||||
|
}) |
||||||
|
r['LiveLocationKalman'] = st.fixed_dictionaries({ |
||||||
|
'angularVelocityCalibrated': r['liveLocationKalman.Measurement'], |
||||||
|
'inputsOK': st.booleans(), |
||||||
|
'posenetOK': st.booleans(), |
||||||
|
}) |
||||||
|
r['CarState'] = st.fixed_dictionaries({ |
||||||
|
'vEgo': floats(width=32), |
||||||
|
'vEgoRaw': floats(width=32), |
||||||
|
'steeringPressed': st.booleans(), |
||||||
|
'steeringAngleDeg': floats(width=32), |
||||||
|
}) |
||||||
|
r['CameraOdometry'] = st.fixed_dictionaries({ |
||||||
|
'frameId': st.integers(min_value=0, max_value=2**32-1), |
||||||
|
'timestampEof': st.integers(min_value=0, max_value=2**64-1), |
||||||
|
'trans': st.lists(floats(width=32), min_size=3, max_size=3), |
||||||
|
'rot': st.lists(floats(width=32), min_size=3, max_size=3), |
||||||
|
'transStd': st.lists(floats(width=32), min_size=3, max_size=3), |
||||||
|
'rotStd': st.lists(floats(width=32), min_size=3, max_size=3), |
||||||
|
}) |
||||||
|
r['SensorEventData.SensorVec'] = st.fixed_dictionaries({ |
||||||
|
'v': st.lists(floats(width=32), min_size=3, max_size=3), |
||||||
|
'status': st.integers(min_value=0, max_value=1), |
||||||
|
}) |
||||||
|
r['SensorEventData_gyro'] = st.fixed_dictionaries({ |
||||||
|
'version': st.just(1), |
||||||
|
'sensor': st.just(5), |
||||||
|
'type': st.just(16), # BMX055 |
||||||
|
'timestamp': st.integers(min_value=0, max_value=2**63-1), |
||||||
|
'source': st.just(8), |
||||||
|
'gyroUncalibrated': r['SensorEventData.SensorVec'], |
||||||
|
}) |
||||||
|
r['SensorEventData_accel'] = st.fixed_dictionaries({ |
||||||
|
'version': st.just(1), |
||||||
|
'sensor': st.just(1), |
||||||
|
'type': st.just(1), # BMX055 |
||||||
|
'timestamp': st.integers(min_value=0, max_value=2**63-1), |
||||||
|
'source': st.just(8), |
||||||
|
'acceleration': r['SensorEventData.SensorVec'], |
||||||
|
}) |
||||||
|
r['SensorEvents'] = st.lists(st.one_of(r['SensorEventData_gyro'], r['SensorEventData_accel']), min_size=1) |
||||||
|
r['GpsLocationExternal'] = st.fixed_dictionaries({ |
||||||
|
'flags': st.integers(min_value=0, max_value=1), |
||||||
|
'latitude': floats(), |
||||||
|
'longitude': floats(), |
||||||
|
'altitude': floats(), |
||||||
|
'speed': floats(width=32), |
||||||
|
'bearingDeg': floats(width=32), |
||||||
|
'accuracy': floats(width=32), |
||||||
|
'timestamp': st.integers(min_value=0, max_value=2**63-1), |
||||||
|
'source': st.just(6), # Ublox |
||||||
|
'vNED': st.lists(floats(width=32), min_size=3, max_size=3), |
||||||
|
'verticalAccuracy': floats(width=32), |
||||||
|
'bearingAccuracyDeg': floats(width=32), |
||||||
|
'speedAccuracy': floats(width=32), |
||||||
|
}) |
||||||
|
r['LiveCalibration'] = st.fixed_dictionaries({ |
||||||
|
'calStatus': st.integers(min_value=0, max_value=1), |
||||||
|
'rpyCalib': st.lists(floats(width=32), min_size=3, max_size=3), |
||||||
|
}) |
||||||
|
|
||||||
|
return st.lists(st.one_of(*[get_event_union_strategy(r, n) for n in event_types])) |
||||||
|
|
||||||
|
|
||||||
|
def get_strategy_for_process(process, finite=False): |
||||||
|
return get_strategy_for_events(get_process_config(process).pub_sub.keys(), finite) |
||||||
|
|
||||||
|
|
||||||
|
def convert_to_lr(msgs): |
||||||
|
return [log.Event.new_message(**m).as_reader() for m in msgs] |
||||||
|
|
||||||
|
|
||||||
|
def assume_all_services_present(cfg, lr): |
||||||
|
tps = Counter([m.which() for m in lr]) |
||||||
|
for p in cfg.pub_sub: |
||||||
|
assume(tps[p] > 0) |
||||||
|
|
||||||
|
|
||||||
|
def is_finite(d, exclude=[], prefix=""): # pylint: disable=dangerous-default-value |
||||||
|
ret = True |
||||||
|
for k, v in d.items(): |
||||||
|
name = prefix + f"{k}" |
||||||
|
if name in exclude: |
||||||
|
continue |
||||||
|
|
||||||
|
if isinstance(v, dict): |
||||||
|
ret = ret and is_finite(v, exclude, name + ".") |
||||||
|
else: |
||||||
|
try: |
||||||
|
ret = ret and np.isfinite(v).all() |
||||||
|
except TypeError: |
||||||
|
pass |
||||||
|
|
||||||
|
return ret |
||||||
|
|
||||||
|
|
||||||
|
def test_process(dat, name): |
||||||
|
cfg = get_process_config(name) |
||||||
|
lr = convert_to_lr(dat) |
||||||
|
assume_all_services_present(cfg, lr) |
||||||
|
return replay_process(cfg, lr, TOYOTA.COROLLA_TSS2) |
||||||
|
|
||||||
|
|
||||||
|
class TestFuzzy(unittest.TestCase): |
||||||
|
@given(get_strategy_for_process('paramsd')) |
||||||
|
@settings(deadline=1000) |
||||||
|
def test_paramsd(self, dat): |
||||||
|
for r in test_process(dat, 'paramsd'): |
||||||
|
lp = r.liveParameters.to_dict() |
||||||
|
assert is_finite(lp) |
||||||
|
|
||||||
|
@given(get_strategy_for_process('locationd', finite=True)) |
||||||
|
@settings(deadline=10000) |
||||||
|
def test_locationd(self, dat): |
||||||
|
exclude = [ |
||||||
|
'positionGeodetic.std', |
||||||
|
'velocityNED.std', |
||||||
|
'orientationNED.std', |
||||||
|
'calibratedOrientationECEF.std', |
||||||
|
] |
||||||
|
for r in test_process(dat, 'locationd'): |
||||||
|
lp = r.liveLocationKalman.to_dict() |
||||||
|
assert is_finite(lp, exclude) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
procs = { |
||||||
|
'locationd': TestFuzzy().test_locationd, |
||||||
|
'paramsd': TestFuzzy().test_paramsd, |
||||||
|
} |
||||||
|
|
||||||
|
if len(sys.argv) != 2: |
||||||
|
print("Usage: ./test_fuzzy.py <process name>") |
||||||
|
sys.exit(0) |
||||||
|
|
||||||
|
proc = sys.argv[1] |
||||||
|
if proc not in procs: |
||||||
|
print(f"{proc} not available") |
||||||
|
sys.exit(0) |
||||||
|
else: |
||||||
|
procs[proc]() |
Loading…
Reference in new issue