move nose tests to unittest (#22665)

* move to unittest

* boardd loopback

* no more nose

* phone only

* fix panda decorator
pull/22689/head
Adeeb Shihadeh 2021-10-25 09:44:04 -07:00 committed by GitHub
parent e556d3d3d3
commit b5960b9dc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 143 additions and 130 deletions

4
Jenkinsfile vendored
View File

@ -170,8 +170,8 @@ pipeline {
steps {
phone_steps("eon", [
["build", "cd selfdrive/manager && ./build.py"],
["test sounds", "nosetests -s selfdrive/ui/tests/test_sounds.py"],
["test boardd loopback", "nosetests -s selfdrive/boardd/tests/test_boardd_loopback.py"],
["test sounds", "python selfdrive/ui/tests/test_soundd.py"],
["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"],
["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"],
["test encoder", "python selfdrive/loggerd/tests/test_encoder.py"],
["test logcatd", "python selfdrive/logcatd/tests/test_logcatd_android.py"],

View File

@ -2,96 +2,104 @@
import os
import random
import time
import unittest
from collections import defaultdict
from functools import wraps
import cereal.messaging as messaging
from cereal import car
from common.basedir import BASEDIR
from common.params import Params
from common.spinner import Spinner
from common.timeout import Timeout
from panda import Panda
from selfdrive.boardd.boardd import can_list_to_can_capnp
from selfdrive.car import make_can_msg
from selfdrive.test.helpers import with_processes
from selfdrive.test.helpers import phone_only, with_processes
def reset_panda(fn):
@wraps(fn)
def wrapper():
def reset_panda(f):
@wraps(f)
def wrapper(*args, **kwargs):
p = Panda()
for i in [0, 1, 2, 0xFFFF]:
p.can_clear(i)
p.reset()
p.close()
fn()
f(*args, **kwargs)
return wrapper
os.environ['STARTED'] = '1'
os.environ['BOARDD_LOOPBACK'] = '1'
os.environ['BASEDIR'] = BASEDIR
@reset_panda
@with_processes(['pandad'])
def test_boardd_loopback():
# wait for boardd to init
spinner = Spinner()
time.sleep(2)
class TestBoardd(unittest.TestCase):
with Timeout(60, "boardd didn't start"):
sm = messaging.SubMaster(['pandaStates'])
while sm.rcv_frame['pandaStates'] < 1:
sm.update(1000)
@classmethod
def setUpClass(cls):
cls.spinner = Spinner()
# boardd blocks on CarVin and CarParams
cp = car.CarParams.new_message()
@classmethod
def tearDownClass(cls):
cls.spinner.close()
safety_config = car.CarParams.SafetyConfig.new_message()
safety_config.safetyModel = car.CarParams.SafetyModel.allOutput
cp.safetyConfigs = [safety_config]
@phone_only
@reset_panda
@with_processes(['pandad'])
def test_loopback(self):
# wait for boardd to init
time.sleep(2)
Params().put("CarVin", b"0"*17)
Params().put_bool("ControlsReady", True)
Params().put("CarParams", cp.to_bytes())
with Timeout(60, "boardd didn't start"):
sm = messaging.SubMaster(['pandaStates'])
while sm.rcv_frame['pandaStates'] < 1:
sm.update(1000)
sendcan = messaging.pub_sock('sendcan')
can = messaging.sub_sock('can', conflate=False, timeout=100)
# boardd blocks on CarVin and CarParams
cp = car.CarParams.new_message()
time.sleep(1)
safety_config = car.CarParams.SafetyConfig.new_message()
safety_config.safetyModel = car.CarParams.SafetyModel.allOutput
cp.safetyConfigs = [safety_config]
n = 1000
for i in range(n):
spinner.update(f"boardd loopback {i}/{n}")
params = Params()
params.put("CarVin", b"0"*17)
params.put_bool("ControlsReady", True)
params.put("CarParams", cp.to_bytes())
sent_msgs = defaultdict(set)
for _ in range(random.randrange(10)):
to_send = []
for __ in range(random.randrange(100)):
bus = random.randrange(3)
addr = random.randrange(1, 1<<29)
dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))])
sent_msgs[bus].add((addr, dat))
to_send.append(make_can_msg(addr, dat, bus))
sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan'))
sendcan = messaging.pub_sock('sendcan')
can = messaging.sub_sock('can', conflate=False, timeout=100)
max_recv = 10
while max_recv > 0 and any(len(sent_msgs[bus]) for bus in range(3)):
recvd = messaging.drain_sock(can, wait_for_one=True)
for msg in recvd:
for m in msg.can:
if m.src >= 128:
k = (m.address, m.dat)
assert k in sent_msgs[m.src-128]
sent_msgs[m.src-128].discard(k)
max_recv -= 1
time.sleep(1)
# if a set isn't empty, messages got dropped
for bus in range(3):
assert not len(sent_msgs[bus]), f"loop {i}: bus {bus} missing {len(sent_msgs[bus])} messages"
n = 1000
for i in range(n):
self.spinner.update(f"boardd loopback {i}/{n}")
spinner.close()
sent_msgs = defaultdict(set)
for _ in range(random.randrange(10)):
to_send = []
for __ in range(random.randrange(100)):
bus = random.randrange(3)
addr = random.randrange(1, 1<<29)
dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))])
sent_msgs[bus].add((addr, dat))
to_send.append(make_can_msg(addr, dat, bus))
sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan'))
max_recv = 10
while max_recv > 0 and any(len(sent_msgs[bus]) for bus in range(3)):
recvd = messaging.drain_sock(can, wait_for_one=True)
for msg in recvd:
for m in msg.can:
if m.src >= 128:
k = (m.address, m.dat)
assert k in sent_msgs[m.src-128]
sent_msgs[m.src-128].discard(k)
max_recv -= 1
# if a set isn't empty, messages got dropped
for bus in range(3):
assert not len(sent_msgs[bus]), f"loop {i}: bus {bus} missing {len(sent_msgs[bus])} messages"
if __name__ == "__main__":
test_boardd_loopback()
unittest.main()

