Skip to content

Commit 6543ac6

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 6543ac6

File tree

1 file changed

+368
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)