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 <device@comma.ai>pull/1825/head
parent
8cadecae8f
commit
f1afb3e3ae
|
@ -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"'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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}"
|
Loading…
Reference in New Issue