#!/bin/sh
# Please consider this file to be under a permissive free/libre/open-source
# modified-BSD-style license (and tell me the appropriate legalese to make it
# so, since I'm not sure how to do that, myself)

# tested and modified to work with `busybox ash` as well as bash
#  (by adding the full path to places where programs need their full GNU
#   options -- grep -o and sed -i -- so it does require the GNU versions...;
#   and using >/dev/null 2>&1 instead of &>/dev/null)

if [ "$1" = "--help" -o "$1" = "-help" -o "$1" = "-h" -o "$1" = "-?" -o -z "$1" ]
then
	echo "Usage: $0 HaskellFileName.[l]hs"
	echo "Tests QuickCheck properties identified by starting with 'prop_' or '_prop_'"
	echo "You must import Test.QuickCheck.  Also, one line will be added to the"
	echo "file if it is not already there."
	exit 0
elif [ "$1" = "--version" -o "$1" = "-version" ]
then
	echo "a bourne-shell quickcheck-tester that is probably a buggy version."
	echo "seriously speaking, I'll call this version 0.1"
	exit 0
fi

# Try to make sorting order consistent between implementations or systems
# so that the file doesn't need to be _changed_ just to reorder some stuff.
LC_COLLATE="C"
export LC_COLLATE

REAL_GREP="$(which grep)"
grep_o() {
	"$REAL_GREP" -o "$@"
}
REAL_SED="$(which sed)"
sed_i() {
	"$REAL_SED" -i "$@"
}

PROP_PREFIX_REGEX='_\?prop_'
PROP_REGEX="${PROP_PREFIX_REGEX}[[:alnum:]_']*"

echo "collecting information..." >&2
TEST_FILE_NAME="$1"
TESTMAIN_NAME=_automatedQuickCheckMainWhichCanBeDeletedWheneverYouWantButPleaseDoNotChangeThisLineBecauseItMightConfuseTheStupidScriptThatProcessesIt
PROP_NAMES="$(grep_o "^>\?[[:space:]]*${PROP_REGEX}" "$TEST_FILE_NAME" | grep_o "${PROP_REGEX}" | sort | uniq)"

MODULE_NAME="$(grep_o "module[[:space:]]\+[[:upper:]_][[:alnum:]_'.]*" "$TEST_FILE_NAME" | grep_o "[[:upper:]_][[:alnum:]_'.]*"'$')"
QUICKCHECK_IMPORT="$(grep "import[[:space:]]\+\(qualified[[:space:]]\+\)\?Test.QuickCheck" "$TEST_FILE_NAME")"
if echo "$TEST_FILE_NAME" | grep '\.lhs$' >/dev/null 2>&1
then WEIRD_PREFIX='> '
else WEIRD_PREFIX=''
fi

EXITING=no
if [ -z "$PROP_NAMES" ]
then
	echo "$0: No QuickCheck properties found in ${TEST_FILE_NAME}!" >&2
	EXITING=0
elif [ -z "$QUICKCHECK_IMPORT" ]
then
	echo "$0: You must import Test.QuickCheck in the module you are testing." >&2
	echo "'qualified' and/or 'as' another name is okay. For example," >&2
	echo "${WEIRD_PREFIX}import qualified Test.QuickCheck" >&2
	EXITING=1
fi
if [ "$EXITING" != no ]
then
	echo "exiting" >&2
	exit "$EXITING"
fi


QUICKCHECK_AS="$(echo "$QUICKCHECK_IMPORT" | grep_o "[[:space:]]\+as[[:space:]]\+[[:upper:]_][[:alnum:]_'.]*" | head -n 1 | grep_o "[[:upper:]_][[:alnum:]_'.]*"'$')"
if [ -n "$QUICKCHECK_AS" ]
then QUICKCHECK_MODULE_NAME="$QUICKCHECK_AS"
else QUICKCHECK_MODULE_NAME="Test.QuickCheck"
fi

