SPEC-3723: restructure meta-agl-demo
[AGL/meta-agl-demo.git] / recipes-test / gcovr-wrapper / gcovr-wrapper / gcovr-wrapper
1 #!/bin/bash
2 #
3 # Copyright (C) 2020 Konsulko Group
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #    http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 #
18 # gcovr wrapper for generating coverage reports against AGL bindings
19 # on target.  With a given binding name or coverage widget file, the
20 # coverage version will be installed, and the pyagl tests for the
21 # binding run before generating a report with gcovr.  The afm-test
22 # test widget or a user-supplied command may be run instead of the
23 # pyagl tests, see usage below, or run with "--help".
24 #
25
26 usage() {
27     cat <<-EOF
28         Usage:
29           $(basename $0) [options] <binding name | widget file>
30
31         Options:
32           -h, --help
33             Print this help and exit
34
35           -k, --keep
36             Do not remove temporary files/directories
37
38           -o, --gcovr-options
39             Additional gcovr options, multiple options should be quoted
40
41           -w, --workdir
42             gcov/gcovr temporary working directory, defaults to /tmp/gcov
43             The directory will be removed after running without --keep if it
44             is empty, use caution if specifying existing system directories!
45
46           --pyagl
47             Run pyagl tests for binding, enabled by default
48
49           --afm-test
50             Run afm-test test widget tests for binding.
51             If specified, disables pyagl tests; note that the last argument in
52             the command-line will take precedence.
53
54           -c, --command
55             Test command to use in place of pyagl or afm-test, should be quote
56             If specified, disables pyagl and afm-test tests.
57
58         EOF
59 }
60
61 # Helper to validate widget install dir
62 check_wgt_install() {
63     if [ ! \( -d $1 -o -f $1/config.xml \) ]; then
64         echo "ERROR: No widget install at $1"
65         exit 1
66     elif [ ! -d $1/src ]; then
67         echo "ERROR: No source in $1/src"
68         exit 1
69     fi
70 }
71
72 # Helper to run gcovr inside mount namespace environment
73 gcovr_runner() {
74     wgt_install_dir=/var/local/lib/afm/applications/$1
75     check_wgt_install ${wgt_install_dir}
76
77     if [ ! -d $workdir/$1 ]; then
78         echo "ERROR: No coverage data in $workdir/$1"
79         exit 1
80     fi
81
82     # Get original source path
83     gcno=$(cd $workdir/$1 && find -name '*.gcno' | head -n 1 | cut -d/ -f2-)
84     if [ -z "$gcno" ]; then
85         echo "ERROR: no gcno file found in $workdir/$1"
86         exit 1
87     fi
88     srcfile=$(strings $workdir/$1/${gcno} | grep "$(basename ${gcno%.gcno})$" | uniq)
89     srcdir=$(echo $srcfile | sed "s|/${gcno%%/*}/.*$||")
90
91     # Set up mounts for chroot to run gcovr in
92     # NOTE: We do not unmount these later, as we assume we are in a
93     #       private mount namespace and they will go away on exit from
94     #       it.
95     echo "Setting up mounts"
96     tmpdir=$(mktemp -d)
97     echo $tmpdir > $workdir/.runner_tmpdir
98     mkdir -p $tmpdir/{lower,upper,work,merged}
99     # NOTE: Could potentially use rbind here, but explicitly mounting
100     #        just what we need seems safer
101     mount --bind / $tmpdir/lower
102     mount -t overlay -o lowerdir=$tmpdir/lower,upperdir=$tmpdir/upper,workdir=$tmpdir/work overlay $tmpdir/merged
103     mount --bind /proc $tmpdir/merged/proc
104     mount --bind /sys $tmpdir/merged/sys
105     mount --bind /dev $tmpdir/merged/dev
106     mount --bind /tmp $tmpdir/merged/tmp
107     # Bind in the data files
108     # NOTE: $workdir is bound instead of specifically just $workdir/$1,
109     #       so that e.g. html output to another directory in /tmp will
110     #       work as expected.  A determined user may be able to shoot
111     #       themselves in the foot, but for now the trade off seems
112     #       acceptable.
113     mkdir -p $tmpdir/merged/$workdir
114     mount --bind $workdir $tmpdir/merged/$workdir
115     # Bind the source files to their expected location
116     mkdir -p $tmpdir/merged/$srcdir
117     mount --bind ${wgt_install_dir}/src $tmpdir/merged/$srcdir
118
119     echo "Entering chroot"
120     echo
121     exec chroot $tmpdir/merged \
122         /usr/bin/gcovr -r $srcdir --object-directory $workdir/$1 --gcov-filter-source-errors -s ${GCOV_RUNNER_GCOVR_OPTIONS}
123 }
124
125 # Helper to clean up after runner
126 gcovr_runner_cleanup() {
127     rm -rf $workdir/$1
128     if [ -f $workdir/.runner_tmpdir ]; then
129         tmpdir=$(cat $workdir/.runner_tmpdir)
130         rm -rf $tmpdir
131         rm -f $workdir/.runner_tmpdir
132     fi
133     if [ "$workdir" != "/tmp" ]; then
134         rmdir $workdir 2>/dev/null || true
135     fi
136 }
137
138 # Parse arguments
139 OPTS=$(getopt -o +hko:pw:c: --longoptions gcovr-runner,afm-test,command:,help,keep,gcovr-options:,pyagl,workdir: -n "$(basename $0)" -- "$@")
140 if [ $? -ne 0 ]; then
141     exit 1
142 fi
143 eval set -- "$OPTS"
144
145 runner=false
146 keep=false
147 wgt=""
148 cmd=""
149 options=""
150 afmtest=false
151 pyagl=true
152 workdir="/tmp/gcov"
153
154 while true; do
155     case "$1" in
156         --gcovr-runner) runner=true; shift;;
157         --afm-test) afmtest=true; pyagl=false; shift;;
158         -c|--command) cmd="$2"; shift; shift;;
159         -h|--help) usage; exit 0;;
160         -k|--keep) keep=true; shift;;
161         -o|--gcovr-options) options="$2"; shift; shift;;
162         -p|--pyagl) pyagl=true; afmtest=false; shift;;
163         -w|--workdir) workdir="$2"; shift; shift;;
164         --) shift; break;;
165         *) break;;
166     esac
167 done
168
169 # Encode the assumption that a specified command means it runs instead
170 # of any other tests.
171 if [ -s "$cmd" ]; then
172     pyagl=false
173     afmtest=false
174 fi
175
176 if [ $# -ne 1 ]; then
177     # Always expect widget name as single non-option argument
178     usage
179     exit 1
180 fi
181
182 # Rationalize workdir just in case
183 workdir=$(realpath "$workdir")
184
185 if [ "$runner" = "true" ]; then
186     if [ "${GCOV_RUNNER_READY}" != "true" ]; then
187         echo "ERROR: gcovr environment not ready!"
188         exit 1
189     fi
190     gcovr_runner $1
191     # If we get here, it'd be an error, so return 1
192     exit 1
193 fi
194
195 binding=$1
196 if [ "${1%.wgt}" != "$1" ]; then
197     # User has specified path to a widget file
198     wgt=$(realpath $1)
199     binding=$(basename "${1%-coverage.wgt}")
200 else
201     wgt=/usr/AGL/apps/coverage/${binding}-coverage.wgt
202 fi
203 if [ ! -f $wgt ]; then
204     echo "ERROR: No widget $wgt"
205     exit 1
206 elif [ "$afmtest" = "true" -a ! -f /usr/AGL/apps/test/${binding}-test.wgt ]; then
207     echo "ERROR: No test widget for $binding"
208     exit 1
209 fi
210
211 # Determine starting systemd unit name
212 service=$(systemctl --all |grep afm-service-$binding |sed 's/^[ *] \([^ ]*\).*/\1/')
213 if [ -z "$service" ]; then
214     echo "ERROR: Could not determine systemd service unit for $binding"
215     exit 1
216 fi
217
218 # Install coverage widget
219 echo "Removing $binding widget"
220 systemctl stop $service
221 afm-util remove $binding
222 echo
223 echo "Installing $binding coverage widget"
224 afm-util install $wgt
225 echo
226
227 wgt_install_dir=/var/local/lib/afm/applications/$binding
228 check_wgt_install ${wgt_install_dir}
229 gcov_src=${wgt_install_dir}/coverage
230 if [ ! -d ${gcov_src} ]; then
231         echo "ERROR: No coverage information in ${gcov_src}"
232         exit 1
233 elif [ ! -f ${gcov_src}/gcov.env ]; then
234         echo "ERROR: No gcov environment file at ${gcov_src}/gcov.env"
235         exit 1
236 fi
237
238 #
239 # NOTE: In theory, the coverage data collection could be done inside
240 #       the mount namespace / chroot, but the potential for issues
241 #       when doing that seems higher than just running gcovr there,
242 #       so a conservative approach is taken.
243 #
244
245 # Set up things for the binary to write out gcda data files
246 #
247 # Having the matching build directory hierarchy in place and
248 # writeable by the target binary before any restart and testing is
249 # key to things working.
250 #
251 # As well, the environment file with the GCOV_PREFIX and
252 # GCOV_PREFIX_STRIP values needs to be present before running so the
253 # gcda files will get written into the relocated build hierarchy.
254 #
255 echo "Installing coverage information for $binding"
256 mkdir -p $workdir
257 rm -rf $workdir/$binding
258 cp -dr ${gcov_src} $workdir/$binding
259 chsmack -r -a System::Log $workdir
260 chmod -R go+w $workdir
261
262 # Install the gcov environment file
263 mkdir -p /etc/afm/widget.env.d/$binding
264 if [ "${workdir}" = "/tmp/gcov" ]; then
265     cp ${gcov_src}/gcov.env /etc/afm/widget.env.d/$binding/gcov
266 else
267     # Update GCOV_PREFIX to point into workdir
268     sed "s|^GCOV_PREFIX=.*|GCOV_PREFIX=${workdir}/$binding|" ${gcov_src}/gcov.env > /etc/afm/widget.env.d/$binding/gcov
269 fi
270 chsmack -r -a _ /etc/afm/widget.env.d/$binding
271
272 # Determine new systemd unit name (version may now be different)
273 service=$(systemctl --all |grep afm-service-$binding |sed 's/^[ *] \([^ ]*\).*/\1/')
274 if [ -z "$service" ]; then
275     echo "ERROR: Could not determine systemd service unit for $binding"
276     exit 1
277 fi
278
279 # Restart the binding
280 systemctl start $service
281 echo
282
283 # Run tests or given command
284 if [ -n "$cmd" ]; then
285     echo "Running command: $cmd"
286     export AGL_AVAILABLE_INTERFACES=${AGL_AVAILABLE_INTERFACES:-ethernet}
287     eval $cmd
288 elif [ "$pyagl" = "true" ]; then
289     echo "Running $binding pyagl tests"
290     export AGL_AVAILABLE_INTERFACES=${AGL_AVAILABLE_INTERFACES:-ethernet}
291     pytest -k "${binding#agl-service-} and not hwrequired" /usr/lib/python3.?/site-packages/pyagl
292 else
293     echo "Running $binding test widget"
294     # NOTE: su to agl-driver is required here to avoid fallout from
295     #       the "afm-util run" in afm-test seemingly triggering the
296     #       start of other per-user bindings for the root user.
297     su -l -c "/usr/bin/afm-test /usr/AGL/apps/test/${binding}-test.wgt" agl-driver
298 fi
299
300 # Restart again to trigger data file writing
301 systemctl restart $service
302 echo
303
304 # Run ourselves in gcovr runner mode inside a private mount namespace
305 export GCOV_RUNNER_READY=true
306 # NOTE: Passing gcovr options in the environment to avoid quoting hassles
307 export GCOV_RUNNER_GCOVR_OPTIONS="$options"
308 runner_options="--workdir ${workdir}"
309 unshare -m $0 --gcovr-runner ${runner_options} $binding
310 rc=$?
311
312 if [ "$keep" != "true" ]; then
313     # Clean up after ourselves
314     gcovr_runner_cleanup $1
315     rm -f /etc/afm/widget.env.d/$1/gcov
316     rmdir /etc/afm/widget.env.d/$1 2>/dev/null || true
317 fi
318
319 exit $rc
320