View File

@ -1,10 +1,9 @@
import time
from functools import wraps
from nose.tools import nottest
from selfdrive.hardware import PC
from selfdrive.version import training_version, terms_version
from selfdrive.manager.process_config import managed_processes
from selfdrive.version import training_version, terms_version
def set_params_enabled():
@ -17,11 +16,13 @@ def set_params_enabled():
params.put_bool("Passive", False)
def phone_only(x):
if PC:
return nottest(x)
else:
return x
def phone_only(f):
@wraps(f)
def wrap(self, *args, **kwargs):
if PC:
self.skipTest("This test is not meant to run on PC")
f(self, *args, **kwargs)
return wrap
def with_processes(processes, init_time=0, ignore_stopped=None):

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
import subprocess
import time
import unittest
from cereal import log, car
import cereal.messaging as messaging
from selfdrive.test.helpers import phone_only, with_processes
# TODO: rewrite for unittest
from common.realtime import DT_CTRL
from selfdrive.hardware import HARDWARE
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
SOUNDS = {
# sound: total writes
AudibleAlert.none: 0,
AudibleAlert.chimeEngage: 173,
AudibleAlert.chimeDisengage: 173,
AudibleAlert.chimeError: 173,
AudibleAlert.chimePrompt: 173,
AudibleAlert.chimeWarning1: 163,
AudibleAlert.chimeWarning2: 216,
AudibleAlert.chimeWarning2Repeat: 470,
AudibleAlert.chimeWarningRepeat: 468,
}
def get_total_writes():
audio_flinger = subprocess.check_output('dumpsys media.audio_flinger', shell=True, encoding='utf-8').strip()
write_lines = [l for l in audio_flinger.split('\n') if l.strip().startswith('Total writes')]
return sum([int(l.split(':')[1]) for l in write_lines])
class TestSoundd(unittest.TestCase):
def test_sound_card_init(self):
assert HARDWARE.get_sound_card_online()
@phone_only
@with_processes(['soundd'])
def test_alert_sounds(self):
pm = messaging.PubMaster(['controlsState'])
# make sure they're all defined
alert_sounds = {v: k for k, v in car.CarControl.HUDControl.AudibleAlert.schema.enumerants.items()}
diff = set(SOUNDS.keys()).symmetric_difference(alert_sounds.keys())
assert len(diff) == 0, f"not all sounds defined in test: {diff}"
# wait for procs to init
time.sleep(1)
for sound, expected_writes in SOUNDS.items():
print(f"testing {alert_sounds[sound]}")
start_writes = get_total_writes()
for _ in range(int(9 / DT_CTRL)):
msg = messaging.new_message('controlsState')
msg.controlsState.alertSound = sound
msg.controlsState.alertType = str(sound)
msg.controlsState.alertText1 = "Testing Sounds"
msg.controlsState.alertText2 = f"playing {alert_sounds[sound]}"
msg.controlsState.alertSize = log.ControlsState.AlertSize.mid
pm.send('controlsState', msg)
time.sleep(DT_CTRL)
tolerance = (expected_writes % 100) * 2
actual_writes = get_total_writes() - start_writes
assert abs(expected_writes - actual_writes) <= tolerance, f"{alert_sounds[sound]}: expected {expected_writes} writes, got {actual_writes}"
if __name__ == "__main__":
unittest.main()

