# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Name: libraries.sh - part of the BeakerLib project
# Description: Functions for importing separate libraries
#
# Author: Petr Muller <muller@redhat.com>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Copyright (c) 2012 Red Hat, Inc. All rights reserved.
#
# This copyrighted material is made available to anyone wishing
# to use, modify, copy, or redistribute it subject to the terms
# and conditions of the GNU General Public License version 2.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod
=head1 NAME
BeakerLib - libraries - mechanism for loading shared test code from libraries
=head1 DESCRIPTION
This file contains functions for bringing external code into the test
namespace.
=head1 FUNCTIONS
=cut
# Extract a list of required libraries from a Makefile
# Takes a directory where the library is placed
__INTERNAL_extractRequires(){
local MAKEFILE="$1/Makefile"
if [ -f "$MAKEFILE" ]
then
# 1) extract RhtsRequires lines, where RhtsRequires is not commented out
# 2) extract test(/Foo/Bar/Library/Baz) patterns
# 3) extract Bar/Baz from the patterns
# 4) make a single line of space-separated library IDs
__INTERNAL_LIBRARY_DEPS="$(grep -E '^[^#]*RhtsRequires' $MAKEFILE \
| grep -E -o -e 'test\(/[^/)]+/[^/)]+/Library/[^/)]+\)' -e '[Ll]ibrary\([^)]*\)' \
| sed -e 's|test(/[^/)]*/\([^/)]*\)/Library/\([^/)]*\))|\1/\2|g' -e 's|[Ll]ibrary(\(.*\))|\1|' \
| tr '\n' ' ')"
else
__INTERNAL_LIBRARY_DEPS=""
fi
echo $__INTERNAL_LIBRARY_DEPS
}
# Extract a location of an original sourcing script from $0
__INTERNAL_extractOrigin(){
local SOURCE
if [ ! -e "$0" ]
then
SOURCE="$( readlink -f . )"
else
SOURCE="$( readlink -f $0 )"
fi
local DIR="$( dirname "$SOURCE" )"
while [ -h "$SOURCE" ]
do
SOURCE="$(readlink -f "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
echo "$DIR"
}
__INTERNAL_TraverseRoot="$(__INTERNAL_extractOrigin)"
# Traverse directories upwards and search for the matching path
__INTERNAL_rlLibraryTraverseUpwards() {
local DIRECTORY="$1"
local COMPONENT="$2"
local LIBRARY="$3"
while [ "$DIRECTORY" != "/" ]
do
DIRECTORY="$( dirname $DIRECTORY )"
if [ -d "$DIRECTORY/$COMPONENT" ]
then
local CANDIDATE="$DIRECTORY/$COMPONENT/Library/$LIBRARY/lib.sh"
if [ -f "$CANDIDATE" ]
then
LIBFILE="$CANDIDATE"
break
fi
local CANDIDATE="$( echo $DIRECTORY/*/$COMPONENT/Library/$LIBRARY/lib.sh )"
if [ -f "$CANDIDATE" ]
then
LIBFILE="$CANDIDATE"
break
fi
fi
done
}
__INTERNAL_rlLibrarySearchInRoot(){
local COMPONENT="$1"
local LIBRARY="$2"
local BEAKERLIB_LIBRARY_PATH="${3:-/mnt/tests}"
rlLogDebug "rlImport: Trying root: [$BEAKERLIB_LIBRARY_PATH]"
local CANDIDATE="$BEAKERLIB_LIBRARY_PATH/$COMPONENT/Library/$LIBRARY/lib.sh"
if [ -f "$CANDIDATE" ]
then
LIBFILE="$CANDIDATE"
return
fi
local CANDIDATE="$( echo $BEAKERLIB_LIBRARY_PATH/*/$COMPONENT/Library/$LIBRARY/lib.sh )"
if [ -f "$CANDIDATE" ]
then
LIBFILE="$CANDIDATE"
return
fi
rlLogDebug "rlImport: Library not found in $BEAKERLIB_LIBRARY_PATH"
}
__INTERNAL_rlLibrarySearch() {
local COMPONENT="$1"
local LIBRARY="$2"
rlLogDebug "rlImport: Looking if we got BEAKERLIB_LIBRARY_PATH"
if [ -n "$BEAKERLIB_LIBRARY_PATH" ]
then
rlLogDebug "rlImport: BEAKERLIB_LIBRARY_PATH is set: trying to search in it"
__INTERNAL_rlLibrarySearchInRoot "$COMPONENT" "$LIBRARY" "$BEAKERLIB_LIBRARY_PATH"
if [ -n "$LIBFILE" ]
then
local VERSION="$(__INTERNAL_extractLibraryVersion "$LIBFILE" "$COMPONENT/$LIBRARY")"
VERSION=${VERSION:+", version '$VERSION'"}
rlLogInfo "rlImport: Found '$COMPONENT/$LIBRARY'$VERSION in BEAKERLIB_LIBRARY_PATH"
return
fi
else
rlLogDebug "rlImport: No BEAKERLIB_LIBRARY_PATH set: trying default"
fi
__INTERNAL_rlLibrarySearchInRoot "$COMPONENT" "$LIBRARY"
if [ -n "$LIBFILE" ]
then
local VERSION="$(__INTERNAL_extractLibraryVersion "$LIBFILE" "$COMPONENT/$LIBRARY")"
VERSION=${VERSION:+", version '$VERSION'"}
rlLogInfo "rlImport: Found '$COMPONENT/$LIBRARY'$VERSION in /mnt/tests"
return
fi
__INTERNAL_rlLibrarySearchInRoot "$COMPONENT" "$LIBRARY" "/usr/share/beakerlib-libraries"
if [ -n "$LIBFILE" ]
then
local VERSION="$(__INTERNAL_extractLibraryVersion "$LIBFILE" "$COMPONENT/$LIBRARY")"
VERSION=${VERSION:+", version '$VERSION'"}
rlLogInfo "rlImport: Found '$COMPONENT/$LIBRARY'$VERSION in /usr/share/beakerlib-libraries"
return
fi
if [ -n "$__INTERNAL_TraverseRoot" ]
then
rlLogDebug "rlImport: Trying to find the library in directories above test"
rlLogDebug "rlImport: Starting search at: $__INTERNAL_TraverseRoot"
__INTERNAL_rlLibraryTraverseUpwards "$__INTERNAL_TraverseRoot" "$COMPONENT" "$LIBRARY"
if [ -n "$LIBFILE" ]
then
local VERSION="$(__INTERNAL_extractLibraryVersion "$LIBFILE" "$COMPONENT/$LIBRARY")"
VERSION=${VERSION:+", version '$VERSION'"}
rlLogInfo "rlImport: Found '$COMPONENT/$LIBRARY'$VERSION during upwards traversal"
return
fi
fi
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlImport
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod
=head3 rlImport
Imports code provided by one or more libraries into the test namespace.
The library search mechanism is based on Beaker test hierarchy system, i.e.:
/component/type/test-name/test-file
When test-file calls rlImport with 'foo/bar' parameter, the directory path
is traversed upwards, and a check for presence of the test /foo/Library/bar/
will be performed. This means this function needs to be called from
the test hierarchy, not e.g. the /tmp directory.
Once library is found, it is sourced and a verifier function is called.
The verifier function is cunstructed by composing the library prefix and
LibraryLoaded. Library prefix can be defined in the library itself.
If the verifier passes the library is ready to use. Also variable
B<E<lt>PREFIXE<gt>LibraryDir> is created and it points to the library folder.
Usage:
rlImport --all
rlImport LIBRARY [LIBRARY2...]
=over
=item --all
Read Makefile in current/original directory, pick library requirements up and
import them all.
=item LIBRARY
Must have 'component/library' format. Identifies the library to import.
=back
Returns 0 if the import of all libraries was successful. Returns non-zero
if one or more library failed to import.
=cut
__INTERNAL_first(){
echo $1
}
__INTERNAL_tail(){
shift
echo $*
}
__INTERNAL_envdebugget() {
local tmp="$(set | sed -r '/^.*\S+ \(\).$/,$d;/^(_|BASH_.*|FUNCNAME|LINENO|PWD|__INTERNAL_LIBRARY_IMPORTS_.*|__INTERNAL_envdebug.*)=/d')"
if [[ -z "$1" ]]; then
__INTERNAL_envdebugvariables="$tmp"
__INTERNAL_envdebugfunctions="$(declare -f)"
else
echo "$tmp"
fi
}
__INTERNAL_envdebugdiff() {
rlLogDebug "rlImport: library $1 changes following environment; changed functions are marked with asterisk (*)"
diff -U0 <(echo "$__INTERNAL_envdebugvariables") <(__INTERNAL_envdebugget 1) | tail -n +3 | grep -E -v '^@@'
local line fn print='' print2 LF="
"
while IFS= read line; do
[[ "$line" =~ ^(.)([^[:space:]]+)[[:space:]]\(\) ]] && {
[[ -n "$print" ]] && {
echo "$fn"
print=''
}
print2=''
local tmp="${BASH_REMATCH[1]}"
[[ "$tmp" == " " ]] && {
print2=1
tmp='*'
}
fn="$tmp${BASH_REMATCH[2]}()"
continue
}
[[ "${line:0:1}" != " " ]] && print=1
[[ "$DEBUG" =~ ^[0-9]+$ ]] && [[ -n "$print2" && $DEBUG -ge 2 || $DEBUG -ge 3 ]] && fn="$fn$LF$line"
done < <(diff -U100000 <(echo "$__INTERNAL_envdebugfunctions") <(declare -f) | tail -n +3 | grep -E -v '^@@'; echo " _ ()")
unset __INTERNAL_envdebugfunctions __INTERNAL_envdebugvariables
}
__INTERNAL_extractLibraryVersion() {
local LIBFILE=$1
local LIBNAME=$2
local VERSION=""
local RESULT=""
# Search in lib.sh
VERSION="${VERSION:+"$VERSION, "}$( grep -E '^#\s*library-version = \S*' $LIBFILE | sed 's|.*library-version = \(\S*\).*|\1|')"
# Search lib in rpms and get version
if RESULT=( $(rpm -q --queryformat "%{NAME} %{VERSION}-%{RELEASE}\n" --whatprovides "library($LIBNAME)") ); then
# Found library-version, set $VERSION
while [[ -n "$RESULT" ]]; do
rljRpmLog $RESULT
RESULT=( "${RESULT[@]:1}" )
VERSION="${VERSION:+"$VERSION, "}$RESULT"
RESULT=( "${RESULT[@]:1}" )
done
fi
echo "$VERSION"
return 0
} #end __INTERNAL_extractLibraryVersion
rlImport() {
local RESULT=0
if [ -z "$1" ]
then
rlLogError "rlImport: At least one argument needs to be provided"
return 1
fi
local WORKLIST="$*"
if [ "$1" == '--all' ]; then
rlLogDebug "Try to import all libraries specified in Makefile"
WORKLIST=$(__INTERNAL_extractRequires "${__INTERNAL_TraverseRoot}")
if [ -z "$WORKLIST" ]
then
rlLogInfo "rlImport: No libraries found in Makefile"
return 0
fi
fi
local PROCESSING="x"
local LIBS_TO_LOAD=''
# Process all arguments
while true
do
rlLogDebug "rlImport: WORKLIST [$WORKLIST]"
# Pick one library from the worklist
PROCESSING="$(__INTERNAL_first $WORKLIST)"
WORKLIST=$(__INTERNAL_tail $WORKLIST)
if [ -z "$PROCESSING" ]
then
break
fi
LIBS_TO_LOAD="$PROCESSING $LIBS_TO_LOAD"
# Extract two identifiers from an 'component/library' argument
local COMPONENT=$( echo $PROCESSING | cut -d '/' -f 1 )
local LIBRARY=$( echo $PROCESSING | cut -d '/' -f 2 )
local COMPONENT_hash=$( rlHash --algorithm hex "$COMPONENT" )
local LIBRARY_hash=$( rlHash --algorithm hex "$LIBRARY" )
local LOCATIONS_varname="__INTERNAL_LIBRARY_LOCATIONS_C${COMPONENT_hash}_L${LIBRARY_hash}"
local IMPORTS_varname="__INTERNAL_LIBRARY_IMPORTS_C${COMPONENT_hash}_L${LIBRARY_hash}"
# If the lib was already processed, do nothing
if [ -n "${!IMPORTS_varname}" ]
then
continue
fi
if [ -z "$COMPONENT" ] || [ -z "$LIBRARY" ] || [ "$COMPONENT/$LIBRARY" != "$PROCESSING" ]
then
rlLogError "rlImport: Malformed argument [$PROCESSING]"
eval $IMPORTS_varname="FAIL"
RESULT=1
continue;
fi
rlLogDebug "rlImport: Searching for library $COMPONENT/$LIBRARY"
# LIBFILE is set inside __INTERNAL_rlLibrarySearch if a suitable path is found
local LIBFILE=""
__INTERNAL_rlLibrarySearch $COMPONENT $LIBRARY
if [ -z "$LIBFILE" ]
then
rlLogError "rlImport: Could not find library $PROCESSING"
eval $IMPORTS_varname="FAIL"
RESULT=1
continue;
else
rlLogInfo "rlImport: Will try to import $COMPONENT/$LIBRARY from $LIBFILE"
fi
rlLogDebug "rlImport: Collecting dependencies for library $COMPONENT/$LIBRARY"
local LIBDIR="$(dirname $LIBFILE)"
if ! eval $LOCATIONS_varname='$LIBDIR'
then
rlLogError "rlImport: Error processing: $LOCATIONS_varname='$LIBDIR'"
RESULT=1
continue
fi
WORKLIST="$WORKLIST $(__INTERNAL_extractRequires $LIBDIR )"
if ! eval $IMPORTS_varname="LOC"
then
rlLogError "rlImport: Error processing: $IMPORTS_varname='LOC'"
RESULT=1
continue
fi
done
rlLogDebug "rlImport: LIBS_TO_LOAD='$LIBS_TO_LOAD'"
local library
for library in $LIBS_TO_LOAD
do
local COMPONENT=$( echo $library | cut -d '/' -f 1 )
local LIBRARY=$( echo $library | cut -d '/' -f 2 )
local COMPONENT_hash=$( rlHash --algorithm hex "$COMPONENT" )
local LIBRARY_hash=$( rlHash --algorithm hex "$LIBRARY" )
local LOCATIONS_varname="__INTERNAL_LIBRARY_LOCATIONS_C${COMPONENT_hash}_L${LIBRARY_hash}"
local IMPORTS_varname="__INTERNAL_LIBRARY_IMPORTS_C${COMPONENT_hash}_L${LIBRARY_hash}"
[ "${!IMPORTS_varname}" != "LOC" ] && {
rlLogDebug "rlImport: skipping $library as it is already processed"
continue
}
local LIBFILE="${!LOCATIONS_varname}/lib.sh"
# Try to extract a prefix comment from the file found
# Prefix comment looks like this:
# library-prefix = wee
local PREFIX="$( grep -E "library-prefix = [a-zA-Z_][a-zA-Z0-9_]*.*" $LIBFILE | sed 's|.*library-prefix = \([a-zA-Z_][a-zA-Z0-9_]*\).*|\1|')"
if [ -z "$PREFIX" ]
then
rlLogError "rlImport: Could not extract prefix from library $library"
RESULT=1
continue;
fi
# Construct the validating function
# Its supposed to be called 'prefixLibraryLoaded'
local VERIFIER="${PREFIX}LibraryLoaded"
rlLogDebug "rlImport: Constructed verifier function: $VERIFIER"
local SOURCEDEBUG=''
# Try to source the library
bash -n $LIBFILE && {
[[ -n "$DEBUG" ]] && {
SOURCEDEBUG=1
__INTERNAL_envdebugget
}
. $LIBFILE
}
# Call the validation callback of the function
if ! eval $VERIFIER
then
rlLogError "rlImport: Import of library $library was not successful (callback failed)"
RESULT=1
eval $IMPORTS_varname='FAIL'
[[ -n "$SOURCEDEBUG" ]] && {
__INTERNAL_envdebugdiff "$library"
}
continue;
fi
eval ${PREFIX}LibraryDir="$(dirname $LIBFILE)"
eval $IMPORTS_varname='PASS'
[[ -n "$SOURCEDEBUG" ]] && {
__INTERNAL_envdebugdiff "$library"
}
done
return $RESULT
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUTHORS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod
=head1 AUTHORS
=over
=item *
Petr Muller <muller@redhat.com>
=item *
Dalibor Pospisil <dapospis@redhat.com>
=back
=cut