summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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 \
+"