Skip to content

Commit 34b6e0a

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 8df0f1a commit 34b6e0a

File tree

1 file changed

+372
-0
lines changed

1 file changed

+372
-0
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
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+
OPT_NAME['t']='timeout' OPT_DESC['t']="ALSA conformance --timeout (Default: none)."
80+
OPT_HAS_ARG['t']=1 OPT_VAL['t']=''
81+
82+
OPT_NAME['A']='allow-channels' OPT_DESC['A']="ALSA conformance --allow-channels (Default: all)."
83+
OPT_HAS_ARG['A']=1 OPT_VAL['A']=''
84+
85+
OPT_NAME['S']='skip-channels' OPT_DESC['S']="ALSA conformance --skip-channels (Default: none skipped)."
86+
OPT_HAS_ARG['S']=1 OPT_VAL['S']=''
87+
88+
func_opt_parse_option "$@"
89+
90+
# Options for the ALSA conformance test script call
91+
CMD_OPTS=()
92+
93+
# Recompose OPT_VAL[$1] option as ALSA test script option $2
94+
add_cmd_option()
95+
{
96+
local opt_val="${OPT_VAL[$1]}"
97+
local prefix=$2
98+
99+
if [ -n "${opt_val}" ]; then
100+
# Split list parameters to separate values
101+
opt_val=("${opt_val//[ ,]/ }")
102+
# shellcheck disable=SC2206
103+
CMD_OPTS+=("${prefix}" ${opt_val[@]})
104+
fi
105+
}
106+
107+
init_globals()
108+
{
109+
add_cmd_option 'r' '--allow-rates'
110+
add_cmd_option 'F' '--allow-formats'
111+
add_cmd_option 'E' '--rate-criteria-diff-pct'
112+
add_cmd_option 'e' '--rate-err-criteria'
113+
add_cmd_option 't' '--timeout'
114+
add_cmd_option 'T' '--test-suites'
115+
add_cmd_option 'A' '--allow-channels'
116+
add_cmd_option 'S' '--skip-channels'
117+
118+
run_verbose=0
119+
if [[ "${OPT_VAL['v']}" -eq 1 ]]; then
120+
run_verbose=1
121+
CMD_OPTS+=("--log-file" "/dev/stdout")
122+
fi
123+
124+
if [[ "${OPT_VAL['a']}" -eq 1 ]]; then
125+
CMD_OPTS+=('--avail-delay')
126+
fi
127+
128+
AUDIOTEST_OUT="${LOG_ROOT}/alsa_conformance"
129+
RESULT_JSON="${LOG_ROOT}/test_result.json"
130+
131+
ALSA_CONFORMANCE_PATH=$([ -n "$ALSA_CONFORMANCE_PATH" ] || realpath "${TESTDIR}/../audiotest")
132+
ALSA_CONFORMANCE_TEST="${ALSA_CONFORMANCE_PATH}/alsa_conformance_test"
133+
}
134+
135+
check_alsa_conformance_suite()
136+
{
137+
if [ -d "${ALSA_CONFORMANCE_PATH}" ]; then
138+
if [ -x "${ALSA_CONFORMANCE_TEST}" ] && [ -x "${ALSA_CONFORMANCE_TEST}.py" ]; then
139+
dlogi "Use ALSA conformance test suite: ${ALSA_CONFORMANCE_TEST}"
140+
return
141+
fi
142+
fi
143+
skip_test "ALSA conformance test suite is missing at: ${ALSA_CONFORMANCE_PATH}"
144+
}
145+
146+
# Returns the PCM's full id if it is found as playback or capture device.
147+
# If only card id is given, then all its devices will be returned.
148+
# Empty output if the device is not found.
149+
get_card_devices()
150+
{
151+
local mode=$1
152+
local arg_pcm=$2
153+
154+
# select all devices by default
155+
[ -z "${arg_pcm}" ] && arg_pcm="[^ ]+"
156+
157+
local alsa_list=''
158+
local res_devs=("${arg_pcm}")
159+
160+
if [ "${mode}" == 'playback' ]; then
161+
alsa_list=('aplay' '-l')
162+
elif [ "${mode}" == 'capture' ]; then
163+
alsa_list=('arecord' '-l')
164+
else
165+
return
166+
fi
167+
168+
if [ -n "${arg_pcm}" ]; then
169+
# check is only card name is given or exact device
170+
if [ "${arg_pcm}" == "${arg_pcm##*,}" ]; then
171+
# strip 'hw:' prefix
172+
arg_pcm="${arg_pcm#*:}"
173+
# shellcheck disable=SC2016
174+
local gawk_script='match($0, /^card [0-9]+: ('"${arg_pcm}"') .+ device ([0-9]+): /, arr) { print "hw:" arr[1] "," arr[2] }'
175+
mapfile -t res_devs < <( "${alsa_list[@]}" | gawk "${gawk_script}" )
176+
fi
177+
printf '%s\n' "${res_devs[@]}"
178+
fi
179+
}
180+
181+
select_PCMs()
182+
{
183+
# Don't quote to split into separate items:
184+
# shellcheck disable=SC2206
185+
alsa_device=(${OPT_VAL['d']//[ ]/ })
186+
# shellcheck disable=SC2206
187+
pcm_p=(${OPT_VAL['p']//[ ]/ })
188+
# shellcheck disable=SC2206
189+
pcm_c=(${OPT_VAL['c']//[ ]/ })
190+
191+
if [ -n "${alsa_device[*]}" ]; then
192+
if [ -n "${pcm_p[*]}" ] || [ -n "${pcm_c[*]}" ]; then
193+
die "Give either an ALSA device (-d), or ALSA playback(-p) and/or capture(-c) PCMs."
194+
fi
195+
# we got only -d
196+
pcm_p=("${alsa_device[@]}")
197+
pcm_c=("${alsa_device[@]}")
198+
elif [ -z "${pcm_p[*]}" ] && [ -z "${pcm_c[*]}" ]; then
199+
dlogi "No ALSA PCM is specified - scan all playback and capture devices"
200+
pcm_p=('')
201+
pcm_c=('')
202+
fi
203+
dlogi "pcm_p=(${pcm_p[*]})"
204+
dlogi "pcm_c=(${pcm_c[*]})"
205+
206+
local p_dev_expanded=()
207+
PLAYBACK_DEVICES=()
208+
209+
for p_dev in "${pcm_p[@]}"
210+
do
211+
mapfile -t p_dev_expanded < <(get_card_devices 'playback' "${p_dev}")
212+
PLAYBACK_DEVICES+=( "${p_dev_expanded[@]}" )
213+
done
214+
dlogi "Playback devices: ${PLAYBACK_DEVICES[*]}"
215+
216+
CAPTURE_DEVICES=()
217+
for c_dev in "${pcm_c[@]}"
218+
do
219+
mapfile -t p_dev_expanded < <(get_card_devices 'capture' "${c_dev}")
220+
CAPTURE_DEVICES+=( "${p_dev_expanded[@]}" )
221+
done
222+
dlogi "Capture devices: ${CAPTURE_DEVICES[*]}"
223+
}
224+
225+
set_alsa()
226+
{
227+
reset_sof_volume
228+
229+
# If MODEL is defined, set proper gain for the platform
230+
if [ -z "$MODEL" ]; then
231+
dlogw "No MODEL is defined. Please define MODEL to run alsa_settings/\${MODEL}.sh"
232+
else
233+
set_alsa_settings "$MODEL"
234+
fi
235+
}
236+
237+
alsa_conformance_device_info()
238+
{
239+
local mode=$1
240+
local device=$2
241+
local opt=()
242+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
243+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
244+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
245+
246+
local run_cmd=("${ALSA_CONFORMANCE_TEST}" "${opt[@]}" "--dev_info_only")
247+
dlogc "${run_cmd[@]}"
248+
local rc=0
249+
"${run_cmd[@]}" || rc=$?
250+
[[ "${rc}" -ne 0 ]] && dloge "Failed to get device info, rc=${rc}"
251+
}
252+
253+
alsa_conformance_test()
254+
{
255+
local mode=$1
256+
local device=$2
257+
local opt=()
258+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
259+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
260+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
261+
262+
local run_prefix=("export" "PATH=${ALSA_CONFORMANCE_PATH}:${PATH}")
263+
local run_cmd=()
264+
run_cmd+=("${ALSA_CONFORMANCE_TEST}.py" "${CMD_OPTS[@]}" "${opt[@]}")
265+
run_cmd+=("--json-file" "${AUDIOTEST_OUT}_${mode}.json")
266+
dlogc "${run_cmd[@]}"
267+
local rc=0
268+
"${run_prefix[@]}" && "${run_cmd[@]}" || rc=$?
269+
[[ "${rc}" -ne 0 ]] && dloge "Failed ${mode} tests, rc=${rc}"
270+
}
271+
272+
report_start()
273+
{
274+
dlogi "Compose ${RESULT_JSON}"
275+
printf '{"options":{%s}, "alsa_conformance":[' "$(options2json)" > "${RESULT_JSON}"
276+
}
277+
278+
json_next_sep=""
279+
280+
report_conformance()
281+
{
282+
local report_type=$1
283+
local report_device=$2
284+
local report_file="${AUDIOTEST_OUT}_${report_type}.json"
285+
if [ -s "${report_file}" ]; then
286+
printf '%s{"device":"%s","%s":' \
287+
"${json_next_sep}" "${report_device}" "${report_type}" >> "${RESULT_JSON}"
288+
jq --compact-output . "${report_file}" >> "${RESULT_JSON}" && rm "${report_file}"
289+
printf '}' >> "${RESULT_JSON}"
290+
json_next_sep=","
291+
else
292+
dlogw "No conformance report for ${report_type}"
293+
fi
294+
}
295+
296+
report_end()
297+
{
298+
printf ']}\n' >> "${RESULT_JSON}"
299+
[[ "${run_verbose}" -ne 0 ]] && cat "${RESULT_JSON}"
300+
}
301+
302+
assert_failures()
303+
{
304+
local report_type=$1
305+
[ -z "${report_type}" ] && return
306+
307+
local report_key="alsa_conformance[].${report_type}"
308+
local failures=""
309+
310+
failures=$(jq "[.${report_key}.fail // 0] | add" "${RESULT_JSON}")
311+
if [ -z "${failures}" ] || [ "${failures}" -ne "${failures}" ]; then
312+
die "${report_type} has invalid ${RESULT_JSON}"
313+
fi
314+
if [ "${failures}" -ne 0 ]; then
315+
die "${report_type} has ${failures} failures."
316+
fi
317+
318+
# we must have something reported as passed, even zero
319+
passes=$(jq "[.${report_key}.pass] | add // empty" "${RESULT_JSON}")
320+
if [ -z "${passes}" ] || [ "${passes}" -ne "${passes}" ]; then
321+
die "${report_type} has no results."
322+
fi
323+
}
324+
325+
run_test()
326+
{
327+
local t_mode=$1
328+
local t_dev=$2
329+
330+
dlogi "Test ${t_mode} ${t_dev}"
331+
alsa_conformance_device_info "${t_mode}" "${t_dev}"
332+
alsa_conformance_test "${t_mode}" "${t_dev}"
333+
report_conformance "${t_mode}" "${t_dev}"
334+
}
335+
336+
main()
337+
{
338+
init_globals
339+
340+
setup_kernel_check_point
341+
342+
start_test
343+
344+
check_alsa_conformance_suite
345+
346+
select_PCMs
347+
348+
logger_disabled || func_lib_start_log_collect
349+
350+
set_alsa
351+
352+
report_start
353+
354+
for p_dev in "${PLAYBACK_DEVICES[@]}"
355+
do
356+
run_test 'playback' "${p_dev}"
357+
done
358+
359+
for c_dev in "${CAPTURE_DEVICES[@]}"
360+
do
361+
run_test 'capture' "${c_dev}"
362+
done
363+
364+
report_end
365+
366+
[ -n "${PLAYBACK_DEVICES[*]}" ] && assert_failures 'playback'
367+
[ -n "${CAPTURE_DEVICES[*]}" ] && assert_failures 'capture'
368+
}
369+
370+
{
371+
main "$@"; exit "$?"
372+
}

0 commit comments

Comments
 (0)