|  | #!/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() { | 
|  | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" | 
|  | } | 
|  | 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 | 
|  | } |