#! /bin/sh -e

# Full test suite.  Run libpqxx tests against a rich sampling of available
# compilers, libpq versions, and backend versions.

TESTDIR="$HOME/tmp/libpqxxtest"
TMPDIR="$TESTDIR/tmp"
OPTIMIZING_COMPILERS="g++ clang++"

POSTGRES_VERSION_PATTERN="^v[91][0-9A-Za-z_.-]\+"
POSTGRES_FTP_SERVER="${PG_FTPSERVER:-ftp.postgresql.org}"
POSTGRES_FTP_DIR="${PG_FTPDIR:-pub/source}"

if which nproc >/dev/null
then
        PROCESSORS=$(nproc)
else
        PROCESSORS=2
fi

CONCURRENCY_LEVEL="${CONCURRENCY_LEVEL:-${PROCESSORS}}"

mkdir -p "$TMPDIR"

log_message() {
	echo "\n"
	date --iso-8601=second
	echo "*** $1 ***"
	echo
}


check_for_compilers() {
	local POTENTIAL_COMPILER 
	for POTENTIAL_COMPILER in $1
	do
		which "$POTENTIAL_COMPILER"
	done
}


get_tarball() {
	# Print full path of libpqxx tarball in current directory.
	echo "$(pwd)/libpqxx-$(cat VERSION).tar.gz"
}

build_tarball() {
	# Produce a lipqxx tarball.
	log_message "Creating tarball"
	rm -f -- "$(get_tarball)"
	env CXXFLAGS= ./autogen.sh
	make dist -j$CONCURRENCY_LEVEL
}


unpack_tarball() {
	# Unpack libpqxx tarball, set it up for building.
	tar xf "$1"
	echo "$(pwd)/$(ls -c -d -v libpqxx-* | tail -n1)"
}


find_backend_dir()  {
	local FBD_OLD_PWD
	FBD_OLD_PWD="$(pwd)"
	cd -- "$TESTDIR/pg/$1"
	pwd
	cd -- "$FBD_OLD_PWD"
}


locate_pidfile() {
	echo "$(find_backend_dir "$1")/postgres.pid"
}


start_backend() {
	# Start up a PostgreSQL backend.
	local ARG_BACKEND BACKEND_DIR SB_OLD_PWD
	log_message "Starting backend $1"
	ARG_BACKEND="$1"
	BACKEND_DIR="$(find_backend_dir "$ARG_BACKEND")"
	SB_OLD_PWD="$(pwd)"
	cd "$TESTDIR/pg"
	if test -f "$BACKEND_DIR/PQXX_SKIP"
	then
		echo "\n\nSkipping backend $ARG_BACKEND.\n"
		false
	else
		# Use postmaster, not postgres; "postgres" didn't support the
		# -h or -k options until version 8.2, and we need both.
		env PATH="$BACKEND_DIR/bin:$PATH:/sbin" \
			LD_LIBRARY_PATH="$BACKEND_DIR/lib" \
			PGDATA="$BACKEND_DIR/data" \
		start-stop-daemon -b -m -p "$(locate_pidfile "$ARG_BACKEND")" \
			--oknodo -S -x "$BACKEND_DIR/bin/postmaster" -- \
				-F -h '' -k "$BACKEND_DIR" \
				>"$BACKEND_DIR/backend.log" 2>&1
	fi
	cd "$SB_OLD_PWD"
}


stop_backend() {
	local BACKEND_PIDFILE
	log_message "Stopping backend $1"
	BACKEND_PIDFILE="$(locate_pidfile "$1")"
	if PATH="$PATH:/sbin" start-stop-daemon -p "$BACKEND_PIDFILE" -K
	then
		rm -- "$BACKEND_PIDFILE"
	fi
}


augment_postgres_config() {
	# Tweak configuration for backend $1.
	local BACKEND_CONFIG
	BACKEND_CONFIG="$1/data/postgresql.conf"
	if ! grep -- '^fsync = false' "$BACKEND_CONFIG"
	then
		log_message "Disabling fsync in $BACKEND_CONFIG"
		cat <<EOF >>"$BACKEND_CONFIG"
# Don't bother syncing to disk.
fsync = false
#unix_socket_directory = "$BACKEND_DIR"
EOF
	fi
}


