#!/usr/bin/env bash if [ "${BASH_SOURCE[0]}" -ef "$0" ]then echo "This script should not be directly executed!" exit 1fi # A declaration of parameters and their default valuesdeclare -A ARGS=( ["profile"]=zutty ["step"]=no # N.B.: --step=new will only stop at new snaps! ["update-sig"]=no ["ci-mode"]=no ["debug"]=no) # Include argument handlersource "testargs.sh" PROFILE=${ARGS["profile"]}STEP_MODE=${ARGS["step"]}UPDATE_SIGNATURE=${ARGS["update-sig"]}if [ ${PROFILE} == "zutty" ] ; then VERIFY_SNAPS=yeselse VERIFY_SNAPS=optional IMAGE_DIFF_TO=zuttyfiif [ ${ARGS["ci-mode"]} == "yes" ] ; then STEP_MODE=no UPDATE_SIGNATURE=no EXIT_ON_FAILURE=yeselse EXIT_ON_FAILURE=nofiif [ ${ARGS["debug"]} == "yes" ] ; then DEBUG=yeselse DEBUG=nofi PLATFORM=$(uname -s) EXIT_CODE=0 export TEST_NAME=$(basename -s .sh "$0") export RED="\\e[1;31m"export GREEN="\\e[1;32m"export YELLOW="\\e[1;33m"export WHITE="\\e[1;37m"export DFLT="\\e[0;39m"export ERASE_PROMPT="\\eM\\e[J" function print_error { printf "${RED}ERROR: $@${DFLT}\n"} function CHECK_DEPS { TOOLS="$@"; shift MISSING=0 for t in ${TOOLS} ; do which $t >/dev/null if [ $? -gt 0 ] ; then print_error "Missing dependency: $t" MISSING=1 fi done if [ $MISSING -gt 0 ] ; then exit 1 fi} function CHECK_FILES { FILES="$@"; shift MISSING=0 for f in ${FILES} ; do if [ ! -f "$f" ] ; then print_error "Missing file: $f" MISSING=1 fi done if [ $MISSING -gt 0 ] ; then exit 1 fi} function CHECK_EXE { FILES="$@"; shift MISSING=0 for f in ${FILES} ; do if [ ! -x "$f" ] ; then print_error "Missing executable: $f" MISSING=1 fi done if [ $MISSING -gt 0 ] ; then exit 1 fi} if [ ! -f "profiles/${PROFILE}.sh" ] ; then print_error "Could not load profile: ${PROFILE}" echo -n "Available profiles:" for p in $(ls profiles/*.sh) ; do echo -n " $(basename -s .sh $p)" done echo exit 1fisource "profiles/${PROFILE}.sh"echo "Running test: ${TEST_NAME} with profile: ${PROFILE}"[ -n "${UUT_VERSION}" ] && echo "UUT version: ${UUT_VERSION}" export TEST_LOG="$(pwd)/output/${PROFILE}/${TEST_NAME}.log"export UUT_LOG="$(pwd)/output/${PROFILE}/${TEST_NAME}.uut.log"export UUT_SNAP="$(pwd)/output/${PROFILE}" export TEST_COUNT=0export NRES_COUNT=0export FAIL_COUNT=0 CHECK_DEPS convert compare identify mogrify xrdb xvkbd wmctrlmkdir -p $(dirname ${UUT_LOG})mkdir -p ${UUT_SNAP} CON_PID=$(ps -p $$ -o ppid=)CON_PID=$(ps -p ${CON_PID} -o ppid=)CON_WID=$(wmctrl -lp | grep ${CON_PID} | awk '{print $1}')if [ -z "${CON_WID}" ] ; then echo "Console pid: ${CON_PID}" printf " ${YELLOW}wid not found, prompts won't focus console${DFLT}\n"else echo "Console pid: ${CON_PID} wid: ${CON_WID}"fi # Add path to installed dependencies, if anyexport PATH="$(pwd)/deps/bin":$PATH # Remove any permanent configuration that could change how Zutty looks/behaves:[ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && xrdb -remove # execute UUT and store its pid${UUT_EXE} ${UUT_ARGS} >${UUT_LOG} 2>&1 &PID=$!echo "UUT pid: ${PID}"echo " log file: ${UUT_LOG}"echo " snap dir: ${UUT_SNAP}" if [ ! -z ${UUT_PID_FROM_NAME} ] ; then sleep 0.5 PID=$(pidof ${UUT_PID_FROM_NAME}) echo "UUT pid: ${PID}"fi # wait until window opens and grab its X window idWID=''while true ; do if [[ -z "$(ps -p ${PID} -o pid=)" ]] ; then echo "UUT process ${PID} gone, aborting." exit 1 fi WID=$(wmctrl -lp | grep ${PID} | awk '{print $1}') [[ -z "${WID}" ]] || breakdoneecho "UUT X window id: ${WID}" # focus the windowwmctrl -ia "${WID}"# set it to float (this is i3 specific):which i3-msg >/dev/null && i3-msg floating enable >/dev/null; function COUNT_PASS { TEST_COUNT=$((TEST_COUNT + 1)) echo -n "$(test_counters)"} function COUNT_FAIL { TEST_COUNT=$((TEST_COUNT + 1)) FAIL_COUNT=$((FAIL_COUNT + 1)) echo -n "$(test_counters)"} function COUNT_NRES { TEST_COUNT=$((TEST_COUNT + 1)) NRES_COUNT=$((NRES_COUNT + 1)) echo -n "$(test_counters)"} function test_counters { echo "(${TEST_COUNT}/${NRES_COUNT}/${FAIL_COUNT}) "} function FINISH_TEST { echo "Total tests: ${TEST_COUNT} No result: ${NRES_COUNT} Failed: ${FAIL_COUNT}" # display process statistics if [ "${PLATFORM}" == "Linux" ] ; then ps -p ${PID} -o pid,vsz,rss,trs,sz,time,maj_flt,min_flt,args \ | tee -a ${TEST_LOG} pmap -x ${PID} >> ${TEST_LOG} echo "Process memory map appended to ${TEST_LOG}" else ps -p ${PID} -o pid,vsz,rss,time,args | tee -a ${TEST_LOG} fi trap - EXIT kill "${PID}" [ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && \ xrdb ${HOME}/.Xresources exit ${EXIT_CODE}} trap FINISH_TEST EXIT trap "exit 1" SIGINT function check_uut { if [ $(wmctrl -lp | grep ${WID} | wc -l) -eq 0 ] ; then # If IN was interrupted, Return key might have gotten stuck... LANG=C xvkbd -window root -no-jump-pointer \ -text "\{-Return}\D3\{-Return}\D3" 2>/dev/null echo "UUT window gone, exiting" echo "---------- LOG TAIL ----------" tail ${UUT_LOG} trap - EXIT [ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && \ xrdb ${HOME}/.Xresources exit 1 fi} function IN { local in="$1"; shift str=$(echo "$in" | sed 's|\\r|\\D3\\{+Return}\\D3\\{-Return}|'); check_uut LANG=C xvkbd -window ${WID} -no-jump-pointer -delay 50 \ -text "${str}\\D3" 2>&1 | \ grep -v "Mode_switch not available as a modifier" | \ grep -v "AltGr may not work correctly"} function FOCUS_CONSOLE { if [ ! -z "${CON_WID}" ] ; then # focus the console window # for some reason, a simple wmctrl -ia does not work; # it selects the window but keyboard focus is not active. LANG=C xvkbd -window ${CON_WID} -no-back-pointer \ -text "\{+Control_L}\D1\{-Control_L}\D1" >/dev/null 2>&1 fi} function do_update_sig { local name="$1"; shift local newsig="$1"; shift sed -i -e "s/SNAP ${name} .*$/SNAP ${name} ${newsig}/" "$(basename $0)" echo "${name}: signature updated to ${newsig}"} function update_sig { case ${UPDATE_SIGNATURE} in all) do_update_sig "$@" ;; yes) FOCUS_CONSOLE read -p "Update signature: [y]es / [N]o / [a]ll ? " ans printf "${ERASE_PROMPT}" case $ans in [aA]* ) export UPDATE_SIGNATURE=all; do_update_sig "$@" ;; [yY]* ) do_update_sig "$@" ;; * ) ;; esac ;; *) ;; esac} function step_prompt { FOCUS_CONSOLE read -p "[S]tep / [N]ew only / [C]ontinue / [Q]uit (s/n/c/q) ? " ans printf "${ERASE_PROMPT}" case $ans in [sS]* ) STEP_MODE=yes ;; [nN]* ) STEP_MODE=new ;; [cC]* ) STEP_MODE=no ;; [qQ]* ) exit ;; * ) ;; esac} function SNAP { local name="$1"; shift local refsig="$1"; shift local imgfile=${UUT_SNAP}/${name}.png check_uut xwd -silent -nobdrs -id ${WID} | convert xwd:- png:- > ${imgfile} mogrify -strip -taint -compress Lossless ${imgfile} case ${VERIFY_SNAPS} in yes) sig=$(identify -verbose ${imgfile} | grep signature | \ awk '{print $2}' | cut -c -32) if [ -z "${refsig}" ] ; then COUNT_NRES printf "${name}: ${YELLOW}NEW${DFLT} ${sig}\n" if [ ${STEP_MODE} == "new" ] ; then step_prompt fi elif [ "${sig}" == "${refsig}" ] ; then COUNT_PASS printf "${name}: ${GREEN}OK${DFLT}\n" else COUNT_FAIL printf "${name}: ${RED}FAIL${DFLT} sig ${sig} ref ${refsig}\n" update_sig ${name} ${sig} EXIT_CODE=1 if [ ${EXIT_ON_FAILURE} == "yes" ] ; then exit fi fi ;; optional) sig=$(identify -verbose ${imgfile} | grep signature | \ awk '{print $2}' | cut -c -32) imgerr=${UUT_SNAP}/${name}.error.png if [ "${sig}" == "${refsig}" ] ; then rm -f ${imgerr} COUNT_PASS printf "${name}: ${GREEN}MATCH${DFLT}\n" else COUNT_NRES if [ ! -z ${IMAGE_DIFF_TO} ] ; then imgref="$(pwd)/output/${IMAGE_DIFF_TO}/${name}.png" rm -f ${imgerr} AE=$(compare ${imgref} ${imgfile} -metric AE ${imgerr} 2>&1) if [ $? -eq 2 ] ; then printf "${name}: ${WHITE}DONE${DFLT} (different dimensions)\n" else printf "${name}: ${WHITE}DONE${DFLT} (AE=${AE})\n" fi else printf "${name}: ${WHITE}DONE${DFLT}\n" fi fi ;; *) COUNT_NRES printf "${name}: ${WHITE}DONE${DFLT}\n" ;; esac if [ ${STEP_MODE} == "yes" ] ; then step_prompt fi} function WAIT_FOR_DOT_COMPLETE { while true ; do [[ -f .complete ]] && break sleep 0.1 done rm -f .complete}
#!/usr/bin/env bash
if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
echo "This script should not be directly executed!"
exit 1
fi
# A declaration of parameters and their default values
declare -A ARGS=(
["profile"]=zutty
["step"]=no # N.B.: --step=new will only stop at new snaps!
["update-sig"]=no
["ci-mode"]=no
["debug"]=no
)
# Include argument handler
source "testargs.sh"
PROFILE=${ARGS["profile"]}
STEP_MODE=${ARGS["step"]}
UPDATE_SIGNATURE=${ARGS["update-sig"]}
if [ ${PROFILE} == "zutty" ] ; then
VERIFY_SNAPS=yes
else
VERIFY_SNAPS=optional
IMAGE_DIFF_TO=zutty
fi
if [ ${ARGS["ci-mode"]} == "yes" ] ; then
STEP_MODE=no
UPDATE_SIGNATURE=no
EXIT_ON_FAILURE=yes
else
EXIT_ON_FAILURE=no
fi
if [ ${ARGS["debug"]} == "yes" ] ; then
DEBUG=yes
else
DEBUG=no
fi
PLATFORM=$(uname -s)
EXIT_CODE=0
export TEST_NAME=$(basename -s .sh "$0")
export RED="\\e[1;31m"
export GREEN="\\e[1;32m"
export YELLOW="\\e[1;33m"
export WHITE="\\e[1;37m"
export DFLT="\\e[0;39m"
export ERASE_PROMPT="\\eM\\e[J"
function print_error {
printf "${RED}ERROR: $@${DFLT}\n"
}
function CHECK_DEPS {
TOOLS="$@"; shift
MISSING=0
for t in ${TOOLS} ; do
which $t >/dev/null
if [ $? -gt 0 ] ; then
print_error "Missing dependency: $t"
MISSING=1
fi
done
if [ $MISSING -gt 0 ] ; then
exit 1
fi
}
function CHECK_FILES {
FILES="$@"; shift
MISSING=0
for f in ${FILES} ; do
if [ ! -f "$f" ] ; then
print_error "Missing file: $f"
MISSING=1
fi
done
if [ $MISSING -gt 0 ] ; then
exit 1
fi
}
function CHECK_EXE {
FILES="$@"; shift
MISSING=0
for f in ${FILES} ; do
if [ ! -x "$f" ] ; then
print_error "Missing executable: $f"
MISSING=1
fi
done
if [ $MISSING -gt 0 ] ; then
exit 1
fi
}
if [ ! -f "profiles/${PROFILE}.sh" ] ; then
print_error "Could not load profile: ${PROFILE}"
echo -n "Available profiles:"
for p in $(ls profiles/*.sh) ; do
echo -n " $(basename -s .sh $p)"
done
echo
exit 1
fi
source "profiles/${PROFILE}.sh"
echo "Running test: ${TEST_NAME} with profile: ${PROFILE}"
[ -n "${UUT_VERSION}" ] && echo "UUT version: ${UUT_VERSION}"
export TEST_LOG="$(pwd)/output/${PROFILE}/${TEST_NAME}.log"
export UUT_LOG="$(pwd)/output/${PROFILE}/${TEST_NAME}.uut.log"
export UUT_SNAP="$(pwd)/output/${PROFILE}"
export TEST_COUNT=0
export NRES_COUNT=0
export FAIL_COUNT=0
CHECK_DEPS convert compare identify mogrify xrdb xvkbd wmctrl
mkdir -p $(dirname ${UUT_LOG})
mkdir -p ${UUT_SNAP}
CON_PID=$(ps -p $$ -o ppid=)
CON_PID=$(ps -p ${CON_PID} -o ppid=)
CON_WID=$(wmctrl -lp | grep ${CON_PID} | awk '{print $1}')
if [ -z "${CON_WID}" ] ; then
echo "Console pid: ${CON_PID}"
printf " ${YELLOW}wid not found, prompts won't focus console${DFLT}\n"
else
echo "Console pid: ${CON_PID} wid: ${CON_WID}"
fi
# Add path to installed dependencies, if any
export PATH="$(pwd)/deps/bin":$PATH
# Remove any permanent configuration that could change how Zutty looks/behaves:
[ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && xrdb -remove
# execute UUT and store its pid
${UUT_EXE} ${UUT_ARGS} >${UUT_LOG} 2>&1 &
PID=$!
echo "UUT pid: ${PID}"
echo " log file: ${UUT_LOG}"
echo " snap dir: ${UUT_SNAP}"
if [ ! -z ${UUT_PID_FROM_NAME} ] ; then
sleep 0.5
PID=$(pidof ${UUT_PID_FROM_NAME})
echo "UUT pid: ${PID}"
fi
# wait until window opens and grab its X window id
WID=''
while true ; do
if [[ -z "$(ps -p ${PID} -o pid=)" ]] ; then
echo "UUT process ${PID} gone, aborting."
exit 1
fi
WID=$(wmctrl -lp | grep ${PID} | awk '{print $1}')
[[ -z "${WID}" ]] || break
done
echo "UUT X window id: ${WID}"
# focus the window
wmctrl -ia "${WID}"
# set it to float (this is i3 specific):
which i3-msg >/dev/null && i3-msg floating enable >/dev/null;
function COUNT_PASS {
TEST_COUNT=$((TEST_COUNT + 1))
echo -n "$(test_counters)"
}
function COUNT_FAIL {
TEST_COUNT=$((TEST_COUNT + 1))
FAIL_COUNT=$((FAIL_COUNT + 1))
echo -n "$(test_counters)"
}
function COUNT_NRES {
TEST_COUNT=$((TEST_COUNT + 1))
NRES_COUNT=$((NRES_COUNT + 1))
echo -n "$(test_counters)"
}
function test_counters {
echo "(${TEST_COUNT}/${NRES_COUNT}/${FAIL_COUNT}) "
}
function FINISH_TEST {
echo "Total tests: ${TEST_COUNT} No result: ${NRES_COUNT} Failed: ${FAIL_COUNT}"
# display process statistics
if [ "${PLATFORM}" == "Linux" ] ; then
ps -p ${PID} -o pid,vsz,rss,trs,sz,time,maj_flt,min_flt,args \
| tee -a ${TEST_LOG}
pmap -x ${PID} >> ${TEST_LOG}
echo "Process memory map appended to ${TEST_LOG}"
else
ps -p ${PID} -o pid,vsz,rss,time,args | tee -a ${TEST_LOG}
fi
trap - EXIT
kill "${PID}"
[ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && \
xrdb ${HOME}/.Xresources
exit ${EXIT_CODE}
}
trap FINISH_TEST EXIT
trap "exit 1" SIGINT
function check_uut {
if [ $(wmctrl -lp | grep ${WID} | wc -l) -eq 0 ] ; then
# If IN was interrupted, Return key might have gotten stuck...
LANG=C xvkbd -window root -no-jump-pointer \
-text "\{-Return}\D3\{-Return}\D3" 2>/dev/null
echo "UUT window gone, exiting"
echo "---------- LOG TAIL ----------"
tail ${UUT_LOG}
trap - EXIT
[ ${PROFILE} == "zutty" ] && [ -f ${HOME}/.Xresources ] && \
xrdb ${HOME}/.Xresources
exit 1
fi
}
function IN {
local in="$1"; shift
str=$(echo "$in" | sed 's|\\r|\\D3\\{+Return}\\D3\\{-Return}|');
check_uut
LANG=C xvkbd -window ${WID} -no-jump-pointer -delay 50 \
-text "${str}\\D3" 2>&1 | \
grep -v "Mode_switch not available as a modifier" | \
grep -v "AltGr may not work correctly"
}
function FOCUS_CONSOLE {
if [ ! -z "${CON_WID}" ] ; then
# focus the console window
# for some reason, a simple wmctrl -ia does not work;
# it selects the window but keyboard focus is not active.
LANG=C xvkbd -window ${CON_WID} -no-back-pointer \
-text "\{+Control_L}\D1\{-Control_L}\D1" >/dev/null 2>&1
fi
}
function do_update_sig {
local name="$1"; shift
local newsig="$1"; shift
sed -i -e "s/SNAP ${name} .*$/SNAP ${name} ${newsig}/" "$(basename $0)"
echo "${name}: signature updated to ${newsig}"
}
function update_sig {
case ${UPDATE_SIGNATURE} in
all)
do_update_sig "$@"
;;
yes)
FOCUS_CONSOLE
read -p "Update signature: [y]es / [N]o / [a]ll ? " ans
printf "${ERASE_PROMPT}"
case $ans in
[aA]* ) export UPDATE_SIGNATURE=all; do_update_sig "$@" ;;
[yY]* ) do_update_sig "$@" ;;
* ) ;;
esac
;;
*)
;;
esac
}
function step_prompt {
FOCUS_CONSOLE
read -p "[S]tep / [N]ew only / [C]ontinue / [Q]uit (s/n/c/q) ? " ans
printf "${ERASE_PROMPT}"
case $ans in
[sS]* ) STEP_MODE=yes ;;
[nN]* ) STEP_MODE=new ;;
[cC]* ) STEP_MODE=no ;;
[qQ]* ) exit ;;
* ) ;;
esac
}
function SNAP {
local name="$1"; shift
local refsig="$1"; shift
local imgfile=${UUT_SNAP}/${name}.png
check_uut
xwd -silent -nobdrs -id ${WID} | convert xwd:- png:- > ${imgfile}
mogrify -strip -taint -compress Lossless ${imgfile}
case ${VERIFY_SNAPS} in
yes)
sig=$(identify -verbose ${imgfile} | grep signature | \
awk '{print $2}' | cut -c -32)
if [ -z "${refsig}" ] ; then
COUNT_NRES
printf "${name}: ${YELLOW}NEW${DFLT} ${sig}\n"
if [ ${STEP_MODE} == "new" ] ; then
step_prompt
fi
elif [ "${sig}" == "${refsig}" ] ; then
COUNT_PASS
printf "${name}: ${GREEN}OK${DFLT}\n"
else
COUNT_FAIL
printf "${name}: ${RED}FAIL${DFLT} sig ${sig} ref ${refsig}\n"
update_sig ${name} ${sig}
EXIT_CODE=1
if [ ${EXIT_ON_FAILURE} == "yes" ] ; then
exit
fi
fi
;;
optional)
sig=$(identify -verbose ${imgfile} | grep signature | \
awk '{print $2}' | cut -c -32)
imgerr=${UUT_SNAP}/${name}.error.png
if [ "${sig}" == "${refsig}" ] ; then
rm -f ${imgerr}
COUNT_PASS
printf "${name}: ${GREEN}MATCH${DFLT}\n"
else
COUNT_NRES
if [ ! -z ${IMAGE_DIFF_TO} ] ; then
imgref="$(pwd)/output/${IMAGE_DIFF_TO}/${name}.png"
rm -f ${imgerr}
AE=$(compare ${imgref} ${imgfile} -metric AE ${imgerr} 2>&1)
if [ $? -eq 2 ] ; then
printf "${name}: ${WHITE}DONE${DFLT} (different dimensions)\n"
else
printf "${name}: ${WHITE}DONE${DFLT} (AE=${AE})\n"
fi
else
printf "${name}: ${WHITE}DONE${DFLT}\n"
fi
fi
;;
*)
COUNT_NRES
printf "${name}: ${WHITE}DONE${DFLT}\n"
;;
esac
if [ ${STEP_MODE} == "yes" ] ; then
step_prompt
fi
}
function WAIT_FOR_DOT_COMPLETE {
while true ; do
[[ -f .complete ]] && break
sleep 0.1
done
rm -f .complete
}