| #!/bin/bash | |
| # Copyright 2019 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. | |
| # | |
| # The bash_test_framework.sh script provides two ways to define a mock equivalent | |
| # of a required executable: call 'btf::make_mock', or add the mocked executable path | |
| # to the BT_MOCKED_TOOLS array in the host test script. This script provides the | |
| # mock implementation, and provides options to tailor the behavior, results, and/or | |
| # side effects when the mocked executable is invoked. | |
| # | |
| # When invoked, instead of running the real program, this program writes its state | |
| # (such as, the arguments passed to the command) such that 'source'ing the state | |
| # file will make the state information available to the caller. | |
| # | |
| # The mock state data (script) is written to a file with the same path as the script | |
| # plus (if executed only once) a ".mock_state" extension (for example, | |
| # "${executable_path}.mock_state"); however, if the mock executable is executed more | |
| # than once, multiple files will be written, with the extension ".mock_state.<index>" | |
| # (with index starting at "1", for example, "${executable_path}.mock_state.1", | |
| # "${executable_path}.mock_state.2"). | |
| # | |
| # After sourcing the ".mock_state(.n)" file, an array variable, "BT_MOCK_ARGS", will be | |
| # set (or overwritten if a prior value was set) to the command name (the ${BASH_SOURCE}) | |
| # followed by the arguments passed to the mocked tool by the last caller. Also, if a | |
| # ".mock_side_effects" file was provided, the variable "BT_SIDE_EFFECT_STATUS" will be | |
| # set to the status returned from sourcing the ".mock_side_effects" file. | |
| # | |
| # Also (simply for convenience), if a "${executable_path}.mock_status" file was present, | |
| # sourcing the "${executable_path}.mock_state(.n)" script will also result in the same | |
| # status returned to the original caller. | |
| # | |
| # To generate a return status other than 0 (success), write the desired status int | |
| # value to "${executable_path}.mock_status" before executing the mock. | |
| # | |
| # To generate a stdout result, similarly, write the desired output to | |
| # "${executable_path}.mock_stdout" before executing the mock; and to generate stderr | |
| # output, write the desired stderr output to "${executable_path}.mock_stderr". | |
| # | |
| # Additional side effects (actions to be taken by the mock script that have some | |
| # actual effect, such as creating a file, or running another program) can be | |
| # executed as well. Write desired actions in bash syntax to | |
| # "${executable_path}.mock_side_effects", to be executed by 'source'ing the file. | |
| # Any and all arguments passed to the mocked executable are forwarded to the | |
| # sourced mock_side_effects script. | |
| # | |
| # Side effects run after writing stdout and stderr, allowing for a possible side | |
| # effect that you may want the mock program to run forever (such as an infinite | |
| # loop with a long sleep). Alternatively, your side effect program can write | |
| # its own output. | |
| # | |
| # Limitations: | |
| # - Input from stdin is ignored. The only way to change the behavior is to | |
| # create the .mock_status, .mock_stdout, and/or .mock_stderr files. | |
| # - stdout results are written first, in entirety, followed by stderr results | |
| # (if supplied) | |
| if [[ -e "${BASH_SOURCE}.mock_stdout" ]]; then | |
| cat "${BASH_SOURCE}.mock_stdout" | |
| fi | |
| if [[ -e "${BASH_SOURCE}.mock_stderr" ]]; then | |
| >&2 cat "${BASH_SOURCE}.mock_stderr" | |
| fi | |
| declare had_side_effect=false | |
| declare -i side_effect_status=0 | |
| if [[ -e "${BASH_SOURCE}.mock_side_effects" ]]; then | |
| source "${BASH_SOURCE}.mock_side_effects" "$@" | |
| side_effect_status=$? | |
| had_side_effect=true | |
| fi | |
| declare -i status=0 | |
| if [[ -e "${BASH_SOURCE}.mock_status" ]]; then | |
| status=$(cat "${BASH_SOURCE}.mock_status") | |
| elif ${had_side_effect}; then | |
| status=${side_effect_status} | |
| fi | |
| declare state_file="${BASH_SOURCE}.mock_state" | |
| 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 $(ls "${state_file}".*); do | |
| index=${file##*.} | |
| max_index=$(( index > max_index ? index : max_index )) | |
| done | |
| state_file="${state_file}.$((max_index+1))" | |
| fi | |
| echo "#!/bin/bash" >>"${state_file}" | |
| # 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. | |
| printf "BT_MOCK_ARGS=( " >>"${state_file}" | |
| printf "%q " "${BASH_SOURCE}" "$@" >>"${state_file}" | |
| printf ")\n" >>"${state_file}" | |
| if ${had_side_effect}; then | |
| echo "declare -i BT_MOCK_SIDE_EFFECT_STATUS=${side_effect_status}" >>"${state_file}" | |
| fi | |
| echo "return ${status}" >>"${state_file}" | |
| # If script was sourced, use 'return', otherwise use 'exit' | |
| (return 0 2>/dev/null) && return ${status} || exit ${status} |