build_postgres() {
	log_message "Building postgres for installation to $1"
	./configure --without-readline --prefix="$1"
	make -j$CONCURRENCY_LEVEL
}


create_database() {
	local PQXX_USER BACKEND_DIR
	PQXX_USER="$(whoami)"
	log_message "(Re-)creating database $PQXX_USER in $1"
	BACKEND_DIR="$(find_backend_dir "$1")"
	env PGHOST="$BACKEND_DIR" \
		LD_LIBRARY_PATH="$BACKEND_DIR/lib" \
		PATH="$BACKEND_DIR/bin:$PATH" \
	createdb "$PQXX_USER" || /bin/true
}

install_postgres() {
	# Download, build, and install a postgres version.
	local ARG_VERSION_NAME STRIPPED_VERSION BASE_NAME PG_TARBALL \
		DOWNLOAD_PATH PG_URL INSTALL_DIR IP_OLD_PWD
	ARG_VERSION_NAME="$1"
	STRIPPED_VERSION="$(echo "$ARG_VERSION_NAME" | sed -e 's/^v//')"
	BASE_NAME="postgresql-$STRIPPED_VERSION"
	PG_TARBALL="$BASE_NAME.tar.bz2"
	DOWNLOAD_PATH="$ARG_VERSION_NAME/$PG_TARBALL"
	PG_URL="ftp://$POSTGRES_FTP_SERVER/$POSTGRES_FTP_DIR/$DOWNLOAD_PATH"
	INSTALL_DIR="$TESTDIR/pg/$ARG_VERSION_NAME"
	IP_OLD_PWD="$(pwd)"
	log_message "Downloading $PG_TARBALL"

	if wget -q "$PG_URL"
	then
		tar xf "$PG_TARBALL"
		rm -f -- "$PG_TARBALL"
		cd -- "$BASE_NAME"
		if build_postgres "$INSTALL_DIR"
		then
			make install
			env PATH="$INSTALL_DIR/bin:$PATH" \
				LD_LIBRARY_PATH="$INSTALL_DIR/lib" \
				PGDATA="$INSTALL_DIR/data" \
			initdb >/dev/null
			augment_postgres_config "$INSTALL_DIR"
		fi
		cd ..
		rm -rf -- "./$BASE_NAME"
		cd -- "$IP_OLD_PWD"
	fi
}


find_upstream_postgreses() {
	lftp "$POSTGRES_FTP_SERVER" -e "cls -1 -B $POSTGRES_FTP_DIR ; exit" |
		grep -o "$POSTGRES_VERSION_PATTERN"
}


find_installed_postgreses() {
	local FIP_OLD_PWD SKIP
	FIP_OLD_PWD="$(pwd)"
	cd -- "$TESTDIR/pg"
	SKIP="$(find v* -maxdepth 1 -name PQXX_SKIP | sed -e 's|/.*||')"
	ls -1 | grep -o "$POSTGRES_VERSION_PATTERN" | grep -vFx "$SKIP"
	cd -- "$FIP_OLD_PWD"
}


stop_all_backends() {
	local SAB_BACKENDS STOPPABLE_BACKEND 
	SAB_BACKENDS=$(find_installed_postgreses)
	for STOPPABLE_BACKEND in $SAB_BACKENDS
	do
		stop_backend $STOPPABLE_BACKEND
	done
}


prepare_postgreses() {
	local PP_OLD_PWD DOWNLOADABLE AVAILABLE_BACKENDS PREPARE_BACKEND 
	PP_OLD_PWD="$(pwd)"
	for DOWNLOADABLE in $(find_upstream_postgreses)
	do
		if ! test -d "$TESTDIR/pg/$DOWNLOADABLE"
		then
			cd -- "$TESTDIR/tmp"
			install_postgres "$DOWNLOADABLE"
			cd -- "$PP_OLD_PWD"
		fi
	done

	stop_all_backends
	sleep 5
	AVAILABLE_BACKENDS=$(find_backends)
	for PREPARE_BACKEND in $AVAILABLE_BACKENDS
	do
		start_backend "$PREPARE_BACKEND"
		sleep 2
	done
	sleep 5
	for PREPARE_BACKEND in $AVAILABLE_BACKENDS
	do
		create_database "$PREPARE_BACKEND" || /bin/true
	done
}


