]> git.ipfire.org Git - thirdparty/systemd.git/blame - test/test-functions
test: introduce a basic testsuite framework
[thirdparty/systemd.git] / test / test-functions
CommitLineData
898720b7
HH
1#!/bin/bash
2# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
3# ex: ts=8 sw=4 sts=4 et filetype=sh
4PATH=/sbin:/bin:/usr/sbin:/usr/bin
5export PATH
6
7setup_basic_dirs() {
8 for d in usr/bin usr/sbin bin etc lib "$libdir" sbin tmp usr var var/log; do
9 [[ -e "${initdir}${prefix}/$d" ]] && continue
10 if [ -L "/$d" ]; then
11 inst_symlink "/$d" "${prefix}/$d"
12 else
13 mkdir -m 0755 -p "${initdir}${prefix}/$d"
14 fi
15 done
16
17 for d in dev proc sys sysroot root run run/lock run/initramfs; do
18 if [ -L "/$d" ]; then
19 inst_symlink "/$d"
20 else
21 mkdir -m 0755 -p "$initdir/$d"
22 fi
23 done
24
25 ln -sfn /run "$initdir/var/run"
26 ln -sfn /run/lock "$initdir/var/lock"
27}
28
29inst_libs() {
30 local _bin=$1
31 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
32 local _file _line
33
34 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
35 [[ $_line = 'not a dynamic executable' ]] && break
36
37 if [[ $_line =~ $_so_regex ]]; then
38 _file=${BASH_REMATCH[1]}
39 [[ -e ${initdir}/$_file ]] && continue
40 inst_library "$_file"
41 continue
42 fi
43
44 if [[ $_line =~ not\ found ]]; then
45 dfatal "Missing a shared library required by $_bin."
46 dfatal "Run \"ldd $_bin\" to find out what it is."
47 dfatal "$_line"
48 dfatal "dracut cannot create an initrd."
49 exit 1
50 fi
51 done
52}
53
54import_testdir() {
55 STATEFILE=".testdir"
56 [[ -e $STATEFILE ]] && . $STATEFILE
57 if [[ -z "$TESTDIR" ]] || [[ ! -d "$TESTDIR" ]]; then
58 TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)
59 echo "TESTDIR=\"$TESTDIR\"" > $STATEFILE
60 export TESTDIR
61 fi
62}
63
64## @brief Converts numeric logging level to the first letter of level name.
65#
66# @param lvl Numeric logging level in range from 1 to 6.
67# @retval 1 if @a lvl is out of range.
68# @retval 0 if @a lvl is correct.
69# @result Echoes first letter of level name.
70_lvl2char() {
71 case "$1" in
72 1) echo F;;
73 2) echo E;;
74 3) echo W;;
75 4) echo I;;
76 5) echo D;;
77 6) echo T;;
78 *) return 1;;
79 esac
80}
81
82## @brief Internal helper function for _do_dlog()
83#
84# @param lvl Numeric logging level.
85# @param msg Message.
86# @retval 0 It's always returned, even if logging failed.
87#
88# @note This function is not supposed to be called manually. Please use
89# dtrace(), ddebug(), or others instead which wrap this one.
90#
91# This function calls _do_dlog() either with parameter msg, or if
92# none is given, it will read standard input and will use every line as
93# a message.
94#
95# This enables:
96# dwarn "This is a warning"
97# echo "This is a warning" | dwarn
98LOG_LEVEL=4
99
100dlog() {
101 [ -z "$LOG_LEVEL" ] && return 0
102 [ $1 -le $LOG_LEVEL ] || return 0
103 local lvl="$1"; shift
104 local lvlc=$(_lvl2char "$lvl") || return 0
105
106 if [ $# -ge 1 ]; then
107 echo "$lvlc: $*"
108 else
109 while read line; do
110 echo "$lvlc: " "$line"
111 done
112 fi
113}
114
115## @brief Logs message at TRACE level (6)
116#
117# @param msg Message.
118# @retval 0 It's always returned, even if logging failed.
119dtrace() {
120 set +x
121 dlog 6 "$@"
122 [ -n "$debug" ] && set -x || :
123}
124
125## @brief Logs message at DEBUG level (5)
126#
127# @param msg Message.
128# @retval 0 It's always returned, even if logging failed.
129ddebug() {
130 set +x
131 dlog 5 "$@"
132 [ -n "$debug" ] && set -x || :
133}
134
135## @brief Logs message at INFO level (4)
136#
137# @param msg Message.
138# @retval 0 It's always returned, even if logging failed.
139dinfo() {
140 set +x
141 dlog 4 "$@"
142 [ -n "$debug" ] && set -x || :
143}
144
145## @brief Logs message at WARN level (3)
146#
147# @param msg Message.
148# @retval 0 It's always returned, even if logging failed.
149dwarn() {
150 set +x
151 dlog 3 "$@"
152 [ -n "$debug" ] && set -x || :
153}
154
155## @brief Logs message at ERROR level (2)
156#
157# @param msg Message.
158# @retval 0 It's always returned, even if logging failed.
159derror() {
160 set +x
161 dlog 2 "$@"
162 [ -n "$debug" ] && set -x || :
163}
164
165## @brief Logs message at FATAL level (1)
166#
167# @param msg Message.
168# @retval 0 It's always returned, even if logging failed.
169dfatal() {
170 set +x
171 dlog 1 "$@"
172 [ -n "$debug" ] && set -x || :
173}
174
175
176# Generic substring function. If $2 is in $1, return 0.
177strstr() { [ "${1#*$2*}" != "$1" ]; }
178
179# normalize_path <path>
180# Prints the normalized path, where it removes any duplicated
181# and trailing slashes.
182# Example:
183# $ normalize_path ///test/test//
184# /test/test
185normalize_path() {
186 shopt -q -s extglob
187 set -- "${1//+(\/)//}"
188 shopt -q -u extglob
189 echo "${1%/}"
190}
191
192# convert_abs_rel <from> <to>
193# Prints the relative path, when creating a symlink to <to> from <from>.
194# Example:
195# $ convert_abs_rel /usr/bin/test /bin/test-2
196# ../../bin/test-2
197# $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test
198convert_abs_rel() {
199 local __current __absolute __abssize __cursize __newpath
200 local -i __i __level
201
202 set -- "$(normalize_path "$1")" "$(normalize_path "$2")"
203
204 # corner case #1 - self looping link
205 [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; }
206
207 # corner case #2 - own dir link
208 [[ "${1%/*}" == "$2" ]] && { echo "."; return; }
209
210 IFS="/" __current=($1)
211 IFS="/" __absolute=($2)
212
213 __abssize=${#__absolute[@]}
214 __cursize=${#__current[@]}
215
216 while [[ ${__absolute[__level]} == ${__current[__level]} ]]
217 do
218 (( __level++ ))
219 if (( __level > __abssize || __level > __cursize ))
220 then
221 break
222 fi
223 done
224
225 for ((__i = __level; __i < __cursize-1; __i++))
226 do
227 if ((__i > __level))
228 then
229 __newpath=$__newpath"/"
230 fi
231 __newpath=$__newpath".."
232 done
233
234 for ((__i = __level; __i < __abssize; __i++))
235 do
236 if [[ -n $__newpath ]]
237 then
238 __newpath=$__newpath"/"
239 fi
240 __newpath=$__newpath${__absolute[__i]}
241 done
242
243 echo "$__newpath"
244}
245
246
247# Install a directory, keeping symlinks as on the original system.
248# Example: if /lib points to /lib64 on the host, "inst_dir /lib/file"
249# will create ${initdir}/lib64, ${initdir}/lib64/file,
250# and a symlink ${initdir}/lib -> lib64.
251inst_dir() {
252 [[ -e ${initdir}/"$1" ]] && return 0 # already there
253
254 local _dir="$1" _part="${1%/*}" _file
255 while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do
256 _dir="$_part $_dir"
257 _part=${_part%/*}
258 done
259
260 # iterate over parent directories
261 for _file in $_dir; do
262 [[ -e "${initdir}/$_file" ]] && continue
263 if [[ -L $_file ]]; then
264 inst_symlink "$_file"
265 else
266 # create directory
267 mkdir -m 0755 -p "${initdir}/$_file" || return 1
268 [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file"
269 chmod u+w "${initdir}/$_file"
270 fi
271 done
272}
273
274# $1 = file to copy to ramdisk
275# $2 (optional) Name for the file on the ramdisk
276# Location of the image dir is assumed to be $initdir
277# We never overwrite the target if it exists.
278inst_simple() {
279 [[ -f "$1" ]] || return 1
280 strstr "$1" "/" || return 1
281
282 local _src=$1 target="${2:-$1}"
283 if ! [[ -d ${initdir}/$target ]]; then
284 [[ -e ${initdir}/$target ]] && return 0
285 [[ -L ${initdir}/$target ]] && return 0
286 [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}"
287 fi
288 # install checksum files also
289 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
290 inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac"
291 fi
292 ddebug "Installing $_src"
293 cp --sparse=always -pfL "$_src" "${initdir}/$target"
294}
295
296# find symlinks linked to given library file
297# $1 = library file
298# Function searches for symlinks by stripping version numbers appended to
299# library filename, checks if it points to the same target and finally
300# prints the list of symlinks to stdout.
301#
302# Example:
303# rev_lib_symlinks libfoo.so.8.1
304# output: libfoo.so.8 libfoo.so
305# (Only if libfoo.so.8 and libfoo.so exists on host system.)
306rev_lib_symlinks() {
307 [[ ! $1 ]] && return 0
308
309 local fn="$1" orig="$(readlink -f "$1")" links=''
310
311 [[ ${fn} =~ .*\.so\..* ]] || return 1
312
313 until [[ ${fn##*.} == so ]]; do
314 fn="${fn%.*}"
315 [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}"
316 done
317
318 echo "${links}"
319}
320
321# Same as above, but specialized to handle dynamic libraries.
322# It handles making symlinks according to how the original library
323# is referenced.
324inst_library() {
325 local _src="$1" _dest=${2:-$1} _lib _reallib _symlink
326 strstr "$1" "/" || return 1
327 [[ -e $initdir/$_dest ]] && return 0
328 if [[ -L $_src ]]; then
329 # install checksum files also
330 if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then
331 inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac"
332 fi
333 _reallib=$(readlink -f "$_src")
334 inst_simple "$_reallib" "$_reallib"
335 inst_dir "${_dest%/*}"
336 [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/}
337 ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}"
338 else
339 inst_simple "$_src" "$_dest"
340 fi
341
342 # Create additional symlinks. See rev_symlinks description.
343 for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do
344 [[ ! -e $initdir/$_symlink ]] && {
345 ddebug "Creating extra symlink: $_symlink"
346 inst_symlink $_symlink
347 }
348 done
349}
350
351# find a binary. If we were not passed the full path directly,
352# search in the usual places to find the binary.
353find_binary() {
354 if [[ -z ${1##/*} ]]; then
355 if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; }; then
356 echo $1
357 return 0
358 fi
359 fi
360
361 type -P $1
362}
363
364# Same as above, but specialized to install binary executables.
365# Install binary executable, and all shared library dependencies, if any.
366inst_binary() {
367 local _bin _target
368 _bin=$(find_binary "$1") || return 1
369 _target=${2:-$_bin}
370 [[ -e $initdir/$_target ]] && return 0
371 [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0
372 local _file _line
373 local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
374 # I love bash!
375 LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do
376 [[ $_line = 'not a dynamic executable' ]] && break
377
378 if [[ $_line =~ $_so_regex ]]; then
379 _file=${BASH_REMATCH[1]}
380 [[ -e ${initdir}/$_file ]] && continue
381 inst_library "$_file"
382 continue
383 fi
384
385 if [[ $_line =~ not\ found ]]; then
386 dfatal "Missing a shared library required by $_bin."
387 dfatal "Run \"ldd $_bin\" to find out what it is."
388 dfatal "$_line"
389 dfatal "dracut cannot create an initrd."
390 exit 1
391 fi
392 done
393 inst_simple "$_bin" "$_target"
394}
395
396# same as above, except for shell scripts.
397# If your shell script does not start with shebang, it is not a shell script.
398inst_script() {
399 local _bin
400 _bin=$(find_binary "$1") || return 1
401 shift
402 local _line _shebang_regex
403 read -r -n 80 _line <"$_bin"
404 # If debug is set, clean unprintable chars to prevent messing up the term
405 [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]')
406 _shebang_regex='(#! *)(/[^ ]+).*'
407 [[ $_line =~ $_shebang_regex ]] || return 1
408 inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@"
409}
410
411# same as above, but specialized for symlinks
412inst_symlink() {
413 local _src=$1 _target=${2:-$1} _realsrc
414 strstr "$1" "/" || return 1
415 [[ -L $1 ]] || return 1
416 [[ -L $initdir/$_target ]] && return 0
417 _realsrc=$(readlink -f "$_src")
418 if ! [[ -e $initdir/$_realsrc ]]; then
419 if [[ -d $_realsrc ]]; then
420 inst_dir "$_realsrc"
421 else
422 inst "$_realsrc"
423 fi
424 fi
425 [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}"
426 [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/}
427 ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target"
428}
429
430# attempt to install any programs specified in a udev rule
431inst_rule_programs() {
432 local _prog _bin
433
434 if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then
435 for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do
436 if [ -x /lib/udev/$_prog ]; then
437 _bin=/lib/udev/$_prog
438 else
439 _bin=$(find_binary "$_prog") || {
440 dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found"
441 continue;
442 }
443 fi
444
445 #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
446 dracut_install "$_bin"
447 done
448 fi
449}
450
451# udev rules always get installed in the same place, so
452# create a function to install them to make life simpler.
453inst_rules() {
454 local _target=/etc/udev/rules.d _rule _found
455
456 inst_dir "/lib/udev/rules.d"
457 inst_dir "$_target"
458 for _rule in "$@"; do
459 if [ "${rule#/}" = "$rule" ]; then
460 for r in /lib/udev/rules.d /etc/udev/rules.d; do
461 if [[ -f $r/$_rule ]]; then
462 _found="$r/$_rule"
463 inst_simple "$_found"
464 inst_rule_programs "$_found"
465 fi
466 done
467 fi
468 for r in '' ./ $dracutbasedir/rules.d/; do
469 if [[ -f ${r}$_rule ]]; then
470 _found="${r}$_rule"
471 inst_simple "$_found" "$_target/${_found##*/}"
472 inst_rule_programs "$_found"
473 fi
474 done
475 [[ $_found ]] || dinfo "Skipping udev rule: $_rule"
476 done
477}
478
479# general purpose installation function
480# Same args as above.
481inst() {
482 local _x
483
484 case $# in
485 1) ;;
486 2) [[ ! $initdir && -d $2 ]] && export initdir=$2
487 [[ $initdir = $2 ]] && set $1;;
488 3) [[ -z $initdir ]] && export initdir=$2
489 set $1 $3;;
490 *) dfatal "inst only takes 1 or 2 or 3 arguments"
491 exit 1;;
492 esac
493 for _x in inst_symlink inst_script inst_binary inst_simple; do
494 $_x "$@" && return 0
495 done
496 return 1
497}
498
499# install any of listed files
500#
501# If first argument is '-d' and second some destination path, first accessible
502# source is installed into this path, otherwise it will installed in the same
503# path as source. If none of listed files was installed, function return 1.
504# On first successful installation it returns with 0 status.
505#
506# Example:
507#
508# inst_any -d /bin/foo /bin/bar /bin/baz
509#
510# Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
511# initramfs.
512inst_any() {
513 local to f
514
515 [[ $1 = '-d' ]] && to="$2" && shift 2
516
517 for f in "$@"; do
518 if [[ -e $f ]]; then
519 [[ $to ]] && inst "$f" "$to" && return 0
520 inst "$f" && return 0
521 fi
522 done
523
524 return 1
525}
526
527# dracut_install [-o ] <file> [<file> ... ]
528# Install <file> to the initramfs image
529# -o optionally install the <file> and don't fail, if it is not there
530dracut_install() {
531 local _optional=no
532 if [[ $1 = '-o' ]]; then
533 _optional=yes
534 shift
535 fi
536 while (($# > 0)); do
537 if ! inst "$1" ; then
538 if [[ $_optional = yes ]]; then
539 dinfo "Skipping program $1 as it cannot be found and is" \
540 "flagged to be optional"
541 else
542 dfatal "Failed to install $1"
543 exit 1
544 fi
545 fi
546 shift
547 done
548}
549
550
551# inst_libdir_file [-n <pattern>] <file> [<file>...]
552# Install a <file> located on a lib directory to the initramfs image
553# -n <pattern> install non-matching files
554inst_libdir_file() {
555 if [[ "$1" == "-n" ]]; then
556 local _pattern=$1
557 shift 2
558 for _dir in $libdirs; do
559 for _i in "$@"; do
560 for _f in "$_dir"/$_i; do
561 [[ "$_i" =~ $_pattern ]] || continue
562 [[ -e "$_i" ]] && dracut_install "$_i"
563 done
564 done
565 done
566 else
567 for _dir in $libdirs; do
568 for _i in "$@"; do
569 for _f in "$_dir"/$_i; do
570 [[ -e "$_f" ]] && dracut_install "$_f"
571 done
572 done
573 done
574 fi
575}
576
577do_test() {
578 [[ $UID != "0" ]] && exit 0
579 command -v qemu-kvm &>/dev/null || exit 0
580# Detect lib paths
581 [[ $libdir ]] || for libdir in /lib64 /lib; do
582 [[ -d $libdir ]] && libdirs+=" $libdir" && break
583 done
584
585 [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do
586 [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
587 done
588
589 import_testdir
590
591 while (($# > 0)); do
592 case $1 in
593 --run)
594 echo "TEST RUN: $TEST_DESCRIPTION"
595 test_run
596 ret=$?
597 if [ $ret -eq 0 ]; then
598 echo "TEST RUN: $TEST_DESCRIPTION [OK]"
599 else
600 echo "TEST RUN: $TEST_DESCRIPTION [FAILED]"
601 fi
602 exit $ret;;
603 --setup)
604 echo "TEST SETUP: $TEST_DESCRIPTION"
605 test_setup
606 exit $?;;
607 --clean)
608 echo "TEST CLEANUP: $TEST_DESCRIPTION"
609 test_cleanup
610 rm -fr "$TESTDIR"
611 rm -f .testdir
612 exit $?;;
613 --all)
614 echo -n "TEST: $TEST_DESCRIPTION ";
615 (
616 test_setup && test_run
617 ret=$?
618 test_cleanup
619 rm -fr "$TESTDIR"
620 rm -f .testdir
621 exit $ret
622 ) </dev/null >test.log 2>&1
623 ret=$?
624 if [ $ret -eq 0 ]; then
625 rm test.log
626 echo "[OK]"
627 else
628 echo "[FAILED]"
629 echo "see $(pwd)/test.log"
630 fi
631 exit $ret;;
632 *) break ;;
633 esac
634 shift
635 done
636}