Skip to content

Commit 497dc8f

Browse files
committed
case-lib: Add multi-topology support and robustness fixes
SOF can now load multiple topology files, pipeline library and support modules need updates to handle colon or comma separated TPLG paths, avoid testing the same physical PCM device more than once, and dont crash when optional options are unset. pipeline.sh: - Add func_tplg_parse_and_validate(): * Normalises colon/comma separators; trims whitespace * Filters topologies per NO_HDMI_MODE / NO_BT_MODE / NO_DMIC_MODE * Resolves each path with func_lib_get_tplg_path(); skips missing files with a warning, aborts only when nothing is left * Exports TPLG_FILES (comma-joined for sof-tplgreader.py) and TPLG_COUNT - Update func_pipeline_export(): * Calls func_tplg_parse_and_validate() instead of single-file lookup * Passes TPLG_FILES to sof-tplgreader.py (native multi-file support) * Captures stdout+stderr; logs and die()s on non-zero exit * Post-eval deduplication by hw:X,Y 'dev' field - renumbers surviving pipelines contiguously and logs removed duplicates * Sorts surviving pipelines by numeric 'id' for consistent ordering - Fix func_pipeline_parse_value(): add 'return 0' on out-of-range access to prevent spurious 'set -e' failures in callers hijack.sh: - Add audio-process cleanup at top of func_exit_handler(): kills leftover aplay/arecord (alsa) or tinyplay/tinycap (tinyalsa) on test abort, preventing EBUSY errors in subsequent tests Uses pgrep + pkill + 0.5 s sleep; all guarded with '|| true' lib.sh: - Fix logger_disabled(): guard OPT_VAL['s'] with ':-1' default so tests that dont declare -s option no longer crash with error 'unary operator expected' error from the empty integer comparison Signed-off-by: Mateusz Junkier <mateusz.junkier@intel.com>
1 parent 865c789 commit 497dc8f

3 files changed

Lines changed: 199 additions & 17 deletions

File tree

case-lib/hijack.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,34 @@ function func_exit_handler()
1212

1313
dlogi "Starting func_exit_handler($exit_status)"
1414

15+
# Cleanup audio processes (aplay/arecord/tinyplay/tinycap) that may be left running
16+
# This prevents "Device or resource busy" errors in subsequent tests
17+
case "$SOF_ALSA_TOOL" in
18+
'alsa')
19+
if pgrep aplay >/dev/null || pgrep arecord >/dev/null; then
20+
dlogi "Cleaning up leftover aplay/arecord processes"
21+
pkill -9 aplay || true
22+
pkill -9 arecord || true
23+
sleep 0.5
24+
fi
25+
;;
26+
'tinyalsa')
27+
if pgrep tinyplay >/dev/null || pgrep tinycap >/dev/null; then
28+
dlogi "Cleaning up leftover tinyplay/tinycap processes"
29+
pkill -9 tinyplay || true
30+
pkill -9 tinycap || true
31+
sleep 0.5
32+
fi
33+
;;
34+
*)
35+
# Unknown or unset - cleanup both
36+
pkill -9 aplay || true
37+
pkill -9 arecord || true
38+
pkill -9 tinyplay || true
39+
pkill -9 tinycap || true
40+
;;
41+
esac
42+
1543
finish_kmsg_collection
1644

1745
func_lib_check_and_disable_pipewire

case-lib/lib.sh

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -990,10 +990,7 @@ kill_process()
990990
local pid="$1"
991991
local sleep_secs="${2:-1}"
992992

993-
[[ -n "$pid" ]] || {
994-
die "kill_process: missing pid"
995-
return 1
996-
}
993+
[[ -n "$pid" ]] || die "kill_process: missing pid"
997994

998995
ps -p "$pid" >/dev/null || return 0
999996

