Skip to content

Commit 462d26a

Browse files
committed
multi-arch-test-build: add generic package tests
Check if any of the package executables return the expected version when called with a generic list of flags, e.g. --version, -v, etc. Check if executable and library symlinks are valid. Check if executables are marked as such. Check for hardcoded paths. Check if binaries are stripped. Check if all shared linked libraries are installed. Check if libraries have sonames and if so, if they have a soname symlink. Generic tests are both enabled and forced by default, i.e. they will run even if a package-specific test is present. Signed-off-by: George Sapkin <george@sapk.in>
1 parent 72ab0ab commit 462d26a

File tree

2 files changed

+245
-30
lines changed

2 files changed

+245
-30
lines changed

.github/scripts/test_entrypoint.sh

Lines changed: 236 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,218 @@ set -o nounset # undefined variables causes script to fail
77
mkdir -p /var/lock/
88
mkdir -p /var/log/
99

10-
if [ $PKG_MANAGER = "opkg" ]; then
10+
CI_HELPERS="${CI_HELPERS:-/scripts/ci_helpers.sh}"
11+
12+
source "$CI_HELPERS"
13+
14+
generic_tests_enabled() {
15+
[ "$ENABLE_GENERIC_TESTS" = 'true' ]
16+
}
17+
18+
generic_tests_forced() {
19+
[ "$FORCE_GENERIC_TESTS" = 'true' ]
20+
}
21+
22+
is_exec() {
23+
[ -x "$1" ] && echo "$1" | grep -qE '^(/bin/|/sbin/|/usr/bin/|/usr/sbin/|/usr/libexec/)'
24+
}
25+
26+
is_lib() {
27+
echo "$1" | grep -qE '^(/lib/|/usr/lib/)'
28+
}
29+
30+
is_apk() {
31+
[ "$PKG_MANAGER" = 'apk' ]
32+
}
33+
34+
is_opkg() {
35+
[ "$PKG_MANAGER" = 'opkg' ]
36+
}
37+
38+
check_hardcoded_paths() {
39+
local file="$1"
40+
41+
if strings "$file" | grep -E '/build_dir/'; then
42+
status_warn "Binary $file contains a hardcoded build path"
43+
return 1
44+
fi
45+
46+
status_pass "Binary $file does not contain any hardcoded build paths"
47+
return 0
48+
}
49+
50+
check_exec() {
51+
local file="$1"
52+
local has_failure=0
53+
54+
if [ -x "$file" ]; then
55+
status_pass "File $file is executable"
56+
else
57+
status_fail "File $file in executable path is not executable"
58+
has_failure=1
59+
fi
60+
61+
local found_version=0
62+
for flag in --version -version version -v -V --help -help -?; do
63+
if "$file" "$flag" 2>&1 | grep -F "$PKG_VERSION"; then
64+
status_pass "Found version $PKG_VERSION in $file"
65+
found_version=1
66+
break
67+
fi
68+
done
69+
70+
if [ "$found_version" = 0 ]; then
71+
status_fail "Failed to find version $PKG_VERSION in $file"
72+
has_failure=1
73+
fi
74+
75+
if [ "$has_failure" = 1 ]; then
76+
return 1
77+
fi
78+
79+
return 0
80+
}
81+
82+
check_linked_libs() {
83+
local file="$1"
84+
local missing_libs
85+
missing_libs=$(ldd "$file" 2>/dev/null | grep "not found" || true)
86+
if [ -n "$missing_libs" ]; then
87+
status_fail "File $file has missing libraries:"
88+
echo "$missing_libs"
89+
return 1
90+
fi
91+
92+
status_pass "All linked libraries for $file are present"
93+
return 0
94+
}
95+
96+
check_lib() {
97+
local file="$1"
98+
local has_failure=0
99+
local soname
100+
soname=$(readelf -d "$file" 2>/dev/null | grep 'SONAME' | sed -E 's/.*\[(.*)\].*/\1/')
101+
if [ -n "$soname" ]; then
102+
if [ "$(basename "$file")" = "$soname" ]; then
103+
status_warn "Library $file has the same name as its SONAME '$soname'. The library file should have a more specific version."
104+
else
105+
status_pass "Library $file has SONAME '$soname'"
106+
fi
107+
108+
# When a library has a SONAME, there should be a symlink with the SONAME
109+
# pointing to the library file. This is usually in the same directory.
110+
local lib_dir
111+
lib_dir=$(dirname "$file")
112+
if [ ! -L "$lib_dir/$soname" ]; then
113+
status_fail "Library $file has SONAME '$soname' but no corresponding symlink was found in $lib_dir"
114+
has_failure=1
115+
elif [ "$(readlink -f "$lib_dir/$soname")" != "$(readlink -f "$file")" ]; then
116+
status_fail "Symlink for SONAME '$soname' does not point to $file"
117+
has_failure=1
118+
else
119+
status_pass "SONAME link for $file is correct"
120+
fi
121+
else
122+
status_warn "Library $file doesn't have a SONAME"
123+
fi
124+
125+
if [ "$has_failure" = 1 ]; then
126+
return 1
127+
fi
128+
129+
return 0
130+
}
131+
132+
do_generic_tests() {
133+
local all_files
134+
if is_opkg; then
135+
all_files=$(opkg files "$PKG_NAME")
136+
elif is_apk; then
137+
all_files=$(apk info --contents "$PKG_NAME" | sed 's#^#/#')
138+
fi
139+
140+
local files
141+
files=$(echo "$all_files" | grep -E '^(/bin/|/sbin/|/usr/bin/|/usr/libexec/|/usr/sbin/|/lib/|/usr/lib/)')
142+
143+
local has_failure=0
144+
for file in $files; do
145+
if [ ! -e "$file" ]; then
146+
# opkg files can list directories
147+
continue
148+
fi
149+
150+
# Check if it is a symlink and if the target exists
151+
if [ -L "$file" ]; then
152+
if [ -e "$(readlink -f "$file")" ]; then
153+
status_pass "Symlink $file points to an existing file"
154+
else
155+
status_fail "Symlink $file points to a non-existent file"
156+
has_failure=1
157+
fi
158+
159+
# Skip symlinks
160+
continue
161+
fi
162+
163+
if is_exec "$file" && ! check_exec "$file"; then
164+
has_failure=1
165+
fi
166+
167+
# Skip non-ELF files
168+
if ! file "$file" | grep -q "ELF"; then
169+
continue
170+
fi
171+
172+
check_hardcoded_paths "$file"
173+
174+
if file "$file" | grep 'not stripped'; then
175+
status_warn "Binary $file is not stripped"
176+
else
177+
status_pass "Binary $file is stripped"
178+
fi
179+
180+
if ! check_linked_libs "$file"; then
181+
has_failure=1
182+
fi
183+
184+
if is_lib "$file" && ! check_lib "$file"; then
185+
has_failure=1
186+
fi
187+
done
188+
189+
if [ "$has_failure" = 1 ]; then
190+
err "Generic tests failed"
191+
return 1
192+
fi
193+
194+
success "Generic tests passed"
195+
return 0
196+
}
197+
198+
if is_opkg; then
11199
echo "src/gz packages_ci file:///ci" >> /etc/opkg/distfeeds.conf
12200
# Disable checking signature for all opkg feeds, since it doesn't look like
13201
# it's possible to do it for the local feed only, which has signing removed.
14202
# This fixes running CI tests.
15203
sed -i '/check_signature/d' /etc/opkg.conf
16204
opkg update
17-
elif [ $PKG_MANAGER = "apk" ]; then
205+
opkg install binutils file
206+
elif is_apk; then
18207
echo "/ci/packages.adb" >> /etc/apk/repositories.d/distfeeds.list
19208
apk update
209+
apk add binutils file
20210
fi
21211

