Skip to content

Commit c08c26c

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 c08c26c

1 file changed

Lines changed: 360 additions & 0 deletions

File tree

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
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+
# Returns the PCM's full id if it is found as playback or capture device.
135+
# If only card id is given, then all its devices will be returned.
136+
# Empty output if the device is not found.
137+
get_card_devices()
138+
{
139+
local mode=$1
140+
local arg_pcm=$2
141+
142+
# select all devices by default
143+
[ -z "${arg_pcm}" ] && arg_pcm="[^ ]+"
144+
145+
local alsa_list=''
146+
local res_devs=("${arg_pcm}")
147+
148+
if [ "${mode}" == 'playback' ]; then
149+
alsa_list=('aplay' '-l')
150+
elif [ "${mode}" == 'capture' ]; then
151+
alsa_list=('arecord' '-l')
152+
else
153+
return
154+
fi
155+
156+
if [ -n "${arg_pcm}" ]; then
157+
# check is only card name is given or exact device
158+
if [ "${arg_pcm}" == "${arg_pcm##*,}" ]; then
159+
# strip 'hw:' prefix
160+
arg_pcm="${arg_pcm#*:}"
161+
# shellcheck disable=SC2016
162+
local gawk_script='match($0, /^card [0-9]+: ('"${arg_pcm}"') .+ device ([0-9]+): /, arr) { print "hw:" arr[1] "," arr[2] }'
163+
mapfile -t res_devs < <( "${alsa_list[@]}" | gawk "${gawk_script}" )
164+
fi
165+
printf '%s\n' "${res_devs[@]}"
166+
fi
167+
}
168+
169+
select_PCMs()
170+
{
171+
# Don't quote to split into separate items:
172+
# shellcheck disable=SC2206
173+
alsa_device=(${OPT_VAL['d']//[ ]/ })
174+
# shellcheck disable=SC2206
175+
pcm_p=(${OPT_VAL['p']//[ ]/ })
176+
# shellcheck disable=SC2206
177+
pcm_c=(${OPT_VAL['c']//[ ]/ })
178+
179+
if [ -n "${alsa_device[*]}" ]; then
180+
if [ -n "${pcm_p[*]}" ] || [ -n "${pcm_c[*]}" ]; then
181+
die "Give either an ALSA device (-d), or ALSA playback(-p) and/or capture(-c) PCMs."
182+
fi
183+
# we got only -d
184+
pcm_p=("${alsa_device[@]}")
185+
pcm_c=("${alsa_device[@]}")
186+
elif [ -z "${pcm_p[*]}" ] && [ -z "${pcm_c[*]}" ]; then
187+
dlogi "No ALSA PCM is specified - scan all playback and capture devices"
188+
pcm_p=('')
189+
pcm_c=('')
190+
fi
191+
dlogi "pcm_p=(${pcm_p[*]})"
192+
dlogi "pcm_c=(${pcm_c[*]})"
193+
194+
local p_dev_expanded=()
195+
PLAYBACK_DEVICES=()
196+
197+
for p_dev in "${pcm_p[@]}"
198+
do
199+
mapfile -t p_dev_expanded < <(get_card_devices 'playback' "${p_dev}")
200+
PLAYBACK_DEVICES+=( "${p_dev_expanded[@]}" )
201+
done
202+
dlogi "Playback devices: ${PLAYBACK_DEVICES[*]}"
203+
204+
CAPTURE_DEVICES=()
205+
for c_dev in "${pcm_c[@]}"
206+
do
207+
mapfile -t p_dev_expanded < <(get_card_devices 'capture' "${c_dev}")
208+
CAPTURE_DEVICES+=( "${p_dev_expanded[@]}" )
209+
done
210+
dlogi "Capture devices: ${CAPTURE_DEVICES[*]}"
211+
}
212+
213+
set_alsa()
214+
{
215+
reset_sof_volume
216+
217+
# If MODEL is defined, set proper gain for the platform
218+
if [ -z "$MODEL" ]; then
219+
dlogw "No MODEL is defined. Please define MODEL to run alsa_settings/\${MODEL}.sh"
220+
else
221+
set_alsa_settings "$MODEL"
222+
fi
223+
}
224+
225+
alsa_conformance_device_info()
226+
{
227+
local mode=$1
228+
local device=$2
229+
local opt=()
230+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
231+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
232+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
233+
234+
local run_cmd=("${ALSA_CONFORMANCE_TEST}" "${opt[@]}" "--dev_info_only")
235+
dlogc "${run_cmd[@]}"
236+
local rc=0
237+
"${run_cmd[@]}" || rc=$?
238+
[[ "${rc}" -ne 0 ]] && dloge "Failed to get device info, rc=${rc}"
239+
}
240+
241+
alsa_conformance_test()
242+
{
243+
local mode=$1
244+
local device=$2
245+
local opt=()
246+
[ "${mode}" == 'playback' ] && opt=("-P" "${device}")
247+
[ "${mode}" == 'capture' ] && opt=("-C" "${device}")
248+
[ -z "${opt[*]}" ] && die "No ALSA PCM parameter."
249+
250+
local run_prefix=("export" "PATH=${ALSA_CONFORMANCE_PATH}:${PATH}")
251+
local run_cmd=()
252+
run_cmd+=("${ALSA_CONFORMANCE_TEST}.py" "${CMD_OPTS[@]}" "${opt[@]}")
253+
run_cmd+=("--json-file" "${AUDIOTEST_OUT}_${mode}.json")
254+
dlogc "${run_cmd[@]}"
255+
local rc=0
256+
"${run_prefix[@]}" && "${run_cmd[@]}" || rc=$?
257+
[[ "${rc}" -ne 0 ]] && dloge "Failed ${mode} tests, rc=${rc}"
258+
}
259+
260+
report_start()
261+
{
262+
dlogi "Compose ${RESULT_JSON}"
263+
printf '{"options":{%s}, "alsa_conformance":[' "$(options2json)" > "${RESULT_JSON}"
264+
}
265+
266+
json_next_sep=""
267+
268+
report_conformance()
269+
{
270+
local report_type=$1
271+
local report_device=$2
272+
local report_file="${AUDIOTEST_OUT}_${report_type}.json"
273+
if [ -s "${report_file}" ]; then
274+
printf '%s{"device":"%s","%s":' \
275+
"${json_next_sep}" "${report_device}" "${report_type}" >> "${RESULT_JSON}"
276+
jq --compact-output . "${report_file}" >> "${RESULT_JSON}" && rm "${report_file}"
277+
printf '}' >> "${RESULT_JSON}"
278+
json_next_sep=","
279+
else
280+
dlogw "No conformance report for ${report_type}"
281+
fi
282+
}
283+
284+
report_end()
285+
{
286+
printf ']}\n' >> "${RESULT_JSON}"
287+
[[ "${run_verbose}" -ne 0 ]] && cat "${RESULT_JSON}"
288+
}
289+
290+
assert_failures()
291+
{
292+
local report_type=$1
293+
[ -z "${report_type}" ] && return
294+
295+
local report_key="alsa_conformance[].${report_type}"
296+
local failures=""
297+
298+
failures=$(jq "[.${report_key}.fail // 0] | add" "${RESULT_JSON}")
299+
if [ -z "${failures}" ] || [ "${failures}" -ne "${failures}" ]; then
300+
die "${report_type} has invalid ${RESULT_JSON}"
301+
fi
302+
if [ "${failures}" -ne 0 ]; then
303+
die "${report_type} has ${failures} failures."
304+
fi
305+
306+
# we must have something reported as passed, even zero
307+
passes=$(jq "[.${report_key}.pass] | add // empty" "${RESULT_JSON}")
308+
if [ -z "${passes}" ] || [ "${passes}" -ne "${passes}" ]; then
309+
die "${report_type} has no results."
310+
fi
311+
}
312+
313+
run_test()
314+
{
315+
local t_mode=$1
316+
local t_dev=$2
317+
318+
dlogi "Test ${t_mode} ${t_dev}"
319+
alsa_conformance_device_info "${t_mode}" "${t_dev}"
320+
alsa_conformance_test "${t_mode}" "${t_dev}"
321+
report_conformance "${t_mode}" "${t_dev}"
322+
}
323+
324+
main()
325+
{
326+
init_globals
327+
328+
setup_kernel_check_point
329+
330+
start_test
331+
332+
check_alsa_conformance_suite
333+
334+
select_PCMs
335+
336+
logger_disabled || func_lib_start_log_collect
337+
338+
set_alsa
339+
340+
report_start
341+
342+
for p_dev in "${PLAYBACK_DEVICES[@]}"
343+
do
344+
run_test 'playback' "${p_dev}"
345+
done
346+
347+
for c_dev in "${CAPTURE_DEVICES[@]}"
348+
do
349+
run_test 'capture' "${c_dev}"
350+
done
351+
352+
report_end
353+
354+
[ -n "${PLAYBACK_DEVICES[*]}" ] && assert_failures 'playback'
355+
[ -n "${CAPTURE_DEVICES[*]}" ] && assert_failures 'capture'
356+
}
357+
358+
{
359+
main "$@"; exit "$?"
360+
}

0 commit comments

Comments
 (0)