@@ -1310,7 +1307,8 @@ is_ipc4()
13101307
logger_disabled()
13111308
{
13121309
# Disable logging when available...
1313-
if [ "${OPT_VAL['s']}" -eq 0 ]; then
1310+
local log_switch="${OPT_VAL['s']:-1}"
1311+
if [ "$log_switch" -eq 0 ]; then
13141312
return 0
13151313
fi
13161314

case-lib/pipeline.sh

Lines changed: 168 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,54 @@
11
#!/bin/bash
22

3+
# Parse and validate multiple topology files from colon/comma-separated input
4+
# Args: $1: topology path(s) - can be single file or colon/comma-separated list
5+
# Returns: Sets global TPLG_FILES (comma-separated) and TPLG_COUNT
6+
# Example: func_tplg_parse_and_validate "$TPLG"
7+
func_tplg_parse_and_validate()
8+
{
9+
local tplg_input="$1"
10+
tplg_input="${tplg_input//,/:}" # Normalize to colon separator
11+
IFS=':' read -ra tplg_array <<< "$tplg_input"
12+
13+
# Validate and collect accessible topologies
14+
local -a valid_tplg_list=()
15+
for single_tplg in "${tplg_array[@]}"; do
16+
single_tplg="${single_tplg## }"; single_tplg="${single_tplg%% }" # Trim whitespace
17+
[[ -z "$single_tplg" ]] && continue
18+
19+
# Filter out topology files based on system config (NO_HDMI_MODE, NO_BT_MODE, NO_DMIC_MODE)
20+
local tplg_basename; tplg_basename=$(basename "$single_tplg")
21+
local tplg_lower="${tplg_basename,,}" # Convert to lowercase
22+
23+
if [ -n "$NO_HDMI_MODE" ] && [[ "$tplg_lower" == *"hdmi"* ]]; then
24+
dlogw "Skipping HDMI topology (NO_HDMI_MODE=true): $single_tplg"
25+
continue
26+
fi
27+
if [ -n "$NO_BT_MODE" ] && [[ "$tplg_lower" == *"bt"* || "$tplg_lower" == *"bluetooth"* ]]; then
28+
dlogw "Skipping Bluetooth topology (NO_BT_MODE=true): $single_tplg"
29+
continue
30+
fi
31+
if [ -n "$NO_DMIC_MODE" ] && [[ "$tplg_lower" == *"dmic"* ]]; then
32+
dlogw "Skipping DMIC topology (NO_DMIC_MODE=true): $single_tplg"
33+
continue
34+
fi
35+
36+
local tplg_path
37+
if tplg_path=$(func_lib_get_tplg_path "$single_tplg"); then
38+
valid_tplg_list+=("$tplg_path")
39+
else
40+
dlogw "Topology $single_tplg not found, skipping"
41+
fi
42+
done
43+
44+
[ ${#valid_tplg_list[@]} -eq 0 ] && die "No topology found from: $tplg_input"
45+
46+
# Export as global variables
47+
TPLG_FILES=$(IFS=,; echo "${valid_tplg_list[*]}")
48+
export TPLG_FILES
49+
export TPLG_COUNT=${#valid_tplg_list[@]}
50+
}
51+
352
# This function will evaluate pipeline parameter related shell code generated by
453
# - sof-tplgreader.py (for SOF, pipeline parameters dumped from topology)
554
# - sof-dump-status.py (for legacy HDA, pipeline paramters dumped from proc)
@@ -32,11 +81,12 @@ func_pipeline_export()
3281
return 0
3382
}
3483

35-
# got tplg_file, verify file exist
36-
tplg_path=$(func_lib_get_tplg_path "$1") || {
37-
die "Topology $1 not found, check the TPLG environment variable or specify topology path with -t"
38-
}
39-
dlogi "$SCRIPT_NAME will use topology $tplg_path to run the test case"
84+
# Support multiple topologies separated by colon (:) or comma (,)
85+
# sof-tplgreader.py natively supports multiple files with comma separator
86+
func_tplg_parse_and_validate "$1"
87+
local tplg_files="$TPLG_FILES"
88+
89+
dlogi "$SCRIPT_NAME will use $TPLG_COUNT topology file(s) to run the test case"
4090

4191
# create block option string
4292
local ignore=""
@@ -60,16 +110,119 @@ func_pipeline_export()
60110
[[ "$ignore" ]] && opt="$opt -b '$ignore'"
61111
[[ "$SOFCARD" ]] && opt="$opt -s $SOFCARD"
62112

113+
# sof-tplgreader.py handles multiple files natively with comma separator
63114
local -a pipeline_lst
64-
local cmd="sof-tplgreader.py $tplg_path $opt -e" line=""
65-
dlogi "Run command to get pipeline parameters"
115+
local cmd="sof-tplgreader.py $tplg_files $opt -e" line=""
116+
dlogi "Run command to get pipeline parameters from all topologies"
66117
dlogc "$cmd"
67-
readarray -t pipeline_lst < <(eval "$cmd")
68-
for line in "${pipeline_lst[@]}"
69-
do
118+
119+
# Capture output and check for errors
120+
local output exit_code
121+
output=$(eval "$cmd" 2>&1)
122+
exit_code=$?
123+
124+
if [ $exit_code -ne 0 ]; then
125+
dloge "sof-tplgreader.py failed with exit code $exit_code"
126+
dloge "Command: $cmd"
127+
dloge "Output: $output"
128+
echo "ERROR: sof-tplgreader.py failed (exit $exit_code)" >&2
129+
echo "Command: $cmd" >&2
130+
echo "Output: $output" >&2
131+
die "Failed to parse topologies"
132+
fi
133+
134+
# Read output into array
135+
readarray -t pipeline_lst <<< "$output"
136+
137+
# Check if we got any output
138+
if [ ${#pipeline_lst[@]} -eq 0 ] || [ -z "${pipeline_lst[0]}" ]; then
139+
dloge "sof-tplgreader.py returned no output"
140+
dloge "Topologies: ${valid_tplg_list[*]}"
141+
echo "ERROR: No output from sof-tplgreader.py" >&2
142+
echo "Command: $cmd" >&2
143+
die "No pipeline data from topologies"
144+
fi
145+
146+
# Evaluate all collected pipeline parameters first
147+
for line in "${pipeline_lst[@]}"; do
70148
eval "$line"
71149
done
72-
[[ ! "$PIPELINE_COUNT" ]] && die "Failed to parse $tplg_path, please check topology parsing command"
150+
151+
# Deduplicate pipelines by 'dev' field (hw:X,Y)
152+
# Same PCM device can be defined in multiple topology files
153+
# Always deduplicate to ensure no duplicate testing (harmless with single topology)
154+
if [ "${PIPELINE_COUNT:-0}" -gt 0 ]; then
155+
local orig_count=$PIPELINE_COUNT
156+
local -A seen_devs
157+
local new_idx=0
158+
159+
# Iterate through all pipelines and keep only first occurrence of each unique 'dev'
160+
for idx in $(seq 0 $((PIPELINE_COUNT - 1))); do
161+
local dev_val; dev_val=$(func_pipeline_parse_value "$idx" "dev")
162+
163+
# Skip if we've seen this dev before
164+
if [[ -n "${seen_devs[$dev_val]}" ]]; then
165+
continue
166+
fi
167+
seen_devs["$dev_val"]=1
168+
169+
# If this is a new unique dev, copy pipeline to new index if needed
170+
if [ $new_idx -ne "$idx" ]; then
171+
# Copy all fields from old index to new index
172+
for key in pcm id dev type fmt fmts rate rates channel channels snd; do
173+
local val; val=$(func_pipeline_parse_value "$idx" "$key")
174+
if [[ -n "$val" ]]; then
175+
eval "PIPELINE_${new_idx}[$key]=\"$val\""
176+
fi
177+
done
178+
fi
179+
new_idx=$((new_idx + 1))
180+
done
181+
182+
# Update pipeline count
183+
PIPELINE_COUNT=$new_idx
184+
if [ "$orig_count" -ne "$PIPELINE_COUNT" ]; then
185+
dlogi "Deduplicated $orig_count pipelines to $PIPELINE_COUNT unique devices"
186+
fi
187+
188+
# Sort pipelines by id to ensure consistent ordering (matches sof-dump-status.py output)
189+
if [ "$PIPELINE_COUNT" -gt 0 ]; then
190+
# Collect all pipelines with their id
191+
local -a sorted_indices=()
192+
for idx in $(seq 0 $((PIPELINE_COUNT - 1))); do
193+
local id_val; id_val=$(func_pipeline_parse_value "$idx" "id")
194+
sorted_indices+=("$id_val:$idx")
195+
done
196+
197+
# Sort by id (numeric)
198+
mapfile -t sorted_indices < <(sort -t: -k1 -n <<<"${sorted_indices[*]}")
199+
200+
# Create temporary copy of all pipelines
201+
local -A temp_pipelines
202+
for idx in $(seq 0 $((PIPELINE_COUNT - 1))); do
203+
for key in pcm id dev type fmt fmts rate rates channel channels snd; do
204+
local val; val=$(func_pipeline_parse_value "$idx" "$key")
205+
[[ -n "$val" ]] && temp_pipelines["${idx}_${key}"]="$val"
206+
done
207+
done
208+
209+
# Reorder pipelines based on sorted id
210+
local new_idx=0
211+
for entry in "${sorted_indices[@]}"; do
212+
local old_idx="${entry#*:}"
213+
for key in pcm id dev type fmt fmts rate rates channel channels snd; do
214+
local val="${temp_pipelines[${old_idx}_${key}]}"
215+
if [[ -n "$val" ]]; then
216+
eval "PIPELINE_${new_idx}[$key]=\"$val\""
217+
fi
218+
done
219+
new_idx=$((new_idx + 1))
220+
done
221+
dlogi "Sorted $PIPELINE_COUNT pipelines by id"
222+
fi
223+
fi
224+
225+
[[ ! "$PIPELINE_COUNT" ]] && die "Failed to parse topologies, please check topology parsing command"
73226
[[ $PIPELINE_COUNT -eq 0 ]] && dlogw "No pipeline found with option: $opt, unable to run $SCRIPT_NAME" && exit 2
74227
return 0
75228
}
@@ -78,7 +231,10 @@ func_pipeline_parse_value()
78231
{
79232
local idx=$1
80233
local key=$2
81-
[[ $idx -ge $PIPELINE_COUNT ]] && echo "" && return
234+
if [[ $idx -ge $PIPELINE_COUNT ]]; then
235+
echo ""
236+
return 0
237+
fi
82238
local array_key='PIPELINE_'"$idx"'['"$key"']'
83239
eval echo "\${$array_key}" # dynmaic echo the target value of the PIPELINE
84240
}

0 commit comments

Comments
 (0)