#!/bin/ksh
######################################################################
# Program:	dcloud_test
# Purpose:	Exercises various dcloud functions to make sure
#		they work correctly.
# Arguments:	A list of tests to perform.  If no list is given,
#		exercises all tests.
# Author:	Perette Barella
#---------------------------------------------------------------------

arg0=$(basename $0)

TPUT_BAD=$(tput setaf 1)
TPUT_GOOD=$(tput setaf 2)
TPUT_WARN=$(tput setaf 5)
TPUT_BOLD=$(tput bold)
TPUT_RESET=$(tput sgr0)

# Process command line options
VERBOSE=false
VERBOSEFLAG=""
BREAKONFAIL=false
USAGE=false
LISTTESTS=false
PIANOD_OPTIONS=""
SCRIPTS="./src/"
while getopts 'bvhls:' option
do
	case "$option" in
		s)	SCRIPTS="$OPTARG" ;;
		l)	LISTTESTS=true ;;
		v)	[ "$VERBOSEFLAG" != "" ] && PIANOD_OPTIONS="-Z 0x2bf"
			$VERBOSE && VERBOSEFLAG="-v"
			VERBOSE=true ;;
		b)	BREAKONFAIL=true ;;
		*)	USAGE=true ;;
	esac
done
shift $(($OPTIND - 1))


# Linux mktemp makes one temp file by default, but
# some mktemp implementations require a file list
# or they won't do anything.
function make_temp {
	mktemp "$@" "/var/tmp/$arg0.$$XXXXXX"
}

function grouping {
	print "${TPUT_BOLD}$*${TPUT_RESET}"
}

function warning {
	print -- "${TPUT_WARN}$*${TPUT_RESET}"
}

function skip {
	print -- "${TPUT_WARN}$*${TPUT_RESET}"
	SKIP_TEST=true
}

function success {
	[ "$1" = "-h" ] && shift && print -n -- "$TPUT_BOLD"
	print -- "${TPUT_GOOD}$*${TPUT_RESET}"
}

function failure {
	[ "$1" = "-h" ] && shift && print -n -- "$TPUT_BOLD"
	print -- "${TPUT_BAD}$*${TPUT_RESET}"
}

function fail {
	failure "$TEST_ID: Failed: $*"
	TEST_OK=false
}

function require {
	typeset utility
	status=0
	for utility
	do
		if ! whence -q "$utility"
		then
			status=1
			skip "Skipping test: $utility not found, required for test."
		fi
	done
	return $status
}

function compare_results {
	typeset -r test_name="$(basename "$1")"
	typeset -r expected_output="$1"
	if [ -d "$2" ]
	then
		typeset -r actual_output="$2/$test_name"
	else
		typeset -r actual_output="$2"
	fi

	if [[ ! -f "$expected_output" ]]
	then
		fail "$test_name: $expected_output: Reference document not found."
		return 1
	fi

	if [[ ! -f "$actual_output" ]]
	then
		fail "$test_name: Test output was not generated."
		return 1
	fi

	typeset diffs
	if diffs="$(diff -u "$expected_output" "$actual_output")"
	then
		success "$test_name: Output matches that expected."
	else
		fail "$test_name: Unexpected output."
		print "$diffs"
	fi
}
	

function compare_output {
	typeset -r command="$SCRIPTS/$1"
	typeset -r test_name="${2}${2:+: }"
	typeset -r comparison_file="${TESTDATA}/${2:-$TEST_ID}.stdout"
	typeset -r comparison_stderr="${TESTDATA}/${2:-$TEST_ID}.stderr"
	if [[ ! -f "$comparison_file" ]]
	then
		fail "$comparison_file: Reference document not found."
		return 1
	fi
	typeset tmp1="$(make_temp)"
	typeset tmp2="$(make_temp)"
	eval "DCLOUD='$PWD/$TESTDATA' DCLOUD_BACKEND=none $command > '$tmp1' 2> '$tmp2'"
	integer -r status=$?

	typeset error=false
	if (( $status > 0))
	then
		fail "${test_name}: Test program exited with error status $status."
		error=true
	fi
	if [[ -s "$tmp2" || -s "$comparision_stderr" ]]
	then
		if [[ ! -f "$comparison_stderr" ]]
		then
			fail "${test_name}Unexpected output on stderr."
			error=true
		elif ! diff -u "$comparison_stderr" "$tmp2"
		then
			fail "${test_name}Stdout incorrect."
			error=true
		fi
	fi

	if diff -u "$comparison_file" "$tmp1"
	then
		$error || success "${test_name}Output matches expected."
	else
		fail "${test_name}Output incorrect."
	fi
	rm -f "$tmp1" "$tmp2"
}



