Skip to content

Commit ec3b743

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 31de74c commit ec3b743

2 files changed

Lines changed: 245 additions & 30 deletions

File tree

.github/scripts/test_entrypoint.sh

Lines changed: 236 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,214 @@ 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
opkg update
13-
elif [ $PKG_MANAGER = "apk" ]; then
201+
opkg install binutils file
202+
elif is_apk; then
14203
echo "/ci/packages.adb" >> /etc/apk/repositories.d/distfeeds.list
15204
apk update
205+
apk add binutils file
16206
fi
17207

18-
CI_HELPERS="${CI_HELPERS:-/scripts/ci_helpers.sh}"
208+
if generic_tests_enabled && generic_tests_forced; then
209+
warn 'Generic tests are enabled and forced'
210+
elif generic_tests_enabled; then
211+
warn 'Generic tests are enabled'
212+
else
213+
warn 'Generic tests are disabled'
214+
fi
19215

20216
for PKG in /ci/*.[ai]pk; do
21-
if [ $PKG_MANAGER = "opkg" ]; then
217+
if is_opkg; then
22218
tar -xzOf "$PKG" ./control.tar.gz | tar xzf - ./control
23219
# package name including variant
24220
PKG_NAME=$(sed -ne 's#^Package: \(.*\)$#\1#p' ./control)
@@ -28,7 +224,7 @@ for PKG in /ci/*.[ai]pk; do
28224
# package source containing test.sh script
29225
PKG_SOURCE=$(sed -ne 's#^Source: \(.*\)$#\1#p' ./control)
30226
PKG_SOURCE="${PKG_SOURCE#/feed/}"
31-
elif [ $PKG_MANAGER = "apk" ]; then
227+
elif is_apk; then
32228
# package name including variant
33229
PKG_NAME=$(apk adbdump --format json "$PKG" | jsonfilter -e '@["info"]["name"]')
34230
# package version without release
@@ -40,52 +236,62 @@ for PKG in /ci/*.[ai]pk; do
40236
fi
41237

42238
echo
43-
echo "Testing package $PKG_NAME in version $PKG_VERSION from $PKG_SOURCE"
239+
info "Testing package version $PKG_VERSION from $PKG_SOURCE"
44240

45241
if ! [ -d "/ci/$PKG_SOURCE" ]; then
46-
echo "$PKG_SOURCE is not a directory"
47-
exit 1
242+
err_die "$PKG_SOURCE is not a directory"
48243
fi
49244

50245
PRE_TEST_SCRIPT="/ci/$PKG_SOURCE/pre-test.sh"
51246
TEST_SCRIPT="/ci/$PKG_SOURCE/test.sh"
52247

53-
if ! [ -f "$TEST_SCRIPT" ]; then
54-
echo "No test.sh script available"
55-
continue
56-
fi
57-
58248
export PKG_NAME PKG_VERSION CI_HELPERS
59249

60250
if [ -f "$PRE_TEST_SCRIPT" ]; then
61-
echo "Use package specific pre-test.sh"
251+
info 'Use the package-specific pre-test.sh'
62252
if sh "$PRE_TEST_SCRIPT" "$PKG_NAME" "$PKG_VERSION"; then
63-
echo "Pre-test successful"
253+
success 'Pre-test passed'
64254
else
65-
echo "Pre-test failed"
66-
exit 1
255+
err_die 'Pre-test failed'
67256
fi
68257
else
69-
echo "No pre-test.sh script available"
258+
info 'No pre-test.sh script available'
70259
fi
71260

72-
if [ $PKG_MANAGER = "opkg" ]; then
261+
if is_opkg; then
73262
opkg install "$PKG"
74-
elif [ $PKG_MANAGER = "apk" ]; then
263+
elif is_apk; then
75264
apk add --allow-untrusted "$PKG"
76265
fi
77266

78-
echo "Use package specific test.sh"
79-
if sh "$TEST_SCRIPT" "$PKG_NAME" "$PKG_VERSION"; then
80-
echo "Test successful"
81-
else
82-
echo "Test failed"
83-
exit 1
267+
SUCCESS=0
268+
269+
if generic_tests_enabled && ( generic_tests_forced || [ ! -f "$TEST_SCRIPT" ] ); then
270+
warn 'Use generic tests'
271+
if do_generic_tests; then
272+
SUCCESS=1
273+
fi
84274
fi
85275

86-
if [ $PKG_MANAGER = "opkg" ]; then
87-
opkg remove "$PKG_NAME" --force-removal-of-dependent-packages --force-remove --autoremove || true
88-
elif [ $PKG_MANAGER = "apk" ]; then
89-
apk del -r "$PKG_NAME"
276+
if [ -f "$TEST_SCRIPT" ]; then
277+
info 'Use the package-specific test.sh'
278+
if sh "$TEST_SCRIPT" "$PKG_NAME" "$PKG_VERSION"; then
279+
success 'Test passed'
280+
SUCCESS=1
281+
else
282+
err 'Test failed'
283+
fi
284+
fi
285+
286+
if is_opkg; then
287+
opkg remove "$PKG_NAME" \
288+
--autoremove \
289+
--force-removal-of-dependent-packages \
290+
--force-remove \
291+
|| true
292+
elif is_apk; then
293+
apk del --rdepends "$PKG_NAME" || true
90294
fi
295+
296+
[ "$SUCCESS" = 1 ] || exit 1
91297
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 }}
@@ -234,6 +241,8 @@ jobs:
234241
if: ${{ matrix.runtime_test && fromJSON(env.HAVE_PKGS) }}
235242
run: |
236243
docker run \
244+
-e ENABLE_GENERIC_TESTS=${{ inputs.enable_generic_tests }} \
245+
-e FORCE_GENERIC_TESTS=${{ inputs.force_generic_tests }} \
237246
-e PKG_MANAGER=${{ env.PKG_MANAGER }} \
238247
--platform linux/${{ matrix.arch }} \
239248
--rm \

0 commit comments

Comments
 (0)