#!/bin/bash -e
set -o pipefail

# Helper script to develop/debug mini-buildd.
#
# Quickstart:
#  Enter your dev chroot (preferably sid). sudo should be configured.
#  $ cd mini-buildd
#  $ ./devel installdeps
#  $ ./devel prepare-system
#  $ ./devel update

logI() { printf "($(date --rfc-3339=ns)) I: ${1}\n" "${@:2}" >&2; }
logE() { printf "($(date --rfc-3339=ns)) E: ${1}\n" "${@:2}" >&2; }

# don't accidentially run this on your host system.
check_devsys()
{
	if ! (ischroot || systemd-detect-virt); then
		printf "W: $0\n" >&2
		printf "W: Never use this script in production systems\n" >&2
		printf "W: This may be not a developer system (not chroot, not systemd container)\n" >&2
		read -p"Sure to continue (Ctrl-c to abort)?" DUMMY
	fi
}

declare -g -a _ON_EXIT=()
on_exit_run()
{
	local line
	for line in "${_ON_EXIT[@]}"; do
		logI "On Exit: %s" "${line}"
		${line}
	done
}
on_exit()
{
	_ON_EXIT+=("${*}")
}
trap "on_exit_run" EXIT
# Avoid some esoteric cases where on_exit is not run (certain emacs compilation buffer kills)
trap "exit 2" INT
trap "exit 3" TERM

_check_prog()
{
	local path
	for path in $(printf "${PATH}" | tr ":" " "); do
		local prog="${path}/${1}"
		if [ -x "${prog}" ]; then
			logI "Found: ${prog}."
			return 0
		fi
	done
	logE "'${1}' not found in path; please install."
	logI "You may use './devel installdeps' to install all deps needed."
	exit 1
}

MBD_PJPATH="$(readlink -f $(dirname $0))"
MBD_PYPATH="${MBD_PJPATH}/src"
MBD_SETUP_CFG="${MBD_PJPATH}/setup.cfg"
MBD_LINTIANRC="${MBD_PJPATH}/.lintianrc"
MBD_CODENAME=$(lsb_release --codename --short)
# Autogenerated by us
MBD_DPUT_CF="${MBD_PJPATH}/devel.dput.cf"
MBD_LAST_CHANGES="${MBD_PJPATH}/devel.last_changes"

#
# Configurable variables
#
MBD_HTTPD_SSL_DESC="Toggle 'https mode' (ssl key is managed via 'mini-buildd-self-signed-certificate'. Should just work after './devel prepare-system')."
: ${MBD_HTTPD_SSL:=false}
MBD_CONFIG+="MBD_HTTPD_SSL "

MBD_HTTPD_PORT_DESC="Custom port -- run a second instance on same network stack (i.e, use '8068' and configure ftp to be at '8069')."
: ${MBD_HTTPD_PORT:=8066}
MBD_CONFIG+="MBD_HTTPD_PORT "

MBD_DEBUG_DESC="Debug options (for 'mini-buildd --debug=xxx')."
: ${MBD_DEBUG:=warnings,exception,http,webapp}
MBD_CONFIG+="MBD_DEBUG "

MBD_HOSTNAME_DESC="Custom hostname -- for a second instance in ssl mode, you must have a different hostname for the certificate."
: ${MBD_HOSTNAME:=$(hostname -f)}
MBD_CONFIG+="MBD_HOSTNAME "

MBD_VENDORS_DESC="'vendors' arg for the 'autosetup' call."
: ${MBD_VENDORS:=debian}
MBD_CONFIG+="MBD_VENDORS "

MBD_DEBIAN_FRONTEND_DESC="debonf frontend for package install and removal/purge. 'noninteractive' will free you from interruptions in test runs (will then purge the package without asking)."
: ${MBD_DEBIAN_FRONTEND:=}
MBD_CONFIG+="MBD_DEBIAN_FRONTEND "

MBD_RESTART_HOOK_DESC="Run arbitrary command directly after 'restart' (handy for developing/testing, but may BREAK full test 'updatetestall')."
: ${MBD_RESTART_HOOK:=}
MBD_CONFIG+="MBD_RESTART_HOOK "

MBD_KEEP_DESC="Don't purge test installs and configs on system."
: ${MBD_KEEP:=false}
MBD_CONFIG+="MBD_KEEP "