function verify_error {
	typeset -r command="$SCRIPTS/$1"
	typeset -r test_name="${2}${2:+: }"
	eval "DCLOUD='$PWD/$TESTDATA' DCLOUD_BACKEND=none $command >/dev/null 2>&1"
	integer -r status=$?
	if (( $status > 0 ))
	then
		success "${test_name}Correctly exited with error condition ($status)."
	else
		fail "${test_name}Exited with 0 status when error was expected."
	fi
}


function compare_ics_files {
	typeset -r tmp1=/var/tmp/$arg0.cmp.$$.1.ics
	typeset -r tmp2=/var/tmp/$arg0.cmp.$$.2.ics

	egrep -v "^(UID|DTSTAMP):" "$1" | sort -o "$tmp1"
	egrep -v "^(UID|DTSTAMP):" "$2" | sort -o "$tmp2"

	diff -u "$tmp1" "$tmp2"
	result=$?
	rm -f "$tmp1" "$tmp2"
	return $result
}


function compare_html_files {
	typeset -r tmp1=/var/tmp/$arg0.cmp.$$.1.html
	typeset -r tmp2=/var/tmp/$arg0.cmp.$$.2.html

	sed "s/datetime='[^']*'//g" "$1" > "$tmp1" || fail "sed exited uncleanly"
	sed "s/datetime='[^']*'//g" "$2" > "$tmp2" || fail "sed exited uncleanly"

	diff -u "$tmp1" "$tmp2"
	result=$?
	rm -f "$tmp1" "$tmp2"
	return $result
}



# Check that if we generate an HTML calendar direct from a queue file,
# it comes out the same as if we generate and ICS from the queue, and HTML
# from that.
function test_20_queue_matches_ics_html {
	typeset -r queue="$TESTDATA/queue.txt"
	typeset -r queuetmp=/var/tmp/$arg0.$$.1.html
	typeset -r icstmp=/var/tmp/$arg0.$$.1.ics
	typeset -r htmltmp=/var/tmp/$arg0.$$.2.html

	trap "rm -f '$queuetmp' '$icstmp' '$htmltmp'" exit

	$SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$queuetmp" -o "$icstmp" -d 2 -l month -u - -t -r 12 ||
		fail "queue2ics.py exited uncleanly."
	$SCRIPTS/queue2ics.py -s "$icstmp" -o "$htmltmp" -d 2 -l month -u - -t -r 12 ||
		fail "queue2ics.py exited uncleanly."
	diff -u "$queuetmp" "$htmltmp" || fail "HTML from queue does not match HTML with ICS intermediary"
}



# Check that when processing a queue file in different time zones,
# we don't get the same ICS data.
function test_21_time_zone_instability {
	typeset -r queue="$TESTDATA/queue.txt"
	typeset -r reftmp=/var/tmp/$arg0.$$.1.ics
	typeset -r checktmp=/var/tmp/$arg0.$$.2.ics
	typeset -r paramtmp=/var/tmp/$arg0.$$.3.ics
	typeset zone

	trap "rm -f '$reftmp' '$checktmp' '$paramtmp'" exit

	TZ=UTC $SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$reftmp" -d 2 -l month -u - -t -r 12 ||
			fail "queue2ics.py exited uncleanly."

	for zone in US/Eastern US/Hawaii Turkey Japan
	do
		grouping "Checking $zone"
		# Make sure different time zones work
		TZ="$zone" $SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$checktmp" -d 2 -l month -u - -t -r 12 ||
			fail "queue2ics.py exited uncleanly."
		cmp -s "$reftmp" "$checktmp" && fail "ICS files identical for UTC and $zone."
		grouping "Checking --input-timezone works same as TZ envvar"
		$SCRIPTS/queue2ics.py --input-timezone="$zone" -c personal -C 'Holiday|Work' -s "$queue" -o "$paramtmp" -d 2 -l month -u - -t -r 12 ||
			fail "queue2ics.py exited uncleanly."
		compare_ics_files "$checktmp" "$paramtmp" || fail "ICS files identical for UTC and $zone."
	done
}



