From f1afb3e3ae4b95a1de0262d1408c49093c5ada6f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh <8762862+adeebshihadeh@users.noreply.github.com> Date: Sun, 5 Jul 2020 17:56:24 -0700 Subject: [PATCH] Sound test (#1820) * WIP sound test * it does something * refactor * phone only * update release files * test sound card init * add to CI * check writes * increase time * unused * only build cereal * small tolerance Co-authored-by: Comma Device --- Jenkinsfile | 13 ++++++ common/android.py | 4 ++ common/testing.py | 8 ---- release/files_common | 2 +- selfdrive/controls/controlsd.py | 5 +-- selfdrive/test/helpers.py | 56 ++++++++++++++++++++++++++ selfdrive/test/phone_ci.py | 2 +- selfdrive/test/test_openpilot.py | 59 +-------------------------- selfdrive/test/test_sounds.py | 68 ++++++++++++++++++++++++++++++++ 9 files changed, 147 insertions(+), 70 deletions(-) delete mode 100644 common/testing.py create mode 100644 selfdrive/test/helpers.py create mode 100755 selfdrive/test/test_sounds.py diff --git a/Jenkinsfile b/Jenkinsfile index 1cd67ef48..6afac3a23 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,6 +40,19 @@ pipeline { } } + stage('Sound Test') { + steps { + lock(resource: "", label: 'eon', inversePrecedence: true, variable: 'eon_ip', quantity: 1){ + timeout(time: 30, unit: 'MINUTES') { + dir(path: 'selfdrive/test') { + sh 'pip install paramiko' + sh 'python phone_ci.py "SCONS_CACHE=1 scons -j3 cereal/ && cd selfdrive/test && nosetests -s test_sounds.py"' + } + } + } + } + } + } } } diff --git a/common/android.py b/common/android.py index e1b1098d3..43bb0f3c1 100644 --- a/common/android.py +++ b/common/android.py @@ -12,6 +12,10 @@ NetworkStrength = log.ThermalData.NetworkStrength ANDROID = os.path.isfile('/EON') +def get_sound_card_online(): + return (os.path.isfile('/proc/asound/card0/state') and + open('/proc/asound/card0/state').read().strip() == 'ONLINE') + def getprop(key): if not ANDROID: return "" diff --git a/common/testing.py b/common/testing.py deleted file mode 100644 index 95c43b574..000000000 --- a/common/testing.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -from nose.tools import nottest - -def phone_only(x): - if os.path.isfile("/init.qcom.rc"): - return x - else: - return nottest(x) diff --git a/release/files_common b/release/files_common index be4044285..b963c6289 100644 --- a/release/files_common +++ b/release/files_common @@ -26,7 +26,6 @@ common/numpy_fast.py common/params.py common/xattr.py common/profiler.py -common/testing.py common/basedir.py common/filter_simple.py common/stat_live.py @@ -316,6 +315,7 @@ selfdrive/thermald/thermald.py selfdrive/thermald/power_monitoring.py selfdrive/test/__init__.py +selfdrive/test/helpers.py selfdrive/test/test_openpilot.py selfdrive/test/test_fingerprints.py selfdrive/test/test_car_models.py diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 43ccc7acb..f64acae10 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -2,7 +2,7 @@ import os import gc from cereal import car, log -from common.android import ANDROID +from common.android import ANDROID, get_sound_card_online from common.numpy_fast import clip from common.realtime import sec_since_boot, set_realtime_priority, set_core_affinity, Ratekeeper, DT_CTRL from common.profiler import Profiler @@ -79,8 +79,7 @@ class Controls: internet_needed or not openpilot_enabled_toggle # detect sound card presence and ensure successful init - sounds_available = (not ANDROID or (os.path.isfile('/proc/asound/card0/state') and - open('/proc/asound/card0/state').read().strip() == 'ONLINE')) + sounds_available = not ANDROID or get_sound_card_online() car_recognized = self.CP.carName != 'mock' # If stock camera is disconnected, we loaded car controls and it's not dashcam mode diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py new file mode 100644 index 000000000..2a6cf501a --- /dev/null +++ b/selfdrive/test/helpers.py @@ -0,0 +1,56 @@ +import subprocess +from functools import wraps +from nose.tools import nottest + +from common.android import ANDROID +from common.apk import update_apks, start_offroad, pm_apply_packages, android_packages +from selfdrive.manager import start_managed_process, kill_managed_process, get_running + +def phone_only(x): + if ANDROID: + return x + else: + return nottest(x) + +def with_processes(processes): + def wrapper(func): + @wraps(func) + def wrap(): + # start and assert started + [start_managed_process(p) for p in processes] + assert all(get_running()[name].exitcode is None for name in processes) + + # call the function + try: + func() + # assert processes are still started + assert all(get_running()[name].exitcode is None for name in processes) + finally: + # kill and assert all stopped + [kill_managed_process(p) for p in processes] + assert len(get_running()) == 0 + return wrap + return wrapper + +def with_apks(): + def wrapper(func): + @wraps(func) + def wrap(): + update_apks() + pm_apply_packages('enable') + start_offroad() + + func() + + try: + for package in android_packages: + apk_is_running = (subprocess.call(["pidof", package]) == 0) + assert apk_is_running, package + finally: + pm_apply_packages('disable') + for package in android_packages: + apk_is_not_running = (subprocess.call(["pidof", package]) == 1) + assert apk_is_not_running, package + return wrap + return wrapper + diff --git a/selfdrive/test/phone_ci.py b/selfdrive/test/phone_ci.py index 7040e8cb5..1c84b9a50 100755 --- a/selfdrive/test/phone_ci.py +++ b/selfdrive/test/phone_ci.py @@ -69,7 +69,7 @@ def run_on_phone(test_cmd): conn.send("exit\n") dat = b"" - conn.settimeout(150) + conn.settimeout(240) while True: try: diff --git a/selfdrive/test/test_openpilot.py b/selfdrive/test/test_openpilot.py index c03a2e41f..fc3b17dde 100644 --- a/selfdrive/test/test_openpilot.py +++ b/selfdrive/test/test_openpilot.py @@ -2,77 +2,22 @@ import os os.environ['FAKEUPLOAD'] = "1" -from common.apk import update_apks, start_offroad, pm_apply_packages, android_packages from common.params import Params from common.realtime import sec_since_boot -from common.testing import phone_only -from selfdrive.manager import manager_init, manager_prepare -from selfdrive.manager import start_managed_process, kill_managed_process, get_running -from selfdrive.manager import start_daemon_process -from functools import wraps +from selfdrive.manager import manager_init, manager_prepare, start_daemon_process +from selfdrive.test.helpers import phone_only, with_processes import json import requests import signal import subprocess import time -DID_INIT = False # must run first @phone_only def test_manager_prepare(): - global DID_INIT manager_init() manager_prepare() - DID_INIT = True - -def with_processes(processes): - def wrapper(func): - @wraps(func) - def wrap(): - if not DID_INIT: - test_manager_prepare() - - # start and assert started - [start_managed_process(p) for p in processes] - assert all(get_running()[name].exitcode is None for name in processes) - - # call the function - try: - func() - # assert processes are still started - assert all(get_running()[name].exitcode is None for name in processes) - finally: - # kill and assert all stopped - [kill_managed_process(p) for p in processes] - assert len(get_running()) == 0 - return wrap - return wrapper - -def with_apks(): - def wrapper(func): - @wraps(func) - def wrap(): - if not DID_INIT: - test_manager_prepare() - - update_apks() - pm_apply_packages('enable') - start_offroad() - - func() - - try: - for package in android_packages: - apk_is_running = (subprocess.call(["pidof", package]) == 0) - assert apk_is_running, package - finally: - pm_apply_packages('disable') - for package in android_packages: - apk_is_not_running = (subprocess.call(["pidof", package]) == 1) - assert apk_is_not_running, package - return wrap - return wrapper @phone_only @with_processes(['loggerd', 'logmessaged', 'tombstoned', 'proclogd', 'logcatd']) diff --git a/selfdrive/test/test_sounds.py b/selfdrive/test/test_sounds.py new file mode 100755 index 000000000..e7aedf4da --- /dev/null +++ b/selfdrive/test/test_sounds.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import time +import subprocess + +from cereal import car +import cereal.messaging as messaging +from selfdrive.test.helpers import phone_only, with_processes +from common.android import get_sound_card_online +from common.realtime import DT_CTRL + +AudibleAlert = car.CarControl.HUDControl.AudibleAlert + +SOUNDS = { + # sound: total writes + AudibleAlert.none: 0, + AudibleAlert.chimeEngage: 85, + AudibleAlert.chimeDisengage: 85, + AudibleAlert.chimeError: 85, + AudibleAlert.chimePrompt: 85, + AudibleAlert.chimeWarning1: 80, + AudibleAlert.chimeWarning2: 107, + AudibleAlert.chimeWarningRepeat: 134, + AudibleAlert.chimeWarning2Repeat: 177, +} + +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 get_sound_card_online() + + +@phone_only +@with_processes(['ui', 'camerad']) +def test_alert_sounds(): + + pm = messaging.PubMaster(['thermal', '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(5) + + msg = messaging.new_message('thermal') + msg.thermal.started = True + pm.send('thermal', msg) + + 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.enabled = True + msg.controlsState.active = True + msg.controlsState.alertSound = sound + msg.controlsState.alertType = str(sound) + pm.send('controlsState', msg) + time.sleep(DT_CTRL) + + actual_writes = get_total_writes() - start_writes + assert abs(expected_writes - actual_writes) <= 2, f"{alert_sounds[sound]}: expected {expected_writes} writes, got {actual_writes}"