| #!/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. | |
| BT_MOCKED_TOOLS=( | |
| tools/devshell/tests/fx-internal/a_tool | |
| ) | |
| a_function() { | |
| : | |
| } | |
| unset -f not_a_function | |
| ####################################### | |
| # Compare the error message from an expected test failure to a given | |
| # expected result. This is used to test a failed test. It both | |
| # validates the expected error message, and suppresses writing that | |
| # message to the terminal (which would otherwise appear as a test | |
| # failure even though the failure is expected). | |
| # | |
| # IMPORTANT: There is no straightforward way to fail the test function | |
| # itself simply because the messages didn't match. The primary reason | |
| # for this approach is to suppress the failure message. Testing the | |
| # message content is beneficial side effect. Tests may still pass even | |
| # if the error messages themselves don't match expected content, but | |
| # the failures will be visible in test output logs, and when run | |
| # interactively. | |
| # | |
| # To use the 'expect_fail' function, redirect the BT_EXPECT_* or | |
| # BT_ASSERT_* stdout to 'expect_fail' via 'process substitution': | |
| # | |
| # BT_EXPECT_EQ not same \ | |
| # > >(expect_fail "(BT_EXPECT_EQ) 'not' != 'same'") | |
| # BT_ASSERT_BAD_STATUS $? | |
| # | |
| # Process substitution, as in 'command > >(next_comand)' is conceptually | |
| # similar to piping the output (like 'command | next_command'), but process | |
| # substitution does not force the initial 'command' to run in a | |
| # subshell. This is important because subshells can cause undesired | |
| # side-effects with the bash_test_framework. | |
| # | |
| # Expected framework errors can also be redirected via |expect_error|. (Note | |
| # that the bash_test_framework typically outputs framework errors to stderr | |
| # but test failure messages to stdout.): | |
| # | |
| # btf::some_function called badly \ | |
| # 2> >(expect_error "Something wrong with file {BT_TEMP_DIR}/some/path." | |
| # BT_ASSERT_BAD_STATUS $? | |
| # | |
| # Note, if an error message includes a reference to a full (absolute) file path | |
| # under the test's temporary directory (a variable path value), the value of | |
| # ${BT_TEMP_DIR} will be replaced by the string '{BT_TEMP_DIR}' (including the | |
| # curly braces). | |
| # | |
| # Inputs: | |
| # The message piped from a BT_EXPECT_* or BT_ASSERT_* function that | |
| # failed its test. | |
| # Arguments | |
| # [$1] - (optional "ERROR" if from expect_error(), and message will start with "ERROR:") | |
| # remaining args - the string expected from stdin (multiple unquoted args | |
| # are also supported) | |
| # Returns: | |
| # 0 if the stdin matches the argument | |
| ####################################### | |
| expect_fail() { | |
| local stdin="$(cat)" | |
| if [[ "${stdin}" == "" ]]; then | |
| return 0 | |
| fi | |
| local prefix=FAIL | |
| if [[ "$1" == "ERROR" ]]; then | |
| prefix="$1"; shift | |
| fi | |
| local expected=\ | |
| "${prefix}: ${_BTF_HOST_SCRIPT_DIR#$BT_TEMP_DIR/}/${_BTF_HOST_SCRIPT_NAME}:{BASH_LINENO}: $*" | |
| local -r strip_escape_sequences=$'s/\033\[[0-9;]*m//g' | |
| local -r replace_line_numbers='s/:[0-9]*:/:{BASH_LINENO}:/' | |
| local -r replace_temp_dir="s#/private${BT_TEMP_DIR}#{BT_TEMP_DIR}#g;s#${BT_TEMP_DIR}#{BT_TEMP_DIR}#g" | |
| local -r strip_private_from_tmp="s#/private/tmp#/tmp#g" # Mac OS result from btf::realpath | |
| local -r delete_assert_abort_message_lines="/${_BTF_ASSERT_ERROR_COUNT_MESSAGE_PREFIX}/d" | |
| local -r delete_blank_lines='/^[ \t]*$/d' | |
| local -r delete_eot_marker_from_assert='/^EOT$/d' | |
| local -r strip_anomolous_eol_blank='s/ $//' | |
| local actual="$(echo "${stdin}" \ | |
| | sed "${strip_escape_sequences} | |
| ${replace_line_numbers} | |
| ${replace_temp_dir} | |
| ${strip_private_from_tmp} | |
| ${delete_assert_abort_message_lines} | |
| ${delete_blank_lines} | |
| ${delete_eot_marker_from_assert} | |
| ${strip_anomolous_eol_blank}")" | |
| BT_EXPECT_EQ "${expected}" "${actual}" "Actual ${prefix} message did not match expected: | |
| expected: '${expected}' | |
| actual: '${actual}'" | |
| } | |
| expect_error() { | |
| expect_fail "ERROR" "$@" | |
| } | |
| TEST_eq() { | |
| BT_EXPECT_EQ same same | |
| BT_ASSERT_EQ same same | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| _btf_test_error_count=0 | |
| BT_EXPECT_EQ not same \ | |
| > >(expect_fail "(BT_EXPECT_EQ) 'not' != 'same'") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_EQ not same \ | |
| > >(expect_fail "(BT_ASSERT_EQ) 'not' != 'same'") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_expect() { | |
| BT_EXPECT "( exit 0 )" | |
| BT_EXPECT_FAIL "( exit 1 )" | |
| BT_EXPECT true | |
| BT_ASSERT true | |
| BT_EXPECT_FAIL false | |
| BT_ASSERT_FAIL false | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT "( exit 1 )" \ | |
| > >(expect_fail "(BT_EXPECT) Exit code: 1; expected 0 status from: ( exit 1 )") | |
| BT_ASSERT_FAIL "( exit 0 )" \ | |
| > >(expect_fail "(BT_ASSERT_FAIL) Exit code: 0; expected non-zero status from: ( exit 0 )") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| expect=5 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FAIL "BT_EXPECT_EQ not same \ | |
| > >(expect_fail \"(BT_EXPECT_EQ) 'not' != 'same'\")" | |
| BT_EXPECT_FAIL "BT_EXPECT_EQ same same" \ | |
| > >(expect_fail "(BT_EXPECT_FAIL) Exit code: 0; expected non-zero status from: BT_EXPECT_EQ same same") | |
| BT_EXPECT "BT_EXPECT_EQ not same > >(expect_fail \"(BT_EXPECT_EQ) 'not' != 'same'\")" \ | |
| > >(expect_fail "(BT_EXPECT) Exit code: 1; expected 0 status from: BT_EXPECT_EQ not same > >(expect_fail \"(BT_EXPECT_EQ) 'not' != 'same'\")") | |
| BT_ASSERT_EQ not same \ | |
| > >(expect_fail "(BT_ASSERT_EQ) 'not' != 'same'") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| BT_EXPECT [[ same == same ]] | |
| BT_EXPECT_FAIL [[ not == same ]] | |
| expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT [[ same != same ]] \ | |
| > >(expect_fail "(BT_EXPECT) Exit code: 1; expected 0 status from: [[ same != same ]]") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FAIL "[[ not != same ]]" \ | |
| > >(expect_fail "(BT_ASSERT_FAIL) Exit code: 0; expected non-zero status from: [[ not != same ]]") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_eq_output() { | |
| local expect=2 | |
| local stdout=$( | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_EQ not same | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_EQ not same | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| ) # <-- This LINENO matches $LINENO value of the first line in command substitution | |
| BT_EXPECT_STRING_CONTAINS_SUBSTRING "$stdout" \ | |
| "tools/devshell/tests/fx-internal/bash_test_framework_test:$((LINENO+2)): (BT_EXPECT_EQ) 'not' != 'same'" | |
| BT_EXPECT_STRING_CONTAINS_SUBSTRING "$stdout" \ | |
| "tools/devshell/tests/fx-internal/bash_test_framework_test:$((LINENO+2)): (BT_ASSERT_EQ) 'not' != 'same'" | |
| } | |
| TEST_success() { | |
| BT_EXPECT_GOOD_STATUS 0 | |
| BT_ASSERT_GOOD_STATUS 0 | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_GOOD_STATUS 42 \ | |
| > >(expect_fail "(BT_EXPECT_GOOD_STATUS) Returned status '42' is not a success") | |
| BT_ASSERT_EQ $? 42 | |
| BT_ASSERT_GOOD_STATUS 42 \ | |
| > >(expect_fail "(BT_ASSERT_GOOD_STATUS) Returned status '42' is not a success") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_empty() { | |
| BT_EXPECT_EMPTY "" | |
| BT_ASSERT_EMPTY "" | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_EMPTY "This ain't empty" \ | |
| > >(expect_fail "(BT_EXPECT_EMPTY) String 'This ain't empty' is not empty") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_EMPTY "This ain't empty" \ | |
| > >(expect_fail "(BT_ASSERT_EMPTY) String 'This ain't empty' is not empty") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_file_exists() { | |
| touch "a_file.txt" | |
| BT_EXPECT_FILE_EXISTS "a_file.txt" | |
| BT_ASSERT_FILE_EXISTS "a_file.txt" | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FILE_EXISTS "not_a_file.txt" \ | |
| > >(expect_fail "(BT_EXPECT_FILE_EXISTS) File 'not_a_file.txt' not found") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FILE_EXISTS "not_a_file.txt" \ | |
| > >(expect_fail "(BT_ASSERT_FILE_EXISTS) File 'not_a_file.txt' not found") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_file_does_not_exist() { | |
| touch "a_file.txt" | |
| BT_EXPECT_FILE_DOES_NOT_EXIST "not_a_file.txt" | |
| BT_ASSERT_FILE_DOES_NOT_EXIST "not_a_file.txt" | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FILE_DOES_NOT_EXIST "a_file.txt" \ | |
| > >(expect_fail "(BT_EXPECT_FILE_DOES_NOT_EXIST) Existing file 'a_file.txt' should not exist") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FILE_DOES_NOT_EXIST "a_file.txt" \ | |
| > >(expect_fail "(BT_ASSERT_FILE_DOES_NOT_EXIST) Existing file 'a_file.txt' should not exist") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_file_contains() { | |
| local content="$(cat << EOF | |
| This file | |
| has this content. | |
| EOF | |
| )" | |
| printf "%s" "${content}" > "a_file.txt" | |
| BT_EXPECT_FILE_CONTAINS "a_file.txt" "${content}" | |
| BT_ASSERT_FILE_CONTAINS "a_file.txt" "${content}" | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FILE_CONTAINS "a_file.txt" "this content" \ | |
| > >(expect_fail "(BT_EXPECT_FILE_CONTAINS) File 'a_file.txt' content does not match expected content: | |
| expected: 'this content' | |
| actual: 'This file | |
| has this content.'") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FILE_CONTAINS "a_file.txt" "this content" \ | |
| > >(expect_fail "(BT_ASSERT_FILE_CONTAINS) File 'a_file.txt' content does not match expected content: | |
| expected: 'this content' | |
| actual: 'This file | |
| has this content.'") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FILE_CONTAINS "not_a_file.txt" "${content}" \ | |
| > >(expect_fail "(BT_EXPECT_FILE_CONTAINS) File 'not_a_file.txt' not found") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FILE_CONTAINS "not_a_file.txt" "${content}" \ | |
| > >(expect_fail "(BT_ASSERT_FILE_CONTAINS) File 'not_a_file.txt' not found") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_file_contains_substring() { | |
| cat > a_file.txt <<EOF | |
| This file | |
| has this content. | |
| EOF | |
| BT_EXPECT_FILE_CONTAINS_SUBSTRING "a_file.txt" "this content" | |
| BT_ASSERT_FILE_CONTAINS_SUBSTRING "a_file.txt" "this content" | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FILE_CONTAINS_SUBSTRING "a_file.txt" "different content" \ | |
| > >(expect_fail "(BT_EXPECT_FILE_CONTAINS_SUBSTRING) Substring 'different content' not found in file 'a_file.txt' | |
| actual file content: 'This file | |
| has this content.'") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FILE_CONTAINS_SUBSTRING "a_file.txt" "different content" \ | |
| > >(expect_fail "(BT_ASSERT_FILE_CONTAINS_SUBSTRING) Substring 'different content' not found in file 'a_file.txt' | |
| actual file content: 'This file | |
| has this content.'") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FILE_CONTAINS_SUBSTRING "not_a_file.txt" "this content" \ | |
| > >(expect_fail "(BT_EXPECT_FILE_CONTAINS_SUBSTRING) File 'not_a_file.txt' not found") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FILE_CONTAINS_SUBSTRING "not_a_file.txt" "this content" \ | |
| > >(expect_fail "(BT_ASSERT_FILE_CONTAINS_SUBSTRING) File 'not_a_file.txt' not found") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_directory_contains_substring() { | |
| local parent_dir="a_directory" | |
| local target_dir="${parent_dir}/subdir" | |
| mkdir -p "${target_dir}" | |
| cat > "${target_dir}/a_file.txt" <<EOF | |
| This file | |
| has this content. | |
| EOF | |
| BT_EXPECT_DIRECTORY_CONTAINS_SUBSTRING "${parent_dir}" "this content" | |
| BT_ASSERT_DIRECTORY_CONTAINS_SUBSTRING "${parent_dir}" "this content" | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_DIRECTORY_CONTAINS_SUBSTRING "${parent_dir}" "different content" \ | |
| > >(expect_fail "(BT_EXPECT_DIRECTORY_CONTAINS_SUBSTRING) Substring 'different content' not found in directory 'a_directory'") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_DIRECTORY_CONTAINS_SUBSTRING "${parent_dir}" "different content" \ | |
| > >(expect_fail "(BT_ASSERT_DIRECTORY_CONTAINS_SUBSTRING) Substring 'different content' not found in directory 'a_directory'") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_DIRECTORY_CONTAINS_SUBSTRING "no_dir" "this content" \ | |
| > >(expect_fail "(BT_EXPECT_DIRECTORY_CONTAINS_SUBSTRING) Directory 'no_dir' not found") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_DIRECTORY_CONTAINS_SUBSTRING "no_dir" "this content" \ | |
| > >(expect_fail "(BT_ASSERT_DIRECTORY_CONTAINS_SUBSTRING) Directory 'no_dir' not found") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| touch not_a_dir.txt | |
| expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_DIRECTORY_CONTAINS_SUBSTRING "not_a_dir.txt" "this content" \ | |
| > >(expect_fail "(BT_EXPECT_DIRECTORY_CONTAINS_SUBSTRING) File 'not_a_dir.txt' is not a directory") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_DIRECTORY_CONTAINS_SUBSTRING "not_a_dir.txt" "this content" \ | |
| > >(expect_fail "(BT_ASSERT_DIRECTORY_CONTAINS_SUBSTRING) File 'not_a_dir.txt' is not a directory") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_string_contains_substring() { | |
| local string="$(cat << EOF | |
| This string | |
| has this content. | |
| EOF | |
| )" | |
| BT_EXPECT_STRING_CONTAINS_SUBSTRING "${string}" "this content" | |
| BT_ASSERT_STRING_CONTAINS_SUBSTRING "${string}" "this content" | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_STRING_CONTAINS_SUBSTRING "${string}" "different content" \ | |
| > >(expect_fail "(BT_EXPECT_STRING_CONTAINS_SUBSTRING) Substring 'different content' not found in string 'This string | |
| has this content.'") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_STRING_CONTAINS_SUBSTRING "${string}" "different content" \ | |
| > >(expect_fail "(BT_ASSERT_STRING_CONTAINS_SUBSTRING) Substring 'different content' not found in string 'This string | |
| has this content.'") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_function_exists() { | |
| BT_EXPECT_FUNCTION_EXISTS a_function | |
| BT_ASSERT_FUNCTION_EXISTS a_function | |
| local expect=2 | |
| ( | |
| _btf_test_error_count=0 | |
| BT_EXPECT_FUNCTION_EXISTS not_a_function \ | |
| > >(expect_fail "(BT_EXPECT_FUNCTION_EXISTS) Function 'not_a_function' not found") | |
| BT_ASSERT_BAD_STATUS $? | |
| BT_ASSERT_FUNCTION_EXISTS not_a_function \ | |
| > >(expect_fail "(BT_ASSERT_FUNCTION_EXISTS) Function 'not_a_function' not found") | |
| exit 0 # not reached | |
| ) | |
| BT_ASSERT_EQ $? ${expect} "Expected ${expect} errors from subshell, but actual error count was $?" | |
| } | |
| TEST_mocked_tool() { | |
| BT_ASSERT_FILE_EXISTS a_tool | |
| BT_EXPECT_FILE_DOES_NOT_EXIST a_tool.mock_state | |
| BT_EXPECT ./a_tool arg1 arg2 | |
| BT_ASSERT_FILE_EXISTS a_tool.mock_state | |
| source a_tool.mock_state | |
| BT_EXPECT_EQ \ | |
| "${BT_MOCK_ARGS[*]}" \ | |
| "./a_tool arg1 arg2" | |
| rm a_tool.mock_state | |
| local a_file_expected_from_tool="${BT_TEMP_DIR}/created_by_mocked_side_effect.txt" | |
| echo "touch ${a_file_expected_from_tool}" > a_tool.mock_side_effects | |
| echo "mocked output" > a_tool.mock_stdout | |
| echo "mocked error" > a_tool.mock_stderr | |
| local -i mock_status=200 | |
| echo ${mock_status} > a_tool.mock_status | |
| mkdir results | |
| BT_EXPECT_FILE_DOES_NOT_EXIST "touch ${a_file_expected_from_tool}" | |
| ./a_tool arg3 arg4 > results/stdout 2> results/stderr | |
| BT_EXPECT_EQ $? ${mock_status} | |
| BT_EXPECT_FILE_EXISTS "${a_file_expected_from_tool}" | |
| BT_EXPECT_FILE_CONTAINS results/stdout "mocked output" | |
| BT_EXPECT_FILE_CONTAINS results/stderr "mocked error" | |
| BT_ASSERT_FILE_EXISTS a_tool.mock_state | |
| source a_tool.mock_state | |
| BT_EXPECT_EQ $? ${mock_status} | |
| BT_EXPECT_EQ \ | |
| "${BT_MOCK_ARGS[*]}" \ | |
| "./a_tool arg3 arg4" | |
| } | |
| TEST_error_message() { | |
| btf::make_mock /tmp/baddie.expect_fail \ | |
| 2> >(expect_error "mocked executable path '/tmp/baddie.expect_fail', | |
| is outside the BT_TEMP_DIR root directory '{BT_TEMP_DIR}'.") | |
| BT_ASSERT_BAD_STATUS $? | |
| } | |
| BT_RUN_TESTS "$@" |