# Check that when we generate HTML calendars from a given queue file
# but in different time zones, they produce the same results.
function test_22_time_zone_stability {
	typeset -r queue="$TESTDATA/queue.txt"
	typeset -r reftmp=/var/tmp/$arg0.$$.1.html
	typeset -r checktmp=/var/tmp/$arg0.$$.3.html
	typeset zone

	trap "rm -f '$reftmp' '$checktmp'" exit

	# Generate original using current time zone.  Doesn't matter what it is.
	$SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$reftmp" -d 2 -l month -u - -t -r 12 ||
		fail "queue2ics.py exited uncleanly"

	for zone in US/Eastern US/Hawaii Turkey Japan
	do
		grouping "Checking $zone"
		TZ="$zone" $SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$checktmp" -d 2 -l month -u - -t -r 12 ||
			fail "queue2ics.py exited uncleanly"
		compare_html_files "$reftmp" "$checktmp" || fail "Calendar changed between current zone and $zone."
	done
}



# Make sure we generate the correct HTML calendar from a queue.
function test_23_queue_to_html {
	typeset -r queue="$TESTDATA/queue.txt"
	typeset -r tmp=/var/tmp/$arg0.$$.1.html

	trap "rm -f '$tmp'" exit

	# Generate original using current time zone.  Doesn't matter what it is.
	$SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$tmp" -d 2 -l month -u - -t -r 12 ||
		fail "queue2ics.py exited uncleanly"

	diff -u "$TESTDATA/$TEST_ID.html" "$tmp" ||
		fail "HTML does not match expected output."
}

# Make sure we generate the correct HTML calendar from a queue.
function test_24_queue_to_html_with_timezone {
	typeset -r queue="$TESTDATA/queue.txt"
	typeset -r tmp=/var/tmp/$arg0.$$.1.html

	trap "rm -f '$tmp'" exit

	grouping "Moving one time zone to the west"
	$SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$tmp" -d 2 -l month -u - -t -r 12 --input-timezone='US/Mountain' --output-timezone='US/Pacific' ||
		fail "queue2ics.py exited uncleanly"
	diff -u "$TESTDATA/$TEST_ID-west.html" "$tmp" ||
		fail "HTML does not match expected output."

	grouping "Check moving one time zone to the east"
	$SCRIPTS/queue2ics.py -c personal -C 'Holiday|Work' -s "$queue" -o "$tmp" -d 2 -l month -u - -t -r 12 --input-timezone='US/Central' --output-timezone='US/Eastern' ||
		fail "queue2ics.py exited uncleanly"
	diff -u "$TESTDATA/$TEST_ID-east.html" "$tmp" ||
		fail "HTML does not match expected output."
}

# Run the output through an HTML validator
function test_25_validate_html {
	typeset -r validator="/usr/local/lib/node_modules/vnu-jar/build/dist/vnu.jar"
	if ! whence -q java
	then
		skip "Java not available for validator."
		return 1
	fi
	if [[ ! -f "$validator" ]]
	then
		skip "Validator.nu not not found at $validator."
		return 1
	fi
	# Borrow another test's reference output rather than make our own.
	java -jar "$validator" "$TESTDATA/test_23_queue_to_html.html" ||
		fail "Validation exited with an error."
}

function test_26_queue_to_ics_and_back {
	typeset -r queue="$TESTDATA/queue.txt"
	typeset -r ics=/var/tmp/$arg0.$$.1.ics
	typeset -r back=/var/tmp/$arg0.$$.1.que

	trap "rm -f '$ics' '$back'" exit

	grouping "Testing queue-to-queue"
	$SCRIPTS/queue2ics.py -k -s "$queue" -o "$back" ||
		fail "queue2ics.py exited uncleanly (direct to ICS)"
	diff -u "$TESTDATA/$TEST_ID.que" "$back" ||
		fail "Resulting file does not match expected output."

	grouping "Testing queue to queue via ICS"
	$SCRIPTS/queue2ics.py -C 'Holiday|Work' -k -s "$queue" -o "$ics" ||
		fail "queue2ics.py exited uncleanly (to ICS)"
	$SCRIPTS/queue2ics.py -s "$ics" -o "$back" ||
		fail "queue2ics.py exited uncleanly (to queue)"
	diff -u "$TESTDATA/$TEST_ID.que" "$back"
}


