Skip to content

Commit 11602d5

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 11602d5

1 file changed

Lines changed: 357 additions & 0 deletions

File tree

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
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+
PLAYBACK_DEVICES+=( "${p_dev_expanded[@]}" )
198+
done
199+
dlogi "Playback devices: ${PLAYBACK_DEVICES[*]}"
200+
201+
CAPTURE_DEVICES=()
202+
for c_dev in "${pcm_c[@]}"
203+
do
204+
mapfile -t p_dev_expanded < <(get_card_devices 'capture' "${c_dev}")
205+
CAPTURE_DEVICES+=( "${p_dev_expanded[@]}" )
206+
done
207+
dlogi "Capture devices: ${CAPTURE_DEVICES[*]}"
208+
}
209+
210+
set_alsa()
211+
{
212+
reset_sof_volume
213+
214+
# If MODEL is defined, set proper gain for the platform
215+
if [ -z "$MODEL" ]; then
216+
dlogw "No MODEL is defined. Please define MODEL to run alsa_settings/\${MODEL}.sh"
217+
else
218+
set_alsa_settings "$MODEL"
219+
fi
220+
}
221+
222+
alsa_conformance_device_info()
223+
{
224+
local mode=$1
225+
local device=$2
226+
local opt=()
227+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
228+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
229+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
230+
231+
local run_cmd=("${ALSA_CONFORMANCE_TEST}" "${opt[@]}" "--dev_info_only")
232+
dlogc "${run_cmd[@]}"
233+
local rc=0
234+
"${run_cmd[@]}" || rc=$?
235+
[[ "${rc}" -ne 0 ]] && dloge "Failed to get device info, rc=${rc}"
236+
}
237+
238+
alsa_conformance_test()
239+
{
240+
local mode=$1
241+
local device=$2
242+
local opt=()
243+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
244+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
245+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
246+
247+
local run_prefix=("export" "PATH=${ALSA_CONFORMANCE_PATH}:${PATH}")
248+
local run_cmd=()
249+
run_cmd+=("${ALSA_CONFORMANCE_TEST}.py" "${CMD_OPTS[@]}" "${opt[@]}")
250+
run_cmd+=("--json-file" "${AUDIOTEST_OUT}_${mode}.json")
251+
dlogc "${run_cmd[@]}"
252+
local rc=0
253+
"${run_prefix[@]}" && "${run_cmd[@]}" || rc=$?
254+
[[ "${rc}" -ne 0 ]] && dloge "Failed ${mode} tests, rc=${rc}"
255+
}
256+
257+
report_start()
258+
{
259+
dlogi "Compose ${RESULT_JSON}"
260+
printf '{"options":{%s}, "alsa_conformance":[' "$(options2json)" > "${RESULT_JSON}"
261+
}
262+
263+
json_next_sep=""
264+
265+
report_conformance()
266+
{
267+
local report_type=$1
268+
local report_device=$2
269+
local report_file="${AUDIOTEST_OUT}_${report_type}.json"
270+
if [ -s "${report_file}" ]; then
271+
printf '%s{"device":"%s","%s":' \
272+
"${json_next_sep}" "${report_device}" "${report_type}" >> "${RESULT_JSON}"
273+
jq --compact-output . "${report_file}" >> "${RESULT_JSON}" && rm "${report_file}"
274+
printf '}' >> "${RESULT_JSON}"
275+
json_next_sep=","
276+
else
277+
dlogw "No conformance report for ${report_type}"
278+
fi
279+
}
280+
281+
report_end()
282+
{
283+
printf ']}\n' >> "${RESULT_JSON}"
284+
[[ "${run_verbose}" -ne 0 ]] && cat "${RESULT_JSON}"
285+
}
286+
287+
assert_failures()
288+
{
289+
local report_type=$1
290+
[ -z "${report_type}" ] && return
291+
292+
local report_key="alsa_conformance[].${report_type}"
293+
local failures=""
294+
295+
failures=$(jq "[.${report_key}.fail // 0] | add" "${RESULT_JSON}")
296+
if [ -z "${failures}" ] || [ "${failures}" -ne "${failures}" ]; then
297+
die "${report_type} has invalid ${RESULT_JSON}"
298+
fi
299+
if [ "${failures}" -ne 0 ]; then
300+
die "${report_type} has ${failures} failures."
301+
fi
302+
303+
# we must have something reported as passed, even zero
304+
passes=$(jq "[.${report_key}.pass] | add // empty" "${RESULT_JSON}")
305+
if [ -z "${passes}" ] || [ "${passes}" -ne "${passes}" ]; then
306+
die "${report_type} has no results."
307+
fi
308+
}
309+
310+
run_test()
311+
{
312+
local t_mode=$1
313+
local t_dev=$2
314+
315+
dlogi "Test ${t_mode} ${t_dev}"
316+
alsa_conformance_device_info "${t_mode}" "${t_dev}"
317+
alsa_conformance_test "${t_mode}" "${t_dev}"
318+
report_conformance "${t_mode}" "${t_dev}"
319+
}
320+
321+
main()
322+
{
323+
init_globals
324+
325+
setup_kernel_check_point
326+
327+
start_test
328+
329+
check_alsa_conformance_suite
330+
331+
select_PCMs
332+
333+
logger_disabled || func_lib_start_log_collect
334+
335+
set_alsa
336+
337+
report_start
338+
339+
for p_dev in "${PLAYBACK_DEVICES[@]}"
340+
do
341+
run_test 'playback' "${p_dev}"
342+
done
343+
344+
for c_dev in "${CAPTURE_DEVICES[@]}"
345+
do
346+
run_test 'capture' "${c_dev}"
347+
done
348+
349+
report_end
350+
351+
[ -n "${PLAYBACK_DEVICES[*]}" ] && assert_failures 'playback'
352+
[ -n "${CAPTURE_DEVICES[*]}" ] && assert_failures 'capture'
353+
}
354+
355+
{
356+
main "$@"; exit "$?"
357+
}

0 commit comments

Comments
 (0)