#!/usr/bin/env bash
set -efuo pipefail
fail() { echo "TestFail: ${*} (${FUNCNAME[1]}:${BASH_LINENO[0]})" >&2; exit 1; }
skip() { echo "TestSkip: ${*}" >&2; exit 77; }

# arguments coming from meson
[[ ${#} -ge 2 && -f ${1} && -f ${2} ]] \
	|| fail "invalid usage, attempted to use directly?"
TEST=${1}
SCRIPT=${2}
INCLUDE=${3:-}

PACKAGE='@package@'
VERSION='@version@'
DATAROOT='@dataroot@'
WORKROOT='@workroot@'
LIBROOT='@libroot@'

LIBD100='@libdummy100@' # initial
LIBD101='@libdummy101@' # no abi changes
LIBD102='@libdummy102@' # broken abi
LIBD200='@libdummy200@' # soname change
LIBA300='@libalt300@' # initial
LIBA400='@libalt400@' # soname change
EXED='@exedummy@' # libdummy symbols
EXEA='@exealt@' # libdummy+libalt symbols

SHELLCHECK='@shellcheck@'

DATADIR=${DATAROOT}/${SCRIPT##*/}/data
WORKDIR=${WORKROOT}/${SCRIPT##*/}/${TEST##*/}

# check_abidiff
#	Calls skip() if abidiff is not usable
check_abidiff() {
	type -P abidiff >/dev/null || skip "abidiff not found in PATH"

	local file
	file=$(file "${LIBD100}") # check on its own for -e exit status

	[[ ${file} == *debug_info* ]] || skip "debug symbols needed for abidiff tests, build with -g"
}

# copydat <files...>
#	Copy data files to ${WORKDIR}
copydat() {
	cp "${@/#/${DATADIR}/}" .
}

# expect [options] <expected-glob> [args]
#	Return true if ${SCRIPT}'s output matches <expected-glob>
#	Options:
#		-! invert match
#		-2 compare with stderr instead of stdout
#		-c take command from [args] instead of default ${SCRIPT}
#		-e use regex instead of glob
#		-s do not auto-escape spaces from glob
expect() {
	local op='==' invert= stdout=true escape=true

	local cmd=("${BASH}" "${SCRIPT}")

	while (( ${#} )); do
		case ${1} in
			-!) invert='!';;
			-2) stdout=false;;
			-c) cmd=();;
			-e) op='=~';;
			-s) escape=false;;
			*) break;;
		esac
		shift
	done

	local output errno=0
	if ${stdout}; then
		output=$("${cmd[@]}" "${@:2}") || errno=${?}
		echo "${output}"
	else
		{ output=$("${cmd[@]}" "${@:2}" 3>&2 2>&1 1>&3-) || errno=${?}; } 2>&1
		echo "${output}" >&2
	fi

	local glob=${1}
	${escape} && glob=${1// /\\ }

	eval "[[ ${invert} \${output} ${op} ${glob} ]]" || fail "output mismatch for '${*}'"

	return ${errno}
}

# get_ebuild [-v] <PN> <PVR> <bin:file,ARG:value,cmd:name/args,...>
# 	Echos path to ebuild created as <PN>-<PVR>.ebuild (category = dev-test/)
#	Lowercase prefix determines do* command to use, e.g. bin: = dobin <file>
#	Uppercase prefix means to set a variable like "RDEPEND:value"
#	cmd prefix will run name/args as-is
#	: on its own means to install nothing
#	-v indicates to use virtual/ category instead of default
#	Note: these ebuilds should not actually build anything, use meson
get_ebuild() {
	[[ -d ${ROOT} ]]

	local cat=dev-test
	if [[ ${1} == -v ]]; then
		cat=virtual
		shift
	fi

	local pn=${1} pvr=${2}
	shift 2

	local key val soname= cmds= files= vars=
	while (( ${#} )); do
		key=${1%%:*}
		val=${1#*:}
		if [[ ! ${key} ]]; then
			files+=':;'
		elif [[ ${key} == cmd ]]; then
			cmds+="${val};"
		elif [[ ${key,,} == ${key} ]]; then
			files+="do${key} "
			if [[ ${val:0:1} != / ]]; then
				files+="${PWD}/"
			fi
			files+="${val};"

			if [[ ${key} == lib.so ]]; then
				val=${val##*/}
				soname=${val##*.so.}
				soname=${val%%.so.*}.so.${soname%%.*}
				files+="dosym -r /usr/\$(get_libdir)/{${val},${soname}};"
				files+="dosym -r /usr/\$(get_libdir)/{${val},${val%%.so.*}.so};"
			fi
		else
			vars+="${key}=\"${val}\";"
		fi
		shift
	done

	if [[ ! ${files} ]]; then
		# always install at least something
		files="newbin - ${pn}-${pvr} <<<'#!/usr/bin/env true';"
	elif [[ ${soname} ]]; then
		files+="newenvd - 00\${PN} <<<LDPATH=\"\\\"${EPREFIX}/usr/\$(get_libdir)\\\"\";"
	fi

	mkdir -p "${ROOT}"/overlay/${cat}/${pn}

	local ebuild=${ROOT}/overlay/${cat}/${pn}/${pn}-${pvr}.ebuild
	cat > "${ebuild}" <<-EOF
		EAPI=8

		DESCRIPTION="ebuild for ${pn}"
		HOMEPAGE="https://localhost/"
		S="\${WORKDIR}"

		LICENSE="BSD-2"
		SLOT="0"
		KEYWORDS="test"

		${vars}

		src_install() {
			${cmds}
			${files}
		}
	EOF

	echo "${ebuild}"
}

# md5cmp <md5sum> <file>
#	Return true if <file> matches <md5sum>
md5cmp() {
	md5sum --quiet -c - <<<"${*}" >/dev/null || fail "checksum mismatch for '${2}'"
}

# set_bashrc
#	Set bashrc to be loaded by ebuild(1), should be done after set_portroot
#	Disables all features by default, should export variables as needed
set_bashrc() {
	echo "IWDT_ALL=n; . '${WORKROOT}/bashrc' || die" \
		> "${ROOT}"/etc/portage/bashrc
}

# set_conf <dir> <suffix> <content>
#	Setup a ${SCRIPT}.<suffix> config file with <content> in ./<dir>
set_conf() {
	[[ -d ${1} ]] || mkdir "${1}"

	echo "${3}" > "${1}/${SCRIPT##*/}.${2}"
}

# set_portroot
#	Setup and export ROOT, EROOT, and PORTAGE_CONFIGROOT
#	Needed for any operation with portageq/qlist/ebuild/etc...
ROOT=
set_portroot() {
	# skip if these commands are missing rather than fail (not on Gentoo?)
	type -P ebuild portageq qatom qlist >/dev/null \
		|| skip "portage or portage-utils missing"

	export ROOT="${PWD}/root"
	export EROOT="${ROOT}"
	export PORTAGE_CONFIGROOT="${ROOT}"

	mkdir -p root/{etc/portage,overlay/{eclass,metadata,profiles},tmp}

	cat > root/etc/portage/repos.conf <<-EOF
		[DEFAULT]
		main-repo = ${PACKAGE}

		[${PACKAGE}]
		location = ${ROOT}/overlay
	EOF

	cat > root/etc/portage/make.conf <<-EOF
		ACCEPT_KEYWORDS="test"
		FEATURES="-* unprivileged"
		PORTAGE_TMPDIR="${ROOT}/tmp"
	EOF

	cat > root/overlay/metadata/layout.conf <<-EOF
		masters =
		thin-manifests = true
	EOF

	cat > root/overlay/profiles/make.defaults <<-EOF
		ARCH="test"
		CHOST="test"
	EOF
	echo test > root/overlay/profiles/arch.list
	cat > root/overlay/profiles/categories <<-EOF
		dev-test
		virtual
	EOF
	echo ${PACKAGE} > root/overlay/profiles/repo_name

	ln -s ../../overlay/profiles root/etc/portage/make.profile

	touch root/etc/ld.so.conf # need to exist for qa-vdb
}

shellcheck() {
	type -P shellcheck >/dev/null || skip "shellcheck not found in PATH"
	[[ ${SHELLCHECK} == 1 ]] || skip "shellcheck test is disabled"

	local sccmd=(
		shellcheck --norc -s bash -x

		# optional checks
		-o require-double-brackets
		-o require-variable-braces

		# expect these to be always intended/known
		-e SC1007 #(Remove space after = if trying to assign a value)
		-e SC2015 #(Note that A && B || C is not if-then-else)
		-e SC2030 #(Modification of var is local)
		-e SC2031 #(var was modified in a subshell)
		-e SC2059 #(Don't use variables in the printf format string)
		-e SC2119 #(Use foo "$@" if function's $1 should mean script's $1)

		# widespread use of "func array" that sets array where SC can't know
		# (use of `set -u` should pick these up in general)
		-e SC2154 #(var is referenced but not assigned)

		"${@}" "${SCRIPT##*/}"
	)

	local l
	while IFS= read -r l; do
		# replace include by direct so SC knows where to look
		if [[ $l =~ ^include\ ([^ ]+) ]]; then
			echo ". \"${LIBROOT}/${BASH_REMATCH[1]}.bashlib\""
			continue
		fi

		# insert directive SC will recognize for !SC[0-9] short comments
		if [[ $l =~ \#.*!((,*SC[0-9]+)+) ]]; then
			echo "# shellcheck disable=${BASH_REMATCH[1]}"
		fi

		echo "${l}"
	done < "${SCRIPT}" > "${SCRIPT##*/}"

	echo "running: ${sccmd[*]}"
	command "${sccmd[@]}"
}

# xfail [-s] <expected-errno> <command> <args...>
#	Run command and abort if value is not <expected-errno>
#	If -s, use a subshell (may not be best if wrapping expect())
xfail() {
	local errno=0

	if [[ ${1} == -s ]]; then
		shift
		("${@:2}") || errno=${?}
	else
		"${@:2}" || errno=${?}
	fi

	[[ ${errno} == ${1} ]] \
		|| fail "expected return value ${1}, got ${errno} for command: '${*:2}'"
}

# setup per-test work dir in build dir, cleanup previous if exists
if [[ -f ${WORKDIR}/test-tmp-dir ]]; then
	rm -r "${WORKDIR}"
fi
mkdir "${WORKDIR}"
touch "${WORKDIR}"/test-tmp-dir # sanity file for safer `rm -r`

# try to clear portage env vars not to confuse ebuild-run-in-ebuild,
# and also clear env known to be used by scripts here
# (tempting to use env -i but users may need obscure env vars)
unset FEATURES {,E,B,SYS,ESYS}ROOT USE
unset "${!ACCEPT@}" "${!EBUILD@}" "${!LIBDIR@}" "${!MULTILIB@}" "${!PORTDIR@}"
unset "${!PKGCORE@}" "${!PORTAGE@}"
unset "${!EOLDNEW@}" "${!IWDT@}" "${!QA@}"
export LC_ALL=C EPREFIX= XDG_CONFIG_HOME=${WORKDIR}

# run test in work dir
cd "${WORKDIR}"
export TMPDIR=${PWD}
[[ ! ${INCLUDE} ]] || . "${SCRIPT}"
. "${TEST}"

# vim: ts=4
