diff --git a/common/README.md b/common/README.md new file mode 100644 index 0000000000000000000000000000000000000000..db4ccc76ea4d7b33c463e8007a76bbc05931e328 --- /dev/null +++ b/common/README.md @@ -0,0 +1,29 @@ +Common files shared accross apertis tests. + +Those files are shared through a git subtree which is included by all test projects. + +Use `common-subtree.sh add` to add the subtree in a tests repository. +For example: +``` +# Retrieve the common-subtree.sh script, we use `git`, but could also use `wget` +$ git clone git@gitlab.apertis.org:tests/common + +# Clone the test repository if needed +$ git clone git@gitlab.apertis.org:tests/iptables-basic +# Enter the test directory +$ cd iptables-basic + +# In the test directory, add the common git subtree +$ ../common/common-subtree.sh add + +# A new commit has been created, push the test with the subtree initialized +$ git push origin +``` + +If a script is supposed to be in the common/ directory, it should be commited in the +[common repository](https://gitlab.apertis.org/tests/common), and then it can be pulled +into the git subtree using the command: +``` +$ ../common/common-subtree.sh pull + +``` diff --git a/common/common-apparmor.sh b/common/common-apparmor.sh new file mode 100644 index 0000000000000000000000000000000000000000..f6a94977038e82708ec2f870dde7df9f396828f6 --- /dev/null +++ b/common/common-apparmor.sh @@ -0,0 +1,43 @@ +# Need to source this file. + +# Pass in audit string, returns formatted block as `aa_log_extract_tokens.pl` +# would have provided. Need to return: +# +# profile:/usr/bin/busctl +# sdmode:REJECTING +# denied_mask:r +# operation:open +# name:/proc/879/stat +# request_mask:r +# +# Note: apparmor uses the term "DENIED" rather than "REJECTING", but our +# expected values want "REJECTING", so tweak for now. +apparmor_parse_journal() { + awk -v event=$2 ' + { + for (i = 1; i <= NF; i++) { + n = index($i, "="); + if(n) { + vars[substr($i, 1, n - 1)] = substr($i, n + 1) + # Strip quotes + gsub("\"","",vars[substr($i, 1, n - 1)]) + } + } + } + { + if (vars["apparmor"] == event) { + print "====" + print "profile:"vars["profile"] + # Match old nomenclature + if (vars["apparmor"] == "DENIED") { + vars["apparmor"] = "REJECTING" + } + print "sdmode:"vars["apparmor"] + print "denied_mask:"vars["denied_mask"] + print "operation:"vars["operation"] + print "name:"vars["name"] + print "request_mask:"vars["requested_mask"] + } + } + ' $1 +} diff --git a/common/common-subtree.sh b/common/common-subtree.sh new file mode 100755 index 0000000000000000000000000000000000000000..59c5e21b3d73466f45e92bef5c038a13600c297f --- /dev/null +++ b/common/common-subtree.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +usage () { + echo "usage: $0 [add|pull]" + echo "" + echo "'$0 add' adds the common subtree to a tests repository" + echo "'$0 pull' is used to update the common subtree in tests" +} + +case $1 in +pull) +git subtree pull -P common git@gitlab.apertis.org:tests/common.git master +;; +add) +git subtree add -P common git@gitlab.apertis.org:tests/common.git master +;; +*) usage;; +esac diff --git a/common/common.sh b/common/common.sh new file mode 100644 index 0000000000000000000000000000000000000000..913e1bfc3a57549fe8e68453dade06e4fc3ad60c --- /dev/null +++ b/common/common.sh @@ -0,0 +1,319 @@ +# Source me! +# I need ROOTDIR, PN and PV to be defined! (see bottom of file) +# vim: set sts=4 sw=4 et : + +# Detect whether stdout is to a terminal +if tty -s <&1; then + IS_TTY="true" +fi + +is_tty() { + [ "${IS_TTY}" = true ] && return 0 + return 1 +} + +########## +# Murder # +########## +_kill_daemons() { + pkill dconf-service >/dev/null 2>&1 || true + return 0 # Never fail +} + +########## +# Output # +########## +cry_n() { + if is_tty; then + # Cry tears of blood + /bin/echo -n -e "\033[01;31m#\033[00m $@" + else + /bin/echo -n -e "# $@" + fi +} + +cry() { + cry_n "$@" + echo +} + +say_n() { + if is_tty; then + # Speak in green + /bin/echo -n -e "\033[01;32m#\033[00m $@" + else + /bin/echo -n -e "# $@" + fi +} + +say() { + say_n "$@" + echo +} + +whine_n() { + if is_tty; then + # Whine in yellow + /bin/echo -n -e "\033[01;33m#\033[00m $@" + else + /bin/echo -n -e "# $@" + fi +} + +whine() { + whine_n "$@" + echo +} + +echo_red() { + if is_tty; then + # Print text in red, without an implicit newline + /bin/echo -n -e "\033[01;31m$@\033[00m" + else + /bin/echo -n -e "$@" + fi +} + +echo_green() { + if is_tty; then + # Print text in green, without an implicit newline + /bin/echo -n -e "\033[01;32m$@\033[00m" + else + /bin/echo -n -e "$@" + fi +} + +################### +# Status Messages # +################### +test_success() { + say "All tests PASSED successfully!" + _kill_daemons + trap - ERR 2>/dev/null || true + trap - EXIT + exit 0 +} + +test_failure() { + [ $? -eq 0 ] && exit + cry "Tests FAILED!" + _kill_daemons + whine "Work directory was: ${WORKDIR}" + exit 0 +} + +setup_success() { + say "Test setup successfully!" + _kill_daemons + trap - ERR 2>/dev/null || true + trap - EXIT + return 0 +} + +setup_failure() { + [ $? -eq 0 ] && exit + cry "Test setup failed!" + _kill_daemons + exit 1 +} + +############# +# Utilities # +############# +create_temp_workdir() { + local tempdir="$(mktemp -d -p ${WORKDIR})" + echo "${tempdir}" 1>&2 + _realpath "${tempdir}" +} + +# Takes an IFS delimited list of files from stdin, checks if each exists, and +# passes it on if it does. If the file doesn't exist, it prefixes the filename +# with "DNE", which is caught by _src_test, which then marks it as a failed test +check_file_exists_tee() { + while read i; do + if [ -e "$i" ]; then + echo "$i" + else + echo "DNE: $i" + fi + done +} + +check_not_root() { + if [ "$(id -ru)" = 0 ]; then + cry "Do not run this test as root!" + return 1 + fi + return 0 +} + +check_have_root() { + if [ "$(id -ru)" != 0 ]; then + cry "Need root to run this test successfully!" + return 1 + fi + return 0 +} + +arch_is_arm() { + case "$(uname -m)" in + arm*) + return 0 + ;; + esac + return 1 +} + +# Check if a session bus is running otherwise fail +ensure_dbus_session() { + local dbus_socket + + dbus_socket="/run/user/$(id -ru)/bus" + if [ ! -e "${dbus_socket}" ]; then + cry "Could not find session bus..." + return 1 + fi + + return 0 +} + +###################### +# Internal functions # +###################### +# Sleep, unless specified otherwise +_sleep() { + if [ -z "${QUICK}" ]; then + /bin/sleep "$@" + else + /bin/sleep 0.2 + fi +} + +# Run external utilities with this +# All output is suppressed by default as LAVA doesn't cope well with very large logs. +# Use DEBUG=1 to enable all output when running tests locally on developer devices. +# Short-circuiting with a return is needed to prevent unexpected exiting due to +# a non-zero return code when set -e is enabled. +_run_cmd() { + if [ "${DEBUG}" = 1 ] + then + "$@" || return + else + "$@" >/dev/null 2>&1 || return + fi +} + +_expect_pass() { + _run_cmd "$@" +} + +_expect_fail() { + ! _run_cmd "$@" +} + +_src_test() { + # Reads a list of tests to run via stdin, and executes them one by one + # All these are supposed to pass + local i + local failed= + local expect=$1 + local prefix="" + shift + + if [ -n "${APERTIS_TESTS_NAME_PREFIX}" ]; then + prefix="${APERTIS_TESTS_NAME_PREFIX}" + fi + + while read i; do + case $i in + [#]*) + echo_red "${prefix}$i: skip\n" + continue + ;; + # See check_file_exists_tee() + DNE*) + echo_red "${prefix}$i: fail\n" + whine "Got an invalid executable name '$i'!" + failed="$failed $i" + continue + ;; + esac + say "Running test '$i' ..." + if ! "$expect" "$i" "$@"; then + failed="$failed $i" + echo_red "${prefix}$i: fail\n" + else + echo_green "${prefix}$i: pass\n" + fi + done + if [ -n "$failed" ]; then + whine "The following tests failed: " + for i in $failed; do + whine "\t$i" + done + return 1 + fi +} + +# Let's not depend on realpath; we don't need it +_realpath() { + cd "$1" + echo "$PWD" +} + +########## +# Phases # +########## +src_test_pass() { + _src_test _expect_pass "$@" +} + +src_test_fail() { + _src_test _expect_fail "$@" +} + +src_unpack() { + mkdir -p "${WORKDIR}" +} + +src_cleanup() { + rm -rf "${WORKDIR}" +} + +src_copy() { + # Copy $1 $2 .. $N-1 to $N + # Essentially just a wrapper around `cp` right now + cp -v -L -r "$@" 1>&2 +} + +src_copy_contents() { + # Copy the contents of $1 to $2 + # FIXME: This ignores dot-files. Use rsync or something? + cp -v -L -r "$1"/* "$2" 1>&2 +} + +############# +# Variables # +############# +# Fix this to not flood /var/tmp with temporary directories +BASEWORKDIR="/var/tmp/chaiwala-tests" +mkdir -p "${BASEWORKDIR}" +WORKDIR="$(mktemp -d -p ${BASEWORKDIR} "$(date +%Y%m%d-%H%M%S)-XXXXXXXXXX")" +# Tests might be run as chaiwala, or as root, or some other user +# Everyone should be able to write here +chown 1000 "${BASEWORKDIR}" +chmod 777 "${BASEWORKDIR}" || true +chmod 777 "${WORKDIR}" || true +sync + +# Wrappers for external commands used +WGET="${WGET:-wget -c}" +# We disable apt-get, and just do a pass-through because these tests are +# integrated into LAVA now +#APT_GET="$(type -P true)" +GDBUS="${GDBUS:-gdbus}" + +# 0 = no output +# 1 = stderr +# 2 = stdout + stderr +DEBUG="${DEBUG:-0}" diff --git a/common/inherit-config.sh b/common/inherit-config.sh new file mode 100644 index 0000000000000000000000000000000000000000..050b5f1054870804866d823c8f805e55ff0ef462 --- /dev/null +++ b/common/inherit-config.sh @@ -0,0 +1,21 @@ +# This file should be symlinked inside your test directory, +# and sourced from config.sh +# It will be modified and copied to the install directory by make + +. "${TESTDIR}/common/common.sh" + +## These variables are properties of the image itself +LIBEXECDIR="${LIBEXECDIR:-/usr/lib}" + +## These variables get modified by `make` during install +# Directory where scripts and other non-binary data is installed +TESTDATADIR="${TESTDIR}" +# Binary executable install directory +TESTLIBDIR="${TESTDIR}" + +REALTESTDIR="$(_realpath ${TESTDIR})" +# Directory where test data/resources are installed +RESOURCE_DIR="${REALTESTDIR}/common/resources" + +## These are derived from the above +MEDIA_RESOURCE_DIR="${RESOURCE_DIR}/media" diff --git a/common/run-aa-test b/common/run-aa-test new file mode 100755 index 0000000000000000000000000000000000000000..347676ee8b7a4d7749d4fdb00ca7159c7925b984 --- /dev/null +++ b/common/run-aa-test @@ -0,0 +1,182 @@ +#!/bin/sh +# +# Copyright © 2018 Collabora Ltd. +# +# Based on python version of run-aa-test +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +PWD=$(cd $(dirname $0); pwd; cd - >/dev/null 2>&1) +. ${PWD}/common-apparmor.sh + +ALTERNATIVE_SEPARATOR="## alternative ##" +END=2 + +case $(echo ${LAUNCH_DBUS} | tr [A-Z] [a-z]) in +0 | no | false) + LAUNCH_DBUS="False" + ;; +*) + LAUNCH_DBUS="True" +esac + +case $(echo ${RUN_AS_USER} | tr [A-Z] [a-z]) in +0 | no | false) + RUN_AS_USER="False" + ;; +*) + RUN_AS_USER="True" +esac + +CHAIWALA_UID=1000 +CHAIWALA_USER="user" + +# Check parameters +if [ $# -lt 2 ]; then + + echo "Usage: run-aa-test <expectation-file> <command> <argument-1> <argument-2> …" + echo "\"export LAUNCH_DBUS=no\" in the test script to not launch a dbus session." + echo "\"export RUN_AS_USER=no\" in the test script to not run as ${CHAIWALA_USER}" + exit 1 +fi + +EXPECT_FILE=$1 +shift + +if [ ! -r ${EXPECT_FILE} ]; then + echo "Cannot read specified expectation file: ${EXPECT_FILE}" + exit 1 +fi + +if [ ! -x $1 ]; then + echo "Cannot execute specified test executable: $1" + exit 1 +fi + +# typically "normal.expected" or "malicious.expected" +TEST_TITLE=$( basename ${EXPECT_FILE} ) + +# Touch .bash_history, which we use in some tests, if it's not there. +bash_history="/home/${CHAIWALA_USER}/.bash_history" +if [ ! -r ${bash_history} ]; then + sudo -u ${CHAIWALA_USER} touch ${bash_history} + RET=$? + if [ "$RET" != "0" ]; then + echo "Failed to create .bash_history: $RET" + exit 1 + fi +fi + +# Create a temporary directory for files +TMP_DIR=$(mktemp -d) + +# Log start time +START_TIME=$(date +"%F %T") + +if [ "${LAUNCH_DBUS}" = "True" ]; then + # Start a new D-Bus session for this test + CMD="dbus-run-session -- $*" +else + CMD="$*" +fi + +CMDLINE="${PWD}/run-test-in-systemd" +if [ ! -x $CMDLINE ]; then + echo "common/run-test-in-systemd not found" + exit 1 +fi + +CMDLINE="${CMDLINE}" + +if [ "${RUN_AA_TEST_TIMEOUT}" != "" ]; then + CMDLINE="${CMDLINE} --timeout=${RUN_AA_TEST_TIMEOUT}" +fi + +if [ "${RUN_AS_USER}" = "True" ]; then + CMDLINE="${CMDLINE} --user=${CHAIWALA_UID}" +else + CMDLINE="${CMDLINE} --system" +fi + +CMDLINE="${CMDLINE} ${CMD}" + +echo "#=== running test script: ${CMDLINE} ===" + +setsid ${CMDLINE} +RET=$? + +echo "#--- end of test script, status: ${RET}" + +if [ "${RET}" = "0" ]; then + echo "${TEST_TITLE}_underlying_tests: pass" +else + echo "# ${CMDLINE} exited ${RET}" + # typically "normal.expected_underlying_tests: fail" + echo "${TEST_TITLE}_underlying_tests: fail" + + exit 1 +fi + +# Give journal time to log the entries. +sleep 3 + +# Get audit information from journal +AUDIT_FILE=${TMP_DIR}/AUDIT +journalctl -S "${START_TIME}" -t audit -o cat > ${AUDIT_FILE} + +echo "#=== ${TEST_TITLE} ===" + +echo "#---8<--- raw apparmor output from journal" +cat ${AUDIT_FILE} | sed 's/^/# /' +echo "#--->8---" + +echo "#---8<--- expected parsed apparmor output from journal" +cat ${EXPECT_FILE} | sed 's/^/# /' +echo "#--->8---" + +csplit ${EXPECT_FILE} -f ${TMP_DIR}/EXPECT -b "%d" "/^${ALTERNATIVE_SEPARATOR}$/" {*} + +# Old versions of csplit don't provide "--suppress-matched", strip separator separately +for FILE in ${TMP_DIR}/EXPECT*; do + sed -i "/^${ALTERNATIVE_SEPARATOR}$/d" ${FILE} +done + +PARSE_FILE="${TMP_DIR}/PARSE" + +# TODO: There is potential for other processes to cause messages to appear in +# the journal that may lead to false failures. If this is found to be an +# issue in practice, then additional filtering of results may be required +apparmor_parse_journal ${AUDIT_FILE} DENIED > ${PARSE_FILE} + +echo "#---8<--- actual parsed apparmor output from journal" +cat ${PARSE_FILE} | sed 's/^/# /' +echo "#--->8---" + +MATCH_EXPECTATION="False" + +# We might have alternative expectations, take that into consideration. +OUTPUT_MD5=$( cat ${PARSE_FILE} | md5sum ) +COUNT=$( ls -1 ${TMP_DIR}/EXPECT* | wc -l ) +NUM=0 +while [ $((${NUM} < ${COUNT})) = 1 ]; do + EXPECTED_MD5=$( cat ${TMP_DIR}/EXPECT${NUM} | md5sum ) + if [ "${OUTPUT_MD5}" = "${EXPECTED_MD5}" ]; then + echo "# audit log matches alternative expectation ${NUM}/${COUNT}" + MATCH_EXPECTATION="True" + fi + NUM=$((${NUM}+1)) +done + +if [ "${MATCH_EXPECTATION}" = "True" ]; then + echo "${TEST_TITLE}: pass" +else + echo "#---8<--- diff" + diff -urN ${TMP_DIR}/EXPECT${NUM} ${PARSE_FILE} + echo "#--->8---" + echo "${TEST_TITLE}: fail" + exit 1 +fi + +exit 0 diff --git a/common/run-test-in-systemd b/common/run-test-in-systemd new file mode 100755 index 0000000000000000000000000000000000000000..ba2c3addc3be213cdf31b395e06f5e505c5f5d7a --- /dev/null +++ b/common/run-test-in-systemd @@ -0,0 +1,330 @@ +#!/bin/sh + +# run-test-in-systemd.sh — run a LAVA test as a systemd unit +# +# Copyright © 2015, 2016, 2017 Collabora Ltd. +# +# SPDX-License-Identifier: MPL-2.0 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +usage=' +Usage: + run-test-in-systemd [OPTIONS...] [--] COMMAND [ARGS...] + Run COMMAND ARGS as a systemd unit +Options: + --system|--user=UID Run as a system or user service + (default: current user, or --system if run as root) + --name=NAME Use NAME as the test-case name, escaping special + characters (default: COMMAND ARGS) + --basename Use the non-directory part of COMMAND as the test-case + name for LAVA, stripping extensions .pl, .py and/or + .sh and escaping special characters + --timeout=EXPR Set a timeout (systemd syntax is allowed, e.g. 300, + 300s, 5min, 300000ms are equivalent; default 15min) + --chdir=DIRNAME Change current working directory to DIRNAME + --exit-status Exit with the status of COMMAND + --no-lava Do not integrate with LAVA (implies --exit-status) + --debug Show journal messages + --full-debug Show full journal logs +' + +set -e + +as_target_user= +debug= +full_debug= +journalctl="journalctl --no-pager --full" +lava_runes=1 +name="" +progname="$(basename "$0")" +systemctl="systemctl" +target_user= +time_adverb="" +timeout=15min +uid="$(id -u)" +use_basename= +use_exit_status= +working_dir="$(pwd)" + +debug () { + [ -z "${debug}" ] || echo "# ${progname}: $*" >&2 +} + +die () { + echo "${progname}: ERROR: $*" >&2 + exit 1 +} + +systemd_escape_argument () { + # Escape an argument for ExecStart + # - escape % as %%, $ as $$ and \ as \\ + # - escape single and double quotes + # - escape control characters, delete and non-ASCII like \xff + # I can't work out how to do the latter in sh so I'm resorting to Perl + perl -e '$_ = $ARGV[0]; + s/([\\%\$])/$1$1/g; + s/([\x22\x27])/\\$1/g; + s/([^\x20-\x7e])/sprintf(q(\\x%02x), ord $1)/ge; + print qq("$_"); + ' -- "$1" +} + +if [ "x${uid}" != x0 ] +then + target_user="${uid}" + journalctl="sudo ${journalctl}" +fi + +if which busybox >/dev/null; then + time_adverb="busybox time" +elif which time >/dev/null; then + time_adverb="time" +else + time_adverb="" +fi + +if ! which "lava-test-case" >/dev/null; then + lava_runes= + use_exit_status=1 +fi + +# -o + means stop parsing at the first non-option argument, so we can do +# things like: run-test-in-systemd sh -c 'echo hello' +args="$(getopt \ + -o '+h' \ + -l "basename,chdir:,debug,exit-status,full-debug,help,name:,no-lava,system,timeout:,user:" \ + -n "${progname}" -- "$@")" +eval set -- "${args}" + +while [ "$#" -gt 0 ]; do + case "$1" in + (--basename) + use_basename=1 + shift + ;; + (--debug) + debug=1 + shift + ;; + (--exit-status) + use_exit_status=1 + shift + ;; + (--full-debug) + full_debug=1 + shift + ;; + (--help|-h) + echo "${usage}" + exit 0 + ;; + (--name) + name="$2" + shift 2 + ;; + (--no-lava) + lava_runes= + use_exit_status=1 + shift + ;; + (--system) + target_user= + as_uid= + shift + ;; + (--timeout) + timeout="$2" + shift 2 + ;; + (--user) + # $2 is a numeric uid or a username + target_user="$(getent passwd "$2")" || die "user not found: $2" + # accept a username or a uid, convert to a uid + target_user="$(echo "${target_user}" | cut -d: -f3)" + shift 2 + ;; + (--chdir) + working_dir="$2" + shift 2 + ;; + (--) + shift + break + ;; + (*) + break + ;; + esac +done + +[ "$#" -gt 0 ] || die "a command is required" + +if [ -n "${use_basename}" ]; then + name="$1" + name="${name##*/}" + name="${name%.pl}" + name="${name%.py}" + name="${name%.sh}" +elif [ -z "${name}" ]; then + name="$*" +fi + +my_dir="$(dirname "$0")" +my_dir="$(cd "$my_dir" && pwd)" +tee_exec="${my_dir}/tee-exec" + +name="$(echo -n "${name}" | tr -c -s 'A-Za-z0-9---_' '_' | head -c 220)" +service="generated-test-case-${name}.service" + +if [ -n "${target_user}" ]; then + debug "running test as a user service for uid ${target_user}" + + # We've probably been invoked in some stack of sudo and su + # commands, so our login environment is probably nonsense; + # fix it so we can contact systemd. + # + # This environment is only used to contact systemd, so it is + # safe for it to be rather minimal. The actual test runs in + # the environment provided by systemd, as documented in systemd.exec(5) + # and potentially augmented by the SetEnvironment() call. + # + # System services can rely on having PATH and LANG. + # + # User services can additionally rely on SHELL, USER, LOGNAME, HOME, + # MANAGERPID, XDG_RUNTIME_DIR. + # + # Non-systemd components in Apertis currently set DISPLAY and + # DBUS_SESSION_BUS_ADDRESS, although the former may be lost in the + # migration to Wayland and the latter is redundant with XDG_RUNTIME_DIR + # (our three supported D-Bus implementations - GDBus, libdbus and sd-bus - + # all default to unix:path=$XDG_RUNTIME_DIR/bus). + XDG_RUNTIME_DIR="/run/user/${target_user}" + export XDG_RUNTIME_DIR + unset DBUS_SESSION_BUS_ADDRESS + + if ! [ -S "${XDG_RUNTIME_DIR}/bus" ]; then + die "${XDG_RUNTIME_DIR}/bus does not exist, cannot proceed" + fi + + if [ "${uid}" != "${target_user}" ]; then + as_target_user="sudo -u #${target_user} env XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}" + fi + + filename="${XDG_RUNTIME_DIR}/systemd/user" + debug "creating ${filename}" + ${as_target_user} install -m 700 -d "${filename}" + filename="${filename}/${service}" + systemctl="systemctl --user" +else + debug "running test as a system service" + filename="/run/systemd/system/${service}" + if [ "$(id -ru)" != 0 ]; then + as_target_user="sudo" + fi +fi + +systemctl="${as_target_user} ${systemctl}" + +debug "getting Journal cursor" +cursor="$(${journalctl} --show-cursor -n 0 -o cat)" +cursor="${cursor#-- cursor: }" +debug "Journal cursor: ${cursor}" + +${as_target_user} rm -f "${filename}.tmp" +${as_target_user} rm -f "${filename}" + +user_log_dir="$(${as_target_user} mktemp -d)" +# We write the output to a fifo (named pipe) so that we can watch it in +# real-time, below. We start watching it before we start the test. +${as_target_user} mkfifo "${user_log_dir}/stdout.fifo" +exec_start="${tee_exec} ${user_log_dir}/stdout.fifo" + +while [ "$#" -gt 0 ]; do + exec_start="${exec_start} $(systemd_escape_argument "$1")" + shift +done + +debug "creating ${filename} to run ${exec_start}" + +# Not using systemd-run because it doesn't give us control over +# the timeout, and systemd is better at enforcing those than LAVA is. +# Use tee to get a sudo'able pseudo-redirection. +${as_target_user} tee ${filename}.tmp >/dev/null <<EOF +[Service] +Type=oneshot +TimeoutSec=${timeout} +WorkingDirectory=${working_dir} +ExecStart=${exec_start} +StandardOutput=journal +StandardError=inherit +SyslogIdentifier=${service} +Restart=no +EOF +${as_target_user} mv "${filename}.tmp" "${filename}" + +if [ -n "${debug}" ]; then + debug ".service file:" + sed -e "s/^/# ${progname}: /" < ${filename} >&2 +fi + +debug "reloading via ${systemctl} daemon-reload" +${systemctl} daemon-reload +${systemctl} stop ${service} || : + +debug "starting test" + +# Watch the output in real time, both for developers' benefit and so that +# LAVA does not think "it has become inactive" and kill us. +${as_target_user} cat "${user_log_dir}/stdout.fifo" & +cat_fifo_pid="$!" + +if ${time_adverb} ${systemctl} start ${service}; then + debug "test passed" + result=pass + exit_status="0" +else + exit_status="$?" + if [ "${exit_status}" = 77 ]; then + debug "test skipped: $?" + result=skip + else + debug "test failed: $?" + result=fail + fi +fi + +debug "killing any leftover test processes" +${systemctl} stop ${service} || : + +debug "waiting for end-of-file on command output..." +wait "$cat_fifo_pid" || result=fail +rm -fr "${user_log_dir}" || result=fail + +if [ -n "${lava_runes}" ]; then + "lava-test-case" "${name}" --result "${result}" +fi + +# Show journal logs if there is an error and any of the `debug` flag was passed, +# otherwise suggest to use debug flags to show more information. +if [ "${exit_status}" != 0 ]; then + if [ -n "${full_debug}" ]; then + debug "Showing full logs (--full-debug passed)" + ${journalctl} -o verbose -b -0 --after-cursor "${cursor}" || : + elif [ -n "${debug}" ]; then + debug "Showing logs (--debug passed)" + ${journalctl} -b -0 --after-cursor "${cursor}" || : + else + echo "NOTE: Run command with the --debug or --full-debug option to enable journal logs" + fi +fi + +${as_target_user} rm -f "${filename}.tmp" +${as_target_user} rm -f "${filename}" + +if [ -n "${use_exit_status}" ]; then + exit "${exit_status}" +fi + +# vim:set sw=4 sts=4 et: diff --git a/common/tee-exec b/common/tee-exec new file mode 100755 index 0000000000000000000000000000000000000000..f793ee5bd8f17ebc00dd2f4783c8cd705b51ebe7 --- /dev/null +++ b/common/tee-exec @@ -0,0 +1,19 @@ +#!/bin/sh + +# tee-exec — like tee, but execute arguments. +# Used by run-test-in-systemd. +# +# Copyright © 2015 Collabora Ltd. +# +# SPDX-License-Identifier: MPL-2.0 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +log="$1" +shift + +# POSIX shell compatible pipefail replacement for the command below: +# set -o pipefail && "$@" 2>&1 | tee -- "$log" +# See https://www.spinics.net/lists/dash/msg00165.html +exec 3>&1; s=$(exec 4>&1 >&3; { "$@" 2>&1 ; echo $? >&4; } | tee -- "$log" ) && exit $s diff --git a/common/update-test-path b/common/update-test-path new file mode 100644 index 0000000000000000000000000000000000000000..61174ffba24e5f9d5fa56269cc22b3911bde093e --- /dev/null +++ b/common/update-test-path @@ -0,0 +1,16 @@ +# Current test directory extracted from pwd +TESTPATH=$(pwd) + +# Path for architecture independant binaries +PATH=${TESTPATH}/common:${TESTPATH}/bin:$PATH + +# Path for architecture specific binaries +case `uname -m` in +x86_64) ARCHDIR=amd64 ;; +armv7l) ARCHDIR=armhf ;; +aarch64) ARCHDIR=arm64 ;; +esac + +PATH=${TESTPATH}/${ARCHDIR}/bin:$PATH +export LD_LIBRARY_PATH=${TESTPATH}/${ARCHDIR}/lib:$LD_LIBRARY_PATH +