find_historic_compilers() {
	local COMPILERS
	COMPILERS="$(find /usr/bin /usr/local/bin -name g++-[0-9].[0-9])"
	# TODO: Any way we can skip the one that g++ links to?
	echo ${COMPILERS:-c++}
}


test_against_compilers() {
	local COMPILER 
	log_message "Testing against compilers: $1"
	for COMPILER in $1
	do
		log_message "Testing against $COMPILER"
		env CXX=${COMPILER} ./configure \
			--enable-shared \
			--enable-maintainer-mode \
			--disable-documentation
		make check -j$CONCURRENCY_LEVEL || exit 1
	done
}


find_frontends() {
	# Find frontends to test against.
	find_installed_postgreses
}


find_backends() {
	# Find backends to test against.
	# Just sample the oldest & newest available major versions.
	find_installed_postgreses | head -n1
	find_installed_postgreses | tail -n1
}


test_against_postgreses() {
	local ARG_FRONTENDS ARG_BACKENDS TEST_FRONTEND TEST_FRONTEND_DIR \
		EXTENDED_PATH LIB_PATH TEST_BACKEND
	ARG_FRONTENDS=$1
	ARG_BACKENDS=$2
	for TEST_FRONTEND in $ARG_FRONTENDS
	do
		log_message "Building against frontend $TEST_FRONTEND"
		TEST_FRONTEND_DIR="$(find_backend_dir "$TEST_FRONTEND")"
		EXTENDED_PATH="$TEST_FRONTEND_DIR/bin:$PATH"
		LIB_PATH="$TEST_FRONTEND_DIR/lib"

		env PATH="$EXTENDED_PATH" LD_LIBRARY_PATH="$LIB_PATH" \
		./configure \
			--enable-shared \
			--enable-maintainer-mode \
			--disable-documentation

		env PATH="$EXTENDED_PATH" LD_LIBRARY_PATH="$LIB_PATH" \
		make -j$CONCURRENCY_LEVEL

		for TEST_BACKEND in $ARG_BACKENDS
		do
			log_message "Testing against backend $TEST_BACKEND"
			TEST_BACKEND_DIR="$(find_backend_dir "$TEST_BACKEND")"
			env PGHOST="$TEST_BACKEND_DIR" \
				PATH="$EXTENDED_PATH" \
				LD_LIBRARY_PATH="$LIB_PATH" \
			make check -j$CONCURRENCY_LEVEL || exit 1
		done
	done
}


main() {
	local PQXX_TARBALL CURRENT_POSTGRES CURRENT_POSTGRES_DIR CURRENT_PATH \
		CURRENT_LIB_PATH
	build_tarball
	PQXX_TARBALL="$(get_tarball)"
	cd -- "$TESTDIR"
	rm -rf build
	mv -- "$(unpack_tarball "$PQXX_TARBALL")" build

	mkdir -p pg
	prepare_postgreses

	CURRENT_POSTGRES="$(find_backends | tail -n 1)"
	CURRENT_POSTGRES_DIR="$(find_backend_dir "$CURRENT_POSTGRES")"
	CURRENT_PATH="$CURRENT_POSTGRES_DIR/bin:$PATH"
	CURRENT_LIB_PATH="$CURRENT_POSTGRES_DIR/lib"

	cd -- "$TESTDIR/build"

	PGHOST="$CURRENT_POSTGRES_DIR"
	export PGHOST
	LD_LIBRARY_PATH="$CURRENT_POSTGRES_DIR/lib"
	export LD_LIBRARY_PATH
	PATH="$CURRENT_POSTGRES_DIR/bin:$PATH"
	export PATH

	test_against_compilers "$(check_for_compilers "$OPTIMIZING_COMPILERS")"

	CXXFLAGS=
	export CXXFLAGS

	test_against_compilers "$(find_historic_compilers)"

	test_against_postgreses "$(find_frontends)" "$(find_backends)"

	stop_all_backends
}


log_message "Starting full libpqxx test"
main
log_message "Done"