# Easily manipulate 'profiles':
# ./devel profile           : Back to all defaults
# ./devel profile <taint>.. : Tainted profile.
# Taints:
# ssl : Enable SSL
# 8068: 2nd instance on same network stack (hostname localhost)
# allvendors: Run for all supported vendors
# testkeyring: Trigger keyring package build after restart.
# testpackage: Trigger test package build after restart.
mbd_profile()
{
	_export() { export ${1}="${2}"; printf "export %s='%s'\n" "${1}" "${2}"; }

	_export MBD_HTTPD_SSL false
	_export MBD_HTTPD_PORT 8066
	_export MBD_DEBUG warnings,exception,http,webapp
	_export MBD_HOSTNAME $(hostname -f)
	_export MBD_VENDORS debian
	_export MBD_DEBIAN_FRONTEND noninteractive
	_export MBD_RESTART_HOOK ""
	_export MBD_KEEP false

	if [[ "${*}" =~ .*\ssl\.* ]]; then
		_export MBD_HTTPD_SSL true
	fi
	if [[ "${*}" =~ .*\8068\.* ]]; then
		_export MBD_HTTPD_PORT 8068
		_export MBD_HOSTNAME localhost
	fi
	if [[ "${*}" =~ .*\nowebdebug\.* ]]; then
		_export MBD_DEBUG warnings,exception,http
	fi
	if [[ "${*}" =~ .*\production\.* ]]; then
		_export MBD_DEBUG " "
	fi
	if [[ "${*}" =~ .*\allvendors\.* ]]; then
		_export MBD_VENDORS "debian ubuntu"
	fi
	if [[ "${*}" =~ .*\testkeyring\.* ]]; then
		_export MBD_RESTART_HOOK "mbd_api keyringpackages --distributions=${MBD_CODENAME}-test-unstable"
	fi
	if [[ "${*}" =~ .*\testpackage\.* ]]; then
		_export MBD_RESTART_HOOK "mbd_api testpackages --sources=mbd-test-archall --distributions=${MBD_CODENAME}-test-unstable"
	fi
}

if ${MBD_HTTPD_SSL}; then
	MBD_HTTPD_ENDPOINT="ssl:port=${MBD_HTTPD_PORT}:privateKey=/etc/ssl/mini-buildd/private/mini-buildd.key:certKey=/etc/ssl/mini-buildd/certs/mini-buildd.crt"
	MBD_HTTPD_PROTO="https"
else
	MBD_HTTPD_ENDPOINT="tcp6:port=${MBD_HTTPD_PORT}"
	MBD_HTTPD_PROTO="http"
fi
MBD_HTTPD_URL="${MBD_HTTPD_PROTO}://${MBD_HOSTNAME}:${MBD_HTTPD_PORT}"
MBD_DEBCONF="mini-buildd mini-buildd/admin_password password admin"
MBD_DEFAULT="MINI_BUILDD_OPTIONS='--verbose --verbose --debug=${MBD_DEBUG} --hostname=${MBD_HOSTNAME} --http-endpoint=${MBD_HTTPD_ENDPOINT}'"

mbd_api()
{
	local cmd="${MBD_PYPATH}/mini-buildd-api -vv --auto-confirm ${1} ${MBD_HTTPD_PROTO}://admin@${MBD_HOSTNAME}:${MBD_HTTPD_PORT} ${@:2}"
	logI "m-b-api: %s" "${cmd}"
	${cmd}
}
mbd_events()
{
	local cmd="${MBD_PYPATH}/mini-buildd-events -vv ${MBD_HTTPD_PROTO}://admin@${MBD_HOSTNAME}:${MBD_HTTPD_PORT} ${@:1}"
	logI "m-b-events: %s" "${cmd}"
	${cmd}
}
mbd_dput()
{
	mbd_api getdputconf >${MBD_DPUT_CF}
	local cmd="${MBD_PYPATH}/mini-buildd-dput -vv --config=${MBD_DPUT_CF} --force mini-buildd-$(hostname) ${@:1}"
	logI "m-b-dput: %s" "${cmd}"
	${cmd}
}

# Shortcuts to build one test/keyring package
mbd_testpackage() { mbd_api testpackages --sources="${1}" --distributions="${MBD_CODENAME}-test-unstable" "${@:2}"; }
mbd_mbd-test-cpp()     { mbd_testpackage "mbd-test-cpp" "${@}"; }
mbd_mbd-test-archall() { mbd_testpackage "mbd-test-archall" "${@}"; }
mbd_mbd-test-ftbfs()   { mbd_testpackage "mbd-test-ftbfs" "${@}"; }

mbd_archive-keyring()  { mbd_api keyringpackages --distributions="${MBD_CODENAME}-test-unstable" "${@}"; }