function test_27_queue_to_ics_with_details_file {
	typeset -r output="$(make_temp).html"
	trap "rm '$output'" exit

	$SCRIPTS/queue2ics.py -s "$TESTDATA/queue.txt" -m "$TESTDATA/detail-map.txt" -o "$output"
	compare_results "$TESTDATA/output.html" "$output"
	
}





function test_30_now {
	compare_output "now -f '$TESTDATA/queue.txt' --past" width-80
	compare_output "now -f '$TESTDATA/queue.txt' --past --width 50" width-50
	compare_output "now -f '$TESTDATA/queue.txt' --past --events=5" event-count
	compare_output "now -f '$TESTDATA/queue.txt' --past --incomplete" incomplete
	compare_output "now -f '$TESTDATA/queue.txt' --past --complete" complete
	compare_output "now -f '$TESTDATA/queue.txt' --past --not-complete" not-complete
	compare_output "now -f '$TESTDATA/queue.txt' --since 2018-12-01 --until 2018-12-31" date-range
	compare_output "now -f '$TESTDATA/queue.txt' --since 2018-12-01 --until 2018-12-31 --unformatted" unformatted

	compare_output "now -f '$TESTDATA/queue.txt' --past new eve" matching
	compare_output "now -f '$TESTDATA/queue.txt' --past --tally cirque fit" tallying
	compare_output "now -f '$TESTDATA/queue.txt' --tally --since 2018-12-01 --until 2018-12-31 holiday" tallying-2

	verify_error "now --bakayaro" invalid-option
	verify_error "now --since 2018-ab-cd" invalid-date
	verify_error "now -f non-existent" non-existent-file
	verify_error "now -e fish" require-numeric-count
	verify_error "now -w fish" require-numeric-width
}

function test_31_pn {
	compare_output "pn --no-goobook dmv" full-word
	compare_output "pn --no-goobook micro" partial-word
	compare_output "pn --no-goobook monroe traffic" multi-word
	compare_output "pn --no-goobook bike" multi-entry
	compare_output "pn --no-goobook park bike henrietta" multi-line
}



function test_41_text_probe {
	typeset -r output="$(make_temp -d)"

	trap "rm -rf '$output'" exit

	$SCRIPTS/publish_cloud.py "$TESTDATA/Input" "$output"
	compare_results "$TESTDATA/plain-text-no-extension.html" "$output"
	compare_results "$TESTDATA/plain-text-with-extension.html" "$output"
	compare_results "$TESTDATA/plain-text-with-title.html" "$output"

	# Validate document title extraction
	compare_results "$TESTDATA/index.html" "$output"
}

function test_42_markdown_probe {
	require multimarkdown || return
	typeset -r output="$(make_temp -d)"

	trap "rm -rf '$output'" exit

	$SCRIPTS/publish_cloud.py "$TESTDATA/Input" "$output"
	compare_results "$TESTDATA/filename.html" "$output"
	compare_results "$TESTDATA/metadata-format.html" "$output"
	compare_results "$TESTDATA/underline.html" "$output"
	compare_results "$TESTDATA/pound-heading.html" "$output"

	# Validate document title extraction
	compare_results "$TESTDATA/index.html" "$output"
}

# Test probe for reStructuredText
function test_43_restructured_probe {
	whence -q rst2html.py || require rst2html || return
	typeset -r output="$(make_temp -d)"

	trap "rm -rf '$output'" exit

	$SCRIPTS/publish_cloud.py "$TESTDATA/Input" "$output"
	compare_results "$TESTDATA/filename.html" "$output"
	compare_results "$TESTDATA/metadata-format.html" "$output"
	compare_results "$TESTDATA/overline.html" "$output"
	compare_results "$TESTDATA/underline.html" "$output"

	# Validate document title extraction
	compare_results "$TESTDATA/index.html" "$output"
}


