Improve coverage script a bit
authorNicolas Williams <nico@twosigma.com>
Wed, 15 Apr 2020 23:48:26 +0000 (18:48 -0500)
committerNicolas Williams <nico@twosigma.com>
Thu, 16 Apr 2020 00:05:21 +0000 (19:05 -0500)
.travis.yml
tools/coveralls-tool

index 8961e6a9d278373d98a10fffcfb53301066b2d7c..0db7432b2847f0cf1a6bf378add484ee730cb346 100644 (file)
@@ -50,11 +50,7 @@ script:
     - if [ x${COVERITY_SCAN_BRANCH} != x1 ]; then ulimit -c unlimited; make check; fi
 
 after_script:
-    - |
-      if [ -n "$COVERAGE" ]; then
-          ../tools/coveralls-tool -v -o cov.json -O $PWD -S ..
-          curl -vvvsf -X POST -F "json_file=@cov.json" -F "Filename=json_file" https://coveralls.io/api/v1/jobs
-      fi
+    - if [ -n "$COVERAGE" ]; then ../tools/coveralls-tool -O $PWD -S ..; fi
 
 after_failure:
     - find . -name test-suite.log -print0 | xargs -0 cat
index 863c9896d2c9eb2a6b8a21eb4d0e18e04fc31f33..981bd1757bef30952187cd79d860ff65b735cd5d 100755 (executable)
@@ -1,5 +1,27 @@
 #!/bin/bash
 
+# This script collates gcov data after one has configured with --enable-gcov,
+# built, and run tests.  It either outputs or POSTs to Coveralls a JSON text in
+# the schema for the Coveralls API, which is documented here:
+#
+# https://docs.coveralls.io/api-introduction
+# https://docs.coveralls.io/api-reference
+#
+# Currently only files in source languages supported by gcov(1) are reported
+# on, though this can easily be extended.  Currently that's only C/C++ files.
+#
+# This script is specifically written for Heimdal, which is an open source C
+# codebases that uses autoconf and libtool for its build system.  This means
+# that sometimes the gcov notes and data files are not necessarily where the
+# gcov(1) utility would find them, which is why this script exists instead of
+# using some other integration script.
+#
+# Although this is specific to Heimdal, it can be extended.
+#
+# Note that one side effect of running this script, gcov(1) will be run for all
+# C/C++ source files in the workspace.  As well, some gcov notes and data files
+# maybe hard-linked to other names.  However, this script should be idempotent.
+
 set -euo pipefail
 set +o noglob
 
@@ -7,54 +29,87 @@ PROG=${0##*/}
 
 job=${TRAVIS_JOB_ID:-}
 out=
-incr=false
+post=false
 repo=
+flst=
+quiet=false
 branch=
 srcdir=$PWD
 objdir=
 token=${COVERALLS_REPO_TOKEN:-}
+origin=
 verbose=0
 
 function usage {
+    ((${1:-1})) && exec 1>&2
     cat <<EOF
 Usage: $PROG [OPTIONS]
-Usage: $PROG [-o FILE] [-S SRCDIR] [-O OBJDIR] [-R URI] [-t TOKEN] [-v] [-x]
     Options:
 
-     -v         Verbose (on stderr).
-     -x         Trace $PROG
-     -o FILE    Output to FILE instead of stdout.
-     -t TOKEN   Token for Coveralls         (defaults to
-                                             \$COVERALLS_REPO_TOKEN)
+     -q         Quiet.  Do not even emit warnings.
+     -v         Verbose (on stderr).  May be given multiple times.
+     -o -       Output to stdout instead of POSTing to Coveralls.
+     -o FILE    Output to FILE   instead of POSTing to Coveralls.
+     -s CI-NAME Name of CI (e.g., "travis-ci")
+                Defaults to travis-ci.
+     -t TOKEN   Token for Coveralls.
+                Defaults to \$COVERALLS_REPO_TOKEN.
+     -b BRANCH  Name of branch the report is for.
+                Defaults to \$TRAVIS_BRANCH or currently-checked out branch in
+                SRCDIR.
      -J ID      Job ID (e.g., Travis-CI job ID)
-     -S SRCDIR  Path to clone               (defaults to \$PWD)
-     -O OBJDIR  Path to object directory    (defaults to SRCDIR)
-     -R URI     Repository URI              (defaults to origin URI
-                                             for SRCDIR)
+                Defaults to \${TRAVIS_JOB_ID}.
+     -i FILE    Lists source files to run gcov(1) against
+                Defaults to \<(git ls-files -- '*.c' '*.cpp').
+     -S SRCDIR  Path to workspace
+                Defaults to \${PWD}.
+     -O OBJDIR  Path to object directory if workspace is built out of tree
+                Defaults to SRCDIR.
+     -U ORIGIN  Name of origin.
+                Defaults to tracked upstream remote of BRANCH.
+     -R URI     Repository URI
+                Defaults to git@github.com:\${TRAVIS_REPO_SLUG} or the push URI
+                for the ORIGIN remote of the workspace at SRCDIR.
 
     $PROG will look for .gcno and .gcda files in OBJDIR for source files
-    in the clone at SRCDIR and will run gcov on them, and produce
+    in the workspace at SRCDIR and will run gcov on them, and produce
     a request body as JSON in FILE (or stdout if -o FILE not given)
-    for Coveralls.
+    for the Coveralls API.
+
+    If -o FILE is not given, then $PROG will POST the JSON to Coveralls.
+    If -o FILE is given, then $PROG will not POST it to Coveralls.
+
+    If SRCDIR == OBJDIR == \$PWD, then -S and -O need not be given.
+    If running in a Travis-CI build, -J, -R, and -b need not be given, and -t
+    should not be given -- instead you should set a secret COVERALLS_REPO_TOKEN
+    environment variable in your project's Travis-CI's settings.
 
     Only C and C++ source files are reported on.  E.g., Yacc/Bison/Flex
     source files are not reported.
+
+    The resulting JSON output is or can be POSTed to Coveralls with:
+
+      $ curl -sfg -X POST -F "json_file=@\${FILE}" -F "Filename=json_file" \\
+             https://coveralls.io/api/v1/jobs
 EOF
+    exit ${1:-1}
 }
 
-while getopts +:J:O:R:S:b:ho:t:vx opt; do
+while getopts +:J:O:R:S:U:b:hi:o:qs:t:vx opt; do
 case "$opt" in
 J) job=$OPTARG;;
 O) cd "$OPTARG"; objdir=$PWD; cd "$OLDPWD";;
 R) repo=$OPTARG;;
 S) cd "$OPTARG"; srcdir=$PWD; cd "$OLDPWD";;
