#!/bin/bash # When running with "-v", the test itself runs in a pipeline with tee, and # without pipefail we get the exit value from tee instead of from the test. set -o pipefail # The linuxcnc starter script sometimes tries to display X windows if # DISPLAY is set. We never want that while running tests, so unset it. unset DISPLAY # Some of our tests emit locale-sensitive strings, so reset the locale # to a sane default. export LC_ALL=C export LANGUAGES= TOPDIR="$(realpath "$(dirname "$0")/..")" # $prefix is used in the substitutions of @...@ strings below # shellcheck disable=SC2034 prefix=@prefix@ # Shellcheck does not know about substitutions # shellcheck disable=SC2050 if [ "@RUN_IN_PLACE@" = yes ]; then . "$TOPDIR/scripts/rip-environment" >&/dev/null export HEADERS=@EMC2_HOME@/include export LIBDIR=${TOPDIR}/lib export REALTIME=realtime else # Set $EMC2_HOME to $prefix for tests that depend on it export SYSTEM_BUILD=1 export EMC2_HOME=@EMC2_HOME@ export HEADERS=@includedir@/linuxcnc export LIBDIR=@EMC2_HOME@/lib export LINUXCNC_EMCSH=@WISH@ export REALTIME=@REALTIME@ export SUDO=sudo fi export PYTHON_CPPFLAGS="@PYTHON_CPPFLAGS@" export PYTHON_EXTRA_LIBS="@PYTHON_EXTRA_LIBS@" export PYTHON_EXTRA_LDFLAGS="@PYTHON_EXTRA_LDFLAGS@" export PYTHON_LIBS="@PYTHON_EXTRA_LIBS@" RUNTESTS="$(readlink -f "$0")" export RUNTESTS NUM=0 FAIL=0; FAIL_NAMES="" XFAIL=0 SHMERR=0 SKIP=0 VERBOSE=0 clean () { find "$*" \( -name "stderr" -or -name "result" \ -or -name "*.var" -or -name "*.var.bak" \) \ -print0 | xargs -0 rm -f } wait_for_result_close() { # Test for the 'result' and 'stderr' files in the current testdir to be # closed. The 'checkresult' script cannot be run if these files are still # in use and would cause a race condition. # This function should be called with the test's directory as CWD. presult="$(realpath -e -q "./result")" pstderr="$(realpath -e -q "./stderr")" if [ -z "$presult" ] || [ -z "$pstderr" ]; then echo "Internal error: Missing 'result' or 'stderr' in wait_for_result_close()" exit 2 fi timeoutcnt=0 while true; do lsof -- "$presult" > /dev/null 2>&1; resresult=$? lsof -- "$pstderr" > /dev/null 2>&1; resstderr=$? if [ $resresult -ne 0 ] && [ $resstderr -ne 0 ]; then # Neither 'result' nor 'stderr' are open anymore break fi if [ $timeoutcnt -ge 30 ]; then if [ $resresult -eq 0 ]; then echo "*** Timeout waiting for 'result' file to close" fi if [ $resstderr -eq 0 ]; then echo "*** Timeout waiting for 'stderr' file to close" fi echo "*** Test results may be invalid when checked." return 1 fi sleep 1 timeoutcnt=$((timeoutcnt + 1)) done return 0 } run_shell_script () { testname=$(basename "$1") testdir=$(dirname "$1") pushd "$testdir" > /dev/null || exit 2 if [ $VERBOSE -eq 1 ]; then (bash -x "$testname" | tee result) 3>&1 1>&2 2>&3 | tee stderr else bash -x "$testname" > result 2> stderr fi exitcode=$? wait_for_result_close popd > /dev/null || exit 2 return "$exitcode" } run_executable () { testname=$(basename "$1") testdir=$(dirname "$1") pushd "$testdir" > /dev/null || exit 2 if [ $VERBOSE -eq 1 ]; then (./"$testname" | tee result) 3>&1 1>&2 2>&3 | tee stderr else ./"$testname" > result 2> stderr fi exitcode=$? wait_for_result_close popd > /dev/null || exit 2 return "$exitcode" } run_without_overruns () { testname=$(basename "$1") testdir=$(dirname "$1") for i in $(seq 10); do if [ "$i" != 1 ]; then echo "--- $testdir: overrun detected in sampler, re-running test" 1>&2 ; fi pushd "$testdir" > /dev/null || exit 2 if [ $VERBOSE -eq 1 ]; then (halrun -f "$testname" | tee result) 3>&1 1>&2 2>&3 | tee stderr else halrun -f "$testname" > result 2> stderr fi exitcode=$? wait_for_result_close popd > /dev/null || exit 2 if ! grep -q '^overrun$' "$testdir/result"; then return "$exitcode"; fi done echo "--- $testdir: $i overruns detected, giving up" 1>&2 return 1 } run_test() { testname="$1" case "$testname" in *.hal) run_without_overruns "$testname" ;; *.sh) run_shell_script "$testname" ;; *) run_executable "$testname" ;; esac } SHMEM_KEY=( "0x00000064" "0x48414c32" "0x48484c34" "0x90280a48" "0x130cf406" "0x434c522b" ) SHMEM_USE=( "Emc motion" "Hal" "UUID" "Rtapi" "Hal scope" "Classicladder" ) test_shmem() { # Test if there are any shared memory segments left. These will # interfere with performing tests as we cannot be assured of a clean # start for each test if a segment exists. mapfile -t keys < <(ipcs -m | grep -Ei "^\\s*($(IFS='|'; echo "${SHMEM_KEY[*]}"))" | awk '{print $1}') if [ "${#keys[@]}" -gt 0 ]; then echo "There are one or more shared memory segments currently allocated." echo "This indicates that there is a LinuxCNC instance running or it" echo "did not cleanup before exit." echo "You should run 'ipcs -m' and look for the following keys to cleanup:" for i in "${!SHMEM_KEY[@]}"; do for j in "${keys[@]}"; do if [ "$j" = "${SHMEM_KEY[$i]}" ]; then echo "${SHMEM_KEY[$i]} - ${SHMEM_USE[$i]} key" break fi done done echo echo "You should remove the key(s) with 'ipcrm -M ' if LinuxCNC is" echo "not running and no processes are attached to the segment(s)." return 1 else return 0 fi } test_and_remove_shmem() { ret=0 for i in "${!SHMEM_KEY[@]}"; do # ipcs returns the following columns: # key shmid owner perms bytes nattch status read -r -a SHM < <(ipcs -m | grep -Ei "^\\s*${SHMEM_KEY[$i]}") if [ "${#SHM[@]}" -ge 6 ]; then echo "*** SHMERR: Shared memory segment ${SHMEM_KEY[$i]} (${SHMEM_USE[$i]} key) was not removed. Removing..." ipcrm -M "${SHMEM_KEY[$i]}" # Check number of attached processes. It should be zero. if [ "${SHM[5]}" -ne 0 ]; then echo "*** SHMERR: Shared memory segment ${SHMEM_KEY[$i]} has at least one attached process." echo "*** SHMERR: Manual intervention is required to solve the situation." return 2 fi ret=1 fi done return $ret } TMPDIR=$(mktemp -d /tmp/runtest.XXXXXX) trap 'rm -rf "$TMPDIR"' 0 1 2 3 15 run_tests () { if ! test_shmem; then exit 1; fi find "$@" -name test.hal -or -name test.sh -or -name test \ | sort > "$TMPDIR/alltests" while read -r testname; do testdir=$(dirname "$testname") # check if there's a "musthave" file with prerequisites from config.h if [ -e "$testdir/musthave" ] ; then # one prerequisite per line while IFS= read -r prereq ; do if ! grep --quiet --regexp "^#define HAVE_$prereq.*" "$TOPDIR/src/config.h" ; then echo "Skipping test for missing prerequisite \"$prereq\": $testdir" 1>&2 SKIP=$((SKIP + 1)) continue 3 fi done < "$testdir/musthave" fi # skip test if there's a "skip" file if [ -e "$testdir/skip" ]; then if ! [ -x "$testdir/skip" ] || ! "$testdir/skip"; then echo "Skipping disabled test: $testdir" 1>&2 SKIP=$((SKIP + 1)) continue fi fi if $NOSUDO && [ -e "$testdir/control" ] && \ grep Restrictions: "$testdir/control" | grep -q sudo; then if ! [ -x "$testdir/skip" ] || ! "$testdir/skip"; then echo "Skipping sudo test: $testdir" 1>&2 SKIP=$((SKIP + 1)) continue fi fi NUM=$((NUM + 1)) TEST_DIR=$(readlink -f "$testdir") export TEST_DIR echo "Running test: $testdir" 1>&2 if test -n "$SYSTEM_BUILD"; then # Tell `halcompile` where to install comps USER_MODULE_DIR=$(readlink -f "$testdir") \ PATH=$(readlink -f "$testdir"):$PATH \ run_test "$testname" else run_test "$testname" fi exitcode=$? if [ "$exitcode" -ne 0 ]; then reason="test run exited with $exitcode" else if [ -e "$testdir/checkresult" ]; then "$testdir/checkresult" "$testdir/result" exitcode=$? reason="checkresult exited with $exitcode" elif [ -f "$testdir/expected" ]; then cmp -s "$testdir/expected" "$testdir/result" exitcode=$? reason="result differed from expected" if [ "$exitcode" -ne 0 ]; then diff -u "$testdir/expected" "$testdir/result" > "$TMPDIR/diff" SIZE=$(wc -l < "$TMPDIR/diff") if [ "$SIZE" -lt 40 ]; then cat "$TMPDIR/diff" else OMIT=$((SIZE-40)) head -40 "$TMPDIR/diff" echo "($OMIT more lines omitted)" fi fi else exitcode=1 reason="Neither expected nor checkresult existed" fi fi if [ "$exitcode" -ne 0 ]; then echo "*** $testdir: XFAIL: $reason" if test $PRINT = 1; then echo "************** result:" tail -500 "$testdir/result" | sed 's/^/ /' echo "************** stderr:" tail -500 "$testdir/stderr" | sed 's/^/ /' echo "**************" fi if [ -f "$testdir/xfail" ]; then XFAIL=$((XFAIL + 1)) if [ $NOCLEAN -eq 0 ]; then rm -f "$testdir/stderr" "$testdir/result" \ "$testdir"/*.var "$testdir"/*.var.bak fi else FAIL=$((FAIL + 1)) FAIL_NAMES="$FAIL_NAMES"$'\n'"$testdir" fi if test $STOP = 1; then break fi else if [ -f "$testdir/xfail" ]; then echo "*** $testdir: XPASS: Passed, but was expected to fail" else if [ $NOCLEAN -eq 0 ]; then rm -f "$testdir/stderr" "$testdir/result" \ "$testdir"/*.var "$testdir"/*.var.bak fi fi fi if ! test_and_remove_shmem; then if [ $? -eq 2 ]; then # Cannot remove attached segments. Fail hard. exit 1 fi SHMERR=$((SHMERR + 1)) fi done < "$TMPDIR/alltests" SUCC=$((NUM-FAIL-XFAIL)) echo "Runtest: $NUM tests run, $SUCC successful, $FAIL failed + $XFAIL expected, $SKIP skipped, $SHMERR shmem errors" if [ "$FAIL" -ne 0 ]; then echo "Failed: $FAIL_NAMES" exit 1; else exit 0; fi } usage () { P=${0##*/} cat <