>From bd692b941b4a106f6234f559093de836f3603633 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, Fedora Rawhide and Debian Testing.

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

See contrib/ci/README.md for further details.
---
 .gitignore                    |   1 +
 Makefile.am                   |  12 +-
 contrib/ci/README.md          |  62 +++++++
 contrib/ci/clean              |  25 +++
 contrib/ci/configure.sh       |  50 ++++++
 contrib/ci/deps.sh            | 119 ++++++++++++++
 contrib/ci/distro.sh          |  86 ++++++++++
 contrib/ci/make-check-wrap    |  52 ++++++
 contrib/ci/misc.sh            | 116 +++++++++++++
 contrib/ci/rpm-spec-builddeps |  35 ++++
 contrib/ci/run                | 371 ++++++++++++++++++++++++++++++++++++++++++
 contrib/ci/valgrind-condense  | 128 +++++++++++++++
 12 files changed, 1056 insertions(+), 1 deletion(-)
 create mode 100644 contrib/ci/README.md
 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/.gitignore b/.gitignore
index f935a35..82432e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -111,3 +111,4 @@ sss_ssh_authorizedkeys
 sss_ssh_knownhostsproxy
 sssd_ssh
 test-authtok
+/ci-build-*
diff --git a/Makefile.am b/Makefile.am
index 6707711..8a0a404 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -310,6 +310,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
 
@@ -320,7 +325,12 @@ 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/README.md \
+    contrib/ci/configure.sh \
+    contrib/ci/deps.sh \
+    contrib/ci/distro.sh \
+    contrib/ci/misc.sh
 
 ###############################
 # Global compilation settings #
