#!/usr/bin/env bash
if [ -n "${PLAIN_OUTPUT}" ]; then
TC_GREEN=""
TC_RED=""
TC_CYAN=""
TC_RESET=""
else
TC_GREEN="\033[32m"
TC_RED="\033[31m"
TC_CYAN="\033[36m"
TC_RESET="\033[0m"
fi
function log_failed(){
printf "${TC_RED}[FAILED] %s\n${TC_RESET}" "$1"
}
function log_info(){
printf "${TC_CYAN}[INFO] %s\n${TC_RESET}" "$1"
}
function log_error(){
printf "${TC_RED}[ERROR] %s\n${TC_RESET}" "$1"
}
[[ "${DEBUG}" == "true" ]] && set -x
SCRIPT_PATH="`dirname \"$0\"`"
SCRIPT_PATH="`( cd \"${SCRIPT_PATH}\" && pwd )`"
echo 'Script path: '${SCRIPT_PATH}
if [ -z "${BEHAT_BIN}" ]
then
BEHAT=${SCRIPT_PATH}/../../../vendor-bin/behat/vendor/bin/behat
else
BEHAT=${BEHAT_BIN}
fi
BEHAT_TAGS_OPTION_FOUND=false
if [ -n "${STEP_THROUGH}" ]
then
STEP_THROUGH_OPTION="--step-through"
fi
if [ -n "${STOP_ON_FAILURE}" ]
then
STOP_OPTION="--stop-on-failure"
fi
if [ -n "${PLAIN_OUTPUT}" ]
then
COLORS_OPTION="--no-colors"
else
COLORS_OPTION="--colors"
fi
if [ -n "${EXPECTED_FAILURES_FILE}" ]
then
${SCRIPT_PATH}/lint-expected-failures.sh
LINT_STATUS=$?
if [ ${LINT_STATUS} -ne 0 ]
then
log_error "Expected failures file ${EXPECTED_FAILURES_FILE} is invalid"
exit ${LINT_STATUS}
fi
fi
if [ -z "${ACCEPTANCE_TEST_TYPE}" ]
then
ACCEPTANCE_TEST_TYPE="api"
fi
while [[ $# -gt 0 ]]
do
key="$1"
case ${key} in
-c|--config)
BEHAT_YML="$2"
shift
;;
--feature)
BEHAT_FEATURE="$2"
shift
;;
--suite)
BEHAT_SUITE="$2"
shift
;;
--loop)
BEHAT_RERUN_TIMES="$2"
shift
;;
--type)
ACCEPTANCE_TEST_TYPE="${2,,}"
shift
;;
--tags)
BEHAT_FILTER_TAGS="$2"
BEHAT_TAGS_OPTION_FOUND=true
shift
;;
--part)
RUN_PART="$2"
DIVIDE_INTO_NUM_PARTS="$3"
if [ ${RUN_PART} -gt ${DIVIDE_INTO_NUM_PARTS} ]
then
echo "cannot run part ${RUN_PART} of ${DIVIDE_INTO_NUM_PARTS}"
exit 1
fi
shift 2
;;
--step-through)
STEP_THROUGH_OPTION="--step-through"
;;
*)
BEHAT_FEATURE="$1"
;;
esac
shift
done
export LANG=C
if [ -z "${ADMIN_USERNAME}" ]
then
ADMIN_USERNAME="admin"
fi
if [ -z "${ADMIN_PASSWORD}" ]
then
ADMIN_PASSWORD="admin"
fi
export ADMIN_USERNAME
export ADMIN_PASSWORD
if [ -z "${BEHAT_RERUN_TIMES}" ]
then
BEHAT_RERUN_TIMES=1
fi
if [ -z "${BEHAT_FLAKY_RERUN_TIMES}" ]
then
BEHAT_FLAKY_RERUN_TIMES=1
fi
declare -a UNEXPECTED_FAILED_SCENARIOS
declare -a UNEXPECTED_PASSED_SCENARIOS
declare -a UNEXPECTED_BEHAT_EXIT_STATUSES
declare -a ALL_FAILED_SCENARIO_PATHS
function run_behat_tests() {
echo "Running ${SUITE_FEATURE_TEXT} tests tagged ${BEHAT_FILTER_TAGS}" | tee ${TEST_LOG_FILE}
if [ "${REPLACE_USERNAMES}" == "true" ]
then
echo "Usernames and attributes in tests are being replaced:"
cat ${SCRIPT_PATH}/usernames.json
fi
echo "Using behat config '${BEHAT_YML}'"
${BEHAT} ${COLORS_OPTION} ${STOP_OPTION} --strict ${STEP_THROUGH_OPTION} -c ${BEHAT_YML} -f pretty ${BEHAT_SUITE_OPTION} --tags ${BEHAT_FILTER_TAGS} ${BEHAT_FEATURE} -v 2>&1 | tee -a ${TEST_LOG_FILE}
BEHAT_EXIT_STATUS=${PIPESTATUS[0]}
sed -i 's/\x0//g' ${TEST_LOG_FILE}
SCENARIO_RESULTS_COLORED=`grep -Ea '^[0-9]+[[:space:]]scenario(|s)[[:space:]]\(' ${TEST_LOG_FILE}`
SCENARIO_RESULTS=$(echo "${SCENARIO_RESULTS_COLORED}" | sed "s/\x1b[^m]*m//g")
if [ ${BEHAT_EXIT_STATUS} -eq 0 ]
then
[[ ${SCENARIO_RESULTS} =~ ([0-9]+) ]]
SCENARIOS_THAT_PASSED=$((SCENARIOS_THAT_PASSED + BASH_REMATCH[1]))
else
if [[ ${SCENARIO_RESULTS} == *"undefined"* ]]
then
log_error "Undefined steps: There were some undefined steps found."
exit 1
fi
MATCHING_COUNT=`grep -ca '^No scenarios$' ${TEST_LOG_FILE}`
if [ ${MATCHING_COUNT} -eq 1 ]
then
log_info "No matching scenarios were found."
BEHAT_EXIT_STATUS=0
else
SCENARIO_RESULTS_COLORED=`grep -Ea '^[0-9]+[[:space:]]scenario(|s)[[:space:]]\(' ${TEST_LOG_FILE}`
SCENARIO_RESULTS=$(echo "${SCENARIO_RESULTS_COLORED}" | sed "s/\x1b[^m]*m//g")
if [[ ${SCENARIO_RESULTS} =~ [0-9]+[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+ ]]
then
SCENARIOS_THAT_PASSED=$((SCENARIOS_THAT_PASSED + BASH_REMATCH[1]))
SCENARIOS_THAT_FAILED=$((SCENARIOS_THAT_FAILED + BASH_REMATCH[2]))
elif [[ ${SCENARIO_RESULTS} =~ [0-9]+[^0-9]+([0-9]+)[^0-9]+ ]]
then
SCENARIOS_THAT_FAILED=$((SCENARIOS_THAT_FAILED + BASH_REMATCH[1]))
fi
fi
fi
FAILED_SCENARIO_PATHS_COLORED=`awk '/Failed (scenarios|hooks):/',0 ${TEST_LOG_FILE} | grep -a feature`
FAILED_SCENARIO_PATHS=$(echo "${FAILED_SCENARIO_PATHS_COLORED}" | sed "s/\x1b[^m]*m//g" | sed "s/AfterScenario \"//g" | sed 's/\(\.feature:[0-9]\+\).*/\1/')
FAILED_SCENARIO_PATHS=$(echo "$FAILED_SCENARIO_PATHS" | awk '!seen[$0]++')
if [ -z "${FAILED_SCENARIO_PATHS}" ]
then
unset FAILED_SCENARIO_PATHS
else
for FAILED_SCENARIO_PATH in ${FAILED_SCENARIO_PATHS}
do
ALL_FAILED_SCENARIO_PATHS+=("${FAILED_SCENARIO_PATH}")
done
fi
if [ -n "${EXPECTED_FAILURES_FILE}" ]
then
if [ -n "${BEHAT_SUITE_TO_RUN}" ]
then
log_info "Checking expected failures for suite: ${BEHAT_SUITE_TO_RUN}"
else
log_info "Checking expected failures..."
fi
for FAILED_SCENARIO_PATH in ${FAILED_SCENARIO_PATHS}
do
SUITE_PATH=`dirname ${FAILED_SCENARIO_PATH}`
SUITE=`basename ${SUITE_PATH}`
SCENARIO=`basename ${FAILED_SCENARIO_PATH}`
SUITE_SCENARIO="${SUITE}/${SCENARIO}"
grep "\[${SUITE_SCENARIO}\]" "${EXPECTED_FAILURES_FILE}" > /dev/null
if [ $? -ne 0 ]
then
log_error "Scenario ${SUITE_SCENARIO} failed but was not expected to fail."
UNEXPECTED_FAILED_SCENARIOS+=("${SUITE_SCENARIO}")
fi
done
while read SUITE_SCENARIO
do
if [[ "${SUITE_SCENARIO}" =~ ^# ]]
then
continue
fi
if [[ "${SUITE_SCENARIO}" =~ \[([a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.feature:[0-9]+)] ]]; then
SUITE_SCENARIO="${BASH_REMATCH[1]}"
else
continue
fi
if [ -n "${BEHAT_SUITE_TO_RUN}" ]
then
REGEX_TO_MATCH="^${BEHAT_SUITE_TO_RUN}/"
if ! [[ "${SUITE_SCENARIO}" =~ ${REGEX_TO_MATCH} ]]
then
continue
fi
fi
echo "${FAILED_SCENARIO_PATHS}" | grep ${SUITE_SCENARIO}$ > /dev/null
if [ $? -ne 0 ]
then
log_error "Scenario ${SUITE_SCENARIO} was expected to fail but did not fail."
UNEXPECTED_PASSED_SCENARIOS+=("${SUITE_SCENARIO}")
fi
done < ${EXPECTED_FAILURES_FILE}
else
for FAILED_SCENARIO_PATH in ${FAILED_SCENARIO_PATHS}
do
SUITE_PATH=$(dirname "${FAILED_SCENARIO_PATH}")
SUITE=$(basename "${SUITE_PATH}")
SCENARIO=$(basename "${FAILED_SCENARIO_PATH}")
SUITE_SCENARIO="${SUITE}/${SCENARIO}"
UNEXPECTED_FAILED_SCENARIOS+=("${SUITE_SCENARIO}")
done
fi
if [ ${BEHAT_EXIT_STATUS} -ne 0 ] && [ ${#FAILED_SCENARIO_PATHS[@]} -eq 0 ]
then
UNEXPECTED_BEHAT_EXIT_STATUSES+=("${SUITE_FEATURE_TEXT} had behat exit status ${BEHAT_EXIT_STATUS}")
fi
if [ "${BEHAT_TAGS_OPTION_FOUND}" != true ]
then
DRY_RUN_FILE=$(mktemp)
SKIP_TAGS="@skip"
${BEHAT} --dry-run {$COLORS_OPTION} -c ${BEHAT_YML} -f pretty ${BEHAT_SUITE_OPTION} --tags "${SKIP_TAGS}" ${BEHAT_FEATURE} 1>${DRY_RUN_FILE} 2>/dev/null
if grep -q -m 1 'No scenarios' "${DRY_RUN_FILE}"
then
:
else
echo ""
log_info "The following tests were skipped because they are tagged @skip:"
cat "${DRY_RUN_FILE}" | tee -a ${TEST_LOG_FILE}
fi
rm -f "${DRY_RUN_FILE}"
fi
}
function rerun_failed_flaky_scenarios() {
if [ ${BEHAT_FLAKY_RERUN_TIMES} -lt 1 ]
then
return
fi
if [ ${#UNEXPECTED_FAILED_SCENARIOS[@]} -eq 0 ] || [ ${#ALL_FAILED_SCENARIO_PATHS[@]} -eq 0 ]
then
return
fi
local recovered=()
for FEATURE_PATH in "${ALL_FAILED_SCENARIO_PATHS[@]}"
do
local suite scenario key
suite=$(basename "$(dirname "${FEATURE_PATH}")")
scenario=$(basename "${FEATURE_PATH}")
key="${suite}/${scenario}"
local is_unexpected=false
for uf in "${UNEXPECTED_FAILED_SCENARIOS[@]}"
do
if [ "${uf}" = "${key}" ]
then
is_unexpected=true
break
fi
done
if [ "${is_unexpected}" != true ]
then
continue
fi
local dry_run_file
dry_run_file=$(mktemp)
${BEHAT} --dry-run --no-colors -c ${BEHAT_YML} -f pretty --tags '@flaky' "${FEATURE_PATH}" 1>${dry_run_file} 2>/dev/null
if grep -q -m 1 'No scenarios' "${dry_run_file}"
then
rm -f "${dry_run_file}"
continue
fi
rm -f "${dry_run_file}"
log_info "Scenario ${key} is tagged @flaky, retrying up to ${BEHAT_FLAKY_RERUN_TIMES} times"
local passed=false
for attempt in $(seq 1 ${BEHAT_FLAKY_RERUN_TIMES})
do
echo -e "\nFlaky retry attempt ${attempt} of ${BEHAT_FLAKY_RERUN_TIMES}: ${key}"
${BEHAT} ${COLORS_OPTION} --strict -c ${BEHAT_YML} -f pretty --tags '@flaky' "${FEATURE_PATH}" -v 2>&1 | tee -a ${TEST_LOG_FILE}
if [ ${PIPESTATUS[0]} -eq 0 ]
then
passed=true
break
fi
done
if [ "${passed}" = true ]
then
log_info "Flaky scenario ${key} passed on retry, not counting it as a failure."
recovered+=("${key}")
else
log_failed "Flaky scenario ${key} still failing after ${BEHAT_FLAKY_RERUN_TIMES} retries."
fi
done
if [ ${#recovered[@]} -eq 0 ]
then
return
fi
local remaining=()
for uf in "${UNEXPECTED_FAILED_SCENARIOS[@]}"
do
local drop=false
for r in "${recovered[@]}"
do
if [ "${uf}" = "${r}" ]
then
drop=true
break
fi
done
if [ "${drop}" != true ]
then
remaining+=("${uf}")
fi
done
UNEXPECTED_FAILED_SCENARIOS=("${remaining[@]}")
}
declare -x TEST_SERVER_URL
if [ -z "${IPV4_URL}" ]
then
IPV4_URL="${TEST_SERVER_URL}"
fi
if [ -z "${IPV6_URL}" ]
then
IPV6_URL="${TEST_SERVER_URL}"
fi
if [ -n "${BEHAT_FEATURE}" ] && [ -z "${BEHAT_SUITE}" ]
then
SUITE_PATH=`dirname ${BEHAT_FEATURE}`
BEHAT_SUITE=`basename ${SUITE_PATH}`
fi
if [ -z "${BEHAT_YML}" ]
then
BEHAT_YML="config/behat.yml"
if [ ! -f "${BEHAT_YML}" ]
then
BEHAT_YML="acceptance/config/behat.yml"
fi
if [ ! -f "${BEHAT_YML}" ]
then
BEHAT_YML="tests/acceptance/config/behat.yml"
fi
if [ ! -f "${BEHAT_YML}" ]
then
BEHAT_YML="${SCRIPT_PATH}/config/behat.yml"
fi
fi
BEHAT_CONFIG_DIR=$(dirname "${BEHAT_YML}")
ACCEPTANCE_DIR=$(dirname "${BEHAT_CONFIG_DIR}")
BEHAT_FEATURES_DIR="${ACCEPTANCE_DIR}/features"
declare -a BEHAT_SUITES
function get_behat_suites() {
TYPE="$1"
if [[ -z "$TYPE" ]]
then
TYPE="api"
fi
ALL_SUITES=`find ${BEHAT_FEATURES_DIR}/ -type d -iname ${TYPE}* | sort | rev | cut -d"/" -f1 | rev`
COUNT_ALL_SUITES=`echo "${ALL_SUITES}" | wc -l`
MIN_SUITES_PER_RUN=$((${COUNT_ALL_SUITES} / ${DIVIDE_INTO_NUM_PARTS}))
MAX_SUITES_PER_RUN=$((${MIN_SUITES_PER_RUN} + 1))
REMAINING_SUITES=$((${COUNT_ALL_SUITES} - (${DIVIDE_INTO_NUM_PARTS} * ${MIN_SUITES_PER_RUN})))
if [[ ${RUN_PART} -le ${REMAINING_SUITES} ]]
then
SUITES_THIS_RUN=${MAX_SUITES_PER_RUN}
SUITES_IN_PREVIOUS_RUNS=$((${MAX_SUITES_PER_RUN} * (${RUN_PART} - 1)))
else
SUITES_THIS_RUN=${MIN_SUITES_PER_RUN}
SUITES_IN_PREVIOUS_RUNS=$((((${MAX_SUITES_PER_RUN} * ${REMAINING_SUITES}) + (${MIN_SUITES_PER_RUN} * (${RUN_PART} - ${REMAINING_SUITES} - 1)))))
fi
if [ ${SUITES_THIS_RUN} -eq 0 ]
then
echo "there are only ${COUNT_ALL_SUITES} suites, nothing to do in part ${RUN_PART}"
exit 0
fi
COUNT_FINISH_AND_TODO_SUITES=$((${SUITES_IN_PREVIOUS_RUNS} + ${SUITES_THIS_RUN}))
BEHAT_SUITES+=(`echo "${ALL_SUITES}" | head -n ${COUNT_FINISH_AND_TODO_SUITES} | tail -n ${SUITES_THIS_RUN}`)
}
if [[ -n "${BEHAT_SUITE}" ]]
then
BEHAT_SUITES+=("${BEHAT_SUITE}")
else
if [[ -n "${RUN_PART}" ]]; then
if [[ "${ACCEPTANCE_TEST_TYPE}" == "core-api" ]]; then
get_behat_suites "core"
else
get_behat_suites "${ACCEPTANCE_TEST_TYPE}"
fi
else
BEHAT_SUITES=(`echo "$BEHAT_SUITES" | tr "," "\n"`)
fi
fi
TEST_TYPE_TEXT="API"
if [ ! -z "${BEHAT_FILTER_TAGS}" ]
then
BEHAT_FILTER_TAGS="${BEHAT_FILTER_TAGS%&&}"
BEHAT_FILTER_TAGS="${BEHAT_FILTER_TAGS#&&}"
fi
if [ -z "${EMAIL_HOST}" ]
then
EMAIL_HOST="127.0.0.1"
fi
if [ -z "${LOCAL_EMAIL_HOST}" ]
then
LOCAL_EMAIL_HOST="${EMAIL_HOST}"
fi
if [ -z "${EMAIL_SMTP_PORT}" ]
then
EMAIL_SMTP_PORT="2500"
fi
if [ "${BEHAT_TAGS_OPTION_FOUND}" = false ]
then
if [[ -z $BEHAT_FILTER_TAGS ]]
then
BEHAT_FILTER_TAGS="~@skip"
elif [[ ! ${BEHAT_FILTER_TAGS} =~ (^|&)@skip(&|$) ]]
then
BEHAT_FILTER_TAGS="${BEHAT_FILTER_TAGS}&&~@skip"
fi
fi
export IPV4_URL
export IPV6_URL
TEST_LOG_FILE=$(mktemp)
SCENARIOS_THAT_PASSED=0
SCENARIOS_THAT_FAILED=0
if [ ${#BEHAT_SUITES[@]} -eq 0 ] && [ -z "${BEHAT_FEATURE}" ]
then
SUITE_FEATURE_TEXT="all ${TEST_TYPE_TEXT}"
run_behat_tests
else
if [ -n "${BEHAT_SUITE}" ]
then
SUITE_FEATURE_TEXT="${BEHAT_SUITE}"
fi
if [ -n "${BEHAT_FEATURE}" ]
then
BEHAT_FEATURE_FILE=`basename ${BEHAT_FEATURE}`
SUITE_FEATURE_TEXT="${SUITE_FEATURE_TEXT} ${BEHAT_FEATURE_FILE}"
fi
fi
for i in "${!BEHAT_SUITES[@]}"
do
BEHAT_SUITE_TO_RUN="${BEHAT_SUITES[$i]}"
BEHAT_SUITE_OPTION="--suite=${BEHAT_SUITE_TO_RUN}"
SUITE_FEATURE_TEXT="${BEHAT_SUITES[$i]}"
for rerun_number in $(seq 1 ${BEHAT_RERUN_TIMES})
do
if ((${BEHAT_RERUN_TIMES} > 1))
then
echo -e "\nTest repeat $rerun_number of ${BEHAT_RERUN_TIMES}"
fi
run_behat_tests
done
done
rerun_failed_flaky_scenarios
if [ ${#UNEXPECTED_FAILED_SCENARIOS[@]} -gt 0 ]
then
UNEXPECTED_FAILURE=true
else
UNEXPECTED_FAILURE=false
fi
if [ ${#UNEXPECTED_PASSED_SCENARIOS[@]} -gt 0 ]
then
UNEXPECTED_SUCCESS=true
else
UNEXPECTED_SUCCESS=false
fi
if [ ${#UNEXPECTED_BEHAT_EXIT_STATUSES[@]} -gt 0 ]
then
UNEXPECTED_BEHAT_EXIT_STATUS=true
else
UNEXPECTED_BEHAT_EXIT_STATUS=false
fi
if [ "${UNEXPECTED_SUCCESS}" = true ]
then
ACTUAL_UNEXPECTED_PASS=()
if [[ -n "${BEHAT_FEATURE}" ]]
then
for unexpected_passed_value in "${UNEXPECTED_PASSED_SCENARIOS[@]}"
do
if [[ $BEHAT_FEATURE == *":"* ]]
then
BEHAT_FEATURE_WITH_LINE_NUM=$BEHAT_FEATURE
else
LINE_NUM=$(echo ${unexpected_passed_value} | cut -d":" -f2)
BEHAT_FEATURE_WITH_LINE_NUM=$BEHAT_FEATURE:$LINE_NUM
fi
if [[ $BEHAT_FEATURE_WITH_LINE_NUM == *"${unexpected_passed_value}" ]]
then
ACTUAL_UNEXPECTED_PASS+=("${unexpected_passed_value}")
fi
done
else
ACTUAL_UNEXPECTED_PASS=("${UNEXPECTED_PASSED_SCENARIOS[@]}")
fi
if [ ${#ACTUAL_UNEXPECTED_PASS[@]} -eq 0 ]
then
UNEXPECTED_SUCCESS=false
fi
fi
if [ "${UNEXPECTED_FAILURE}" = false ] && [ "${UNEXPECTED_SUCCESS}" = false ] && [ "${UNEXPECTED_BEHAT_EXIT_STATUS}" = false ]
then
FINAL_EXIT_STATUS=0
else
FINAL_EXIT_STATUS=1
fi
if [ -n "${EXPECTED_FAILURES_FILE}" ]
then
log_info "Exit code after checking expected failures: ${FINAL_EXIT_STATUS}"
fi
if [ "${UNEXPECTED_FAILURE}" = true ]
then
log_failed "Total unexpected failed scenarios:"
printf "${TC_RED}- %s\n${TC_RESET}" "${UNEXPECTED_FAILED_SCENARIOS[@]}"
echo ""
else
log_info "There were no unexpected failures."
fi
if [ "${UNEXPECTED_SUCCESS}" = true ]
then
log_error "Total unexpected passed scenarios:"
printf "${TC_GREEN}- %s\n${TC_RESET}" "${ACTUAL_UNEXPECTED_PASS[@]}"
echo ""
else
log_info "There were no unexpected success."
fi
if [ "${UNEXPECTED_BEHAT_EXIT_STATUS}" = true ]
then
log_error "The following Behat test runs exited with non-zero status:"
printf "${TC_RED}%s\n${TC_RESET}" "${UNEXPECTED_BEHAT_EXIT_STATUSES[@]}"
fi
TOTAL_SCENARIOS=$((SCENARIOS_THAT_PASSED + SCENARIOS_THAT_FAILED))
printf "Summary: %s scenarios (${TC_GREEN}%s passed${TC_RESET}, ${TC_RED}%s failed${TC_RESET})" "${TOTAL_SCENARIOS}" "${SCENARIOS_THAT_PASSED}" "${SCENARIOS_THAT_FAILED}"
echo ""
exit ${FINAL_EXIT_STATUS}