View File

@ -1,65 +0,0 @@
#!/usr/bin/env python3
import time
import subprocess
from cereal import log, car
import cereal.messaging as messaging
from selfdrive.test.helpers import phone_only, with_processes
from common.realtime import DT_CTRL
from selfdrive.hardware import HARDWARE
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
SOUNDS = {
# sound: total writes
AudibleAlert.none: 0,
AudibleAlert.chimeEngage: 173,
AudibleAlert.chimeDisengage: 173,
AudibleAlert.chimeError: 173,
AudibleAlert.chimePrompt: 173,
AudibleAlert.chimeWarning1: 163,
AudibleAlert.chimeWarning2: 216,
AudibleAlert.chimeWarning2Repeat: 470,
AudibleAlert.chimeWarningRepeat: 468,
}
def get_total_writes():
audio_flinger = subprocess.check_output('dumpsys media.audio_flinger', shell=True, encoding='utf-8').strip()
write_lines = [l for l in audio_flinger.split('\n') if l.strip().startswith('Total writes')]
return sum([int(l.split(':')[1]) for l in write_lines])
@phone_only
def test_sound_card_init():
assert HARDWARE.get_sound_card_online()
@phone_only
@with_processes(['soundd'])
def test_alert_sounds():
pm = messaging.PubMaster(['controlsState'])
# make sure they're all defined
alert_sounds = {v: k for k, v in car.CarControl.HUDControl.AudibleAlert.schema.enumerants.items()}
diff = set(SOUNDS.keys()).symmetric_difference(alert_sounds.keys())
assert len(diff) == 0, f"not all sounds defined in test: {diff}"
# wait for procs to init
time.sleep(1)
for sound, expected_writes in SOUNDS.items():
print(f"testing {alert_sounds[sound]}")
start_writes = get_total_writes()
for _ in range(int(9 / DT_CTRL)):
msg = messaging.new_message('controlsState')
msg.controlsState.alertSound = sound
msg.controlsState.alertType = str(sound)
msg.controlsState.alertText1 = "Testing Sounds"
msg.controlsState.alertText2 = f"playing {alert_sounds[sound]}"
msg.controlsState.alertSize = log.ControlsState.AlertSize.mid
pm.send('controlsState', msg)
time.sleep(DT_CTRL)
tolerance = (expected_writes % 100) * 2
actual_writes = get_total_writes() - start_writes
assert abs(expected_writes - actual_writes) <= tolerance, f"{alert_sounds[sound]}: expected {expected_writes} writes, got {actual_writes}"