blob: a250cc39166aea99c49294ba43f5cedd8e95056f [file] [log] [blame]
#!/bin/bash
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Collection of utility functions to help test the bash scripts. The contents
# of this library are prefixed with gn-test.
#
# This file expects the Bash Test Framework to be sourced. Due to the use of
# BT_EXPECT_EQ and related functions.
set -e
function is-mac {
[[ "$(uname -s)" == "Darwin" ]] && return 0
return 1
}
# Runs md5sum or equivalent on mac.
function run-md5 {
if is-mac; then
MD5_CMD=("/sbin/md5" "-r")
else
MD5_CMD=("md5sum")
fi
MD5_CMD+=("$@")
"${MD5_CMD[@]}"
}
if is-mac; then
# realpath is called as 'realpath -m "$path"'
realpath() {
[[ $2 = /* ]] && echo "$2" || echo "$PWD/${2#./}"
}
fi
# Runs a bash script. The function provides these conveniences over calling the
# script directly:
#
# * Rather than calling the bash script directly, this command explicitly
# invokes Bash and propagates some option flags.
# * Rather than showing the bash output, this command only outputs output if a
# test fails.
#
# Args: the script to run and all args to pass.
gn-test-run-bash-script() {
local shell_flags
# propagate certain bash flags if present
shell_flags=()
if [[ $- == *x* ]]; then
shell_flags+=( "-x" )
fi
local output
if ! output=$(bash "${shell_flags[@]}" "$@" 2>&1); then
echo "${output}"
return 1
fi
return 0
}
# Verifies that the arguments in BT_MOCK_ARGS match the arguments to this function.
# The number and order of arguments must match, or non-zero is returned.
# If a value of an argument is un-important it can be marked with the string
# _ANY_. This allows for matching arguments that may have unique values, such as temp
# filenames.
# Args: The expected arguments.
# Returns: 0 if found; 1 if not found.
gn-test-check-mock-args() {
BT_EXPECT_EQ "$#" "${#BT_MOCK_ARGS[@]}"
local expected=("$@")
local actual=("${BT_MOCK_ARGS[@]}")
for (( i=0; i<"${#expected[@]}"; i++ )); do
if [[ "${expected[$i]}" != "_ANY_" ]]; then
BT_EXPECT_EQ "${actual[$i]}" "${expected[$i]}"
fi
done
return 0
}
# Verifies that the given arguments appear in the command line invocation of the
# most previously sourced mock state. Any arguments passed to this function will
# be searched for in the actual arguments. This succeeds if the arguments are
# found in adjacent positions in the correct order.
#
# This function only checks for presence. As a result, it will NOT verify any of
# the following:
#
# * The arguments only appear once.
# * The arguments don't appear with conflicting arguments.
# * Any given argument --foo isn't overridden, say with a --no-foo flag later.
#
# Args: any number of arguments to check.
# Returns: 0 if found; 1 if not found.
gn-test-check-mock-partial() {
local expected=("$@")
for j in "${!BT_MOCK_ARGS[@]}"; do
local window=("${BT_MOCK_ARGS[@]:$j:${#expected}}")
local found=true
for k in "${!expected[@]}"; do
if [[ "${expected[$k]}" != "${window[$k]}" ]]; then
found=false
break
fi
done
if [[ "${found}" == "true" ]]; then
return 0
fi
done
BT_EXPECT false "Could not find expected:\n${expected[*]}\nin arguments:\n${BT_MOCK_ARGS[*]}"
return 1
}
# Returns the machine architecture specific subdirectory for tools in the sdk.
function gn-test-tools-subdir {
local machine
machine="$(uname -m)"
local dir
case "${machine}" in
x86_64)
dir="tools/x64"
;;
aarch64*)
dir="tools/arm64"
;;
armv8*)
dir="tools/arm64"
;;
*)
dir="tools/${machine}"
;;
esac
echo "${dir}"
}
# Custom mock logger. This is needed when there are calls to the
# same mock in the backgroun and foreground close to each other.
#
# Args:
# FILENAME: the file to write the mock state to. If the same mock is
# called multiple times, the invocation ordinal is appended.
#
# RC: the return code the mock is returning.
#
# Remainder of args are recorded as arguments passed to the mock.
#
function gn-test-log-mock {
FILENAME="$1"
shift
RC="$1"
shift
declare state_file="${FILENAME}"
if [[ -e "${state_file}" ]]; then
# Command was executed more than once. Use numeric suffixes.
mv "${state_file}" "${state_file}.1"
state_file="${state_file}.2"
elif [[ -e "${state_file}.1" ]]; then
declare -i index
declare -i max_index=1
for file in "${state_file}".*; do
[[ -e $file ]] || break # handle no files found.
index=${file##*.}
max_index=$(( index > max_index ? index : max_index ))
done
state_file="${state_file}.$((max_index+1))"
fi
# Write the args into the status file.
#
# This is split into three steps, the middle of which writes the Bash array
# literal. The array is written using printf and %q to quote or escape the
# elements of the $@ array. This is important for a number of reasons:
#
# * Using escaped double quotes around $@ causes all of the arguments to be
# concatenated into a single space-separated string.
# * Using escaped double quotes isn't safe if any item in the array contains a
# double quotation mark.
# * Using printf allows all strings to be safely included in the array.
# * Using printf prevents variable expansion when the status file is sourced as
# a script.
{
echo "#!/bin/bash"
printf "BT_MOCK_ARGS=( "
printf "%q " "${0}" "$@"
printf ")\n"
echo return "$RC"
} >> "${state_file}"
}
# Returns the latest name for the mock file given.
# The Bash test framework creates a file named <mock_state> (by default
# <tool>.mock_state) for the first execution of the mock. For a second
# execution, it renames the first file to <mock_state>.1 and creates a
# <mock_state>.2. After that, each subsequent execution of the mock creates
# a file named <mock_state>.<n>. For most tests, it is relevant to check
# the n-th specific execution, but in some rare cases, for example when the
# mock is executed in a loop, the test can care only about the latest execution
# and finding it is a tedious process. This method helps with that, returning
# <mock_state> if there was none or only a single invocation of the mock, or
# <mock_state>.<n> if there were <n> executions of the mock.
#
# Args:
# name: the mock state filename without the ".<n>" suffix.
#
function gn-test-latest-mock {
local path="$1"
if [[ -f "$path" ]]; then
echo "$path"
else
# find the number of dots in the mock state path:
local dots="${path//[^.]}"
# the key will be the last dot-separated token of numbered mock_state's, so:
# my.path.with.dots/my.tool.mock_state
# will be keyed by
# (# dots before numbering + 1 dot to separate the number + 1) = 7th field
# my.path.with.dots/my.tool.mock_state.10 and
# my.path.with.dots/my.tool.mock_state.9 will be correctly sorted by
# numeric order.
local key=$(( ${#dots} + 1 + 1 ))
find "$(dirname "${path}")" -mindepth 1 -maxdepth 1 \
-name "$(basename "${path}")\.[0-9]*" 2>/dev/null \
| sort -t. -k${key} -n -r \
| head -1
fi
}