# Shortcuts for pid and kill
mbd_pid()
{
	if cat /var/lib/mini-buildd/.mini-buildd.pid; then
		logI "PID from pidfile"
	elif pgrep --uid=mini-buildd mini-buildd; then
		logI "PID from pgrep"
	fi
}

mbd_signal()
{
	sudo kill -${1} $(mbd_pid)
}

mbd_installdeps()
{
	sudo apt-get update
	sudo apt-get --no-install-recommends install devscripts disorderfs diffoscope equivs

	# Extra tools needed for checks
	sudo apt-get install --no-install-recommends pycodestyle pylint3 python3-apt python3-pip python3-wheel devscripts faketime python3-bs4 python3-keyrings.alt tidy codespell wget apache2-utils ssl-cert libnss3-tools libdistro-info-perl
	# Extra tools needed vc and package building
	sudo apt-get install --no-install-recommends git git-buildpackage
	# binary package dependencies so we can just dpkg -i for testing
	sudo apt-get install --no-install-recommends --target-release='*' sbuild schroot reprepro debootstrap lintian

	# Debian package build dependencies; using target-release=*
	# here to always allow highest versions of any sources
	# configured (for example, for backports).
	mk-build-deps --install --root-cmd=sudo --remove --tool="apt-get --no-install-recommends --target-release='*'"
}

# Find files with python code
declare -a MBD_PYFINDPARAMS=(-not -wholename './debian/*' -not -wholename './.git/*' -not -wholename './build/*' -not -wholename './.pybuild/*' -type f)
mbd_pyscripts()
{
	local f
	for f in $(find \( "${MBD_PYFINDPARAMS[@]}" \) -a -executable); do
		if head -1 "${f}" | grep --quiet "bin/python"; then
			printf "%s\n" "${f}"
		fi
	done
}

mbd_pymodules()
{
	local -a exceptions=(-true)
	[ -z "${*}" ] || exceptions=($(printf " -not -wholename %s" "${@}"))
	find -name "*.py" -a \( "${MBD_PYFINDPARAMS[@]}" \) -a \( "${exceptions[@]}" \)
}

mbd_pysources()
{
	mbd_pyscripts
	mbd_pymodules
}

mbd_pyenv()
{
	printf "export PYTHONPATH=\"${MBD_PYPATH}\"\n"
}

pyenv()
{
	eval "$(mbd_pyenv)"
	python3 ./setup.py build_py
}

mbd_installdjango()
{
	dpkg -s python3-django | grep "^Version" || true
	sudo dpkg --install ../django-versions/python3-django*${1}*.deb
	dpkg -s python3-django | grep "^Version"
}

mbd_service()
{
	if sudo ischroot && [ -d /run/systemd/system ]; then
		# Seems we are in a chroot, and host is running systemd
		# The service will not be started in that case
		# For now, we really want to start the service anyway, so this is still usable in "traditional" chroots
		# (Rather use a container-based environment to test)
		sudo mv /lib/lsb/init-functions.d/40-systemd /lib/lsb/init-functions.d/40-systemd.DISABLED || true
		sudo /etc/init.d/mini-buildd ${1}
		sudo mv /lib/lsb/init-functions.d/40-systemd.DISABLED /lib/lsb/init-functions.d/40-systemd || true
	else
		sudo service mini-buildd "${1}"
	fi
}

# Create "big files" for HTTPD tests and benchamrking.
# POST: MBD_HTTPD_TESTFILES (fileItem: sha1sum) will be available globally.
mbd_gen_httpd_testfiles()
{
	declare -g -A MBD_HTTPD_TESTFILES=()
	local fileSize relPath="repositories/test/pool/testfiles"
	local absPath="/var/lib/mini-buildd/${relPath}"
	sudo mkdir -p "${absPath}"
	sudo chown mini-buildd:mini-buildd "${absPath}"

	for fileSize in 5 50 100; do
		local id="file${fileSize}M"
		local of="/var/lib/mini-buildd/${relPath}/${id}.bin"
		[ -e "${of}" ] || sudo dd if="/dev/urandom" of="${of}" count="${fileSize}" bs="1M" >/dev/null 2>&1
		sudo chown mini-buildd:mini-buildd "${of}"
		MBD_HTTPD_TESTFILES["${id}:${MBD_HTTPD_URL}/${relPath}/${id}.bin"]="$(sha1sum < "${of}")"
	done
}

