>From 7416e09b11ed17d2a78dd78e2672fa8a98031f50 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
Date: Tue, 25 Mar 2014 12:01:00 +0200
Subject: [PATCH 1/1] Add basic support for CI test execution

Add basic support for executing continuous integration (CI) tests on
RHEL6, RHEL7, Fedora 20 and Debian Testing.

This adds two front-end scripts which can be executed either locally by
developers, or on the CI server: contrib/ci/run and contrib/ci/clean.
The first one will run the tests and the second will wipe out the
artifacts. Note that contrib/ci/run will itself clean before running.

The contrib/ci/run script accepts options to choose from three test
sets: essential, moderate and full (-e/-m/-f), with the essential set
selected by default.

Essential tests include building everything and running tests under
Valgrind, completing in about 2.5 minutes. Valgrind failures are ignored
for now.

Moderate tests include essential tests, plus a distcheck target build
and mock builds for fedora20 and epel6, on distros where mock exists.
They complete in about 15 minutes.

Full test set includes the moderate set, plus a pass with Clang static
analyzer over the whole build and test execution with code coverage
collection, completing in about 20 minutes. Static analyzer failures are
ignored for now.

The first stage (install-deps) would invoke sudo to install dependencies
using the appropriate package manager. A relatively strict rule can be
added to sudoers to avoid password prompts. In case the user is not in
"mock" group when mock is executed, sudo will be invoked as well.
---
 Makefile.am                   |  11 +-
 contrib/ci/clean              |  25 ++++
 contrib/ci/configure.sh       |  43 ++++++
 contrib/ci/deps.sh            | 114 +++++++++++++++
 contrib/ci/distro.sh          |  72 ++++++++++
 contrib/ci/make-check-wrap    |  52 +++++++
 contrib/ci/misc.sh            | 116 +++++++++++++++
 contrib/ci/rpm-spec-builddeps |  35 +++++
 contrib/ci/run                | 322 ++++++++++++++++++++++++++++++++++++++++++
 contrib/ci/valgrind-condense  | 128 +++++++++++++++++
 10 files changed, 917 insertions(+), 1 deletion(-)
 create mode 100755 contrib/ci/clean
 create mode 100644 contrib/ci/configure.sh
 create mode 100644 contrib/ci/deps.sh
 create mode 100644 contrib/ci/distro.sh
 create mode 100755 contrib/ci/make-check-wrap
 create mode 100644 contrib/ci/misc.sh
 create mode 100755 contrib/ci/rpm-spec-builddeps
 create mode 100755 contrib/ci/run
 create mode 100755 contrib/ci/valgrind-condense

diff --git a/Makefile.am b/Makefile.am
index 83999f3..30bb53a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -301,6 +301,11 @@ dist_noinst_SCRIPTS = \
     contrib/rhel/update_debug_levels.py \
     contrib/fedora/bashrc_sssd \
     contrib/fedora/make_srpm.sh \
+    contrib/ci/clean \
+    contrib/ci/make-check-wrap \
+    contrib/ci/rpm-spec-builddeps \
+    contrib/ci/run \
+    contrib/ci/valgrind-condense \
     src/tests/pyhbac-test.py \
     src/tests/pysss_murmur-test.py
 
@@ -311,7 +316,11 @@ dist_noinst_DATA = \
     src/config/testconfigs/sssd-badversion.conf \
     src/config/testconfigs/sssd-invalid.conf \
     src/config/testconfigs/sssd-invalid-badbool.conf \
-    src/config/etc/sssd.api.d/crash_test_dummy
+    src/config/etc/sssd.api.d/crash_test_dummy \
+    contrib/ci/configure.sh \
+    contrib/ci/deps.sh \
+    contrib/ci/distro.sh \
+    contrib/ci/misc.sh
 
 ###############################
 # Global compilation settings #