# Test index generation
function test_47_indexing {
	typeset -r input="$(make_temp -d)"
	typeset -r output="$(make_temp -d)"
	typeset file title tit base path manner

	trap "rm -rf '$input' '$output'" exit

	# Generate some test files.
	for file in top_level_file another_top_level_file zee_last_top_level_file \
		Folder/folders_first_document Folder/folders_second_document \
		Second_folder/Folders_first_document Second_folder/Folders_second_document \
		Second_folder/Folders_third_document \
		Deep_folder/folders_first_document Deep_folder/folders_second_document \
		Deep_folder/Subfolder/first_document Deep_folder/Subfolder/second_document
	do
		typeset target="$input/$file"
		path="$(dirname "$file")"
		if [[ "$path" != "." ]]
		then
			mkdir -p "$input/$path"
		fi
		title=""
		for tit in $(basename "${file//_/ }")
		do
			title="${title}${title:+ }$(print -- "${tit:0:1}" | tr 'a-z' 'A-Z')${tit:1}"
		done
		print -- "$title" > "$target"
		print >> "$target"
		print "This is the body of the document $file." >> "$target"
	done

	for manner in type name
	do
		$SCRIPTS/publish_cloud.py -s "$manner" "$input" "$output" ||
			fail "publish_cloud.py exited with non-0 status."
		# Enable line below to snapshot new output.
		# Comment again after manually verifying all output.
		# cp "$output/index.html" "$TESTDATA/index-$manner.html"
		compare_results "$TESTDATA/index-$manner.html" "$output/index.html"
	done
}




if $USAGE
then
	print -- "$arg0: Usage: $arg0 [-v] [-b] [test] ..."
	print "  -v : verbose; always display pianod session output."
	print "  -b : break on failed test, and leave intermediate files."
	print "  -l : list tests"
fi

if $USAGE || $LISTTESTS
then
	print "Tests available:"
	functions | grep '^function test' | awk '{print $2}' | sed 's/^test_/	/'
	$USAGE && exit 1
	exit 0
fi

if [ $# -gt 0 ]
then
	for item in "$@"
	do
		test_list="$test_list test_$item"
	done
else
	test_list=$(functions | grep '^function test' | awk '{print $2}')
fi

print -- "=========================================================="
grouping "dcloud test report"
print -- "=========================================================="
print -- "Performed: $(date)"
print -- "On: $(uname -a)"
print -- "Korn shell: ${.sh.version}"
print -- "Python: $(python3 --version 2>&1)"
print

failed_tests=""
skipped_tests=""
for TEST_ID in $test_list
do
	print -- "=========================================================="
	grouping "Start of test ${TEST_ID#test_}"
	print -- "=========================================================="
	if ! functions "$TEST_ID" >/dev/null
	then
		failed_tests="$failed_tests ${TEST_ID#test_}"
		failure "TEST NOT FOUND!"
		continue
	fi
	SKIP_TEST=false
	TEST_OK=true
	TESTDATA="TestData/${TEST_ID}"
	[ ! -d "$TESTDATA" ] &&
		TESTDATA="TestData/Generic"
	eval "$TEST_ID"
	if $SKIP_TEST
	then
		skipped_tests="$skipped_tests ${TEST_ID#test_}"
	elif $TEST_OK
	then
		success "Test passed."
	else
		failed_tests="$failed_tests ${TEST_ID#test_}"
		failure -h "TEST FAILED!"
		$BREAKONFAIL && exit 1
	fi
	print; print; print
done

print -- "=========================================================="
grouping "Test Summary"
print -- "=========================================================="
print

if [ "$skipped_tests" != "" ]
then
	print "The following tests were skipped:"
	for test_id in $skipped_tests
	do
		print -- "\t${test_id}"
	done
fi

if [ "$failed_tests" = "" ]
then
	[ $# -gt 0 ] && success -h "Requested tests passed."
	[ $# -eq 0 ] && success -h "All tests passed."
	exit 0
else
	print "The following tests failed:"
	for test_id in $failed_tests
	do
		print -- "\t${test_id}"
	done
	exit 1
fi

