summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorScott Murray <scott.murray@konsulko.com>2020-09-21 13:21:10 -0400
committerJan-Simon Moeller <jsmoeller@linuxfoundation.org>2020-09-25 09:05:14 +0000
commit6ac60a0f98d9048fa7dd3cbcd95a9409cbe0c4fa (patch)
tree71c96bd078a5e4180384a45ca65ebbe6e4ba3012
parentf5b7b4a5c5ffcd51509c3ed57dfb08fdce6cf01c (diff)
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 <scott.murray@konsulko.com> Change-Id: Ia2fdc200e4e0683f93a6e2a863311cd2107c962b Reviewed-on: https://gerrit.automotivelinux.org/gerrit/c/AGL/meta-agl/+/25337 Tested-by: Jenkins Job builder account <agl-jobbuilder@automotivelinux.org> ci-image-build: Jenkins Job builder account <agl-jobbuilder@automotivelinux.org> ci-image-boot-test: Jenkins Job builder account <agl-jobbuilder@automotivelinux.org> Reviewed-by: Jan-Simon Moeller <jsmoeller@linuxfoundation.org>
-rw-r--r--meta-agl-profile-core/recipes-devtools/packagegroups/packagegroup-agl-core-devel.bb3
-rw-r--r--meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper/gcovr-wrapper320
-rw-r--r--meta-agl-profile-core/recipes-test/gcovr-wrapper/gcovr-wrapper_1.0.bb17
-rw-r--r--meta-agl-profile-core/recipes-test/gcovr/gcovr/0001-add-gcov-filter-source-errors-option.patch68
-rw-r--r--meta-agl-profile-core/recipes-test/gcovr/gcovr_%.bbappend2
-rw-r--r--meta-agl-profile-core/recipes-test/gcovr/gcovr_git.bb32
6 files changed, 442 insertions, 0 deletions
diff --git a/meta-agl-profile-core/recipes-devtools/packagegroups/packagegroup-agl-core-devel.bb b/meta-agl-profile-core/recipes-devtools/packagegroups/packagegroup-agl-core-devel.bb
index 647a0a81b..c360f2a72 100644
--- a/meta-agl-profile-core/recipes-devtools/packagegroups/packagegroup-agl-core-devel.bb
+++ b/meta-agl-profile-core/recipes-devtools/packagegroups/packagegroup-agl-core-devel.bb
@@ -27,4 +27,7 @@ RDEPENDS_${PN} = "\
pciutils \
pyagl \
gcov \
+ gcov-symlinks \
+ gcovr \
+ 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] <binding name | widget file>
+
+ 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"
diff --git a/meta-agl-profile-core/recipes-test/gcovr/gcovr/0001-add-gcov-filter-source-errors-option.patch b/meta-agl-profile-core/recipes-test/gcovr/gcovr/0001-add-gcov-filter-source-errors-option.patch
new file mode 100644
index 000000000..be1dcf829
--- /dev/null
+++ b/meta-agl-profile-core/recipes-test/gcovr/gcovr/0001-add-gcov-filter-source-errors-option.patch
@@ -0,0 +1,68 @@
+Add option to filter gcov source errors
+
+Add "--gcov-filter-source-errors" to apply filters to the source
+files in the errors from gcov. If all source files in the errors
+are filtered, then the error is ignored so that the file will be
+processed. This enables the usecase of running on a target where
+only the source tree for a binary is available, but not all of the
+external source headers are.
+
+Upstream-Status: pending
+
+Signed-off-by: Scott Murray <scott.murray@konsulko.com>
+
+diff --git a/gcovr/configuration.py b/gcovr/configuration.py
+index 1356097..083532c 100644
+--- a/gcovr/configuration.py
++++ b/gcovr/configuration.py
+@@ -915,6 +915,14 @@ GCOVR_CONFIG_OPTIONS = [
+ "Default: {default!s}.",
+ action="store_true",
+ ),
++ GcovrConfigOption(
++ "gcov_filter_source_errors", ['--gcov-filter-source-errors'],
++ group="gcov_options",
++ help="Apply filters to missing source file errors in GCOV files "
++ "instead of exiting with an error. "
++ "Default: {default!s}.",
++ action="store_true",
++ ),
+ GcovrConfigOption(
+ "objdir", ['--object-directory'],
+ group="gcov_options",
+diff --git a/gcovr/gcov.py b/gcovr/gcov.py
+index de79215..171d68d 100644
+--- a/gcovr/gcov.py
++++ b/gcovr/gcov.py
+@@ -667,11 +667,27 @@ def run_gcov_and_process_files(
+ chdir=chdir,
+ tempdir=tempdir)
+
++ skip = False
+ if source_re.search(err):
+- # gcov tossed errors: try the next potential_wd
+- error(err)
+- done = False
+- else:
++ ignore = False
++ if options.gcov_filter_source_errors:
++ # Check if errors are all from source that is filtered
++ ignore = True
++ for line in err.splitlines():
++ src_fname = line.split()[-1]
++ filtered, excluded = apply_filter_include_exclude(
++ src_fname, options.filter, options.exclude)
++ if not (filtered or excluded):
++ ignore = False
++ break
++
++ if not ignore:
++ # gcov tossed errors: try the next potential_wd
++ error(err)
++ skip = True
++
++ done = False
++ if not skip:
+ # Process *.gcov files
+ for fname in active_gcov_files:
+ process_gcov_data(fname, covdata, abs_filename, options)
diff --git a/meta-agl-profile-core/recipes-test/gcovr/gcovr_%.bbappend b/meta-agl-profile-core/recipes-test/gcovr/gcovr_%.bbappend
new file mode 100644
index 000000000..699aea214
--- /dev/null
+++ b/meta-agl-profile-core/recipes-test/gcovr/gcovr_%.bbappend
@@ -0,0 +1,2 @@
+# Add not yet upstreamed patch that enables on target gcov usage
+SRC_URI += "file://0001-add-gcov-filter-source-errors-option.patch"
diff --git a/meta-agl-profile-core/recipes-test/gcovr/gcovr_git.bb b/meta-agl-profile-core/recipes-test/gcovr/gcovr_git.bb
new file mode 100644
index 000000000..6da04cc53
--- /dev/null
+++ b/meta-agl-profile-core/recipes-test/gcovr/gcovr_git.bb
@@ -0,0 +1,32 @@
+SUMMARY = "Generate GCC code coverage reports"
+DESCRIPTION = "Gcovr provides a utility for managing the use of the GNU gcov \
+utility and generating summarized code coverage results."
+HOMEPAGE = "https://gcovr.com"
+LICENSE = "BSD-3-Clause"
+LIC_FILES_CHKSUM = "file://LICENSE.txt;md5=221e634a1ceafe02ef74462cbff2fb16"
+
+PV = "4.2+git${SRCPV}"
+SRC_URI = "git://github.com/gcovr/gcovr.git;protocol=https"
+SRCREV = "1bc72e3bb59b9296e962b350691732ddafbd3195"
+
+S = "${WORKDIR}/git"
+
+inherit setuptools3
+
+RDEPENDS_${PN} += " \
+ python3-compression \
+ python3-core \
+ python3-crypt \
+ python3-datetime \
+ python3-difflib \
+ python3-io \
+ python3-jinja2 \
+ python3-json \
+ python3-lxml \
+ python3-multiprocessing \
+ python3-pygments \
+ python3-pytest \
+ python3-shell \
+ python3-threading \
+ python3-typing \
+"