#!/bin/sh
#
# units - Units test harness for ctags
#
# Copyright (C) 2014 Masatake YAMATO
#
# 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 2 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 .
#
if test -n "${ZSH_VERSION+set}"; then
set -o SH_WORD_SPLIT
set +o NOMATCH
fi
#
# Global Parameters
#
SHELL=/bin/sh
CTAGS=./ctags
READTAGS=./readtags
WITH_TIMEOUT=
WITH_VALGRIND=
COLORIZED_OUTPUT=yes
[ -f /dev/stdout ] && COLORIZED_OUTPUT=no
CATEGORIES=
UNITS=
LANGUAGES=
PRETENSE_OPTS=
RUN_SHRINK=
QUIET=
SHOW_DIFF_OUTPUT=
VALIDATORS=
#
# Internal variables and constants
#
_CMDLINE=
_CMDLINE_FOR_SHRINKING=
_PREPERE_ENV=
_FEATURE_LIST=
readonly _DEFAULT_CATEGORY=ROOT
readonly _TIMEOUT_EXIT=124
readonly _VG_TIMEOUT_FACTOR=10
readonly _VALGRIND_EXIT=58
readonly _BASH_INTERRUPT_EXIT=59
readonly _LINE_SPLITTER=$(if type dos2unix > /dev/null 2>&1; then echo "dos2unix"; else echo "cat"; fi)
readonly _STDERR_OUTPUT_NAME="STDERR.tmp"
readonly _DIFF_OUTPUT_NAME="DIFF.tmp"
readonly _NOISE_REPORT_MAX_COLUMN=50
readonly _VALIDATION_EXIT_INVALID=2
readonly _NOOP_VALIDATOR="NONE"
readonly _KNOWN_INVALIDATION_VALIDATOR="KNOWN-INVALIDATION"
if stat --help >/dev/null 2>&1; then
readonly _FSIZE="stat -c %s" # GNU coreutils
else
readonly _FSIZE="stat -f %z" # BSD based OSes including macOS
fi
_RUNNABLE_VALIDATORS=
_UNAVAILABLE_VALIDATORS=
#
# Result files and results
#
readonly R_PASSED="_PASSED.result"
readonly R_FIXED="_FIXED.result"
readonly R_FAILED_BY_STATUS="_FAILED_BY_STATUS.result"
readonly R_FAILED_BY_DIFF="_FAILED_BY_DIFF.result"
readonly R_SKIPPED_BY_FEATURES="_SKIPPED_BY_FEATURES.result"
readonly R_SKIPPED_BY_LANGUAGES="_SKIPPED_BY_LANGUAGES.result"
readonly R_SKIPPED_BY_ILOOP="_SKIPPED_BY_ILOOP.result"
readonly R_KNOWN_BUGS="_KNOWN_BUGS.result"
readonly R_FAILED_BY_TIMEED_OUT="_FAILED_BY_TIMEED_OUT.result"
readonly R_BROKEN_ARGS_CTAGS="_BROKEN_ARGS_CTAGS.result"
readonly R_VALGRIND="_VALGRIND.result"
L_PASSED=
L_FIXED=
L_FAILED_BY_STATUS=
L_FAILED_BY_DIFF=
L_SKIPPED_BY_FEATURES=
L_SKIPPED_BY_LANGUAGES=
L_SKIPPED_BY_ILOOP=
L_KNOWN_BUGS=
L_FAILED_BY_TIMEED_OUT=
L_BROKEN_ARGS_CTAGS=
L_VALGRIND=
V_VALID=0
V_INVALID=0
V_SKIP_VALIDATOR_UNAVAILABLE=0
V_SKIP_KNOWN_INVALIDATION=0
#
# TODO
#
# * write new class 'r' (category directory) to units.rst.
# * write new class 'v' (skip the checking by valgrind) to units.rst.
#
action_help ()
{
cat <&2
exit $status_
}
line()
{
local c=${1:--}
local no_newline="${2}"
local i=0
while [ $i -lt 60 ]; do
printf "%c" "$c"
i=$(( i + 1 ))
done
if ! [ "${no_newline}" = "--no-newline" ]; then
echo
fi
}
count_list ()
{
echo $#
}
member_p ()
{
local elt="$1"
shift
local x
for x in "$@"; do
if [ "$x" = "$elt" ]; then
return 0
fi
done
return 1
}
clean_tcase ()
{
local d="$1"
local bundles="$2"
local b
if [ -d "$d" ]; then
if [ -f "${bundles}" ]; then
while read b; do
rm -rf "${b}"
done < "${bundles}"
rm ${bundles}
fi
rm -f "$d"/*.tmp "$d"/*.TMP
fi
}
check_availability()
{
local cmd="$1"
shift
type "${cmd}" > /dev/null 2>&1 || ERROR 1 "${cmd} command is not available"
}
check_units ()
{
local name="$1"
local category="$2"
shift 2
local u
for u in "$@"; do
if echo "${u}" | grep -q /; then
if [ "${u%/*}" = "${category}" ] && [ "${u#*/}" = "${name}" ]; then
return 0
fi
elif [ "${u}" = "${name}" ]; then
return 0
fi
done
return 1
}
init_features()
{
_FEATURE_LIST=$( ${CTAGS} --quiet --options=NONE \
--list-features --with-list-header=no \
2> /dev/null \
| "${_LINE_SPLITTER}" \
| cut -f 1 -d ' ')
}
check_features()
{
local flag="$1"
local ffile
local feature
if [ "${flag}" = "-f" ]; then
ffile="$2"
elif [ "${flag}" = "-e" ]; then
feature="$2"
fi
shift 2
local f
local found
local found_unexpectedly
local expected;
for expected in $([ -f "$ffile" ] && cat "$ffile") ${feature}; do
found=no
found_unexpectedly=no
for f in ${_FEATURE_LIST} ; do
[ "$expected" = "$f" ] && found=yes
[ "$expected" = '!'"$f" ] && found_unexpectedly=yes
done
if [ "${found_unexpectedly}" = yes ]; then
echo "$expected"
return 1
elif ! [ "$found" = yes ]; then
echo "$expected"
return 1
fi
done
return 0
}
check_languages()
{
local lfile="$1"
shift
local l
local found
local expected;
#
# TODO: consider the value of LANGUAGES
#
while read expected; do
found=no
for l in $( ${_CMDLINE} --list-languages 2>/dev/null | "${_LINE_SPLITTER}" |sed -e 's/ //' ); do
[ "$expected" = "$l" ] && found=yes
done
if ! [ "$found" = yes ]; then
echo "$expected"
return 1
fi
done < "$lfile"
return 0
}
decorate ()
{
local decorator="$1"
local msg="$2"
case "$decorator" in
red) decorator=31 ;;
green) decorator=32 ;;
yellow) decorator=33 ;;
*) ERROR 1 "INTERNAL ERROR: wrong run_result function: $f"
esac
if [ "${COLORIZED_OUTPUT}" = 'yes' ]; then
printf '%b\n' "\033[${decorator}m${msg}\033[39m"
else
printf '%b\n' "${msg}"
fi
}
run_result ()
{
local result_type="$1"
local msg="$2"
local output="$3"
shift 3
local f="run_result_${result_type}"
local tmp
type "$f" > /dev/null 2>&1 || ERROR 1 \
"${msg}INTERNAL ERROR: wrong run_result function: $f"
"$f" "${msg}" "$@"
tmp="${COLORIZED_OUTPUT}"
COLORIZED_OUTPUT=no
"$f" "${msg}" "$@" > "${output}"
COLORIZED_OUTPUT="${tmp}"
}
run_result_skip ()
{
local msg="$1"
shift 1
if [ -n "$1" ]; then
printf '%b%b\n' "${msg}" $(decorate yellow "skipped")" ($1)"
else
printf '%b%b\n' "${msg}" $(decorate yellow "skipped")
fi
}
run_result_error ()
{
local msg="$1"
shift 1
if [ ! -n "$1" ]; then
printf '%b%b\n' "${msg}" $(decorate red "failed")
else
printf '%b%b\n' "${msg}" $(decorate red "failed")" ($1)"
fi
}
run_result_ok ()
{
local msg="$1"
shift 1
if [ ! -n "$1" ]; then
printf '%b%b\n' "${msg}" $(decorate green "passed")
else
printf '%b%b\n' "${msg}" $(decorate green "passed")" ($1)"
fi
}
run_result_known_error ()
{
local msg="$1"
shift 1
printf '%b%b\n' "${msg}" $(decorate yellow "failed")" (KNOWN bug)"
}
run_shrink ()
{
local cmdline_template="$1"
local input="$2"
local output="$3"
local lang="$4"
shift 4
echo "Shrinking ${input} as ${lang}"
shrink_main "${cmdline_template}" "${input}" "${output}" 1 yes
}
# filters out the directory prefix in a ctags input
ctags_basename_filter_regex='s%\(^[^ ]\{1,\} \)\(/\{0,1\}\([^/ ]\{1,\}/\)*\)%\1%'
ctags_basename_filter()
{
sed "${ctags_basename_filter_regex}"
}
# About "input" in the expression, see units.py.
etags_basename_filter_regex='s%.*\/\(input[-._][[:print:]]\{1,\}\),\([0-9]\{1,\}$\)%\1,\2%'
etags_basename_filter()
{
sed "${etags_basename_filter_regex}"
}
xref_basename_filter_regex='s%\(.*[[:digit:]]\{1,\} \)\([^ ]\{1,\}[^ ]\{1,\}\)/\([^ ].\{1,\}.\{1,\}$\)%\1\3%'
xref_basename_filter()
{
sed "${xref_basename_filter_regex}"
}
json_basename_filter_regex='s%\("path": \)"[^"]\{1,\}/\([^/"]\{1,\}\)"%\1"\2"%'
json_basename_filter()
{
sed "${json_basename_filter_regex}"
}
run_record_cmdline ()
{
local ffilter="$1"
local ocmdline="$2"
printf "%s\n%s \\\\\n| %s \\\\\n| %s\n" \
"${_PREPERE_ENV}" \
"${_CMDLINE}" \
"sed '${tags_basename_filter_regex}'" \
"${ffilter}" \
> "${ocmdline}"
}
#
# All files and directories other than input.*, expected.tags,
# args.ctags, README*, features, languages, and filters under srcdir
# are copied to builddir. These copied files are called bundles.
#
prepare_bundles ()
{
local from=$1
local to=$2
local obundles=$3
local src
local dist
for src in ${from}/*; do
if [ "${from}"'/*' = "${src}" ]; then
break
fi
case "${src##*/}" in
input.*)
continue
;;
expected.tags*)
continue
;;
README*)
continue
;;
features|languages|filters)
continue
;;
args.ctags)
continue
;;
*)
dist="${to}/${src##*/}"
if ! cp -a "${src}" "${to}"; then
ERROR 1 "failure in copying bundle file \"${src}\" to \"${to}\""
else
echo ${dist} >> ${obundles}
fi
;;
esac
done
}
direq ()
{
[ "$(cd ${1} && pwd)" = "$(cd ${2} && pwd)" ]
return $?
}
anon_normalize ()
{
local ctags=$1
local input_actual
if [ -n "$2" ]; then
input_actual=$2
shift 2
# TODO: "Units" should not be hardcoded.
local input_expected="./Units${input_actual#*/Units}"
local actual=$(${CTAGS} --quiet --options=NONE --_anonhash="${input_actual}")
local expected=$(${CTAGS} --quiet --options=NONE --_anonhash="${input_expected}")
sed -e s/${actual}/${expected}/g | anon_normalize "${ctags}" "$@"
else
cat
fi
}
run_tcase ()
{
local input="$1"
local t="$2"
local name="$3"
local class="$4"
local category="$5"
local build_t="$6"
shift 6
# The rest of arguments ($@) are extra inputs
# I violate the naming convention of build_* to reduce typing
local o=${build_t}
local fargs="$t/args.ctags"
local ffeatures="$t/features"
local flanguages="$t/languages"
local ffilter="$t/filter"
#
# tags-e if for etags output(-e). TAGS is good
# suffix but foo.tags and foo.TAGS may be the same on Windows.
# tags-x is for cross reference output(-x).
# tags-json is for json output.
#
# fexpected must be set even if none of
# expected.{tags,tags-e,tags-x,tags-json} exits.
#
local fexpected="$t/expected.tags"
local output_type=ctags
local output_label=
local output_tflag=
local output_feature=
local output_lang_extras=
if [ -f "$t/expected.tags" ]; then
:
elif [ -f "$t/expected.tags-e" ]; then
fexpected=$t/expected.tags-e
output_type=etags
output_label=/${output_type}
output_tflag="-e --tag-relative=no"
elif [ -f "$t/expected.tags-x" ]; then
fexpected=$t/expected.tags-x
output_type=xref
output_label=/${output_type}
output_tflag=-x
elif [ -f "$t/expected.tags-json" ]; then
fexpected=$t/expected.tags-json
output_type=json
output_label=/${output_type}
output_tflag="--output-format=json"
output_feature=json
fi
if [ $# -gt 0 ]; then
output_lang_extras=" (multi inputs)"
fi
[ -x "$ffilter" ] || ffilter=cat
#
# All generated file must have suffix ".tmp".
#
local ostderr="$o/${_STDERR_OUTPUT_NAME}"
local orawout="$o/RAWOUT.tmp"
local ofiltered="$o/FILTERED.tmp"
local odiff="$o/${_DIFF_OUTPUT_NAME}"
local ocmdline="$o/CMDLINE.tmp"
local ovalgrind="$o/VALGRIND.tmp"
local oresult="$o/RESULT.tmp"
local oshrink_template="$o/SHRINK-%s.tmp"
local obundles="$o/BUNDLES"
local oshrink
local guessed_lang
local guessed_lang_no_slash
local cmdline_template
local timeout_value
local tmp
local msg
local broke_args_ctags
#
# Filtered by UNIT
#
if [ -n "${UNITS}" ]; then
check_units "${name}" "${category}" ${UNITS} || return 1
fi
#
# Build _CMDLINE
#
_CMDLINE="${CTAGS} --verbose --options=NONE --fields=-T $PRETENSE_OPTS --optlib-dir=+$t/optlib -o -"
[ -f "${fargs}" ] && _CMDLINE="${_CMDLINE} --options=${fargs}"
if [ -f "${fargs}" ] && ! ${_CMDLINE} --_force-quit=0 > /dev/null 2>&1; then
broke_args_ctags=1
fi
#
# Filtered by LANGUAGES
#
guessed_lang=$( ${_CMDLINE} --print-language "$input" 2>/dev/null | sed -n 's/^.*: //p')
if [ -n "${LANGUAGES}" ]; then
member_p "${guessed_lang}" ${LANGUAGES} || return 1
fi
guessed_lang_no_slash=$(echo "${guessed_lang}" | tr '/' '-')
oshrink=$(printf "${oshrink_template}" "${guessed_lang_no_slash}")
clean_tcase "${o}" "${obundles}"
mkdir -p "${o}"
if ! direq "${o}" "${t}"; then
prepare_bundles ${t} ${o} "${obundles}"
fi
msg=$(printf '%-60s' "Testing ${name} as ${guessed_lang}${output_lang_extras}${output_label}")
if tmp=$( ( [ -n "${output_feature}" ] && ! check_features -e "${output_feature}" ) ||
( [ -f "${ffeatures}" ] && ! check_features -f "${ffeatures}" ) ); then
echo "${category}/${name}" >> ${R_SKIPPED_BY_FEATURES}
case "${tmp}" in
!*) run_result skip "${msg}" "${oresult}" "unwanted feature \"${tmp#?}\" is available";;
*) run_result skip "${msg}" "${oresult}" "required feature \"${tmp}\" is not available";;
esac
return 1
elif [ -f "${flanguages}" ] && ! tmp=$(check_languages "${flanguages}"); then
echo "${category}/${name}" >> ${R_SKIPPED_BY_LANGUAGES}
run_result skip "${msg}" "${oresult}" "required language parser \"$tmp\" is not available"
return 1
elif [ "$WITH_TIMEOUT" = 0 ] && [ "${class}" = 'i' ]; then
echo "${category}/${name}" >> ${R_SKIPPED_BY_ILOOP}
run_result skip "${msg}" "${oresult}" "may cause an infinite loop"
return 1
elif [ "$broke_args_ctags" = 1 ]; then
run_result error "${msg}" '/dev/null' "broken args.ctags?"
echo "${category}/${name}/" >> ${R_BROKEN_ARGS_CTAGS}
return 1
fi
cmdline_template="${_CMDLINE} --language-force=${guessed_lang} %s > /dev/null 2>&1"
_CMDLINE="${_CMDLINE} ${output_tflag} ${input} $@"
timeout_value=$WITH_TIMEOUT
if [ "$WITH_VALGRIND" = yes ]; then
_CMDLINE="valgrind --leak-check=full --error-exitcode=${_VALGRIND_EXIT} --log-file=${ovalgrind} ${_CMDLINE}"
timeout_value=$(( timeout_value * ${_VG_TIMEOUT_FACTOR} ))
fi
if ! [ "$timeout_value" = 0 ]; then
_CMDLINE="timeout $timeout_value ${_CMDLINE}"
fi
{
(
#
# When a launched process is exited abnormally, the parent shell reports it
# to stderr: See j_strsignal function call in wait_for in bash-4.2/nojobs.c.
# This becomes noise; close the stderr of subshell.
#
exec 2>&-;
#
# The original bug report(#1100 by @techee):
# --------------------------------------------------------------------------
# When running
#
# make units VG=1
#
# one cannot stop its execution by pressing Ctrl+C and
# there doesn't seem to be any way (except for looking at
# processes which run and killing them) to stop its
# execution.
#
trap "exit ${_BASH_INTERRUPT_EXIT}" INT;
${_CMDLINE} 2> "${ostderr}" > "${orawout}"
)
tmp="$?"
run_record_cmdline "${ffilter}" "${ocmdline}"
}
if [ "$tmp" != 0 ]; then
if [ "${tmp}" = "${_BASH_INTERRUPT_EXIT}" ]; then
ERROR 1 "The execution is interrupted"
elif ! [ "$WITH_TIMEOUT" = 0 ] && [ "${tmp}" = "${_TIMEOUT_EXIT}" ]; then
echo "${category}/${name}" >> ${R_FAILED_BY_TIMEED_OUT}
run_result error "${msg}" "${oresult}" "TIMED OUT"
run_record_cmdline "${ffilter}" "${ocmdline}"
[ "${RUN_SHRINK}" = 'yes' ] \
&& [ $# -eq 0 ] \
&& run_shrink "${cmdline_template}" "${input}" "${oshrink}" "${guessed_lang}"
return 1
elif [ "$WITH_VALGRIND" = 'yes' ] && [ "${tmp}" = "${_VALGRIND_EXIT}" ] && ! [ "${class}" = v ]; then
echo "${category}/${name}" >> ${R_VALGRIND}
run_result error "${msg}" "${oresult}" "valgrind-error"
run_record_cmdline "${ffilter}" "${ocmdline}"
return 1
elif [ "$class" = 'b' ]; then
echo "${category}/${name}" >> ${R_KNOWN_BUGS}
run_result known_error "${msg}" "${oresult}"
run_record_cmdline "${ffilter}" "${ocmdline}"
[ "${RUN_SHRINK}" = 'yes' ] \
&& [ $# -eq 0 ] \
&& run_shrink "${cmdline_template}" "${input}" "${oshrink}" "${guessed_lang}"
return 0
else
echo "${category}/${name}" >> ${R_FAILED_BY_STATUS}
run_result error "${msg}" "${oresult}" "unexpected exit status: $tmp"
run_record_cmdline "${ffilter}" "${ocmdline}"
[ "${RUN_SHRINK}" = 'yes' ] \
&& [ $# -eq 0 ] \
&& run_shrink "${cmdline_template}" "${input}" "${oshrink}" "${guessed_lang}"
return 1
fi
elif [ "$WITH_VALGRIND" = 'yes' ] && [ "$class" = 'v' ]; then
echo "${category}/${name}" >> ${R_FIXED}
fi
if ! [ -f "${fexpected}" ]; then
clean_tcase "${o}" "${obundles}"
if [ "$class" = 'b' ]; then
echo "${category}/${name}" >> ${R_FIXED}
elif [ "$class" = 'i' ]; then
echo "${category}/${name}" >> ${R_FIXED}
fi
echo "${category}/${name}" >> ${R_PASSED}
run_result ok "${msg}" '/dev/null' "\"expected.tags*\" not found"
return 0
fi
${output_type}_basename_filter < "${orawout}" | \
anon_normalize "${CTAGS}" "${input}" "$@" | \
$ffilter > "${ofiltered}"
{
diff -U 0 -I '^!_TAG' --strip-trailing-cr "${fexpected}" "${ofiltered}" > "${odiff}"
tmp="$?"
}
if [ "${tmp}" = 0 ]; then
clean_tcase "${o}" "${obundles}"
if [ "${class}" = 'b' ]; then
echo "${category}/${name}" >> ${R_FIXED}
elif ! [ "$WITH_TIMEOUT" = 0 ] && [ "${class}" = 'i' ]; then
echo "${category}/${name}" >> ${R_FIXED}
fi
echo "${category}/${name}" >> ${R_PASSED}
run_result ok "${msg}" '/dev/null'
return 0
else
if [ "${class}" = 'b' ]; then
echo "${category}/${name}" >> ${R_KNOWN_BUGS}
run_result known_error "${msg}" "${oresult}"
run_record_cmdline "${ffilter}" "${ocmdline}"
return 0
else
echo "${category}/${name}" >> ${R_FAILED_BY_DIFF}
run_result error "${msg}" "${oresult}" "unexpected output"
run_record_cmdline "${ffilter}" "${ocmdline}"
return 1
fi
fi
}
failure_in_globing ()
{
# skip if globing failed, also ignore backup files
case $1 in
*\~) return 0 ;;
*\**) return 0 ;;
*) return 1 ;;
esac
}
run_dir ()
{
local category="$1"
local base_dir="$2"
local build_base_dir="$3"
shift 3
local tcase_dir
local build_tcase_dir
local input
local name
local dname
local class
local extra_tmp
local extra_inputs
#
# Filtered by CATEGORIES
#
if [ -n "$CATEGORIES" ] && ! member_p "${category}" $CATEGORIES; then
return 1
fi
echo
echo "Category: $category"
line
for input in ${base_dir}/*.[dbtiv]/input.*; do
if failure_in_globing "$input"; then
continue
fi
dname=$(dirname $input)
extra_inputs=$(for extra_tmp in $dname/input[-_][0-9].* \
$dname/input[-_][0-9][-_]*.* ; do
if failure_in_globing "$extra_tmp"; then
continue
fi
echo "$extra_tmp"
done | sort)
tcase_dir="${input%/input.*}"
build_tcase_dir="${build_base_dir}/${tcase_dir#${base_dir}/}"
name="${tcase_dir%.[dbtiv]}"
name="${name##*/}"
class="${tcase_dir#*${name}.}"
# Run this in parallel
run_tcase "${input}" "${tcase_dir}" "${name}" "${class}" "${category}" "${build_tcase_dir}" ${extra_inputs} &
done
wait
return 0
}
run_show_diff_output ()
{
local units_dir="$1"
local t="$2"
printf " "
line .
sed -e 's/^.*$/ &/' ${units_dir}/${t}.*/${_DIFF_OUTPUT_NAME}
echo
}
run_show_stderr_output ()
{
local units_dir="$1"
local t="$2"
printf " "
line .
sed -e 's/^.*$/ &/' ${units_dir}/${t}.*/${_STDERR_OUTPUT_NAME} | tail -50
echo
}
run_summary ()
{
local build_dir="${1}"
local t
echo
echo "Summary (see CMDLINE.tmp to reproduce without test harness)"
line
printf ' %-40s' "#passed:"
L_PASSED=$([ -f $R_PASSED ] && cat $R_PASSED)
count_list $L_PASSED
printf ' %-40s' "#FIXED:"
L_FIXED=$([ -f $R_FIXED ] && cat $R_FIXED)
count_list $L_FIXED
for t in $L_FIXED; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
printf ' %-40s' "#FAILED (broken args.ctags?):"
L_BROKEN_ARGS_CTAGS=$([ -f $R_BROKEN_ARGS_CTAGS ] && cat $R_BROKEN_ARGS_CTAGS)
count_list $L_BROKEN_ARGS_CTAGS
for t in $L_BROKEN_ARGS_CTAGS; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
printf ' %-40s' "#FAILED (unexpected-exit-status):"
L_FAILED_BY_STATUS=$([ -f $R_FAILED_BY_STATUS ] && cat $R_FAILED_BY_STATUS)
count_list $L_FAILED_BY_STATUS
for t in $L_FAILED_BY_STATUS; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
if [ "${SHOW_DIFF_OUTPUT}" = yes ]; then
run_show_stderr_output "${build_dir}" "${t#${_DEFAULT_CATEGORY}/}"
fi
done
printf ' %-40s' "#FAILED (unexpected-output):"
L_FAILED_BY_DIFF=$([ -f $R_FAILED_BY_DIFF ] && cat $R_FAILED_BY_DIFF)
count_list $L_FAILED_BY_DIFF
for t in $L_FAILED_BY_DIFF; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
if [ "${SHOW_DIFF_OUTPUT}" = yes ]; then
run_show_stderr_output "${build_dir}" "${t#${_DEFAULT_CATEGORY}/}"
run_show_diff_output "${build_dir}" "${t#${_DEFAULT_CATEGORY}/}"
fi
done
if ! [ "$WITH_TIMEOUT" = 0 ]; then
printf ' %-40s' "#TIMED-OUT (${WITH_TIMEOUT}s)"
L_FAILED_BY_TIMEED_OUT=$([ -f $R_FAILED_BY_TIMEED_OUT ] && cat $R_FAILED_BY_TIMEED_OUT)
count_list $L_FAILED_BY_TIMEED_OUT
for t in $L_FAILED_BY_TIMEED_OUT; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
fi
printf ' %-40s' "#skipped (features):"
L_SKIPPED_BY_FEATURES=$([ -f $R_SKIPPED_BY_FEATURES ] && cat $R_SKIPPED_BY_FEATURES)
count_list $L_SKIPPED_BY_FEATURES
for t in $L_SKIPPED_BY_FEATURES; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
printf ' %-40s' "#skipped (languages):"
L_SKIPPED_BY_LANGUAGES=$([ -f $R_SKIPPED_BY_LANGUAGES ] && cat $R_SKIPPED_BY_LANGUAGES)
count_list $L_SKIPPED_BY_LANGUAGES
for t in $L_SKIPPED_BY_LANGUAGES; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
if [ "$WITH_TIMEOUT" = 0 ]; then
printf ' %-40s' "#skipped (infinite-loop):"
L_SKIPPED_BY_ILOOP=$([ -f $R_SKIPPED_BY_ILOOP ] && cat $R_SKIPPED_BY_ILOOP)
count_list $L_SKIPPED_BY_ILOOP
for t in $L_SKIPPED_BY_ILOOP; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
fi
printf ' %-40s' "#known-bugs:"
L_KNOWN_BUGS=$([ -f $R_KNOWN_BUGS ] && cat $R_KNOWN_BUGS)
count_list $L_KNOWN_BUGS
for t in $L_KNOWN_BUGS; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
if [ "$WITH_VALGRIND" = yes ]; then
printf ' %-40s' "#valgrind-error:"
L_VALGRIND=$([ -f $R_VALGRIND ] && cat $R_VALGRIND)
count_list $L_VALGRIND
for t in $L_VALGRIND; do
echo " ${t#${_DEFAULT_CATEGORY}/}"
done
fi
}
make_pretense_map ()
{
local ifs=$IFS
local p
local r
IFS=,
for p in $1; do
newlang=${p%/*}
oldlang=${p#*/}
if [ -z "$newlang" ]; then
ERROR 1 "newlang part of --pretend option arg is empty"
fi
if [ -z "$oldlang" ]; then
ERROR 1 "oldlang part of --pretend option arg is empty"
fi
r="$r --_pretend-$newlang=$oldlang"
done
IFS=$ifs
echo $r
}
delete_result_files ()
{
rm -f ${R_PASSED} ${R_FIXED} ${R_FAILED_BY_STATUS} ${R_FAILED_BY_DIFF} \
${R_SKIPPED_BY_FEATURES} ${R_SKIPPED_BY_LANGUAGES} \
${R_SKIPPED_BY_ILOOP} ${R_KNOWN_BUGS} ${R_FAILED_BY_TIMEED_OUT} \
${R_BROKEN_ARGS_CTAGS}
}
action_run ()
{
local action="$1"
shift
local units_dir
local build_dir
local d
local build_d
local category
local c
while [ $# -gt 0 ]; do
case $1 in
--ctags)
shift
CTAGS="$1"
shift
;;
--ctags=*)
CTAGS="${1#--ctags=}"
shift
;;
--categories)
shift
for c in $(echo "$1" | tr ',' ' '); do
if [ "$c" = "ROOT" ]; then
CATEGORIES="$CATEGORIES ROOT"
else
CATEGORIES="$CATEGORIES ${c%.r}.r"
fi
done
shift
;;
--categories=*)
for c in $(echo "${1#--categories=}" | tr ',' ' '); do
if [ "$c" = "ROOT" ]; then
CATEGORIES="$CATEGORIES ROOT"
else
CATEGORIES="$CATEGORIES ${c%.r}.r"
fi
done
shift
;;
--units)
shift
UNITS=$(echo "$1" | tr ',' ' ')
shift
;;
--units=*)
UNITS=$(echo "${1#--units=}" | tr ',' ' ')
shift
;;
--languages)
shift
LANGUAGES=$(echo "${1}" | tr ',' ' ')
shift
;;
--languages=*)
LANGUAGES=$(echo "${1#--languages=}" | tr ',' ' ')
shift
;;
--with-timeout)
shift
WITH_TIMEOUT="$1"
shift
;;
--with-timeout=*)
WITH_TIMEOUT="${1#--with-timeout=}"
shift
;;
--with-valgrind)
shift
WITH_VALGRIND=yes
;;
--colorized-output)
shift
COLORIZED_OUTPUT="$1"
shift
;;
--colorized-output=*)
COLORIZED_OUTPUT="${1#--colorized-output=}"
shift
;;
--run-shrink)
RUN_SHRINK=yes
shift
;;
--show-diff-output)
SHOW_DIFF_OUTPUT=yes
shift
;;
--with-pretense-map)
shift
PRETENSE_OPTS=$(make_pretense_map "$1")
shift
;;
--with-pretense-map=*)
PRETENSE_OPTS=$(make_pretense_map "${1#--with-pretense-map=}")
shift
;;
-*)
ERROR 1 "unknown option \"${1}\" for ${action} action"
;;
*)
units_dir="$1"
shift
build_dir="${1:-${units_dir}}"
shift
break
;;
esac
done
if [ $# -gt 0 ]; then
ERROR 1 "too many arguments for ${action} action: $*"
elif [ -z "$units_dir" ]; then
ERROR 1 "UNITS_DIR parameter is not given in ${action} action"
fi
if ! [ -d "$units_dir" ]; then
ERROR 1 "No such directory: ${units_dir}"
fi
case "${build_dir}" in
/*) ;;
*) build_dir=$(pwd)/${build_dir} ;;
esac
if ! [ -d "$build_dir" ]; then
ERROR 1 "No such directory(build_dir): ${build_dir}"
fi
if ! [ -f "${CTAGS}" ]; then
ERROR 1 "no such file: ${CTAGS}"
elif ! [ -e "${CTAGS}" ]; then
ERROR 1 "${CTAGS} is not an executable file"
fi
if ! ( [ "${COLORIZED_OUTPUT}" = 'yes' ] || [ "${COLORIZED_OUTPUT}" = 'no' ] ); then
ERROR 1 "unexpected option argument for --colorized-output: ${COLORIZED_OUTPUT}"
fi
: ${WITH_TIMEOUT:=0}
[ "$WITH_TIMEOUT" = 0 ] || check_availability timeout
[ "$WITH_VALGRIND" = 'yes' ] && check_availability valgrind
[ "$MSYSTEM" != '' ] && check_availability dos2unix
check_availability grep
check_availability diff
init_features
delete_result_files
category="${_DEFAULT_CATEGORY}"
if [ -z "$CATEGORIES" ] \
|| ( [ -n "$CATEGORIES" ] && member_p "${category}" $CATEGORIES ); then
run_dir "${category}" "${units_dir}" "${build_dir}"
fi
for d in ${units_dir}/*.r; do
[ -d "$d" ] || continue
category="${d##*/}"
build_d=${build_dir}/${category}
run_dir "${category}" "$d" "${build_d}"
done
run_summary "${build_dir}"
delete_result_files
if [ -n "${L_FAILED_BY_STATUS}" ] ||
[ -n "${L_FAILED_BY_DIFF}" ] ||
[ -n "${L_FAILED_BY_TIMEED_OUT}" ] ||
[ -n "${L_BROKEN_ARGS_CTAGS}" ]; then
return 1
else
return 0
fi
}
help_run ()
{
cat </dev/null > "${output}"
}
shrink_test ()
{
local cmdline="$1"
local input="$2"
local start="$3"
local len="$4"
local output="$5"
local r
local msg
shrink_prepare "${output}" "${input}" "${start}" "${len}"
[ "${QUIET}" = 'yes' ] || printf "[%-5u %6u]..." "${start}" $(( start + len )) 1>&2
eval "${cmdline}" > /dev/null 2>&1
r="$?"
if [ "$r" -eq 0 ]; then
msg='ok'
elif [ "$r" -eq "${_TIMEOUT_EXIT}" ]; then
msg='timeout'
else
msg='failed'
fi
[ "${QUIET}" = 'yes' ] || printf "%s(%u)\n" "$msg" "$r" 1>&2
return $r
}
shrink_bisect ()
{
local cmdline="$1"
local input="$2"
local len="$3"
local output="$4"
local end
local start
local step
local delta
local failed
local successful
end="${len}"
failed="${len}"
successful=0
step=0
while true; do
delta=$((len >> (step + 1)))
if [ "${delta}" -eq 0 ]; then
delta=1
fi
if shrink_test "${cmdline}" "${input}" 0 "${end}" "${output}"; then
successful="${end}"
if [ $(( end + 1 )) -eq "${failed}" ]; then
end="${failed}"
break
else
end=$((end + delta))
fi
else
failed="$end"
if [ $(( successful + 1 )) -eq "${end}" ]; then
break
else
end=$((end - delta))
fi
fi
step=$((step + 1 ))
done
len="${end}"
start=0
failed=0
successful="${end}"
step=0
while true; do
delta=$((len >> (step + 1)))
if [ "${delta}" -eq 0 ]; then
delta=1
fi
if shrink_test "${cmdline}" "${input}" "${start}" $((end - start)) "${output}"; then
successful="${start}"
if [ $(( start - 1 )) -eq "${failed}" ]; then
start=$((start - 1))
break
else
start=$((start - delta))
fi
else
failed="${start}"
if [ $((successful - 1)) -eq "${start}" ]; then
break
else
start=$((start + delta))
fi
fi
step=$((step + 1))
done
len=$((end - start))
shrink_prepare "${output}" "${input}" "${start}" "${len}"
[ "${QUIET}" = 'yes' ] || echo "Minimal badinput: ${output}"
[ "${QUIET}" = 'yes' ] || line .
cat "${output}"
echo
return 0
}
shrink_main ()
{
local cmdline_template="$1"
local cmdline
local input="$2"
local len
local output="$3"
local duration="$4"
local foreground="$5"
if ! [ -f "${input}" ]; then
ERROR 1 "No such file: ${input}"
elif ! [ -r "${input}" ]; then
ERROR 1 "Cannot read a file: ${input}"
fi
if ! cat < /dev/null > "${output}"; then
ERROR 1 "Cannot modify a file: ${output}"
fi
cmdline=$(printf "${cmdline_template}" "${output}")
if [ -n "${duration}" ] && ! [ "${duration}" -eq 0 ]; then
if [ "${foreground}" = 'yes' ]; then
cmdline="timeout --foreground ${duration} ${cmdline}"
else
cmdline="timeout ${duration} ${cmdline}"
fi
fi
len=$(${_FSIZE} "${input}")
if shrink_test "${cmdline}" "${input}" 0 "${len}" "${output}"; then
printf "the target command line exits normally against the original input\n" 1>&2
return 1
fi
if ! shrink_test "${cmdline}" "${input}" 0 0 "${output}"; then
printf "the target command line exits abnormally against the empty input\n" 1>&2
return 1
fi
shrink_bisect "${cmdline}" "${input}" "${len}" "${output}"
}
action_shrink ()
{
local action="$1"
shift
local cmdline_template
local input
local output
local timeout
local duration
local foreground
while [ $# -gt 0 ]; do
case $1 in
--timeout)
shift
duration=$1
shift
;;
--timeout=*)
duration="${1#--timeout=}"
shift
;;
--foreground)
foreground=yes
shift
;;
--quiet)
QUIET=yes
shift
;;
-*)
ERROR 1 "unknown option \"${1}\" for ${action} action"
;;
*)
break
;;
esac
done
if [ $# -lt 3 ]; then
ERROR 1 "too few arguments for ${action} action: $*"
elif [ $# -gt 3 ]; then
ERROR 1 "too many arguments for ${action} action: $*"
fi
if [ -n "${foreground}" ] && [ -z "${duration}" ]; then
ERROR 1 "--foreground option is meaningful only if --timeout option is specified."
fi
cmdline_template=$1
input=$2
output=$3
shift 3
shrink_main "${cmdline_template}" "${input}" "${output}" ${duration} ${foreground}
return $?
}
help_shrink ()
{
cat < "${ocmdline}"
${cmdline} > /dev/null
r=$?
case $r in
0)
rm -f "${ovalgrind}" "${ocmdline}"
return 0
;;
${_TIMEOUT_EXIT})
[ "${QUIET}" = 'yes' ] || echo
printf '%-40s' "[timeout $lang]"
echo "$f"
[ "${RUN_SHRINK}" = 'yes' ] && fuzz_shrink "${cmdline_for_shirking}" "${file}" "${oshrink}" "${lang}"
return 1
;;
${_VALGRIND_EXIT})
[ "${QUIET}" = 'yes' ] || echo
printf '%-40s' "[valgrind-error $lang]"
echo "$f"
return 1
;;
*)
[ "${QUIET}" = 'yes' ] || echo
printf '%-40s' "[unexpected-status($r) $lang]"
echo "$f"
[ "${RUN_SHRINK}" = 'yes' ] && fuzz_shrink "${cmdline_for_shirking}" "${file}" "${oshrink}" "${lang}"
return 1
;;
esac
return $r
}
fuzz_lang ()
{
local lang="$1"
local dir="$2"
shift 2
local f
local r=0
[ "${QUIET}" = 'yes' ] || printf '%-60s\n' "Semi-fuzzing (${lang})"
for f in $(find "${dir}" -type f -name 'input.*'); do
if ! fuzz_lang_file "${lang}" "${f}"; then
r=1
break
fi
done
[ "${QUIET}" = 'yes' ] || echo
return $r
}
action_fuzz ()
{
action_fuzz_common fuzz_lang "$@"
}
action_fuzz_common ()
{
local fn="$1"
local action="$2"
shift 2
local units_dir
local cmdline
local lang
local r
while [ $# -gt 0 ]; do
case $1 in
--ctags)
shift
CTAGS="$1"
shift
;;
--ctags=*)
CTAGS="${1#--ctags=}"
shift
;;
--languages)
shift
LANGUAGES=$(echo "${1}" | tr ',' ' ')
shift
;;
--languages=*)
LANGUAGES=$(echo "${1#--languages=}" | tr ',' ' ')
shift
;;
--quiet)
QUIET=yes
shift
;;
--with-timeout)
shift
WITH_TIMEOUT="$1"
shift
;;
--with-timeout=*)
WITH_TIMEOUT="${1#--with-timeout=}"
shift
;;
--with-valgrind)
shift
WITH_VALGRIND=yes
;;
--colorized-output)
shift
COLORIZED_OUTPUT="$1"
shift
;;
--colorized-output=*)
COLORIZED_OUTPUT="${1#--colorized-output=}"
shift
;;
--run-shrink)
RUN_SHRINK=yes
shift
;;
-*)
ERROR 1 "unknown option \"${1}\" for ${action} action"
;;
*)
units_dir="$1"
shift
break;
;;
esac
done
if [ $# -gt 0 ]; then
ERROR 1 "too many arguments for ${action} action: $*"
elif [ -z "$units_dir" ]; then
ERROR 1 "UNITS_DIR parameter is not given in ${action} action"
fi
if ! [ -d "$units_dir" ]; then
ERROR 0 "No such directory: ${units_dir}"
fi
if ! [ -f "${CTAGS}" ]; then
ERROR 1 "no such file: ${CTAGS}"
elif ! [ -e "${CTAGS}" ]; then
ERROR 1 "${CTAGS} is not an executable file"
fi
if ! ( [ "${COLORIZED_OUTPUT}" = 'yes' ] || [ "${COLORIZED_OUTPUT}" = 'no' ] ); then
ERROR 1 "unexpected option argument for --colorized-output: ${COLORIZED_OUTPUT}"
fi
: ${WITH_TIMEOUT:=2}
[ "$WITH_TIMEOUT" = 0 ] || check_availability timeout
[ "$WITH_VALGRIND" = 'yes' ] && check_availability valgrind
check_availability find
cmdline="${CTAGS} --quiet --options=NONE --kinds-all=* --fields=*"
_CMDLINE="${cmdline} -G -o - "
_CMDLINE_FOR_SHRINKING="${_CMDLINE}"
if [ "$WITH_VALGRIND" = yes ]; then
_CMDLINE="valgrind --leak-check=full --error-exitcode=${_VALGRIND_EXIT} --log-file=%s ${_CMDLINE}"
WITH_TIMEOUT=$(( WITH_TIMEOUT * ${_VG_TIMEOUT_FACTOR} ))
fi
if ! [ "$WITH_TIMEOUT" = 0 ]; then
_CMDLINE="timeout --foreground $WITH_TIMEOUT ${_CMDLINE}"
_CMDLINE_FOR_SHRINKING="timeout --foreground 1 ${_CMDLINE_FOR_SHRINKING}"
fi
for lang in $( ${cmdline} --list-languages 2>/dev/null | "${_LINE_SPLITTER}" |sed -e 's/ //' ) ; do
if [ -n "${LANGUAGES}" ] && ! member_p "${lang}" ${LANGUAGES}; then
continue
fi
"${fn}" "${lang}" "${units_dir}"
r=$?
done
return $r
}
help_fuzz ()
{
cat < "$onoised" 2> /dev/null
progress_offset=$(( pos % _NOISE_REPORT_MAX_COLUMN ))
if [ "${progress_offset}" -eq 0 ]; then
[ $pos -gt 0 ] && printf " %s %d/%d" "${how}" "$pos" "${len}"
echo
fi
echo "${cmdline}" > "${ocmdline}"
( exec 2>&-; ${cmdline} 2> /dev/null > /dev/null )
r=$?
case $r in
0)
printf 'o'
noise_report_line "${pos}" "${len}" "${r}" "${how}"
rm "${onoised}"
rm -f "${ovalgrind}" "${ocmdline}"
;;
${_TIMEOUT_EXIT})
printf "T"
noise_report_line "${pos}" "${len}" "${r}" "${how}"
printf '\n%-20s\n' "[timeout $lang]" "$onoised"
[ "${RUN_SHRINK}" = 'yes' ] && fuzz_shrink "${cmdline_for_shirking}" "${onoised}" "${oshrink}" "${lang}"
;;
${_VALGRIND_EXIT})
printf "V"
noise_report_line "${pos}" "${len}" "${r}" "${how}"
printf '\n%-20s %s\n' "[valgrind-error $lang]" "$onoised"
;;
*)
printf "!"
noise_report_line "${pos}" "${len}" "${r}" "${how}"
printf '\n%-20s %s\n' "[unexpected-status($r) $lang]" "$onoised"
[ "${RUN_SHRINK}" = 'yes' ] && fuzz_shrink "${cmdline_for_shirking}" "${onoised}" "${oshrink}" "${lang}"
;;
esac
return $r
}
noise_lang_file ()
{
local lang="$1"
local input="$2"
shift 2
local cmdline
local cmdline_for_shirking
local len=$(${_FSIZE} "${input}")
local r
local i
local c
local guessed_lang
guessed_lang=$( ${_CMDLINE_FOR_SHRINKING} --print-language "${input}" 2>/dev/null | sed -n 's/^.*: //p')
if [ "${lang}" != "${guessed_lang}" ]; then
return 0
fi
i=0
c='!'
echo "Testing cases derived from: ${input}"
line '.' --no-newline
while [ "$i" -lt "$len" ]; do
if noise_lang_file_noisespec "${input}" "${len}" "$i" "$c" noise_reduce "${lang}" -; then
i=$(( i + 1 ))
else
echo
return 1
fi
done
for c in 'a' '0' \
'!' '@' '#' '$' '%' '^' '&' '*' '(' ')' '-' '=' '_' \
'+' '|' '[' ']' '{' '}' '\' ';' "'" ':' '"' ',' '.' \
'/' '<' '>' '?' '`' '~'; do
i=0
while [ "$i" -lt "$len" ]; do
if noise_lang_file_noisespec "${input}" "${len}" "$i" "$c" noise_inject "${lang}" +; then
i=$(( i + 1 ))
else
echo
return 1
fi
done
done
echo
return 0
}
noise_lang ()
{
local lang="$1"
local dir="$2"
shift 2
local f
local r
printf '%-60s\n' "Noised-fuzzing (${lang})"
line '-'
r=0
for f in $(find "${dir}" -type f -name 'input.*'); do
if ! noise_lang_file "${lang}" "${f}"; then
r=1
break
fi
done
echo
return $r
}
action_noise ()
{
action_fuzz_common noise_lang "$@"
}
help_noise ()
{
cat < ${generated} 2>&1; then
run_result ok "${msg}" '/dev/null'
rm ${generated}
return 0
else
run_result error "${msg}" '/dev/null' "diff: ${generated}"
return 1
fi
}
failed_git_marker ()
{
local f=$1
local l
if type "git" > /dev/null 2>&1; then
l=$(git ls-files -- "$f")
if [ -z "$l" ]; then
echo ''
fi
fi
}
is_crashed ()
{
local f=$1
grep -q -i "core dump" "$f"
}
print_backtraces()
{
local ctags_exe=$1
shift 1
local coref
for coref in "$@"; do
if [ -f "${coref}" ]; then
gdb "${ctags_exe}" -c "${coref}" -ex where -batch
else
echo "no such file: ${coref}"
fi
done
}
CODE_FOR_IGNORING_THIS_TMAIN_TEST=77
tmain_run ()
{
local topdir=$1
local build_topdir=$2
shift 2
local units="$@"
local subdir
local basedir
local test_name
local r_failed="_failed.result"
local failed
local f
local aspect
local engine
local r
local a
local status_=0
local msg
local need_rearrange
if ! [ $(basename "${CTAGS}") = 'ctags' ]; then
need_rearrange=yes
fi
rm -f ${r_failed}
basedir=$(pwd)
for subdir in ${topdir}/*.d; do
if [ "${subdir}" = ${topdir}/'*.d' ]; then
return 1
fi
test_name=$(basename ${subdir} .d)
if [ -n "${units}" ] && ! member_p "${test_name}" ${units}; then
continue
fi
build_subdir=${build_topdir}/$(basename ${subdir})
if ! mkdir -p ${build_subdir}; then
return 1
fi
# Run this block in parallel
(
rm -f ${build_subdir}/*-actual.txt
msg="\nTesting ${test_name}"
msg="${msg}\n$(line '-')"
(
cd ${subdir}
${SHELL} run.sh \
${basedir}/${CTAGS} \
${build_subdir} \
${basedir}/${READTAGS}
) > ${build_subdir}/stdout-actual.txt 2> ${build_subdir}/stderr-actual.txt
r=$?
echo $r > ${build_subdir}/exit-actual.txt
if [ -n "${need_rearrange}" ]; then
sed -i -e 's|^'$(basename "${CTAGS}")':|ctags:|' ${build_subdir}/stderr-actual.txt
fi
if [ $r = $CODE_FOR_IGNORING_THIS_TMAIN_TEST ]; then
msg="${msg}\n$(run_result skip "" '/dev/null' "$(cat ${build_subdir}/stdout-actual.txt)")"
for a in ${build_subdir}/*-actual.txt; do
if [ -f "$a" ]; then
rm $a
fi
done
printf "%b\n" "${msg}"
exit
fi
if [ -f ${build_subdir}/tags ]; then
mv ${build_subdir}/tags ${build_subdir}/tags-actual.txt
fi
for aspect in stdout stderr exit tags; do
if [ -f ${subdir}/${aspect}-expected.txt ]; then
engine=compare
msg="${msg}\n$(tmain_${engine} ${subdir} ${build_subdir} ${aspect})"
if [ $? -eq 0 ]; then
rm ${build_subdir}/${aspect}-actual.txt
else
echo "${test_name}/${aspect}-${engine}$(failed_git_marker ${subdir}/${aspect}-expected.txt)" >> ${r_failed}
if [ ${aspect} = stderr ] &&
is_crashed ${build_subdir}/${aspect}-actual.txt &&
type "gdb" > /dev/null 2>&1; then
print_backtraces "${basedir}/${CTAGS}" \
${build_subdir}/core* \
> ${build_subdir}/gdb-backtrace.txt
fi
fi
elif [ -f ${build_subdir}/${aspect}-actual.txt ]; then
rm ${build_subdir}/${aspect}-actual.txt
fi
done
printf "%b\n" "${msg}"
) &
done
wait
if [ -f "${r_failed}" ]; then
status_=1
echo
echo Failed tests
line '='
failed=$(cat ${r_failed})
for f in ${failed}; do
echo $f | sed -e 's|| (not committed/cached yet)|'
done
echo
if [ "${SHOW_DIFF_OUTPUT}" = yes ]; then
engine=compare
echo Detail "[$engine]"
line '-'
tmain_${engine}_result ${build_topdir}
fi
rm ${r_failed}
fi
return $status_
}
action_tmain ()
{
local action="$1"
shift
local tmain_dir
local build_dir
while [ $# -gt 0 ]; do
case $1 in
--ctags)
shift
CTAGS="$1"
shift
;;
--ctags=*)
CTAGS="${1#--ctags=}"
shift
;;
--colorized-output)
shift
COLORIZED_OUTPUT="$1"
shift
;;
--colorized-output=*)
COLORIZED_OUTPUT="${1#--colorized-output=}"
shift
;;
--with-valgrind)
shift
WITH_VALGRIND=yes
;;
--show-diff-output)
SHOW_DIFF_OUTPUT=yes
shift
;;
--readtags=*)
READTAGS="${1#--readtags=}"
shift
;;
--units)
shift
UNITS=$(echo "$1" | tr ',' ' ')
shift
;;
--units=*)
UNITS=$(echo "${1#--units=}" | tr ',' ' ')
shift
;;
-*)
ERROR 1 "unknown option \"${1}\" for ${action} action"
;;
*)
tmain_dir="$1"
shift
build_dir=${1:-${tmain_dir}}
if [ -n "$1" ]; then
shift
fi
break
;;
esac
done
if [ $# -gt 0 ]; then
ERROR 1 "too many arguments for ${action} action: $*"
elif [ -z "$tmain_dir" ]; then
ERROR 1 "TMAIN_DIR parameter is not given in ${action} action"
fi
if ! [ -d "$tmain_dir" ]; then
ERROR 1 "No such directory(tmain_dir): ${tmain_dir}"
fi
case "${build_dir}" in
/*) ;;
*) build_dir=$(pwd)/${build_dir} ;;
esac
if ! [ -d "$build_dir" ]; then
ERROR 1 "No such directory(build_dir): ${build_dir}"
fi
if ! [ -f "${CTAGS}" ]; then
ERROR 1 "no such file: ${CTAGS}"
elif ! [ -e "${CTAGS}" ]; then
ERROR 1 "${CTAGS} is not an executable file"
fi
if ! ( [ "${COLORIZED_OUTPUT}" = 'yes' ] || [ "${COLORIZED_OUTPUT}" = 'no' ] ); then
ERROR 1 "unexpected option argument for --colorized-output: ${COLORIZED_OUTPUT}"
fi
check_availability awk
check_availability diff
tmain_run ${tmain_dir} ${build_dir} ${UNITS}
return $?
}
help_tmain ()
{
cat </dev/null | sed -n 's/^.*: //p')
if [ "${lang}" != "${guessed_lang}" ]; then
return 0
fi
i=0
echo "Testing cases derived from: ${input}"
line '.' --no-newline
r=0
while [ "$i" -lt "$len" ]; do
if chop_lang_file_chopspec "${endpoint}" "${input}" "${len}" "$i" "${lang}"; then
i=$(( i + 1 ))
else
r=1
break
fi
done
echo
return $r
}
chop()
{
local endpoint=$1
local input=$2
local pos=$3
local len=$4
if [ "${endpoint}" = "tail" ]; then
dd if=$input bs=1 count=$pos
else
dd if=$input bs=1 count=$((len - pos)) skip=$pos
fi
}
chop_lang_file_chopspec()
{
local endpoint=$1
shift 1
local input="$1"
local len="$2"
local pos="$3"
local lang="$4"
shift 4
local dir="${input%/*}"
local ochopped=$(printf "%s/CHOP-INPUT-%s.tmp" "${dir}" "$pos")
local ocmdline=$(printf "%s/CHOP-CMDLINE-%s.tmp" "${dir}" "$pos")
local ovalgrind=$(printf "%s/CHOP-VALGRIND-%s.tmp" "${dir}" "$pos")
local oshrink=$(printf "%s/CHOP-SHRINK-%s.tmp" "${dir}" "$pos")
local cmdline
local cmdline_for_shirking
local progress_offset
local r
rm -f "${ocmdline}" "${ovalgrind}" "${ochopped}" "${oshrink}"
if [ "${WITH_VALGRIND}" = 'yes' ]; then
cmdline=$( printf "${_CMDLINE} --language-force=${lang} ${ochopped}" "${ovalgrind}" )
else
cmdline="${_CMDLINE} --language-force=${lang} ${ochopped}"
fi
cmdline_for_shirking="${_CMDLINE_FOR_SHRINKING} --language-force=${lang} %s"
chop "${endpoint}" "${input}" "${pos}" "${len}" > "$ochopped" 2> /dev/null
progress_offset=$(( pos % _NOISE_REPORT_MAX_COLUMN ))
if [ "${progress_offset}" -eq 0 ]; then
[ $pos -gt 0 ] && printf " %d/%d" "$pos" "${len}"
echo
fi
echo "${cmdline}" > "${ocmdline}"
( exec 2>&-; ${cmdline} 2> /dev/null > /dev/null )
r=$?
case $r in
0)
printf 'o'
noise_report_line "${pos}" "${len}" "${r}" ""
rm "${ochopped}"
rm -f "${ovalgrind}" "${ocmdline}"
;;
${_TIMEOUT_EXIT})
printf "T"
noise_report_line "${pos}" "${len}" "${r}" ""
printf '\n%-20s\n' "[timeout $lang]" "$ochopped"
[ "${RUN_SHRINK}" = 'yes' ] && fuzz_shrink "${cmdline_for_shirking}" "${ochopped}" "${oshrink}" "${lang}"
;;
${_VALGRIND_EXIT})
printf "V"
noise_report_line "${pos}" "${len}" "${r}" ""
printf '\n%-20s %s\n' "[valgrind-error $lang]" "$ochopped"
;;
*)
printf "!"
noise_report_line "${pos}" "${len}" "${r}" ""
printf '\n%-20s %s\n' "[unexpected-status($r) $lang]" "$ochopped"
[ "${RUN_SHRINK}" = 'yes' ] && fuzz_shrink "${cmdline_for_shirking}" "${ochopped}" "${oshrink}" "${lang}"
;;
esac
return $r
}
help_validate_input ()
{
cat <&2
exit 1
fi
case $1 in
help|-h|--help)
action_help
return 0
;;
run)
action_run "$@"
return $?
;;
clean)
action_clean "$@"
return $?
;;
fuzz)
action_fuzz "$@"
return $?
;;
noise)
action_noise "$@"
return $?
;;
tmain)
action_tmain "$@"
return $?
;;
clean-tmain)
action_clean_tmain "$@"
return $?
;;
shrink)
action_shrink "$@"
return $?
;;
chop)
action_chop "$@"
return $?
;;
slap)
action_chop "$@"
return $?
;;
validate-input)
action_validate_input "$@"
return $?
;;
*)
ERROR 1 "unknown action: $1"
;;
esac
}
prepare_environment
main "$@"
exit $?