-h) usage 0;;
+U) origin=$OPTARG;;
 b) branch=;;
-i) incr=true;;
+h) usage 0;;
+i) flst=$OPTARG;;
 o) out=$OPTARG;;
+q) quiet=true; verbose=0;;
+s) ci=$OPTARG;;
 t) token=$OPTARG;;
-v) ((verbose++)) || true;;
-x) set -vx;;
+v) quiet=false; ((verbose++)) || true; ((verbose > 3)) && set -vx;;
 *) usage 1;;
 esac
 done
@@ -63,9 +118,27 @@ done
 # path, we do the right thing.
 
 : ${objdir:=${srcdir}}
-: ${repo:=git@github.com:${TRAVIS_REPO_SLUG:-heimdal/heimdal}}
-: ${repo:=$(cd "$srcdir"; git remote get-url origin)}
-: ${branch:=${TRAVIS_BRANCH:-}}
+: ${branch:=${TRAVIS_BRANCH:-$(cd "$srcdir" && git rev-parse --abbrev-ref HEAD)}}
+
+if [[ -z ${origin:-} ]]; then
+    origin=$(
+        git for-each-ref \
+            --format="%(refname:short) %(upstream:remotename)" refs/heads |
+            while read gb gr; do
+                [[ $gb = $branch ]] || continue
+                printf '%s\n' "$gr"
+                break
+            done
+    )
+fi
+
+if [[ -z ${repo:-} ]]; then
+    if [[ -n ${TRAVIS_REPO_SLUG:-} ]]; then
+        repo=git@github.com:${TRAVIS_REPO_SLUG:-heimdal/heimdal}
+    else
+        repo=$(cd "$srcdir" && git remote get-url --push "$origin")
+    fi
+fi
 
 if ((verbose > 1)); then
     exec 3>&2
@@ -84,7 +157,8 @@ touch "${d}/f"
 
 declare -a gcov
 
-(cd "$srcdir" && git ls-files -- '*.c' '*.cpp') |
+(cd "$srcdir" &&
+ if [[ -n $flst ]]; then cat "$flst"; else git ls-files -- '*.c' '*.cpp'; fi) |
 while read f; do
     # Remember to be careful to refer to ${srcdir}/${f}
     ((verbose)) && printf 'Processing: %s\n' "$f" 1>&2
@@ -107,28 +181,26 @@ while read f; do
         [[ -n $gcno && -f $gcno ]] && ln -f "$gcno" "${objdir}/${dir}/.libs/${base}.gcno"
         [[ -n $gcda && -f $gcda ]] && ln -f "$gcda" "${objdir}/${dir}/.libs/${base}.gcda"
         if [[ ( -n $gcda && ! -f $gcda ) || ( -n $gcno && ! -f $gcno ) ]]; then