mbd_changes() # [<arch>]
{
	local arch=${1-$(dpkg-architecture --query=DEB_BUILD_ARCH)}
	printf "../mini-buildd_$(dpkg-parsechangelog --show-field=version)_${arch}.changes"
}

#
# Runner functions: mbd_run:<sequence>:<level>:<name>
#
# <sequence>: Two-digit sequence number (0-9) -- order in which to run.
# <level>: Higher second digit, higher "cost".
#  00-09: deploy: Actions needed to test-deploy only.
#  10-19: check : Static tests, fully automatic.
#  20-29: test  : Live tests, system tests.

mbd_run:00:99:prepare-system()
{
	# Hack to be able to do authorized non-interactive API calls
	mbd_pythonkeyringtestconfig()
	{
		sudo apt-get install python3-keyrings.alt || true   # for the PlainTextKeyring (newer versions only)
		local configDir
		# python-keyring 21.3.0 fixes config file location to '~/.config/python_keyring'; we need to support the old path for some time still.
		for configDir in "${HOME}/.config/python_keyring" "${HOME}/.local/share/python_keyring"; do
			mkdir -p "${configDir}"
			local configFile="${configDir}/keyringrc.cfg"
			cat <<EOF >"${configFile}"
# suitable for python3-keyring > 8
[backend]
default-keyring=keyrings.alt.file.PlaintextKeyring
EOF
			logI "Written ${configFile}"
		done
	}

	# Add custom self-signed cert
	mbd_setupcert()
	{
		sudo ${MBD_PJPATH}/src/mini-buildd-self-signed-certificate create ${MBD_HOSTNAME}

		# Update some cert stores: system, chrome, firefox
		local cert="/etc/ssl/mini-buildd/certs/mini-buildd.crt"
		( cd "/usr/local/share/ca-certificates/" && sudo ln -s -f "${cert}" .)
		sudo update-ca-certificates --fresh

		local pkiDir="${HOME}/.pki" trust="C,,"
		[ "${MBD_CODENAME}" != "buster" ] || trust="P,,"
		local certDB=$(find  ${HOME}/.mozilla/ -name "cert*.db")
		local c
		for c in /usr/local/share/ca-certificates/*.crt; do
			logI "Adding ${c} to chromium..."
			[ ! -e "${pkiDir}/nssdb" ] || certutil -d sql:${pkiDir}/nssdb -A -t "${trust}" -n"$(basename ${c} .pem)" -i "${c}"
			logI "Adding ${c} to firefox..."
			[ ! -e "${certDB}" ] || certutil -d $(dirname ${certDB}) -A -t "${trust}" -n"$(basename ${c} .pem)" -i "${c}"
		done
	}

	mbd_pipclear()
	{
		local installed=$(pip3 freeze --user)
		[ -z "${installed}" ] || pip3 uninstall --yes ${installed}
	}

	mbd_pipinstall()
	{
		pip3 install --upgrade --ignore-installed prospector importchecker
	}

	mbd_pythonkeyringtestconfig
	mbd_setupcert
	mbd_pipclear
	mbd_pipinstall
}

# See .prospector.yaml for config
mbd_run:01:10:prospector()
{
	_check_prog prospector
	(
		pyenv
		prospector $(mbd_pysources)
	)
}

# importchecker: None of the other check tools report unused absolute imports for some reason.
mbd_run:01:11:importchecker()
{
	_check_prog importchecker
	(
		ok=true
		pyenv
		for f in $(mbd_pysources); do
			result=$(importchecker "${f}")
			if [ -n "${result}" ]; then
				printf "%s\n" "${result}"
				ok=false
			fi
		done
		${ok}
	)
}

mbd_run:01:12:codespell()
{
	local ups=$(codespell --ignore-words-list='referer,fpr,stati,ser' --quiet-level=2 $(mbd_pysources))
	if [ -n "${ups}" ]; then
		logE "codespell failed:\n${ups}"
		return 1
	fi
}

mbd_run:02:12:pydoctests()
{
	(
		pyenv
		for m in $(mbd_pymodules ./setup.py ./doc/conf.py); do  # ./setup.py && ./doc/conf.py can't be used for doctests
			local module="$(basename $(tr '/' '.' <<< ${m:4}) '.py' | cut -d. -f2-)"
			logI "=> Doctest on %s (%s)" "${m}" "${module}"
			( cd ./src/ && ./run-doctest "${module}" )
		done
		python3 -m doctest -v src/mini-buildd src/mini-buildd-api
	)
}

# Same call as in debian/rules, but warnings are errors
mbd_run:02:12:sphinx-build()
{
	python3 -m sphinx -N -bhtml -W ./doc/ ./build/sphinx/html/
}

mbd_run:03:00:changelog()
{
	# Checking changelog (must be unchanged)...
	git diff-index --exit-code HEAD debian/changelog
	on_exit git checkout debian/changelog

	# Try to get snapshot changelog so package might be uploaded to testsuite mini-buildd later for testing
	gbp dch --auto \
			--new-version="$(dpkg-parsechangelog --show-field=Version)~test~SID+0+0~$(date --utc +'%Y%m%d%H%M%S')" \
			--dch-opt="--force-bad-version" --distribution=${MBD_CODENAME}-test-experimental --force-distribution
	mbd_changes >"${MBD_LAST_CHANGES}"
}

mbd_run:04:00:build()
{
	DEB_BUILD_OPTIONS+="nocheck" debuild --no-lintian -us -uc
}

mbd_run:05:11:lintian()
{
	local changes=$(cat "${MBD_LAST_CHANGES}" || mbd_changes)
	logI "Lintian-checking: %s" "${changes}"
	local result=$(lintian --cfg="${MBD_LINTIANRC}" "${@}" "${changes}")
	printf "%s\n---\n" "${result}"
	if grep "^[EW]:" <<< ${result}; then
		read -p "Lintian FAILED (RETURN to ignore)" DUMMY
	fi
}

# This also checks "full" package building (with doc and check)
mbd_run:06:21:debrepro()
{
	debrepro
}

mbd_run:10:23:remove()
{
	mbd_service stop || true
	[ -z "${MBD_DEBIAN_FRONTEND}" ] || export DEBIAN_FRONTEND="${MBD_DEBIAN_FRONTEND}"
	sudo --preserve-env=DEBIAN_FRONTEND dpkg --${1:-remove} mini-buildd mini-buildd-utils mini-buildd-doc python3-mini-buildd python-mini-buildd mini-buildd-common
}

mbd_run:10:23:purge()
{
	mbd_run:10:23:remove purge
}

mbd_run:11:00:install()
{
	printf "%s" "${MBD_DEBCONF}" | sudo debconf-set-selections --verbose -
	[ -z "${MBD_DEBIAN_FRONTEND}" ] || export DEBIAN_FRONTEND="${MBD_DEBIAN_FRONTEND}"
	sudo --preserve-env=DEBIAN_FRONTEND apt-get --yes --allow-downgrades install $(mbd_changes)
	printf "%s" "${MBD_DEFAULT}" | sudo tee /etc/default/mini-buildd
}

mbd_run:12:00:restart()
{
	mbd_service restart
	until mbd_api status; do
		sleep 1
	done
	if [ -n "${MBD_RESTART_HOOK}" ]; then
		logI "Running: %s" "${MBD_RESTART_HOOK}"
		${MBD_RESTART_HOOK}
	fi
}

mbd_run:13:20:tidy()
{
	local url html="./devel.tidy.html"
	for url in \
		${MBD_HTTPD_URL}/mini_buildd/home.html \
		${MBD_HTTPD_URL}/mini_buildd/api_view/index.html \
		${MBD_HTTPD_URL}/mini_buildd/api_view?command=status \
		${MBD_HTTPD_URL}/mini_buildd/api_view?command=show\&source=mbd-test-cpp \
		${MBD_HTTPD_URL}/mini_buildd/log/test/ \
		${MBD_HTTPD_URL}/accounts/login/ \
		; do
		logI "Testing HTML: ${html} from '${url}'"
		wget --quiet --output-document="${html}" "${url}"

		# PROPRIETARY_ATTRIBUTE: With django 3, tidy complains about 'proprietary attribute "autocapitalize"', but this seems to be valid html5.
		# BACKSLASH_IN_URI,ESCAPED_ILLEGAL_URI,ILLEGAL_URI_CODEPOINT: False-positives due to django "escapejs" tag.
		# TRIM_EMPTY_ELEMENT: Just too picky.
		local result=$(tidy --quiet yes --gnu-emacs yes --mute-id yes -output /dev/null \
												--mute PROPRIETARY_ATTRIBUTE \
												--mute BACKSLASH_IN_URI,ESCAPED_ILLEGAL_URI,ILLEGAL_URI_CODEPOINT \
												--mute TRIM_EMPTY_ELEMENT \
												"${html}" 2>&1)
		if [ -n "${result}" ]; then
			printf "%s" "${result}"
			return 1
		fi
	done
}

mbd_run:14:23:auto-setup()
{
	mbd_api autosetup --vendor ${MBD_VENDORS}
	mbd_api keyringpackages
	mbd_api testpackages --check
}

mbd_run:15:20:build-ourselves()
{
	local since=$(date --iso-8601=ns)
	local changes=$(cat "${MBD_LAST_CHANGES}" || mbd_changes)
	mbd_dput ${changes}
	# TODO: For the time being, mini-buildd needs Debian experimental, so can't be build in a default auto setup.
	# TODO: Go for INSTALLED only once that is fixed.
	mbd_events --types "INSTALLED" "FAILED" "REJECTED" --source="mini-buildd" --distribution="${MBD_CODENAME}-test-experimental" --since="${since}" --stop
}

mbd_run:15:23:build-port-migrate()
{
	local since=$(date --iso-8601=ns)
	mbd_mbd-test-cpp --auto-ports buster-test-unstable buster-toast-unstable  # One failing port to check error handling (manually)
	mbd_events --types "INSTALLED" --source="mbd-test-cpp" --distribution="${MBD_CODENAME}-test-unstable" --since="${since}" --stop
	mbd_events --types "INSTALLED" --source="mbd-test-cpp" --distribution="buster-test-unstable" --since="${since}" --stop

	mbd_api migrate --full "mbd-test-cpp" "${MBD_CODENAME}-test-unstable"
	mbd_events --types "MIGRATED" --source="mbd-test-cpp" --distribution="${MBD_CODENAME}-test-testing" --since="${since}" --stop
	mbd_events --types "MIGRATED" --source="mbd-test-cpp" --distribution="${MBD_CODENAME}-test-stable" --since="${since}" --stop
}

mbd_run:16:23:api-getters()
{
	mbd_api status
	mbd_api getuploaders
	mbd_api getkey
	mbd_api getdputconf
	mbd_api getsourceslist "${MBD_CODENAME}"
	mbd_api logcat
	mbd_api list "mbd-test-cpp"
	mbd_api show "mbd-test-cpp"
	mbd_api find "mbd-test-cpp"
}

_test_install()
{
	${MBD_KEEP} || on_exit sudo dpkg --purge "${1}"
	sudo apt-get "${@:2}" install "${1}"
}

mbd_run:16:23:apt-tofu-bootstrap()
{
	# Setup cleanup
	local sources_list="/etc/apt/sources.list.d/mbd-testsuite.list"
	${MBD_KEEP} || on_exit sudo rm -v -f "${sources_list}"
	on_exit sudo apt-get update

	# Install keyring package via a forcibly trusted stable apt line
	mbd_api getsourceslist "${MBD_CODENAME}" --suite=stable --options="trusted=yes" | sudo tee "${sources_list}"
	sudo apt-get update
	_test_install $(hostname)-archive-keyring

	# Re-instate 'normal' apt line and test apt update
	mbd_api getsourceslist "${MBD_CODENAME}" --suite=stable | sudo tee "${sources_list}"
	sudo apt-get update
}

mbd_run:17:23:apt-snapshot()
{
	# Test snapshot gen, del and sources.list
	local snap="TESTSUITE_$(date --iso-8601=seconds)"
	mbd_api gensnapshot "${MBD_CODENAME}-test-stable" "${snap}"
	mbd_api delsnapshot "${MBD_CODENAME}-test-stable" "${snap}"
	mbd_api gensnapshot "${MBD_CODENAME}-test-stable" "${snap}"
	mbd_api getsnapshots "${MBD_CODENAME}-test-stable"
	mbd_api getsourceslist "${MBD_CODENAME}" --suite=stable --snapshot=${snap}
}

mbd_run:17:23:apt-install()
{
	# install previously built test package
	_test_install mbd-test-cpp
}

mbd_run:18:23:testsuite-packages()
{
	local location="./share/testsuite-packages/" dst changes

	# Create package DSCs
	for dst in $(find ${location} -maxdepth 1 -mindepth 1 -type d); do
		[ -f ${dst}*.changes ] || ( cd ${dst} && dpkg-buildpackage -us -uc -S; )
	done

	# Tests: Upload and expect event
	for changes in ${location}/*${1}*.changes; do
		local since=$(date --iso-8601=ns)
		local source=$(basename ${changes} | cut -d'_' -f1)
		local expect=$(cut -d'-' -f1 <<<${source} | tr '[:lower:]' '[:upper:]')
		logI "Testing ${source} (expect=${expect})"

		case ${source} in
			installed-portext)
				cp "${location}/installed-portext_1.0.0.dsc" "${location}/installed-portext_1.0.0.tar.gz" "/tmp/"
				mbd_api portext "file:///tmp/installed-portext_1.0.0.dsc" "${MBD_CODENAME}-test-unstable"
				;;
			*)
				# Standard dput, dput-ng can't do ftps.
				mbd_dput "${changes}"
				;;
		esac
		mbd_events --types "${expect}" --source="${source}" --since="${since}" --stop
		if [ "${expect}" == "INSTALLED" ]; then
			local distribution=$(grep "^Distribution:" "${changes}" | cut -d: -f2 | tr -d " ")
			mbd_api remove "${source}" "${distribution}" --no-rollback
		fi
	done
}

mbd_run:19:23:httpd-testfiles()
{
	mbd_gen_httpd_testfiles
	local item
	for item in ${!MBD_HTTPD_TESTFILES[@]}; do
		local id=$(cut -d: -f1 <<<${item}) url=$(cut -d: -f2- <<<${item})
		local of="$(mktemp)"
		wget --output-document="${of}" "${url}"
		local sha1sum="$(sha1sum < "${of}")"
		if [ "${sha1sum}" = "${MBD_HTTPD_TESTFILES[${item}]}" ]; then
			logI "HTTP download OK: %s=%s %s" "${id}" "${of}" "${sha1sum}"
		else
			logE "HTTP download FAILED: %s=%s %s != %s" "${id}" "${of}" "${sha1sum}" "${MBD_HTTPD_TESTFILES[${item}]}"
			return 1
		fi
	done
}

mbd_run:19:23:httpd-benchmark()
{
	mbd_gen_httpd_testfiles

	_ab_val() { cut -d: -f2- | cut -d[ -f1 | tr -d '[:space:]'; }

	local abRequests=50 item

	for item in django:${MBD_HTTPD_URL}/mini_buildd/repositories/test/ index:${MBD_HTTPD_URL}/repositories/test/pool/main/m/mbd-test-cpp/ ${!MBD_HTTPD_TESTFILES[@]}; do
		local id=$(cut -d: -f1 <<<${item})
		local url=$(cut -d: -f2- <<<${item})
		local c
		for c in 1 4; do
			local abResult=$(ab -n "${abRequests}" -c "${c}" "${url}")
			local server=$(grep "^Server Software:.*" <<<${abResult} | _ab_val)
			if (( c > 1 )); then
				local tpr=$(grep "^Time per request:.*(mean, across all concurrent requests)" <<<${abResult} | _ab_val)
			else
				local tpr=$(grep "^Time per request:.*(mean)" <<<${abResult} | _ab_val)
			fi
			{
				if grep "^Non-2xx responses:" <<<${abResult}; then
					printf "%s" "${abResult}"
					printf "\n\n%s\n" "E: Above ab call has non-200 responses!"
					tpr="-1.0"
				fi
			} >&2
			# Note: decimal separator in "ab" is ".", so e need to set LANG for printf
			LANG="C.UTF-8" printf "[%20s] %-12s: % 8.3f\n" "${server}" "${id} c=${c}" "${tpr}"
		done
	done
}

mbd_sequence()  # [<levelRegex>=00] [<name>] <hr>
{
	local levelRegex="${1:-00}" name="${2}" hr="${3}"
	for func in $(declare -F | cut -d" " -f3- | grep "^mbd_run:[[:alnum:]][[:alnum:]]:${levelRegex}:${name}" | sort || true); do
		if [ -n "${hr}" ]; then
			printf "%s " "$(cut -d: -f4 <<<${func})"
		else
			printf "%s " "${func}"
		fi
	done
}

mbd_run()  # [<levelRegex>=00] [<name>] [<customArgs>...]
{
	local -a info=()
	local func totalStartStamp=$(date +%s)
	local -i count=0
	for func in $(mbd_sequence "${1}" "${2}"); do
		logI "Running %s..." "${func}"
		local startStamp=$(date +%s)
		${func} "${@:3}"
		count+=1
		info+=("$(printf "OK (%03d seconds): %s" "$(($(date +%s) - startStamp))" "${func}")")
	done
	if ((count <= 0)); then
		logE "No runs for this sequence filter: ${1} ${2}"
		return 1
	fi
	logI "Sequence results (%03d seconds):" "$(($(date +%s) - totalStartStamp))"
	logI "%s" "${info[@]}"
	logI "OK, %s runs succeeded ($(date))." "${count}"
}

# Shortcuts
declare -A MBD_RUN_SHORTCUTS=(
	["check"]="10"
	["update"]="00"
	["updatecheck"]="[0-1][0-1]"
	["updatetest"]="[0-9][0-2]"
	["updatetestall"]="[0-9][0-9]"
)
# We can't iterate through the associative array in the given order later, so we at least want a sorted key list as helper
MBD_RUN_SHORTCUTS_SORTED="$(printf '%s\n' "${!MBD_RUN_SHORTCUTS[@]}" | sort -n)"

# Some extra targets
mbd_daemon.log()
{
	# less --follow-name +F /var/lib/mini-buildd/var/log/daemon.log  # tends to hog CPU
	tail --follow=name --retry /var/lib/mini-buildd/var/log/daemon.log
}

mbd_access.log()
{
	# less --follow-name +F /var/lib/mini-buildd/var/log/access.log  # tends to hog CPU
	tail --follow=name --retry /var/lib/mini-buildd/var/log/access.log
}

mbd_pygrep()
{
	[ -n "${*}" ] || return 0  # Return immediately on empty args, else grep will stall
	local p
	for p in $(mbd_pysources); do
		grep "${@}" "${p}" || true
	done
}

mbd_pylintgeneratedmembers()
{
	# Generate all identifiers with "has no xxx member" error. If
	# needed, manually add those that are _actually_
	# false-positive due to django to -> .pylintrc.
	for o in $(pylint src/mini_buildd/models/ | grep "has no.*member" | cut -d"'" -f4 | sort | uniq); do
		printf "${o},"
	done
	printf "mbd_check,"
	printf "\n"
}

# Try to reproduce deadlock
# This try w/ events does not yet reproduce the deadlock; saving for later.
mbd_deadlock()
{
	local -i count=0
	while mbd_events --source mbd-test-cpp --stop >/dev/null; do
		# echo ${count}; count+=1;
		sleep 0;
	done
}

mbd_bash-completion()
{
	local non_runners=$(declare -F | cut -d" " -f3 | grep "^mbd_" | grep --invert-match "^mbd_run" | cut -b5-)
	printf "complete -W '%s' ./devel" "$(printf '%s ' "${!MBD_RUN_SHORTCUTS[@]}") $(mbd_sequence ".." "" "hr") ${non_runners}"
}

mbd_run-lintian()
{
	mbd_run:03:00:changelog
	mbd_run:04:00:build
	mbd_run:05:11:lintian
}

mbd_supertestall()
{
	mbd_profile allvendors ssl
	./devel updatetestall | tee ../updatetestall.allvendors_ssl.log 2>&1

	mbd_profile allvendors
	./devel updatetestall | tee ../updatetestall.allvendors.log 2>&1
}

main()
{
	if [ -z "${1}" ]; then
		local p="./$(basename "${0}")" b=$(tput bold) i=$(tput sitm) r=$(tput sgr0)
		cat <<EOF
Usage: [<env>] ${p} <shortcut-or-runner-or-special> | run <groupRegex><levelRegex>${r} [<customArgs>...]

mini-buildd development helper.

Environment (prefix to command or export):

$(for VAR in ${MBD_CONFIG}; do VAR_DESC="${VAR}_DESC"; printf "%s\n  %s\n" "${VAR}='${!VAR}'" "${!VAR_DESC}"; done)

${b}Sequence filter shortcuts${r}:
$(for s in ${MBD_RUN_SHORTCUTS_SORTED}; do printf "  ${i}${p} %-15s${r}: (%-10s) %s\n" "${s}" "${MBD_RUN_SHORTCUTS[${s}]}" "$(mbd_sequence "${MBD_RUN_SHORTCUTS[${s}]}" "" "hr")"; done)

${b}Runners${r}:
 ${i}${p} $(mbd_sequence ".." "" "hr" | tr " " "|")

${b}Special (non-runner) targets${r}:
 ${i}${p} supertestall: Iterates 'updatetestall' through all test profiles (currently normal+ssl).
 ${i}${p} daemon.log  : Follow daemon logs.
 ...-> Check source for other possible esoteric calls.
EOF
	else
		check_devsys
		local shortcut="${MBD_RUN_SHORTCUTS[${1}]}"
		if [ -n "${shortcut}" ]; then
			mbd_run "${shortcut}" "" "${@:2}"
		else
			local func="mbd_${1}"  # direct function
			if [ "$(type -t "${func}")" = "function" ]; then
				${func} "${@:2}"
			else
				mbd_run ".." "${1}" "${@:2}"
			fi
		fi
	fi
}

main "${@}"
