Blob Blame History Raw
#!/bin/sh
# Copyright 2017 Red Hat, Inc.
#
# Author: Jan Pokorny <jpokorny@redhat.com>
#
# This file is part of libqb.
#
# libqb is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2.1 of the License, or
# (at your option) any later version.
#
# libqb is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with libqb.  If not, see <http://www.gnu.org/licenses/>.

# Given the source RPM for libqb, this will run through the basic test matrix
# so as to figure out the outcomes for particular linker (pre-2.29 and 2.29+
# differing in visibility of orphan section delimiting boundary symbols and
# hence possibly causing harm to the logging facility of libqb) being used
# for particular part of the composite logging system (libqb itself,
# it's direct client / (client library + it's own client that uses logging)
# as well).  While this is tailored to Fedora, it should be possible to
# run this testsuite wherever following is present:
#
#   - rpm (for parsing archive name embedded in libqb.src.rpm [note that
#     rpm2cpio is part of rpm package as well] because the extracted dir
#     follows the same naming, which we need to know)
#   - mock (https://github.com/rpm-software-management/mock/)
#     + dependencies + fedora-27-${arch} configuration for mock
#     (or whatever other configuration if some variables below are
#     changed appropriately)
#   - koji (https://pagure.io/koji/) + dependencies (but binutils packages
#     can be precached when downloaded from https://koji.fedoraproject.org/
#     manually)
#   - internet connection (but see the above statement for koji, and
#     possibly the full package set within the build'n'test underlying
#     container can be precached without further details on "how")
#   - commons (coreutils, findutils, sed, ...)
#
# The testsuite will not mangle with your host system as mock spawns
# it's somewhat private container for be worked with under the hood.
#
# Note that in order not to get mad when entering the root password anytime
# mock is invoked, you can add the user initiating the test run to the
# 'mock' group.  Be aware of the associated security risks, though:
# https://github.com/rpm-software-management/mock/wiki#setup

set -eu

# change following as suitable

arch=x86_64
mock_args="-r fedora-27-${arch}"
pkg_binutils_228=binutils-2.28-14.fc27
#pkg_binutils_228=binutils-2.27-23.fc27  # alternatively test with 2.27 ...
pkg_binutils_229=binutils-2.29-6.fc27
#pkg_binutils_229=binutils-2.29.1-2.fc28  # alternatively test with 2.29.1

#
# prettified reporters
#

do_progress () { printf "\x1b[7m%s\x1b[0m\n" "$*"; }
do_info () { printf "\x1b[36mINFO: %s\x1b[0m\n" "$*"; }
do_warn () { printf "\x1b[31mWARNING: %s\x1b[0m\n" "$*"; }
do_die () { printf "\x1b[31mFATAL: %s\x1b[0m\n" "$*"; exit 1; }

#
# actual building blocks
#

# $1, ... $N: packages (and possibly related subpackages) to be downloaded
do_download () {
	while test $# -gt 0; do
		if test -d "_pkgs/$1" 2>/dev/null; then
			do_info "$1 already downloaded"
			shift; continue
		fi
		mkdir -p "_pkgs/$1"
		( cd "_pkgs/$1" && koji download-build --arch="${arch}" "$1" )
		shift
	done
}

