From 6ac60a0f98d9048fa7dd3cbcd95a9409cbe0c4fa Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Mon, 21 Sep 2020 13:21:10 -0400 Subject: meta-agl-profile-core: Add support for running gcovr on target Changes: - Add recipe for gcovr gcov-based coverage report generating tool. - Add bbappend to apply patch to gcovr to add a new command-line option that allows working with the coverage information from the AGL widgets on target. The goal is to get this change upstream, hence not having it in the recipe (which also will be sent for inclusion in meta-oe). - Add a recipe to install an included wrapper script for gcovr (unsurprisingly named "gcovr-wrapper") that enables running it against bindings on target. The script supports installing the coverage version of a binding and running pyagl tests, afm-test test widget, or a user-supplied command before running gcovr in a carefully crafted environment that will allow it to work without impacting the root filesystem of the target. - Add gcov-symlinks, gcovr, and gcovr-wrapper to the packagegroup-agl-core-devel package group so they will be available in agl-devel images. The gcov-symlinks package adds /usr/bin/gcov, which simplifies picking it up for gcovr. Bug-AGL: SPEC-3589 Signed-off-by: Scott Murray Change-Id: Ia2fdc200e4e0683f93a6e2a863311cd2107c962b Reviewed-on: https://gerrit.automotivelinux.org/gerrit/c/AGL/meta-agl/+/25337 Tested-by: Jenkins Job builder account ci-image-build: Jenkins Job builder account ci-image-boot-test: Jenkins Job builder account Reviewed-by: Jan-Simon Moeller --- .../gcovr-wrapper/gcovr-wrapper/gcovr-wrapper | 320 +++++++++++++++++++++ .../gcovr-wrapper/gcovr-wrapper_1.0.bb | 17 ++ 2 files changed, 337 insertions(+) create mode 100644 meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper/gcovr-wrapper create mode 100644 meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper_1.0.bb (limited to 'meta-agl-profile-core/recipes-test/gcovr-wrapper') diff --git a/meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper/gcovr-wrapper b/meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper/gcovr-wrapper new file mode 100644 index 000000000..12580237d --- /dev/null +++ b/meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper/gcovr-wrapper @@ -0,0 +1,320 @@ +#!/bin/bash +# +# Copyright (C) 2020 Konsulko Group +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# gcovr wrapper for generating coverage reports against AGL bindings +# on target. With a given binding name or coverage widget file, the +# coverage version will be installed, and the pyagl tests for the +# binding run before generating a report with gcovr. The afm-test +# test widget or a user-supplied command may be run instead of the +# pyagl tests, see usage below, or run with "--help". +# + +usage() { + cat <<-EOF + Usage: + $(basename $0) [options] + + Options: + -h, --help + Print this help and exit + + -k, --keep + Do not remove temporary files/directories + + -o, --gcovr-options + Additional gcovr options, multiple options should be quoted + + -w, --workdir + gcov/gcovr temporary working directory, defaults to /tmp/gcov + The directory will be removed after running without --keep if it + is empty, use caution if specifying existing system directories! + + --pyagl + Run pyagl tests for binding, enabled by default + + --afm-test + Run afm-test test widget tests for binding. + If specified, disables pyagl tests; note that the last argument in + the command-line will take precedence. + + -c, --command + Test command to use in place of pyagl or afm-test, should be quote + If specified, disables pyagl and afm-test tests. + + EOF +} + +# Helper to validate widget install dir +check_wgt_install() { + if [ ! \( -d $1 -o -f $1/config.xml \) ]; then + echo "ERROR: No widget install at $1" + exit 1 + elif [ ! -d $1/src ]; then + echo "ERROR: No source in $1/src" + exit 1 + fi +} + +# Helper to run gcovr inside mount namespace environment +gcovr_runner() { + wgt_install_dir=/var/local/lib/afm/applications/$1 + check_wgt_install ${wgt_install_dir} + + if [ ! -d $workdir/$1 ]; then + echo "ERROR: No coverage data in $workdir/$1" + exit 1 + fi + + # Get original source path + gcno=$(cd $workdir/$1 && find -name '*.gcno' | head -n 1 | cut -d/ -f2-) + if [ -z "$gcno" ]; then + echo "ERROR: no gcno file found in $workdir/$1" + exit 1 + fi + srcfile=$(strings $workdir/$1/${gcno} | grep "$(basename ${gcno%.gcno})$" | uniq) + srcdir=$(echo $srcfile | sed "s|/${gcno%%/*}/.*$||") + + # Set up mounts for chroot to run gcovr in + # NOTE: We do not unmount these later, as we assume we are in a + # private mount namespace and they will go away on exit from + # it. + echo "Setting up mounts" + tmpdir=$(mktemp -d) + echo $tmpdir > $workdir/.runner_tmpdir + mkdir -p $tmpdir/{lower,upper,work,merged} + # NOTE: Could potentially use rbind here, but explicitly mounting + # just what we need seems safer + mount --bind / $tmpdir/lower + mount -t overlay -o lowerdir=$tmpdir/lower,upperdir=$tmpdir/upper,workdir=$tmpdir/work overlay $tmpdir/merged + mount --bind /proc $tmpdir/merged/proc + mount --bind /sys $tmpdir/merged/sys + mount --bind /dev $tmpdir/merged/dev + mount --bind /tmp $tmpdir/merged/tmp + # Bind in the data files + # NOTE: $workdir is bound instead of specifically just $workdir/$1, + # so that e.g. html output to another directory in /tmp will + # work as expected. A determined user may be able to shoot + # themselves in the foot, but for now the trade off seems + # acceptable. + mkdir -p $tmpdir/merged/$workdir + mount --bind $workdir $tmpdir/merged/$workdir + # Bind the source files to their expected location + mkdir -p $tmpdir/merged/$srcdir + mount --bind ${wgt_install_dir}/src $tmpdir/merged/$srcdir + + echo "Entering chroot" + echo + exec chroot $tmpdir/merged \ + /usr/bin/gcovr -r $srcdir --object-directory $workdir/$1 --gcov-filter-source-errors -s ${GCOV_RUNNER_GCOVR_OPTIONS} +} + +# Helper to clean up after runner +gcovr_runner_cleanup() { + rm -rf $workdir/$1 + if [ -f $workdir/.runner_tmpdir ]; then + tmpdir=$(cat $workdir/.runner_tmpdir) + rm -rf $tmpdir + rm -f $workdir/.runner_tmpdir + fi + if [ "$workdir" != "/tmp" ]; then + rmdir $workdir 2>/dev/null || true + fi +} + +# Parse arguments +OPTS=$(getopt -o +hko:pw:c: --longoptions gcovr-runner,afm-test,command:,help,keep,gcovr-options:,pyagl,workdir: -n "$(basename $0)" -- "$@") +if [ $? -ne 0 ]; then + exit 1 +fi +eval set -- "$OPTS" + +runner=false +keep=false +wgt="" +cmd="" +options="" +afmtest=false +pyagl=true +workdir="/tmp/gcov" + +while true; do + case "$1" in + --gcovr-runner) runner=true; shift;; + --afm-test) afmtest=true; pyagl=false; shift;; + -c|--command) cmd="$2"; shift; shift;; + -h|--help) usage; exit 0;; + -k|--keep) keep=true; shift;; + -o|--gcovr-options) options="$2"; shift; shift;; + -p|--pyagl) pyagl=true; afmtest=false; shift;; + -w|--workdir) workdir="$2"; shift; shift;; + --) shift; break;; + *) break;; + esac +done + +# Encode the assumption that a specified command means it runs instead +# of any other tests. +if [ -s "$cmd" ]; then + pyagl=false + afmtest=false +fi + +if [ $# -ne 1 ]; then + # Always expect widget name as single non-option argument + usage + exit 1 +fi + +# Rationalize workdir just in case +workdir=$(realpath "$workdir") + +if [ "$runner" = "true" ]; then + if [ "${GCOV_RUNNER_READY}" != "true" ]; then + echo "ERROR: gcovr environment not ready!" + exit 1 + fi + gcovr_runner $1 + # If we get here, it'd be an error, so return 1 + exit 1 +fi + +binding=$1 +if [ "${1%.wgt}" != "$1" ]; then + # User has specified path to a widget file + wgt=$(realpath $1) + binding=$(basename "${1%-coverage.wgt}") +else + wgt=/usr/AGL/apps/coverage/${binding}-coverage.wgt +fi +if [ ! -f $wgt ]; then + echo "ERROR: No widget $wgt" + exit 1 +elif [ "$afmtest" = "true" -a ! -f /usr/AGL/apps/test/${binding}-test.wgt ]; then + echo "ERROR: No test widget for $binding" + exit 1 +fi + +# Determine starting systemd unit name +service=$(systemctl --all |grep afm-service-$binding |sed 's/^[ *] \([^ ]*\).*/\1/') +if [ -z "$service" ]; then + echo "ERROR: Could not determine systemd service unit for $binding" + exit 1 +fi + +# Install coverage widget +echo "Removing $binding widget" +systemctl stop $service +afm-util remove $binding +echo +echo "Installing $binding coverage widget" +afm-util install $wgt +echo + +wgt_install_dir=/var/local/lib/afm/applications/$binding +check_wgt_install ${wgt_install_dir} +gcov_src=${wgt_install_dir}/coverage +if [ ! -d ${gcov_src} ]; then + echo "ERROR: No coverage information in ${gcov_src}" + exit 1 +elif [ ! -f ${gcov_src}/gcov.env ]; then + echo "ERROR: No gcov environment file at ${gcov_src}/gcov.env" + exit 1 +fi + +# +# NOTE: In theory, the coverage data collection could be done inside +# the mount namespace / chroot, but the potential for issues +# when doing that seems higher than just running gcovr there, +# so a conservative approach is taken. +# + +# Set up things for the binary to write out gcda data files +# +# Having the matching build directory hierarchy in place and +# writeable by the target binary before any restart and testing is +# key to things working. +# +# As well, the environment file with the GCOV_PREFIX and +# GCOV_PREFIX_STRIP values needs to be present before running so the +# gcda files will get written into the relocated build hierarchy. +# +echo "Installing coverage information for $binding" +mkdir -p $workdir +rm -rf $workdir/$binding +cp -dr ${gcov_src} $workdir/$binding +chsmack -r -a System::Log $workdir +chmod -R go+w $workdir + +# Install the gcov environment file +mkdir -p /etc/afm/widget.env.d/$binding +if [ "${workdir}" = "/tmp/gcov" ]; then + cp ${gcov_src}/gcov.env /etc/afm/widget.env.d/$binding/gcov +else + # Update GCOV_PREFIX to point into workdir + sed "s|^GCOV_PREFIX=.*|GCOV_PREFIX=${workdir}/$binding|" ${gcov_src}/gcov.env > /etc/afm/widget.env.d/$binding/gcov +fi +chsmack -r -a _ /etc/afm/widget.env.d/$binding + +# Determine new systemd unit name (version may now be different) +service=$(systemctl --all |grep afm-service-$binding |sed 's/^[ *] \([^ ]*\).*/\1/') +if [ -z "$service" ]; then + echo "ERROR: Could not determine systemd service unit for $binding" + exit 1 +fi + +# Restart the binding +systemctl start $service +echo + +# Run tests or given command +if [ -n "$cmd" ]; then + echo "Running command: $cmd" + export AGL_AVAILABLE_INTERFACES=${AGL_AVAILABLE_INTERFACES:-ethernet} + eval $cmd +elif [ "$pyagl" = "true" ]; then + echo "Running $binding pyagl tests" + export AGL_AVAILABLE_INTERFACES=${AGL_AVAILABLE_INTERFACES:-ethernet} + pytest -k "${binding#agl-service-} and not hwrequired" /usr/lib/python3.?/site-packages/pyagl +else + echo "Running $binding test widget" + # NOTE: su to agl-driver is required here to avoid fallout from + # the "afm-util run" in afm-test seemingly triggering the + # start of other per-user bindings for the root user. + su -l -c "/usr/bin/afm-test /usr/AGL/apps/test/${binding}-test.wgt" agl-driver +fi + +# Restart again to trigger data file writing +systemctl restart $service +echo + +# Run ourselves in gcovr runner mode inside a private mount namespace +export GCOV_RUNNER_READY=true +# NOTE: Passing gcovr options in the environment to avoid quoting hassles +export GCOV_RUNNER_GCOVR_OPTIONS="$options" +runner_options="--workdir ${workdir}" +unshare -m $0 --gcovr-runner ${runner_options} $binding +rc=$? + +if [ "$keep" != "true" ]; then + # Clean up after ourselves + gcovr_runner_cleanup $1 + rm -f /etc/afm/widget.env.d/$1/gcov + rmdir /etc/afm/widget.env.d/$1 2>/dev/null || true +fi + +exit $rc + diff --git a/meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper_1.0.bb b/meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper_1.0.bb new file mode 100644 index 000000000..2ff39b211 --- /dev/null +++ b/meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper_1.0.bb @@ -0,0 +1,17 @@ +SUMMARY = "AGL gcovr wrapper" +DESCRIPTION = "This wrapper script enables running gcovr against a \ +AGL binding to generate a coverage report of running pyagl tests, \ +the afm-test test widget, or a user-supplied command." + +LICENSE = "Apache-2.0" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10" + +SRC_URI += "file://gcovr-wrapper" + +inherit allarch + +do_install() { + install -D -m 0755 ${WORKDIR}/gcovr-wrapper ${D}${bindir}/gcovr-wrapper +} + +RDEPENDS_${PN} = "bash gcovr" -- cgit 1.2.3-korg