diff --git a/contrib/ci/clean b/contrib/ci/clean
new file mode 100755
index 0000000..ee18c10
--- /dev/null
+++ b/contrib/ci/clean
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Clean source tree after a run of integration tests.
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+set -o nounset -o pipefail -o errexit
+export PATH=`dirname "\`readlink -f \"\$0\"\`"`:$PATH
+
+. misc.sh
+
+rm_rf_ro ci-*
diff --git a/contrib/ci/configure.sh b/contrib/ci/configure.sh
new file mode 100644
index 0000000..8af2730
--- /dev/null
+++ b/contrib/ci/configure.sh
@@ -0,0 +1,43 @@
+#
+# Configure argument management.
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if [ -z ${_CONFIGURE_SH+set} ]; then
+declare -r _CONFIGURE_SH=
+
+. distro.sh
+
+# List of "configure" arguments.
+declare -a CONFIGURE_ARG_LIST=(
+    "--disable-dependency-tracking"
+    "--disable-rpath"
+    "--disable-static"
+    "--enable-ldb-version-check"
+    "--with-syslog=journald"
+)
+
+
+if [[ "$DISTRO_BRANCH" == -redhat-redhatenterprise*-6.*- ]]; then
+    CONFIGURE_ARG_LIST+=(
+        "--disable-cifs-idmap-plugin"
+        "--with-syslog=syslog"
+    )
+fi
+
+declare -r -a CONFIGURE_ARG_LIST
+
+fi # _CONFIGURE_SH
diff --git a/contrib/ci/deps.sh b/contrib/ci/deps.sh
new file mode 100644
index 0000000..01f1fd8
--- /dev/null
+++ b/contrib/ci/deps.sh
@@ -0,0 +1,114 @@
+#
+# Dependency management.
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if [ -z ${_DEPS_SH+set} ]; then
+declare -r _DEPS_SH=
+
+. distro.sh
+
+# Dependency list
+declare -a DEPS_LIST=(
+    lcov
+    valgrind
+    xqilla
+)
+
+# True, if all test dependencies are satisfied by the package list
+declare DEPS_TESTS_SATISFIED=true
+
+if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
+    declare _DEPS_LIST_SPEC
+    _DEPS_LIST_SPEC=`
+        sed -e 's/@PACKAGE_VERSION@/0/g' \
+            -e 's/@PACKAGE_NAME@/package-name/g' \
+            -e 's/@PRERELEASE_VERSION@//g' contrib/sssd.spec.in |
+            rpm-spec-builddeps /dev/stdin`
+    readarray -t -O "${#DEPS_LIST[@]}" DEPS_LIST <<<"$_DEPS_LIST_SPEC"
+    if [[ "$DISTRO_BRANCH" == *-redhatenterprise*-6.*- ]]; then
+        DEPS_TESTS_SATISFIED=false
+    fi
+fi
+
+if [[ "$DISTRO_BRANCH" == -debian-* ]]; then
+    DEPS_LIST+=(
+        autoconf
+        automake
+        autopoint
+        check
+        cifs-utils
+        clang
+        dh-apparmor
+        dnsutils
+        docbook-xml
+        docbook-xsl
+        gettext
+        krb5-config
+        libaugeas-dev
+        libc-ares-dev
+        libcmocka-dev
+        libcollection-dev
+        libdbus-1-dev
+        libdhash-dev
+        libglib2.0-dev
+        libini-config-dev
+        libkeyutils-dev
+        libkrb5-dev
+        libldap2-dev
+        libldb-dev
+        libltdl-dev
+        libnl-3-dev
+        libnl-route-3-dev
+        libnspr4-dev
+        libnss3-dev
+        libpam0g-dev
+        libpcre3-dev
+        libpopt-dev
+        libsasl2-dev
+        libselinux1-dev
+        libsemanage1-dev
+        libsmbclient-dev
+        libsystemd-journal-dev
+        libtalloc-dev
+        libtdb-dev
+        libtevent-dev
+        libtool
+        libxml2-utils
+        python-dev
+        samba-dev
+        systemd
+        xml-core
+        xsltproc
+    )
+fi
+
+declare -a -r DEPS_LIST
+declare -r DEPS_TESTS_SATISFIED
+
+# Install dependencies.
+function deps_install()
+{
+    distro_pkg_install "${DEPS_LIST[@]}"
+}
+
+# Remove dependencies.
+function deps_remove()
+{
+    distro_pkg_remove "${DEPS_LIST[@]}"
+}
+
+fi # _DEPS_SH
diff --git a/contrib/ci/distro.sh b/contrib/ci/distro.sh
new file mode 100644
index 0000000..5dc3e70
--- /dev/null
+++ b/contrib/ci/distro.sh
@@ -0,0 +1,72 @@
+#
+# Distribution version discovery
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if [ -z ${_DISTRO_SH+set} ]; then
+declare -r _DISTRO_SH=
+
+# Distribution branch (lowercase)
+declare DISTRO_BRANCH=
+
+if [ -e /etc/redhat-release ]; then
+    DISTRO_BRANCH+=-redhat
+elif [ -e /etc/debian_version ]; then
+    DISTRO_BRANCH+=-debian
+else
+    DISTRO_BRANCH+=-unknown
+fi
+
+DISTRO_BRANCH+=-`lsb_release --id | sed -e 's/^[^:]*:\s*\(.*\)$/\L\1\E/'`
+DISTRO_BRANCH+=-`lsb_release --release | sed -e 's/^[^:]*:\s*\(.*\)$/\L\1\E/'`
+DISTRO_BRANCH+=-
+
+declare -r DISTRO_BRANCH
+
+
+# Install packages.
+# Args: [pkg_name...]
+function distro_pkg_install()
+{
+    declare prompt=$'Need root permissions to install packages.\n'
+    prompt+="Enter sudo password for $USER: "
+    if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
+        [ $# != 0 ] && sudo -p "$prompt" yum --assumeyes install -- "$@"
+    elif [[ "$DISTRO_BRANCH" == -debian-* ]]; then
+        [ $# != 0 ] && sudo -p "$prompt" apt-get --yes install -- "$@"
+    else
+        echo "Cannot install packages on $DISTRO_BRANCH" >&2
+        exit 1
+    fi
+}
+
+# Remove packages.
+# Args: [pkg_name...]
+function distro_pkg_remove()
+{
+    declare prompt=$'Need root permissions to remove packages.\n'
+    prompt+="Enter sudo password for $USER: "
+    if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
+        [ $# != 0 ] && sudo -p "$prompt" yum --assumeyes remove -- "$@"
+    elif [[ "$DISTRO_BRANCH" == -debian-* ]]; then
+        [ $# != 0 ] && sudo -p "$prompt" apt-get --yes remove -- "$@"
+    else
+        echo "Cannot remove packages on $DISTRO_BRANCH" >&2
+        exit 1
+    fi
+}
+
+fi # _DISTRO_SH
diff --git a/contrib/ci/make-check-wrap b/contrib/ci/make-check-wrap
new file mode 100755
index 0000000..4ad323e
--- /dev/null
+++ b/contrib/ci/make-check-wrap
@@ -0,0 +1,52 @@
+#!/bin/bash
+#
+# Build a make check target, prepending test commands with specified command
+# and arguments.
+# Args: [make_arg...] [-- wrap_cmd wrap_arg...]
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+set -o nounset -o pipefail -o errexit
+
+declare -a argv=("make")
+declare arg
+
+while [ $# != 0 ]; do
+    arg="$1"
+    shift
+    if [ "$arg" == "--" ]; then
+        break
+    fi
+    argv+=("$arg")
+done
+
+if [ $# != 0 ]; then
+    # If Makefile supports LOG_COMPILER
+    if grep -q -w LOG_COMPILER Makefile; then
+        printf -v arg 'LOG_COMPILER=%q' $1
+        argv+=("$arg")
+        shift
+        if [ $# != 0 ]; then
+            printf -v arg ' %q' "$@"
+            argv+=("LOG_FLAGS=$arg")
+        fi
+    else
+        printf -v arg ' %q' "$@"
+        argv+=("AUX_TESTS_ENVIRONMENT=$arg")
+    fi
+fi
+
+"${argv[@]}"
diff --git a/contrib/ci/misc.sh b/contrib/ci/misc.sh
new file mode 100644
index 0000000..9af848c
--- /dev/null
+++ b/contrib/ci/misc.sh
@@ -0,0 +1,116 @@
+#
+# Miscellaneous routines.
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if [ -z ${_MISC_SH+set} ]; then
+declare -r _MISC_SH=
+
+# Remove files and directories recursively, forcing write permissions on
+# directories.
+# Args: path...
+function rm_rf_ro()
+{
+    chmod -Rf u+w -- "$@" || true
+    rm -Rf -- "$@"
+}
+
+# Run "scan-build" with output to a single specified directory, instead of
+# a subdirectory.
+# Args: dir [scan_build_arg...]
+function scan_build_single()
+{
+    declare -r dir="$1";    shift
+    declare entry
+    declare subdir
+    declare status
+
+    set +o errexit
+    scan-build -o "$dir" "$@"
+    status="$?"
+    set -o errexit
+
+    for entry in "$dir/"*; do
+        if [ -n "${subdir+set}" ] || ! [ -d "$entry" ]; then
+            echo 'Unexpected entries in scan-build output directory' >&2
+            exit 1
+        fi
+        subdir="$entry"
+    done
+
+    mv "$subdir/"* "$dir"
+    rmdir "$subdir"
+    return "$status"
+}
+
+# Check if a scan-build result directory has any non-empty .plist files.
+# Args: dir
+function scan_check()
+{
+    declare -r dir="$1"
+    declare f
+    for f in "$dir"/*.plist; do
+        if [ "`xqilla -i \"\$f\" /dev/stdin \
+                <<<'count(/plist/dict/array[count(*) > 0])'`" != 0 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# Extract line and function coverage percentage from a "genhtml" or "lcov
+# --summary" output.
+# Input: "genhtml" or "lcov --summary" output
+# Output: lines funcs
+function lcov_summary()
+{
+    sed -ne 's/^ *\(lines\|functions\)\.*: \([0-9]\+\).*$/ \2/p' |
+        tr -d '\n'
+    echo
+}
+
+# Check if a "genhtml" or "lcov --summary" output has a minimum coverage
+# percentage of lines and functions.
+# Input: "genhtml" or "lcov --summary" output
+# Args: min_lines min_funcs
+function lcov_check()
+{
+    declare -r min_lines="$1";      shift
+    declare -r min_funcs="$1";      shift
+    declare lines
+    declare funcs
+
+    read -r lines funcs < <(lcov_summary)
+    ((lines >= min_lines && funcs >= min_funcs)) && return 0 || return 1
+}
+
+# Check if the current user belongs to a group.
+# Args: group_name
+function memberof()
+{
+    declare -r group_name="$1"
+    declare group_id
+    declare id
+    group_id=`getent group "$group_name" | cut -d: -f3` || return 1
+    for id in "${GROUPS[@]}"; do
+        if [ "$id" == "$group_id" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+
+fi # _MISC_SH
diff --git a/contrib/ci/rpm-spec-builddeps b/contrib/ci/rpm-spec-builddeps
new file mode 100755
index 0000000..b73ed20
--- /dev/null
+++ b/contrib/ci/rpm-spec-builddeps
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#
+# Extract build dependencies from an RPM .spec file.
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import re
+import rpm
+
+def usage(file):
+    file.write(("Usage: %s SPEC\n" +
+                "Extract build dependencies from an RPM .spec file.\n") %
+               re.match(".*?([^/]+)$", sys.argv[0]).group(1))
+
+if len(sys.argv) != 2:
+    usage(sys.stderr)
+    sys.exit(1)
+
+spec = rpm.spec(sys.argv[1])
+for d in rpm.ds(spec.sourceHeader, 'requires'):
+    print d.DNEVR()[2:]
diff --git a/contrib/ci/run b/contrib/ci/run
new file mode 100755
index 0000000..30e7897
--- /dev/null
+++ b/contrib/ci/run
@@ -0,0 +1,322 @@
+#!/bin/bash
+#
+# Run continuous integration tests.
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+set -o nounset -o pipefail -o errexit
+export PATH=`dirname "\`readlink -f \"\$0\"\`"`:$PATH
+export LC_ALL=C
+
+. deps.sh
+. distro.sh
+. configure.sh
+. misc.sh
+
+declare -r DEBUG_CFLAGS="-g3 -O2"
+declare -r COVERAGE_CFLAGS="-g3 -O0 --coverage"
+declare -r ARCH=`uname -m`
+declare -r CPU_NUM=`getconf _NPROCESSORS_ONLN`
+declare -r TITLE_WIDTH=24
+declare -r RESULT_WIDTH=18
+
+# Minimum percentage of code lines covered by tests
+declare -r COVERAGE_MIN_LINES=15
+# Minimum percentage of code functions covered by tests
+declare -r COVERAGE_MIN_FUNCS=0
+
+declare BASE_PFX=""
+declare BASE_DIR=`pwd`
+declare MODERATE=false
+declare FULL=false
+
+# Output program usage information.
+function usage()
+{
+    cat <<EOF
+Usage: `basename "$0"` [OPTION...]
+Run continuous integration tests.
+
+Options:
+    -h, --help          Output this help message and exit.
+    -p, --prefix=STRING Use STRING as the prefix to prepend to file and
+                        directory paths in output.
+    -e, --essential     Run an essential subset of tests.
+    -m, --moderate      Run a moderate subset of tests.
+    -f, --full          Run all tests.
+
+Default options: --essential
+EOF
+}
+
+# Output a file display path: a path relocated from base directory (BASE_DIR)
+# to base prefix (BASE_PFX).
+# Args: path
+function disppath()
+{
+    declare -r path=`readlink -f "$1"`
+    printf "%s" "$BASE_PFX${path:${#BASE_DIR}+1}"
+}
+
+# Run a stage.
+# Args: id cmd [arg...]
+function stage()
+{
+    declare -r id="$1";     shift
+    declare -r log="ci-$id.log"
+    declare status
+    declare start
+    declare end
+    declare duration
+
+    printf "%-${TITLE_WIDTH}s" "$id:"
+
+    {
+        printf "Start: "
+        start=`date +%s`
+        date --date="@$start"
+        set +o errexit
+        (
+            set -o errexit -o xtrace
+            "$@"
+        )
+        status=$?
+        set -o errexit
+        printf "End: "
+        end=`date +%s`
+        date --date="@$end"
+    } &> "$log"
+
+    duration=$((end - start))
+
+    if [ "$status" == 0 ]; then
+        printf 'success  '
+    else
+        printf 'failure  '
+    fi
+    printf "%02u:%02u:%02u " \
+           $((duration / (60 * 60))) \
+           $((duration / 60 % 60)) \
+           $((duration % 60))
+    disppath "$log"
+    printf "\n"
+
+    return "$status"
+}
+
+# Execute mock as is, or, if the user is not in the "mock" group, under sudo,
+# which has password prompt/input on the console, instead of stderr/stdin.
+# Args: [mock_arg...]
+function mock_privileged()
+{
+    if memberof mock; then
+        mock "$@"
+    else
+        declare prompt=$'Not a "mock" group member.\n'
+        prompt+="To run mock enter sudo password for $USER: "
+        sudo -p "$prompt" mock "$@"
+    fi
+}
+
+# Run debug build checks.
+function build_debug()
+{
+    export CFLAGS="$DEBUG_CFLAGS"
+    declare test_dir
+    declare test_dir_distcheck
+    declare distcheck_configure_args
+    declare status
+
+    test_dir=`mktemp --directory /dev/shm/ci-test-dir.XXXXXXXX`
+    stage configure         "$BASE_DIR/configure" \
+                                "${CONFIGURE_ARG_LIST[@]}" \
+                                --with-test-dir="$test_dir"
+
+    # Not building "tests" due to https://fedorahosted.org/sssd/ticket/2350
+    stage make-tests        make-check-wrap -j $CPU_NUM check -- true
+
+    # Ignored until issues found by Valgrind are fixed
+    status=0
+    CK_FORK=no \
+        stage make-check-valgrind \
+                make-check-wrap -j $CPU_NUM check -- \
+                                libtool --mode=execute \
+                                    valgrind-condense 99 '!(*.py)' -- \
+                                    --vgdb=no \
+                                    --trace-children=yes \
+                                    --trace-children-skip='*/bin/*,*/sbin/*' \
+                                    --leak-check=full ||
+            status=$?
+    mv "$test_dir" ci-test-dir
+
+    if "$MODERATE"; then
+        test_dir_distcheck=`mktemp --directory /dev/shm/ci-test-dir.XXXXXXXX`
+        # Single thread due to https://fedorahosted.org/sssd/ticket/2354
+        status=0
+        printf -v distcheck_configure_args " %q" \
+                    "${CONFIGURE_ARG_LIST[@]}" \
+                    "--with-test-dir=$test_dir_distcheck"
+        stage make-distcheck    make distcheck \
+                                    AUX_DISTCHECK_CONFIGURE_FLAGS=" \
+                                        $distcheck_configure_args" ||
+                status=$?
+        mv "$test_dir_distcheck" ci-test-dir-distcheck
+        ((status == 0))
+
+        if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
+            stage make-srpm     env -u CFLAGS -- make srpm
+            stage mock-epel6    mock_privileged \
+                                     --root "epel-6-$ARCH" \
+                                     --resultdir ci-mock-result-epel6 \
+                                     rpmbuild/SRPMS/*.src.rpm
+            stage mock-fedora20 mock_privileged \
+                                     --root "fedora-20-$ARCH" \
+                                     --resultdir ci-mock-result-fedora20 \
+                                     rpmbuild/SRPMS/*.src.rpm
+        fi
+    fi
+
+    unset CFLAGS
+}
+
+# Run coverage build checks.
+function build_coverage()
+{
+    declare -r scan_report_dir="ci-report-scan"
+    declare -r coverage_report_dir="ci-report-coverage"
+    declare test_dir
+
+    export CFLAGS="$COVERAGE_CFLAGS"
+
+    test_dir=`mktemp --directory /dev/shm/ci-test-dir.XXXXXXXX`
+    stage configure         scan-build "$BASE_DIR/configure" \
+                                           "${CONFIGURE_ARG_LIST[@]}" \
+                                           --with-test-dir="$test_dir"
+
+    # Build everything, including tests
+    # Not building "tests" due to https://fedorahosted.org/sssd/ticket/2350
+    stage scan-make-tests   scan_build_single \
+                                "$scan_report_dir" \
+                                -plist-html \
+                                --html-title="sssd - scan-build report" \
+                                make-check-wrap -j $CPU_NUM check -- true
+    printf "%-$((TITLE_WIDTH + RESULT_WIDTH))s%s\n" \
+            "scan report:" \
+            "`disppath \"\$scan_report_dir/index.html\"`"
+    # Ignored until issues found by the scanner are fixed
+    stage scan-check        scan_check "$scan_report_dir" ||
+                            true
+
+    stage lcov-pre          lcov --capture --initial --directory . \
+                                 --base-directory "$BASE_DIR" \
+                                 --output-file ci-base.info
+    # Run tests
+    stage make-check        scan-build make -j $CPU_NUM check || true
+    mv "$test_dir" ci-test-dir
+
+    stage lcov-post         lcov --capture --directory . \
+                                 --base-directory "$BASE_DIR" \
+                                 --output-file ci-check.info
+    stage lcov-merge        lcov --add-tracefile ci-base.info \
+                                 --add-tracefile ci-check.info \
+                                 --output-file ci-dirty.info
+    stage lcov-clean        lcov --remove ci-dirty.info \
+                                 "/usr/*" "src/tests/*" \
+                                 --output-file ci.info
+    stage genhtml           eval 'genhtml --output-directory \
+                                          "$coverage_report_dir" \
+                                          --title "sssd" --show-details \
+                                          --legend --prefix "$BASE_DIR" \
+                                          ci.info |& tee ci-genhtml.out'
+    printf "%-$((TITLE_WIDTH + RESULT_WIDTH))s%s\n" \
+            "coverage report:" \
+            "`disppath \"\$coverage_report_dir/index.html\"`"
+
+    # If dependencies for all tests are satisfied
+    # and so all the tests should have been built and ran.
+    if "$DEPS_TESTS_SATISFIED"; then
+        stage lcov-check        eval 'lcov_check "$COVERAGE_MIN_LINES" \
+                                                 "$COVERAGE_MIN_FUNCS" \
+                                        < ci-genhtml.out'
+    fi
+
+    unset CFLAGS
+}
+
+# Run a build inside a sub-directory.
+# Args: id cmd [arg...]
+function run_build()
+{
+    declare -r id="$1"; shift
+    declare -r dir="ci-build-$id"
+
+    mkdir "$dir"
+    printf "%-$((TITLE_WIDTH + RESULT_WIDTH))s%s\n" \
+           "${id^^} BUILD:" "`disppath \"\$dir\"`"
+
+    cd "$dir"
+    "$@"
+    cd ..
+}
+
+#
+# Main routine
+#
+declare args_expr
+args_expr=`getopt --name \`basename "\$0"\` \
+                  --options hp:emf \
+                  --longoptions help,prefix:,essential,moderate,full \
+                  -- "$@"`
+eval set -- "$args_expr"
+
+while true; do
+    case "$1" in
+        -h|--help)
+            usage;          exit 0;;
+        -p|--prefix)
+            BASE_PFX="$2";  shift 2;;
+        -e|--essential)
+            MODERATE=false; FULL=false; shift;;
+        -m|--moderate)
+            MODERATE=true;  FULL=false; shift;;
+        -f|--full)
+            MODERATE=true;  FULL=true;  shift;;
+        --)
+            shift;          break;;
+        *)
+            echo "Unknown option: $1" >&2
+            exit 1;;
+    esac
+done
+
+if [ $# != 0 ]; then
+    echo "Positional arguments are not accepted." >&2
+    usage >&2
+    exit 1
+fi
+
+trap 'echo FAILURE' EXIT
+rm_rf_ro ci-*
+export V=1
+stage install-deps  deps_install
+stage autoreconf    autoreconf --install --force
+run_build debug     build_debug
+if "$FULL"; then
+    run_build coverage  build_coverage
+fi
+unset V
+trap - EXIT
+echo SUCCESS
diff --git a/contrib/ci/valgrind-condense b/contrib/ci/valgrind-condense
new file mode 100755
index 0000000..b838039
--- /dev/null
+++ b/contrib/ci/valgrind-condense
@@ -0,0 +1,128 @@
+#!/bin/bash
+#
+# Run Valgrind, condensing logged reports into an exit code.
+#
+# Copyright (C) 2014 Red Hat
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+set -o nounset -o pipefail -o errexit
+shopt -s extglob
+
+function usage()
+{
+    cat <<EOF
+Usage: `basename "$0"` ERROR_EXITCODE [PATH_PATTERN...] [-- VALGRIND_ARG...]
+Run Valgrind, condensing logged reports into an exit code.
+
+Arguments:
+    ERROR_EXITCODE  An exit code to return if at least one error is found in
+                    Valgrind log files.
+    PATH_PATTERN    An extended glob pattern matching (original) paths to
+                    programs to execute under Valgrind. Execution is skipped
+                    and success is returned for non-matching programs. Without
+                    patterns, all programs match.
+    VALGRIND_ARG    An argument to pass to Valgrind after the arguments
+                    specified by `basename "$0"`.
+
+The first non-option VALGRIND_ARG will be considered the path to the program
+to execute under Valgrind and will be used in naming Valgrind log files as
+such:
+
+    PROGRAM_NAME.PID.valgrind.log
+
+where PROGRAM_NAME is the filename portion of the program path and PID is the
+executed process ID. If the last directory of the program path is ".libs" and
+the filename begins with "lt-", both are removed to match the name of libtool
+frontend script. All files matching PROGRAM_NAME.*.valgrind.log are removed
+before invoking Valgrind.
+
+If an error is found in Valgrind log files, ERROR_EXITCODE is returned,
+otherwise Valgrind exit code is returned.
+EOF
+}
+
+
+if [[ $# == 0 ]]; then
+    echo "Invalid number of arguments." >&2
+    usage >&2
+    exit 1
+fi
+
+declare error_exitcode="$1";    shift
+declare -a path_pattern_list=()
+declare arg
+declare got_dash_dash
+declare program_path
+declare path_pattern
+declare match
+declare program_name
+declare status=0
+
+# Extract path patterns
+while [[ $# != 0 ]]; do
+    arg="$1"
+    shift
+    if [[ "$arg" == "--" ]]; then
+        break
+    else
+        path_pattern_list+=("$arg")
+    fi
+done
+
+# Find program path argument
+got_dash_dash=false
+for arg in "$@"; do
+    if [[ "$arg" == "--" ]]; then
+        got_dash_dash=true
+    elif "$got_dash_dash" || [[ "$arg" != -* ]]; then
+        program_path="$arg"
+        break
+    fi
+done
+
+if [[ -z "${program_path+set}" ]]; then
+    echo "Program path not specified." >&2
+    usage >&2
+    exit 1
+fi
+
+# Match against path patterns, if any
+if [[ ${#path_pattern_list[@]} != 0 ]]; then
+    match=false
+    for path_pattern in "${path_pattern_list[@]}"; do
+        if [[ "$program_path" == $path_pattern ]]; then
+            match=true
+        fi
+    done
+    if ! $match; then
+        exit 0
+    fi
+fi
+
+# Generate original path from libtool path
+program_path=`sed -e 's/^\(.*\/\)\?\.libs\/lt-\([^\/]\+\)$/\1\2/' \
+                    <<<"$program_path"`
+
+program_name=`basename "$program_path"`
+
+rm -f "$program_name".*.valgrind.log
+valgrind --log-file="$program_name.%p.valgrind.log" "$@" || status=$?
+
+if grep -q '^==[0-9]\+== *ERROR SUMMARY: *[1-9]' \
+            "$program_name".*.valgrind.log; then
+    exit "$error_exitcode"
+else
+    exit "$status"
+fi
-- 
2.0.0

