blob: b873501f8f13b1e94378b6a1699193bce017732e [file]
#!/bin/sh -efu
# Copyright 2011-2012 Intel Corporation
# Author: Artem Bityutskiy
# License: GPLv2
srcdir="$(readlink -ev -- ${0%/*})"
PATH="$srcdir:$srcdir/..:$srcdir/../helpers:$srcdir/../helpers/libshell:$PATH"
. shell-error
. shell-args
. shell-signal
. aiaiai-sh-functions
. aiaiai-email-sh-functions
PROG="${0##*/}"
export message_time="yes"
# This is a small trick to make sure the script is portable - check if 'dash'
# is present, and if yes - use it.
if can_switch_to_dash; then
exec dash -euf -- "$srcdir/$PROG" "$@"
exit $?
fi
show_usage()
{
cat <<-EOF
Usage: $PROG [options] <cfgfile.ini>
The mbox file containing the patches to test is expected to come from stdin
(unless --input option is specified).
<cfgfile.ini> - the configuration file.
Options:
-i, --input=MBOX use the MBOX file instead of stdin;
-v, --verbose be verbose;
-h, --help show this text and exit.
EOF
}
fail_usage()
{
[ -z "$1" ] || printf "%s\n" "$1"
show_usage
exit 1
}
verbose=
mbox=
tmpdir=
cleanup_handler()
{
rm $verbose -rf -- "$mbox" >&2
if [ "$cfg_preserve_files" = "1" ]; then
verbose "Preserved tmpdir: $tmpdir"
else
[ -z "$tmpdir" ] || verbose "Removing $tmpdir";
rm -rf -- "$tmpdir" >&2
fi
}
set_cleanup_handler cleanup_handler
# List currently supported projects.
list_projects()
{
local prj
get_cfgfile_projects_list "$cfgfile" | \
while read -r prj; do
# Get project description
local descr
ini_config_get_or_die descr "$cfgfile" "prj_$prj" "description"
local email="$cfg_ownmail_local+$prj@$cfg_ownmail_domain"
# If given a url, display a sample clone line in the project
url="$(ini_config_get "$cfgfile" "prj_$prj" "canonical_url")"
branch="$(ini_config_get "$cfgfile" "prj_$prj" "branch")"
if [ -n "$url" ]; then
printf "* %s\n" "$prj ($email) [git clone -b $branch $url]: $descr"
else
printf "* %s\n" "$prj ($email): $descr"
fi
done
}
# Send an e-mail reply to the patch submitter.
send_email()
{
compose_email "$reply_to" "$reply_cc" "$reply_subj" "$reply_id" \
> "$tmpdir/mail"
[ -z "$verbose" ] || cat -- "$tmpdir/mail" >&2
if [ "$cfg_disable_notifications" != "1" ]; then
mutt -x -H "$tmpdir/mail" </dev/null
else
verbose "Email nofications have been disabled in the configuration file"
fi
}
# This function is called when the target project for the patch under test was
# not specified. It sends a sensible reply back to the submitter, without
# carbon-copying anyone else.
error_no_project_specified()
{
send_email <<EOF
Sorry, but you have not specified the project name. Please, specify it
using the "+" symbol in the e-mail address of $cfg_ownname. For example,
"$cfg_ownmail_local+XYZ@$cfg_ownmail_domain" would mean project "XYZ".
List of projects $cfg_ownname supports:
$(list_projects)
Please, contact "$cfg_adminname" <$cfg_adminmail>
if you have any questions.
EOF
exit 0
}
# This function is called when a hook has requested discarding a patch, and
# should be passed the main body content from the hook output as stdin.
error_hook_rejected_patch()
{
reply_cc="$(merge_addresses "$reply_cc" "$cfg_adminmail")"
send_email <<EOF
$(cat)
List of projects $cfg_ownname supports:
$(list_projects)
Please, contact "$cfg_adminname" <$cfg_adminmail>
if you have any questions.
EOF
exit 0
}
# This function is called when the patch submitter specifies a non-existing
# project. It sends a sensible reply back, without carbon-copying anyone else.
error_project_not_found()
{
send_email <<EOF
Sorry, but project "$prj" is not supported. List of projects $cfg_ownname
currently supports:
$(list_projects)
Please, contact "$cfg_adminname" <$cfg_adminmail>
if you have any questions.
EOF
exit 0
}
# This function is called when an internal error occurs, such as when
# aiaiai-test-patchset fails. This most probably means a bug or configuration
# issue occurred. This function sends a corresponding email notification.
error_internal_error_occurred()
{
send_email <<EOF
Sorry, but an internal $cfg_ownname error happened. Please, contact
"$cfg_adminname" <$cfg_adminmail>.
EOF
exit 0
}
# This is a helper function which sends a notification about the patch under
# test being accepted for testing.
send_accepted_email()
{
send_email <<EOF
Your patch or patch-set:
$(fetch_header_per_patch "Subject" < "$mbox" | sort)
has been accepted by $cfg_ownname and scheduled for testing.
EOF
}
# This is a helper function which sends the reply e-mail with the results of
# testing.
send_results_email()
{
send_email <<EOF
$cfg_built_preamble
$(fetch_header_per_patch "Subject" < "$mbox" | sort)
Project: $pcfg_name ($pcfg_description)
Configurations: $pcfg_configs
$(cat -- $tmpdir/test-patchset.log)
EOF
}
TEMP=`getopt -n $PROG -o i:,C:,p,v,h --long input:,verbose,help -- "$@"` ||
fail_usage ""
eval set -- "$TEMP"
mbox=
while true; do
case "$1" in
-i|--input)
mbox="$(opt_check_read "$1" "$2")"
shift
;;
-v|--verbose) verbose=-v
;;
-h|--help)
show_usage
exit 0
;;
--) shift; break
;;
*) fail_usage "Unrecognized option: $1"
;;
esac
shift
done
[ "$#" -eq 1 ] || fail_usage "Insufficient or too many arguments"
program_required "mutt" ""
program_required "grep" ""
program_required "sed" ""
program_required "formail" ""
cfgfile="$(readlink -fv -- "$1")"; shift
parse_config "$cfgfile"
# Save the mbox to a temporary file if it comes from stdin
if [ -z "$mbox" ]; then
mbox="$(mktemp -t "$PROG.mbox.XXXX")"
cat > "$mbox"
fi
# Use the cover letter subject and message ID if possible. If the cover letter
# was present, 'aiaiai-email-lda' would save them in special private headers.
subj="$(fetch_header "X-Aiaiai-Cover-Letter-Subject" < "$mbox")"
[ -n "$subj" ] || fetch_header_or_die subj "Subject" < "$mbox"
id="$(fetch_header "X-Aiaiai-Cover-Letter-Message-Id" < "$mbox")"
[ -n "$id" ] || fetch_header_or_die id "Message-Id" < "$mbox"
fetch_header_or_die from "From" < "$mbox"
to="$(fetch_all_headers "To" < "$mbox")"
cc="$(fetch_all_headers "Cc" < "$mbox")"
# If there are multiple "To:" or "Cc:" headers in the first patch, we need to
# merge them into a single comma-separated list of addresses.
to="$(merge_addresses "$to")"
cc="$(merge_addresses "$cc")"
# Either "To:" or "Cc:" must exist
if [ -z "$to" ] && [ -z "$cc" ]; then
die "Neither \"To:\" nor \"Cc:\" header found"
fi
printf "\n"
verbose "Testing mbox: \"$from: $subj (Message-Id: $id)\""
verbose "parsing config file \"$cfgfile\""
mkdir $verbose -p -- "$cfg_workdir" >&2
tmpdir="$(mktemp --tmpdir="$cfg_workdir" -dt "$PROG.XXXX")"
mv $verbose -- "$mbox" "$tmpdir/mbox" >&2
mbox="$tmpdir/mbox"
hookoutput="$tmpdir/hook"
touch -- "$hookoutput"
# Replies will refer the first patch of the patch-set under test
reply_subj="$subj"
reply_id="$id"
# Replies will be sent to the patch submitter
reply_to="$from"
# And do not Cc anyone thus far
reply_cc=
# Run the aiaiai email hook. Output is stored in $hookoutput and we can parse
# this via fetch_header similar to how we parse the mbox.
hookscript="$(readlink -ev -- $cfg_email_hook)"
if [ -f "$hookscript" ] && [ -x "$hookscript" ]; then
# Hook points to an executable file, so we run it
verbose "Executing \"$hookscript\""
# Grab the error code here, using an || section to prevent exit on
# command failure. Otherwise, the non-zero exit code from the hook
# script would crash aiaiai-email-test-patchset
hookret="0"
"$hookscript" "$cfgfile" "$mbox" > "$hookoutput" || hookret="$?"
# Error code 127 is an expected output of the hook, and
# indicates that we should reject this patch. The reply email
# will be sent to the user, and the hook is expected to have
# outputted the rejection indication. As a precaution, the
# rejection email will include a list of projects supported.
if [ "$hookret" -eq "127" ]; then
error_hook_rejected_patch < "$hookoutput"
elif [ "$hookret" -ne "0" ]; then
verbose "Hook exited with error code \"$hookret\"..."
error_internal_error_occurred
fi
fi
# Find out the project name
prj="$(fetch_header "X-Aiaiai-Project" < "$hookoutput")"
[ -n "$prj" ] || prj="$(fetch_project_name "$to" "$cfg_ownmail")"
verbose "Project \"$prj\""
# Reject the e-mail if the project has not been specified
if [ -z "$prj" ]; then
error_no_project_specified
fi
# Get the project configuration
parse_prj_config "$cfgfile" "$prj"
# Check if the project specified by the submitter exists
if [ -z "$pcfg_name" ]; then
error_project_not_found
fi
bisectability=
sparse=
smatch=
cppcheck=
coccinelle=
checkpatch= # Note, checkpatch is enabled by default
[ "$pcfg_bisectability" != "1" ] || bisectability="--bisectability"
[ "$pcfg_sparse" != "1" ] || sparse="--sparse"
[ "$pcfg_smatch" != "1" ] || smatch="--smatch"
[ "$pcfg_cppcheck" != "1" ] || cppcheck="--cppcheck"
[ "$pcfg_coccinelle" != "1" ] || coccinelle="--coccinelle"
[ "$pcfg_checkpatch" = "1" ] || checkpatch="--nocheckpatch"
# Create the Cc list for replies that we'll be sending
if [ "$pcfg_reply_to_all" = "1" ]; then
# All the patch recipients will be CCed
reply_cc="$(merge_addresses "$to" "$cc" "$pcfg_always_cc")"
reply_cc=$(strip_address "$reply_cc" "$cfg_ownmail")
else
reply_cc="$pcfg_always_cc"
fi
# Notify the sender that the patches have been accepted
if [ "$pcfg_accept_notify" = "1" ]; then
verbose "Sending \"accepted\" e-mail"
send_accepted_email
fi
# Use the supplied commit from the hook or default to branch head
commit="$(fetch_header "X-Aiaiai-Commit" < "$hookoutput")"
[ -n "$commit" ] || commit="$pcfg_branch"
# Test the path (or patch-set)
verbose "Test configs \"$pcfg_configs\" branch \"$pcfg_branch\" of \"$pcfg_path\""
aiaiai-test-patchset $verbose ${cfg_preserve_files:+--preserve} \
${pcfg_targets:+--targets "$pcfg_targets"} $bisectability \
$sparse $smatch $cppcheck $coccinelle $checkpatch \
-i "$mbox" -j "$cfg_jobs" -c "$commit" -w "$tmpdir" \
${pcfg_defconfigdir:+-C "$pcfg_defconfigdir"} \
${pcfg_unwanted_keywords:+-K "$pcfg_unwanted_keywords"} \
${pcfg_kmake_opts:+-M "$pcfg_kmake_opts"} -- \
"$pcfg_path" "$pcfg_configs" > "$tmpdir/test-patchset.log" ||
{
verbose "aiaiai-test-patchset failed"
error_internal_error_occurred
}
# Mail the results of testing
verbose "Test is finished, sending back the results"
send_results_email