-            ((verbose)) && printf 'Warning: %s has no gcov notes file\n' "$f" 1>&3
+            $quiet || printf 'Warning: %s has no gcov notes file\n' "$f" 1>&2
             continue
         fi
     fi
 
-    if $incr && [[ -f ${objdir}/${f}.gcov ]]; then
-        true
-    elif [[ -f ${objdir}/${dir}/.libs/${base}.gcda ]]; then
-        ((verbose)) && printf 'Running gcov for %s using gcda from .libs\n' "$f"
-        if ! (cd "${objdir}/${f%/*}"; ((verbose > 1)) && set -vx; gcov -o .libs "${f##*/}") 1>&3; then
-            printf 'Warning: gcov failed for %s\n' "$f" 1>&2
+    if [[ -f ${objdir}/${dir}/.libs/${base}.gcda ]]; then
+        ((verbose > 1)) && printf 'Running gcov for %s using gcda from .libs\n' "$f" 1>&2
+        if ! (cd "${objdir}/${f%/*}"; ((verbose > 2)) && set -vx; gcov -o .libs "${f##*/}") 1>&3; then
+            $quiet || printf 'Warning: gcov failed for %s\n' "$f" 1>&2
             continue
         fi
     elif [[ -f ${objdir}/${dir}/${base}.gcda ]]; then
-        if ! (cd "${objdir}/${f%/*}"; ((verbose > 1)) && set -vx; gcov "${f##*/}") 1>&3; then
-            printf 'Warning: gcov failed for %s\n' "$f" 1>&2
+        if ! (cd "${objdir}/${f%/*}"; ((verbose > 2)) && set -vx; gcov "${f##*/}") 1>&3; then
+            $quiet || printf 'Warning: gcov failed for %s\n' "$f" 1>&2
             continue
         fi
     fi
 
     if [[ ! -f ${objdir}/${f}.gcov ]]; then
-        printf 'Warning: gcov did not produce a .gcov file for %s\n' "$f" 1>&2
+        $quiet || printf 'Warning: gcov did not produce a .gcov file for %s\n' "$f" 1>&2
         continue
     fi
 
@@ -155,35 +227,51 @@ while read f; do
     ' "${objdir}/${f}.gcov" >> "${d}/f"
 done
 
+function make_report {
+    jq -s --arg job "$job" \
+          --arg ci "${ci:-travis-ci}" \
+          --arg token "$token" \
+          --arg repo "$repo" \
+          --arg branch "$branch" \
+          --arg upstream "$origin" \
+          --arg head "$(git log -n1 --format=%H)" \
+          --arg subject "$(git log -n1 --format=%s)" \
+          --arg aN "$(git log -n1 --format=%aN)" \
+          --arg ae "$(git log -n1 --format=%ae)" \
+          --arg cN "$(git log -n1 --format=%cN)" \
+          --arg ce "$(git log -n1 --format=%ce)" \
+        '{
+            service_job_id: $job,
+            service_name: $ci,
+            repo_token: $token,
+            git: {
+                id: $head,
+                author_name:  $aN,
+                author_email: $ae,
+                committer_name:  $cN,
+                committer_email: $ce,
+                message: $subject,
+                branch: $branch,
+                remotes: [ {
+                    "name": $upstream,
+                    "url": $repo
+                    }
+                ]
+            },
+            source_files: .
+        }' "${d}/f"
+}
 
-[[ -n $out ]] && exec 1>"$out"
-jq -s --arg job "$job" \
-      --arg token "$token" \
-      --arg repo "$repo" \
-      --arg branch "$TRAVIS_BRANCH" \
-      --arg head "$(git log -n1 --format=%H)" \
-      --arg subject "$(git log -n1 --format=%s)" \
-      --arg aN "$(git log -n1 --format=%aN)" \
-      --arg ae "$(git log -n1 --format=%ae)" \
-      --arg cN "$(git log -n1 --format=%cN)" \
-      --arg ce "$(git log -n1 --format=%ce)" \
-    '{
-        service_job_id: $job,
-        service_name: "travis-ci",
-        repo_token: $token,
-        git: {
-            id: $head,
-            author_name:  $aN,
-            author_email: $ae,
-            committer_name:  $cN,
-            committer_email: $ce,
-            message: $subject,
-            branch: $branch,
-            remotes: [ {
-                "name": "origin",
-                "url": $repo
-                }
-            ]
-        },
-        source_files: .
-    }' "${d}/f"
+if [[ -z $out ]]; then
+    post=true
+    make_report > "${d}/out"
+elif [[ $out = - ]]; then
+    make_report
+else
+    make_report > "${out}"
+fi
+
+if $post && [[ $out != /dev/stdout ]]; then
+    curl -sfg -X POST -F "json_file=@${d}/out" -F "Filename=json_file" \
+         https://coveralls.io/api/v1/jobs
+fi