# Could TESTMAIN_BODY also add, at the end, type-signatures for all prop_s
#that don't have them already, to dispose of uninteresting warnings?
#How would it find out the types? hugs can do it well enough. Too bad
#it can't be like 'prop_x :: ignored' to skip a particular warning...
#OBVIOUS_TOP_LEVEL_SIGNEDS="$(grep_o "^>\?[[:space:][:alnum:]_']\+::" ${TEST_FILE_NAME})"
PROPS_WITH_TYPE_SIGNATURES="$(grep_o "^>\?[[:space:][:alnum:]_']\+::" ${TEST_FILE_NAME} | grep_o "${PROP_REGEX}")"
# | sort | uniq
PROPS_WITHOUT_TYPE_SIGNATURES="$( (echo "$PROP_NAMES"; echo "$PROPS_WITH_TYPE_SIGNATURES") | sort | uniq -u)"


TEST_DIRNAME="_test"
mkdir -p "$TEST_DIRNAME"
TEST_EXECUTABLE_NAME="${TEST_DIRNAME}/_test.exe"
TEST_FILEBACKUPNAME="${TEST_DIRNAME}/_orig.bak"
TEST_LOG_NAME="${TEST_DIRNAME}/_testlog.txt"
TEST_GHC_OUTPUT_DIR="${TEST_DIRNAME}/ghc-$(ghc --numeric-version)"
mkdir -p "$TEST_GHC_OUTPUT_DIR"


ORIGINAL_ADDEDBODY="$(grep_o "$TESTMAIN_NAME = .*" "${TEST_FILE_NAME}" | sed "s/$TESTMAIN_NAME = //")"
cp -p "${TEST_FILE_NAME}" "$TEST_FILEBACKUPNAME"

if [ -n "$PROPS_WITHOUT_TYPE_SIGNATURES" ] && which hugs >/dev/null
then
	echo "finding type signatures with hugs (first deleting"
	echo "   any old generated type-signature information) ..."
	sed_i "s/$TESTMAIN_NAME = .*/$TESTMAIN_NAME = return ();/" "$TEST_FILE_NAME"
	PROP_TYPE_SIGNATURES="$(
		(for prop in $PROPS_WITHOUT_TYPE_SIGNATURES
		 do echo ":type $prop"
		 done) | hugs -98 "${TEST_FILE_NAME}" |
		  grep_o "${PROP_REGEX}[[:space:]]*::.*" | tr '\n' ';')"
fi

TESTMAIN_HEAD="${WEIRD_PREFIX}$TESTMAIN_NAME :: IO (); $TESTMAIN_NAME = "
# t_p: short for test_prop (where prop is short for proposition)
TESTMAIN_BODY="let { t_p name prop = do { putStr (\"testing \"++name++\"...\"); ${QUICKCHECK_MODULE_NAME}.quickCheck prop } } in do { $(
for prop in $PROP_NAMES
do
echo -n "t_p \"$prop\" $prop; "
done
) };${PROP_TYPE_SIGNATURES}"

if [ "$ORIGINAL_ADDEDBODY" = "$TESTMAIN_BODY" ]
then
	echo "editing file (by restoring identical original) ..." >&2
	mv -f "$TEST_FILEBACKUPNAME" "$TEST_FILE_NAME"
else
	echo "editing file..." >&2
	if grep "$TESTMAIN_NAME" "$TEST_FILE_NAME" >/dev/null 2>&1
	then
		sed_i "s/$TESTMAIN_NAME = .*/$TESTMAIN_NAME = $TESTMAIN_BODY/" "$TEST_FILE_NAME"
	else
		#Make sure to look separated (and to be far enough apart for lhs files)
		echo >> "$TEST_FILE_NAME"
		echo >> "$TEST_FILE_NAME"
		echo "${TESTMAIN_HEAD}${TESTMAIN_BODY}" >> "$TEST_FILE_NAME"
	fi
fi


#save verbosity for later
MESSAGE_TARGET="/dev/null"

#compiling-command perhaps can be replaced with a Cabal executable target,
#though that requires Cabal rather than just this script (and -main-is substitute?)
(
 (echo "compiling..." >&2 ; ghc --make -hidir "$TEST_GHC_OUTPUT_DIR" -odir "$TEST_GHC_OUTPUT_DIR" -Wall -Werror -package QuickCheck -main-is "${MODULE_NAME}.${TESTMAIN_NAME}" -o "$TEST_EXECUTABLE_NAME" "$TEST_FILE_NAME") &&
 (echo "running..." >&2 ; echo >&2 ; ( ./"$TEST_EXECUTABLE_NAME" | tee "$TEST_LOG_NAME" > "$MESSAGE_TARGET" ) &&
  ( ERRORS="$(grep -v "OK, passed" "$TEST_LOG_NAME" | sed 's/^testing //')" ;
    if [ -n "$ERRORS" ]; then ( echo 'ERRORS:'; echo "$ERRORS" ) ; else ( echo "OK" ); fi))) || echo "UNSUCCESSFUL" >&2
#for faster compile and slower running, use this:
#echo "$TESTMAIN_NAME" | hugs
#I expect Yhc could be a better compromise for normal test-amounts...