# $1, ... $N: descriptors of packages to be installed
do_install () {
	while test $# -gt 0; do
		if test -d "_pkgs/$1" 2>/dev/null; then
			do_install_inner "_pkgs/$1"/*.rpm
		else
			do_warn "$1 is not downloaded, hence skipped"
		fi
		shift
	done
}

# $1, ... $N: concrete packages to be installed
do_install_inner () {
	_remove_cmd="mock ${mock_args} -- shell \"rpm --nodeps --erase"
	_install_cmd="mock ${mock_args}"
	while test $# -gt 0; do
		case "$1" in
		*.src.rpm|*-debuginfo*|*-debugsource*) ;;
		*)
			_pkg_name="$(basename "$1" | sed 's|\(-[0-9].*\)||')"
			_remove_cmd="${_remove_cmd} \'${_pkg_name}\'"
			_install_cmd="${_install_cmd} --install \"$1\"";;
		esac
		shift
	done
	eval "${_remove_cmd}\"" || :  # extra quotation mark intentional
	eval "${_install_cmd}"
}

# $1: full path of srpm to be rebuilt
# $2: %{dist} macro for rpmbuild (distinguishing the builds)
do_buildsrpm () {
	_pkg_descriptor="$(basename "$1" | sed 's|\.src\.rpm$||')"
	# need to prune due to possible duplicates caused by differing %{dist}
	rm -f -- "_pkgs/${_pkg_descriptor}"/*
	mock ${mock_args} -Nn --define "dist $2" --define '_without_check 1' \
	  --resultdir "_pkgs/${_pkg_descriptor}" --rebuild "$1"
}

# $1: full path srpm to be rebuilt
# $2: extra (presumably) variable assignments for the make goal invocation
do_compile_interlib () {
	mock ${mock_args} --shell \
		"find \"builddir/build/BUILD/$1/tests/functional\" \
		\( -name log_internal -o -name '*.c' \) -prune \
		-o -name '*liblog_inter*' \
		-exec rm -- {} \;"
	mock ${mock_args} --shell "( cd \"builddir/build/BUILD/$1\"; ./configure )"
	mock ${mock_args} --shell \
		"make -C \"builddir/build/BUILD/$1/tests/functional/log_external\" \
		liblog_inter.la $2"
}

# $1: full path srpm to be rebuilt
# $2: which type of client to work with (client/interclient)
# $3: base (on-host) directory for test results
# $4: output file to capture particular test result
# $5: extra (presumably) variable assignments for the make goal invocation
do_compile_and_test_client () {
	_result=$4
	case "$2" in
	interclient)
		_logfile=log_test_interlib_client
		mock ${mock_args} --shell \
			"find \"builddir/build/BUILD/$1/tests/functional\" \
			\( -name log_internal -o -name '*.err' -o -name '*.c' \) -prune \
			-o \( -name '*log_interlib_client*' -o -name \"${_logfile}.log\" \) \
			-exec rm -- {} \;"
		;;
	client|*)
		_logfile=log_test_client
		mock ${mock_args} --shell \
			"find \"builddir/build/BUILD/$1/tests/functional\" \
			\( -name log_internal -o -name '*.err' -o -name '*.c' \) -prune \
			-o \( -name '*log_client*' -o -name \"${_logfile}.log\" \) \
			-exec rm -- {} \;"
		;;
	esac
	mock ${mock_args} --copyin "syslog-stdout.py" "builddir"
	mock ${mock_args} --shell "( cd \"builddir/build/BUILD/$1\"; ./configure )"
	mock ${mock_args} --shell \
		"python3 builddir/syslog-stdout.py \
		  >\"builddir/build/BUILD/$1/tests/functional/log_external/.syslog\" & \
		{ sleep 2; make -C \"builddir/build/BUILD/$1/tests/functional/log_external\" \
		  check-TESTS \"TESTS=../${_logfile}.sh\" $5; } \
		&& ! test -s \"builddir/build/BUILD/$1/tests/functional/log_external/.syslog\"; \
		ret_ec=\$?; \
		( cd \"builddir/build/BUILD/$1/tests/functional/log_external\"; \
		  cat .syslog >> test-suite.log; \
		  echo SYSLOG-begin; cat .syslog; echo SYSLOG-end ); \
		ret () { return \$1; }; ret \${ret_ec}" \
	  && _result="${_result}_good" \
	  || _result="${_result}_bad"
	mock ${mock_args} --copyout \
		"builddir/build/BUILD/$1/tests/functional/log_external/test-suite.log" \
		"$3/${_result}"
}

do_shell () {
	mock ${mock_args} --shell
}

# $1, ... $N: "argv"
do_proceed () {

	_makevars=
	_resultsdir_tag=
	_selfcheck=1
	_clientlogging=1
	_interliblogging=1
	while :; do
		case "$1" in
		shell) shift; do_shell "$@"; return;;
		-nsc) _resultsdir_tag="${_resultsdir_tag}$1"; shift; _selfcheck=0;;
		-ncl) _resultsdir_tag="${_resultsdir_tag}$1"; shift; _clientlogging=0;;
		-nil) _resultsdir_tag="${_resultsdir_tag}$1"; shift; _interliblogging=0;;
		-*) do_die "Uknown option: $1";;
		*) break;;
		esac
	done

	if test -n "${_resultsdir_tag}"; then
		_makevars="CPPFLAGS=\"$(test "${_selfcheck}" -eq 1 || printf %s ' -DNSELFCHECK') \
		           $(test "${_clientlogging}" -eq 1 || printf %s ' -DNLOG') \
		           $(test "${_interliblogging}" -eq 1 || printf %s ' -DNLIBLOG')\""
		_makevars=$(echo ${_makevars})
	fi

	test -s "$1" || do_die "Not an input file: $1"
	_libqb_descriptor_path="$1"
	_libqb_descriptor="$(basename "${_libqb_descriptor_path}" \
	                     | sed 's|\.src\.rpm$||')"
	_libqb_descriptor_archive="$(rpm -q --qf '[%{FILENAMES}\n]' \
	                             -p "${_libqb_descriptor_path}" \
	                             | sed -n '/\.tar/{s|\.tar\.[^.]*$||;p;q}')"

	_resultsdir="_results/$(date '+%y%m%d_%H%M%S')_${_libqb_descriptor}${_resultsdir_tag}"
	mkdir -p "${_resultsdir}"
	rm -f -- "${_resultsdir}/*"

	_dist=
	_outfile=
	_outfile_client=
	_outfile_qb=

	do_download "${pkg_binutils_228}" "${pkg_binutils_229}"

	for _pkg_binutils_libqb in "${pkg_binutils_228}" "${pkg_binutils_229}"; do

		case "${_pkg_binutils_libqb}" in
		${pkg_binutils_228}) _outfile_qb="qb+"; _dist=.binutils228;;
		${pkg_binutils_229}) _outfile_qb="qb-"; _dist=.binutils229;;
		*) _outfile_qb="?";;
		esac

		do_progress "installing ${_pkg_binutils_libqb} so as to build" \
		            "libqb [${_outfile_qb}]"
		do_install "${_pkg_binutils_libqb}"

		do_progress "building ${_libqb_descriptor_path} with" \
		            "${_pkg_binutils_libqb} [${_outfile_qb}]"
		do_buildsrpm "${_libqb_descriptor_path}" "${_dist}"

		do_progress "installing ${_libqb_descriptor}-based packages" \
		            "built with ${_pkg_binutils_libqb} [${_outfile_qb}]"
		do_install "${_libqb_descriptor}"
		# from now on, we can work fully offline, also to speed
		# the whole thing up (and not to bother the mirrors)
		mock_args="${mock_args} --offline"

		for _pkg_binutils_interlib in none "${pkg_binutils_228}" "${pkg_binutils_229}"; do

			case "${_pkg_binutils_interlib}" in
			none) _outfile="${_outfile_qb}";;
			${pkg_binutils_228}) _outfile="${_outfile_qb}_il+";;
			${pkg_binutils_229}) _outfile="${_outfile_qb}_il-";;
			*) _outfile="${_outfile_qb}_?";;
			esac

			case "${_pkg_binutils_interlib}" in
			none)	;;
			*)
				do_progress "installing ${_pkg_binutils_interlib}" \
				            "so as to build interlib [${_outfile}]"
				do_install "${_pkg_binutils_interlib}"

				do_progress "building interlib with ${_libqb_descriptor_archive}" \
				            "+ ${_pkg_binutils_interlib} [${_outfile}]" \
				            "{${_makevars}}"
				do_compile_interlib "${_libqb_descriptor_archive}" "${_makevars}"
				;;
			esac

			for _pkg_binutils_client in "${pkg_binutils_228}" "${pkg_binutils_229}"; do

				_client=client
				test "${_pkg_binutils_interlib}" = none || _client=interclient

				case "${_pkg_binutils_client}" in
				${pkg_binutils_228}) _outfile_client="${_outfile}_c+";;
				${pkg_binutils_229}) _outfile_client="${_outfile}_c-";;
				*) _outfile_client="${_outfile}_?";;
				esac

				do_progress "installing ${_pkg_binutils_client}" \
				            "so as to build ${_client} [${_outfile_client}]"
				do_install "${_pkg_binutils_client}"
				do_progress "building ${_client} with ${_libqb_descriptor_archive}" \
				            "+ ${_pkg_binutils_client} [${_outfile_client}]" \
				            "{${_makevars}}"
				do_compile_and_test_client "${_libqb_descriptor_archive}" \
				                           "${_client}" "${_resultsdir}" \
				                           "${_outfile_client}" "${_makevars}"
			done
		done
	done
}

{ test $# -eq 0 || test "$1" = -h || test "$1" = --help; } \
  && printf '%s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n' \
            "usage: $0 {[-n{sc,cl,il}]* <libqb.src.rpm> | shell}" \
            "- use '-nsc' to suppress optional self-check (\"see whole story\")" \
            "- use '-ncl' to suppress client-side logging" \
            "- use '-nil' to suppress interlib-side logging" \
            "- 'make -C ../.. srpm' (or so) can generate the requested input" \
            "   (in that case, apparently, refer to '../../libqb-X.src.rpm')" \
            "- _pkgs dir caches (intermediate or not) packages to work with" \
            "- results stored in '_results/<timestamp>_<input_name>[_<tag>]'" \
  || do_proceed "$@"