diff --git a/contrib/ci/README.md b/contrib/ci/README.md
new file mode 100644
index 0000000..6c87200
--- /dev/null
+++ b/contrib/ci/README.md
@@ -0,0 +1,62 @@
+Continuous integration
+======================
+
+The executables and modules in this directory implement continuous integration
+(CI) tests, which can be run to verify SSSD code quality and validity.
+
+Supported host distros are Fedora 20 and later, RHEL 6.5 and later, and Debian
+Testing.
+
+The tests are executed by running `contrib/ci/run` from the source tree root.
+It accepts options to choose from three test sets: "essential", "moderate" and
+"rigorous" (-e/-m/-r), with the essential set selected by default.
+
+Essential tests include building everything and running the built-in test
+suite under Valgrind, completing in under 5 minutes. Valgrind failures are
+ignored for now.
+
+Moderate tests include essential tests, plus a distcheck target build and mock
+package builds for Fedora and RHEL on Red Hat distros. They complete in about
+15 minutes.
+
+Rigorous tests include moderate tests, plus a pass with Clang static analyzer
+over the whole build and test execution with code coverage collection and
+verification, completing in 30 minutes. Static analyzer failures are ignored
+for now.
+
+Use `contrib/ci/clean` to remove test results from the source tree.
+
+
+Setup
+-----
+
+CI requires `lsb_release` command to be available in order to determine host
+distro version. On Red Hat distros it is contained in the `redhat-lsb-core`
+package and on Debian in `lsb-release`.
+
+The rest of the required packages CI will attempt to install itself, using
+the distribution's package manager invoked through sudo.
+
+A sudo rule can be employed to selectively avoid password prompts on Red Hat
+distros:
+
+    <USER> ALL=(ALL:ALL) NOPASSWD: /usr/bin/yum --assumeyes install -- *
+
+and Debian-based distros:
+
+    <USER> ALL=(ALL:ALL) NOPASSWD: /usr/bin/apt-get --yes install -- *
+
+Where `<USER>` is the user invoking CI.
+
+On Red Hat distros a repository carrying dependencies missing from some
+distros needs to be added to yum configuration. See instructions on the
+[Copr project page](http://copr-fe.cloud.fedoraproject.org/coprs/lslebodn/sssd-deps/).
+That repository is also automatically used by CI during mock builds.
+
+Package installation can be disabled with the -n/--no-deps option, e.g.  for
+manual dependency management, or for shaving off a few seconds of execution
+time, when dependency changes are not expected.
+
+On Red Hat distros, where mock builds are ran, it is better to have the
+invoking user added to the `mock` group. Otherwise mock builds will be
+executed through sudo.
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..b92b094
--- /dev/null
+++ b/contrib/ci/configure.sh
@@ -0,0 +1,50 @@
+#
+# 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
+
+if [[ "$DISTRO_BRANCH" == -debian-* ]]; then
+    CONFIGURE_ARG_LIST+=(
+        # TODO Remove once libini_config >= 1.1.0 becomes available
+        "--without-samba"
+    )
+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..ba59f78
--- /dev/null
+++ b/contrib/ci/deps.sh
@@ -0,0 +1,119 @@
+#
+# 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+=(
+        clang-analyzer
+        mock
+        rpm-build
+    )
+    _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..5416bff
--- /dev/null
+++ b/contrib/ci/distro.sh
@@ -0,0 +1,86 @@
+#
+# 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 family (lowercase)
+declare DISTRO_FAMILY=
+# Distribution ID (lowercase)
+declare DISTRO_ID=
+# Distribution release (lowercase)
+declare DISTRO_RELEASE=
+
+if [ -e /etc/redhat-release ]; then
+    DISTRO_FAMILY=redhat
+elif [ -e /etc/debian_version ]; then
+    DISTRO_FAMILY=debian
+else
+    DISTRO_FAMILY=unknown
+fi
+declare -r DISTRO_FAMILY
+
+DISTRO_ID=`lsb_release --id | sed -e 's/^[^:]*:\s*\(.*\)$/\L\1\E/'`
+declare -r DISTRO_ID
+DISTRO_RELEASE=`lsb_release --release | sed -e 's/^[^:]*:\s*\(.*\)$/\L\1\E/'`
+declare -r DISTRO_RELEASE
+
+# Distribution branch (lowercase)
+declare -r DISTRO_BRANCH="-$DISTRO_FAMILY-$DISTRO_ID-$DISTRO_RELEASE-"
+
+
+# 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 -- "$@" |&
+            # Pass input to output, fail if a missing package is reported
+            # TODO Remove and switch to DNF once
+            # https://bugzilla.redhat.com/show_bug.cgi?id=1128139 is fixed
+            awk 'BEGIN {s=0}
+                 /^No package .* available.$/ {s=1}
+                 {print}
+                 END {exit s}'
+    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..b8a296a
--- /dev/null
+++ b/contrib/ci/run
@@ -0,0 +1,371 @@
+#!/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 DEPS=true
+declare BASE_DIR=`pwd`
+declare MODERATE=false
+declare RIGOROUS=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.
+    -n, --no-deps       Don't attempt to install dependencies.
+    -e, --essential     Run the essential subset of tests.
+    -m, --moderate      Run the moderate subset of tests.
+    -r, --rigorous,
+    -f, --full          Run the rigorous (full) set of 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
+}
+
+# Execute mock_privileged with extra chroot configuration added.
+# Args: chroot [mock_arg...]
+# Input: extra configuration
+function mock_privileged_conf()
+{
+    declare -r chroot="$1"; shift
+    declare conf_dir
+
+    conf_dir=`mktemp --tmpdir --directory mock-config.XXXXXXXX`
+    trap 'trap - RETURN; rm -R "$conf_dir";' RETURN
+    cp -r /etc/mock/* "$conf_dir"/
+    cat >> "${conf_dir}/${chroot}.cfg"
+    mock_privileged --configdir="$conf_dir" --root="$chroot" "$@"
+}
+
+# Execute mock_privileged with dependency package source configuration added.
+# Args: chroot [mock_arg...]
+function mock_privileged_deps()
+{
+    declare -r chroot="$1"; shift
+    declare repo
+
+    if [[ "$chroot" == fedora-* ]]; then
+        repo='fedora-$releasever-$basearch'
+    elif [[ "$chroot" =~ epel-([0-9]+) ]]; then
+        repo="epel-${BASH_REMATCH[1]}-\$basearch"
+    else
+        echo "Unknown chroot config: $chroot" >&2
+        exit 1
+    fi
+
+    mock_privileged_conf "$chroot" "$@" <<<"
+config_opts['yum.conf'] += '''
+[sssd-deps]
+name=Extra SSSD dependencies
+baseurl=http://copr-be.cloud.fedoraproject.org/results/lslebodn/sssd-deps/$repo/
+skip_if_unavailable=true
+gpgcheck=0
+enabled=1
+'''
+"
+}
+
+# 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_deps "epel-6-$ARCH" \
+                                     --resultdir ci-mock-result-epel6 \
+                                     rpmbuild/SRPMS/*.src.rpm
+            stage mock-fedora20 mock_privileged_deps "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:nemrf \
+                  --longoptions help,prefix:,no-deps \
+                  --longoptions essential,moderate,rigorous,full \
+                  -- "$@"`
+eval set -- "$args_expr"
+
+while true; do
+    case "$1" in
+        -h|--help)
+            usage;          exit 0;;
+        -p|--prefix)
+            BASE_PFX="$2";  shift 2;;
+        -n|--no-deps)
+            DEPS=false;     shift;;
+        -e|--essential)
+            MODERATE=false; RIGOROUS=false; shift;;
+        -m|--moderate)
+            MODERATE=true;  RIGOROUS=false; shift;;
+        -r|--rigorous|-f|--full)
+            MODERATE=true;  RIGOROUS=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
+if "$DEPS"; then
+    stage install-deps  deps_install
+fi
+stage autoreconf    autoreconf --install --force
+run_build debug     build_debug
+if "$RIGOROUS"; 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.1

