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
This commit is contained in:
Willem Melching 2021-05-05 13:25:29 +02:00 committed by GitHub
parent 5d82a760db
commit b3838fb91e
5 changed files with 218 additions and 29 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ venv/
.vscode*
model2.png
a.out
.hypothesis
*.dylib
*.DSYM

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:690153f35d93395bae301cfcd607d141203117839855754381973636a0b8a846
size 1887
oid sha256:803c2edc32d1e8fbfb92207f5d8f073d3c89da025e3658e6c2f1c9ec649f5e0d
size 1904

4
Pipfile.lock generated
View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2965278134cfd52e027ef646eaf43b92e856fa895cfa67c81fac2ffe7887d32f
size 202729
oid sha256:050349245ca49edc60182ef21c32281623b5a5bd7c16ab1b1a8e48aecd99cc08
size 203871

View File

@ -14,7 +14,7 @@ from cereal import car, log
from cereal.services import service_list
from common.params import Params
from selfdrive.car.fingerprints import FW_VERSIONS
from selfdrive.car.car_helpers import get_car
from selfdrive.car.car_helpers import get_car, interfaces
from selfdrive.manager.process import PythonProcess
from selfdrive.manager.process_config import managed_processes
@ -148,7 +148,7 @@ class FakePubMaster(messaging.PubMaster):
return dat
def fingerprint(msgs, fsm, can_sock):
def fingerprint(msgs, fsm, can_sock, fingerprint):
print("start fingerprinting")
fsm.wait_on_getitem = True
@ -172,7 +172,11 @@ def fingerprint(msgs, fsm, can_sock):
print("finished fingerprinting")
def get_car_params(msgs, fsm, can_sock):
def get_car_params(msgs, fsm, can_sock, fingerprint):
if fingerprint:
CarInterface, _, _ = interfaces[fingerprint]
CP = CarInterface.get_params(fingerprint)
else:
can = FakeSocket(wait=False)
sendcan = FakeSocket(wait=False)
@ -328,14 +332,14 @@ CONFIGS = [
]
def replay_process(cfg, lr):
def replay_process(cfg, lr, fingerprint=None):
if cfg.fake_pubsubmaster:
return python_replay_process(cfg, lr)
return python_replay_process(cfg, lr, fingerprint)
else:
return cpp_replay_process(cfg, lr)
return cpp_replay_process(cfg, lr, fingerprint)
def python_replay_process(cfg, lr):
def python_replay_process(cfg, lr, fingerprint=None):
sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub]
pub_sockets = [s for s in cfg.pub_sub.keys() if s != 'can']
@ -356,8 +360,6 @@ def python_replay_process(cfg, lr):
params.put_bool("CommunityFeaturesToggle", True)
os.environ['NO_RADAR_SLEEP'] = "1"
os.environ['SKIP_FW_QUERY'] = ""
os.environ['FINGERPRINT'] = ""
# TODO: remove after getting new route for civic & accord
migration = {
@ -365,6 +367,12 @@ def python_replay_process(cfg, lr):
"HONDA ACCORD 2018 SPORT 2T": "HONDA ACCORD 2018 2T",
}
if fingerprint is not None:
os.environ['SKIP_FW_QUERY'] = "1"
os.environ['FINGERPRINT'] = fingerprint
else:
os.environ['SKIP_FW_QUERY'] = ""
os.environ['FINGERPRINT'] = ""
for msg in lr:
if msg.which() == 'carParams':
car_fingerprint = migration.get(msg.carParams.carFingerprint, msg.carParams.carFingerprint)
@ -385,7 +393,7 @@ def python_replay_process(cfg, lr):
if cfg.init_callback is not None:
if 'can' not in list(cfg.pub_sub.keys()):
can_sock = None
cfg.init_callback(all_msgs, fsm, can_sock)
cfg.init_callback(all_msgs, fsm, can_sock, fingerprint)
CP = car.CarParams.from_bytes(params.get("CarParams", block=True))
@ -421,7 +429,7 @@ def python_replay_process(cfg, lr):
return log_msgs
def cpp_replay_process(cfg, lr):
def cpp_replay_process(cfg, lr, fingerprint=None):
sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] # We get responses here
pm = messaging.PubMaster(cfg.pub_sub.keys())
sockets = {s: messaging.sub_sock(s, timeout=1000) for s in sub_sockets}

View File

@ -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]()