22-
CI_HELPERS="${CI_HELPERS:-/scripts/ci_helpers.sh}"
212+
if generic_tests_enabled && generic_tests_forced; then
213+
warn 'Generic tests are enabled and forced'
214+
elif generic_tests_enabled; then
215+
warn 'Generic tests are enabled'
216+
else
217+
warn 'Generic tests are disabled'
218+
fi
23219

24220
for PKG in /ci/*.[ai]pk; do
25-
if [ $PKG_MANAGER = "opkg" ]; then
221+
if is_opkg; then
26222
tar -xzOf "$PKG" ./control.tar.gz | tar xzf - ./control
27223
# package name including variant
28224
PKG_NAME=$(sed -ne 's#^Package: \(.*\)$#\1#p' ./control)
@@ -32,7 +228,7 @@ for PKG in /ci/*.[ai]pk; do
32228
# package source containing test.sh script
33229
PKG_SOURCE=$(sed -ne 's#^Source: \(.*\)$#\1#p' ./control)
34230
PKG_SOURCE="${PKG_SOURCE#/feed/}"
35-
elif [ $PKG_MANAGER = "apk" ]; then
231+
elif is_apk; then
36232
# package name including variant
37233
PKG_NAME=$(apk adbdump --format json "$PKG" | jsonfilter -e '@["info"]["name"]')
38234
# package version without release
@@ -44,52 +240,62 @@ for PKG in /ci/*.[ai]pk; do
44240
fi
45241

46242
echo
47-
echo "Testing package $PKG_NAME in version $PKG_VERSION from $PKG_SOURCE"
243+
info "Testing package version $PKG_VERSION from $PKG_SOURCE"
48244

49245
if ! [ -d "/ci/$PKG_SOURCE" ]; then
50-
echo "$PKG_SOURCE is not a directory"
51-
exit 1
246+
err_die "$PKG_SOURCE is not a directory"
52247
fi
53248

54249
PRE_TEST_SCRIPT="/ci/$PKG_SOURCE/pre-test.sh"
55250
TEST_SCRIPT="/ci/$PKG_SOURCE/test.sh"
56251

57-
if ! [ -f "$TEST_SCRIPT" ]; then
58-
echo "No test.sh script available"
59-
continue
60-
fi
61-
62252
export PKG_NAME PKG_VERSION CI_HELPERS
63253

64254
if [ -f "$PRE_TEST_SCRIPT" ]; then
65-
echo "Use package specific pre-test.sh"
255+
info 'Use the package-specific pre-test.sh'
66256
if sh "$PRE_TEST_SCRIPT" "$PKG_NAME" "$PKG_VERSION"; then
67-
echo "Pre-test successful"
257+
success 'Pre-test passed'
68258
else
69-
echo "Pre-test failed"
70-
exit 1
259+
err_die 'Pre-test failed'
71260
fi
72261
else
73-
echo "No pre-test.sh script available"
262+
info 'No pre-test.sh script available'
74263
fi
75264

76-
if [ $PKG_MANAGER = "opkg" ]; then
265+
if is_opkg; then
77266
opkg install "$PKG"
78-
elif [ $PKG_MANAGER = "apk" ]; then
267+
elif is_apk; then
79268
apk add --allow-untrusted "$PKG"
80269
fi
81270

82-
echo "Use package specific test.sh"
83-
if sh "$TEST_SCRIPT" "$PKG_NAME" "$PKG_VERSION"; then
84-
echo "Test successful"
85-
else
86-
echo "Test failed"
87-
exit 1
271+
SUCCESS=0
272+
273+
if generic_tests_enabled && ( generic_tests_forced || [ ! -f "$TEST_SCRIPT" ] ); then
274+
warn 'Use generic tests'
275+
if do_generic_tests; then
276+
SUCCESS=1
277+
fi
88278
fi
89279

90-
if [ $PKG_MANAGER = "opkg" ]; then
91-
opkg remove "$PKG_NAME" --force-removal-of-dependent-packages --force-remove --autoremove || true
92-
elif [ $PKG_MANAGER = "apk" ]; then
93-
apk del -r "$PKG_NAME"
280+
if [ -f "$TEST_SCRIPT" ]; then
281+
info 'Use the package-specific test.sh'
282+
if sh "$TEST_SCRIPT" "$PKG_NAME" "$PKG_VERSION"; then
283+
success 'Test passed'
284+
SUCCESS=1
285+
else
286+
err 'Test failed'
287+
fi
288+
fi
289+
290+
if is_opkg; then
291+
opkg remove "$PKG_NAME" \
292+
--autoremove \
293+
--force-removal-of-dependent-packages \
294+
--force-remove \
295+
|| true
296+
elif is_apk; then
297+
apk del --rdepends "$PKG_NAME" || true
94298
fi
299+
300+
[ "$SUCCESS" = 1 ] || exit 1
95301
done

.github/workflows/multi-arch-test-build.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ name: Feeds Package Test Build
22

33
on:
44
workflow_call:
5+
inputs:
6+
enable_generic_tests:
7+
type: boolean
8+
default: true
9+
force_generic_tests:
10+
type: boolean
11+
default: true
512

613
concurrency:
714
group: ${{ github.workflow }}-${{ github.ref }}
@@ -241,6 +248,8 @@ jobs:
241248
if: ${{ matrix.runtime_test && fromJSON(env.HAVE_PKGS) }}
242249
run: |
243250
docker run \
251+
-e ENABLE_GENERIC_TESTS=${{ inputs.enable_generic_tests }} \
252+
-e FORCE_GENERIC_TESTS=${{ inputs.force_generic_tests }} \
244253
-e PKG_MANAGER=${{ env.PKG_MANAGER }} \
245254
--platform linux/${{ matrix.arch }} \
246255
--rm \

0 commit comments

Comments
 (0)