Skip to content

Commit 34b33eb

Browse files
committed
test-case: Add ALSA conformance tests
Add ALSA conformance tests from ChromeOS Audio Test package. The new test case `check-alsa-conformance.sh` executes `alsa_conformnance_test` and compose its results into a JSON file. Signed-off-by: Dmitrii Golovanov <dmitrii.golovanov@intel.com>
1 parent 4a5a1d9 commit 34b33eb

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed
Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
#!/bin/bash
2+
3+
# Copyright(c) 2025 Intel Corporation.
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
##
7+
## Case Name: Execute ALSA conformance tests.
8+
##
9+
## Preconditions:
10+
## - ChromeOS Audio Test package is installed
11+
## https://chromium.googlesource.com/chromiumos/platform/audiotest
12+
##
13+
## Description:
14+
## Run `alsa_conformance_test.py` for the playback devices
15+
## and the capture devices with the test suite paramenters given.
16+
## Compose resulting JSON reports.
17+
##
18+
## To select PCMs use either -d, or -p with or without -c parameters.
19+
## If a PCM id has no device id (e.g. 'hw:sofnocodec' instead of 'hw:sofnocodec,0')
20+
## then all devices on that card will be selected for the test run.
21+
## To select all available PCMs omit any -d, -p, -c parameters.
22+
##
23+
## Pass multiple values of the test parameters -d, -p, -c, -r, -F enclosing them
24+
## in quotes, eg. `-F 'U8 S16_LE'` or `-p 'sofnocodec,1 sofnocodec,2'`
25+
##
26+
## Case steps:
27+
## 0. Set ALSA parameters.
28+
## 1. For each PCM selected:
29+
## 1.1 Try to start `alsa_conformance_test` in device info mode.
30+
## 1.2 Start `alsa conformance_test.py` for playback devices.
31+
## 1.3 Start `alsa conformance_test.py` for capture devices.
32+
## 2. Compose the resulting JSON report.
33+
##
34+
## Expect result:
35+
## ALSA conformance results collected and saved in `test_result.json` file.
36+
## Exit status 0.
37+
## In case of errors this test tries to continue and have its JSON report correctly structured.
38+
##
39+
40+
TESTDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
41+
TESTLIB="${TESTDIR}/case-lib"
42+
43+
# shellcheck source=case-lib/lib.sh
44+
source "${TESTLIB}/lib.sh"
45+
46+
OPT_NAME['d']='device' OPT_DESC['d']='ALSA pcm device for playback and capture. Example: hw:0'
47+
OPT_HAS_ARG['d']=1 OPT_VAL['d']=''
48+
49+
OPT_NAME['p']='pcm_p' OPT_DESC['p']='ALSA pcm device for playback only. Example: hw:soundwire,0'
50+
OPT_HAS_ARG['p']=1 OPT_VAL['p']=''
51+
52+
OPT_NAME['c']='pcm_c' OPT_DESC['c']='ALSA pcm device for capture only. Example: hw:soundwire,1'
53+
OPT_HAS_ARG['c']=1 OPT_VAL['c']=''
54+
55+
OPT_NAME['r']='rates' OPT_DESC['r']='Sample ratis to try. Default: check all available rates.'
56+
OPT_HAS_ARG['r']=1 OPT_VAL['r']=''
57+
58+
OPT_NAME['F']='formats' OPT_DESC['F']='Data formats to try. Default: check all available formats.'
59+
OPT_HAS_ARG['F']=1 OPT_VAL['F']=''
60+
61+
OPT_NAME['s']='sof-logger' OPT_DESC['s']="Open sof-logger trace the data will store at $LOG_ROOT"
62+
OPT_HAS_ARG['s']=0 OPT_VAL['s']=1
63+
64+
OPT_NAME['v']='verbose' OPT_DESC['v']='Verbose logging.'
65+
OPT_HAS_ARG['v']=0 OPT_VAL['v']=0
66+
67+
OPT_NAME['E']='rate-diff' OPT_DESC['E']="ALSA conformance --rate-criteria-diff-pct (difference, %)."
68+
OPT_HAS_ARG['E']=1 OPT_VAL['E']=''
69+
70+
OPT_NAME['e']='rate-err' OPT_DESC['e']="ALSA conformance --rate-err-criteria (max rate error)."
71+
OPT_HAS_ARG['e']=1 OPT_VAL['e']=''
72+
73+
OPT_NAME['a']='avail-delay' OPT_DESC['a']="ALSA conformance --avail-delay"
74+
OPT_HAS_ARG['a']=0 OPT_VAL['a']=0
75+
76+
OPT_NAME['T']='test-suites' OPT_DESC['T']="ALSA conformance --test-suites (Default: all)."
77+
OPT_HAS_ARG['T']=1 OPT_VAL['T']=''
78+
79+
func_opt_parse_option "$@"
80+
81+
# Options for the ALSA conformance test script call
82+
CMD_OPTS=()
83+
84+
# Recompose OPT_VAL[$1] option as ALSA test script option $2
85+
add_cmd_option()
86+
{
87+
local opt_val="${OPT_VAL[$1]}"
88+
local prefix=$2
89+
90+
if [ -n "${opt_val}" ]; then
91+
# Split list parameters to separate values
92+
opt_val=("${opt_val//[ ,]/ }")
93+
# shellcheck disable=SC2206
94+
CMD_OPTS+=("${prefix}" ${opt_val[@]})
95+
fi
96+
}
97+
98+
init_globals()
99+
{
100+
add_cmd_option 'r' '--allow-rates'
101+
add_cmd_option 'F' '--allow-formats'
102+
add_cmd_option 'E' '--rate-criteria-diff-pct'
103+
add_cmd_option 'e' '--rate-err-criteria'
104+
add_cmd_option 'T' '--test-suites'
105+
106+
run_verbose=0
107+
if [[ "${OPT_VAL['v']}" -eq 1 ]]; then
108+
run_verbose=1
109+
CMD_OPTS+=("--log-file" "/dev/stdout")
110+
fi
111+
112+
if [[ "${OPT_VAL['a']}" -eq 1 ]]; then
113+
CMD_OPTS+=('--avail-delay')
114+
fi
115+
116+
AUDIOTEST_OUT="${LOG_ROOT}/alsa_conformance"
117+
RESULT_JSON="${LOG_ROOT}/test_result.json"
118+
119+
ALSA_CONFORMANCE_PATH=$([ -n "$ALSA_CONFORMANCE_PATH" ] || realpath "${TESTDIR}/../audiotest")
120+
ALSA_CONFORMANCE_TEST="${ALSA_CONFORMANCE_PATH}/alsa_conformance_test"
121+
}
122+
123+
check_alsa_conformance_suite()
124+
{
125+
if [ -d "${ALSA_CONFORMANCE_PATH}" ]; then
126+
if [ -x "${ALSA_CONFORMANCE_TEST}" ] && [ -x "${ALSA_CONFORMANCE_TEST}.py" ]; then
127+
dlogi "Use ALSA conformance test suite: ${ALSA_CONFORMANCE_TEST}"
128+
return
129+
fi
130+
fi
131+
skip_test "ALSA conformance test suite is missing at: ${ALSA_CONFORMANCE_PATH}"
132+
}
133+
134+
get_card_devices()
135+
{
136+
local mode=$1
137+
local arg_pcm=$2
138+
139+
# select all devices by default
140+
[ -z "${arg_pcm}" ] && arg_pcm="[^ ]+"
141+
142+
local alsa_list=''
143+
local res_devs=("${arg_pcm}")
144+
145+
if [ "${mode}" == 'playback' ]; then
146+
alsa_list=('aplay' '-l')
147+
elif [ "${mode}" == 'capture' ]; then
148+
alsa_list=('arecord' '-l')
149+
else
150+
return
151+
fi
152+
153+
if [ -n "${arg_pcm}" ]; then
154+
# check is only card name is given or exact device
155+
if [ "${arg_pcm}" == "${arg_pcm##*,}" ]; then
156+
# strip 'hw:' prefix
157+
arg_pcm="${arg_pcm#*:}"
158+
# shellcheck disable=SC2016
159+
local gawk_script='match($0, /^card [0-9]+: ('"${arg_pcm}"') .+ device ([0-9]+): /, arr) { print "hw:" arr[1] "," arr[2] }'
160+
mapfile -t res_devs < <( "${alsa_list[@]}" | gawk "${gawk_script}" )
161+
fi
162+
echo "${res_devs[@]}"
163+
fi
164+
}
165+
166+
select_PCMs()
167+
{
168+
# Don't quote to split into separate items:
169+
# shellcheck disable=SC2206
170+
alsa_device=(${OPT_VAL['d']//[ ]/ })
171+
# shellcheck disable=SC2206
172+
pcm_p=(${OPT_VAL['p']//[ ]/ })
173+
# shellcheck disable=SC2206
174+
pcm_c=(${OPT_VAL['c']//[ ]/ })
175+
176+
if [ -n "${alsa_device[*]}" ]; then
177+
if [ -n "${pcm_p[*]}" ] || [ -n "${pcm_c[*]}" ]; then
178+
die "Give either an ALSA device (-d), or ALSA playback(-p) and/or capture(-c) PCMs."
179+
fi
180+
# we got only -d
181+
pcm_p=("${alsa_device[@]}")
182+
pcm_c=("${alsa_device[@]}")
183+
elif [ -z "${pcm_p[*]}" ] && [ -z "${pcm_c[*]}" ]; then
184+
dlogi "No ALSA PCM is specified - scan all playback and capture devices"
185+
pcm_p=('')
186+
pcm_c=('')
187+
fi
188+
dlogi "pcm_p=(${pcm_p[*]})"
189+
dlogi "pcm_c=(${pcm_c[*]})"
190+
191+
local p_dev_expanded=()
192+
PLAYBACK_DEVICES=()
193+
194+
for p_dev in "${pcm_p[@]}"
195+
do
196+
mapfile -t p_dev_expanded < <(get_card_devices 'playback' "${p_dev}")
197+
# shellcheck disable=SC2206
198+
PLAYBACK_DEVICES+=( ${p_dev_expanded[@]} )
199+
done
200+
dlogi "Playback devices: ${PLAYBACK_DEVICES[*]}"
201+
202+
CAPTURE_DEVICES=()
203+
for c_dev in "${pcm_c[@]}"
204+
do
205+
mapfile -t p_dev_expanded < <(get_card_devices 'capture' "${c_dev}")
206+
# shellcheck disable=SC2206
207+
CAPTURE_DEVICES+=( ${p_dev_expanded[@]} )
208+
done
209+
dlogi "Capture devices: ${CAPTURE_DEVICES[*]}"
210+
}
211+
212+
set_alsa()
213+
{
214+
reset_sof_volume
215+
216+
# If MODEL is defined, set proper gain for the platform
217+
if [ -z "$MODEL" ]; then
218+
dlogw "No MODEL is defined. Please define MODEL to run alsa_settings/\${MODEL}.sh"
219+
else
220+
set_alsa_settings "$MODEL"
221+
fi
222+
}
223+
224+
alsa_conformance_device_info()
225+
{
226+
local mode=$1
227+
local device=$2
228+
local opt=()
229+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
230+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
231+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
232+
233+
local run_cmd=("${ALSA_CONFORMANCE_TEST}" "${opt[@]}" "--dev_info_only")
234+
dlogc "${run_cmd[@]}"
235+
local rc=1
236+
"${run_cmd[@]}" ; rc=$?
237+
[[ "${rc}" -ne 0 ]] && dloge "Failed to get device info, rc=${rc}"
238+
}
239+
240+
alsa_conformance_test()
241+
{
242+
local mode=$1
243+
local device=$2
244+
local opt=()
245+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
246+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
247+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
248+
249+
local run_prefix=("export" "PATH=${ALSA_CONFORMANCE_PATH}:${PATH}")
250+
local run_cmd=()
251+
run_cmd+=("${ALSA_CONFORMANCE_TEST}.py" "${CMD_OPTS[@]}" "${opt[@]}")
252+
run_cmd+=("--json-file" "${AUDIOTEST_OUT}_${mode}.json")
253+
dlogc "${run_cmd[@]}"
254+
local rc=1
255+
"${run_prefix[@]}" && "${run_cmd[@]}" ; rc=$?
256+
[[ "${rc}" -ne 0 ]] && dloge "Failed ${mode} tests, rc=${rc}"
257+
}
258+
259+
report_start()
260+
{
261+
dlogi "Compose ${RESULT_JSON}"
262+
printf '{"options":{%s}, "alsa_conformance":[' "$(options2json)" > "${RESULT_JSON}"
263+
}
264+
265+
json_next_sep=""
266+
267+
report_conformance()
268+
{
269+
local report_type=$1
270+
local report_device=$2
271+
local report_file="${AUDIOTEST_OUT}_${report_type}.json"
272+
if [ -s "${report_file}" ]; then
273+
printf '%s{"device":"%s","%s":' \
274+
"${json_next_sep}" "${report_device}" "${report_type}" >> "${RESULT_JSON}"
275+
jq --compact-output . "${report_file}" >> "${RESULT_JSON}" && rm "${report_file}"
276+
printf '}' >> "${RESULT_JSON}"
277+
json_next_sep=","
278+
else
279+
dlogw "No conformance report for ${report_type}"
280+
fi
281+
}
282+
283+
report_end()
284+
{
285+
printf ']}\n' >> "${RESULT_JSON}"
286+
[[ "${run_verbose}" -ne 0 ]] && cat "${RESULT_JSON}"
287+
}
288+
289+
assert_failures()
290+
{
291+
local report_type=$1
292+
[ -z "${report_type}" ] && return
293+
294+
local report_key="alsa_conformance[].${report_type}"
295+
local failures=""
296+
297+
failures=$(jq "[.${report_key}.fail // 0] | add" "${RESULT_JSON}")
298+
if [ -z "${failures}" ] || [ "${failures}" -ne "${failures}" ]; then
299+
die "${report_type} has invalid ${RESULT_JSON}"
300+
fi
301+
if [ "${failures}" -ne 0 ]; then
302+
die "${report_type} has ${failures} failures."
303+
fi
304+
305+
# we must have something reported as passed, even zero
306+
passes=$(jq "[.${report_key}.pass] | add // empty" "${RESULT_JSON}")
307+
if [ -z "${passes}" ] || [ "${passes}" -ne "${passes}" ]; then
308+
die "${report_type} has no results."
309+
fi
310+
}
311+
312+
run_test()
313+
{
314+
local t_mode=$1
315+
local t_dev=$2
316+
317+
dlogi "Test ${t_mode} ${t_dev}"
318+
alsa_conformance_device_info "${t_mode}" "${t_dev}"
319+
alsa_conformance_test "${t_mode}" "${t_dev}"
320+
report_conformance "${t_mode}" "${t_dev}"
321+
}
322+
323+
main()
324+
{
325+
init_globals
326+
327+
setup_kernel_check_point
328+
329+
start_test
330+
331+
check_alsa_conformance_suite
332+
333+
select_PCMs
334+
335+
logger_disabled || func_lib_start_log_collect
336+
337+
set_alsa
338+
339+
report_start
340+
341+
for p_dev in "${PLAYBACK_DEVICES[@]}"
342+
do
343+
run_test 'playback' "${p_dev}"
344+
done
345+
346+
for c_dev in "${CAPTURE_DEVICES[@]}"
347+
do
348+
run_test 'capture' "${c_dev}"
349+
done
350+
351+
report_end
352+
353+
[ -n "${PLAYBACK_DEVICES[*]}" ] && assert_failures 'playback'
354+
[ -n "${CAPTURE_DEVICES[*]}" ] && assert_failures 'capture'
355+
}
356+
357+
{
358+
main "$@"; exit "$?"
359+
}

0 commit comments

Comments
 (0)