diff options
Diffstat (limited to 'src/test/test_parseconf.sh')
-rwxr-xr-x | src/test/test_parseconf.sh | 583 |
1 files changed, 517 insertions, 66 deletions
diff --git a/src/test/test_parseconf.sh b/src/test/test_parseconf.sh index eeb80cdfa7..4fe27d9f5d 100755 --- a/src/test/test_parseconf.sh +++ b/src/test/test_parseconf.sh @@ -8,15 +8,22 @@ # Valid configurations are tested with --dump-config, which parses and # validates the configuration before writing it out. We then make sure that # the result is what we expect, before parsing and dumping it again to make -# sure that there is no change. +# sure that there is no change. Optionally, we can also test the log messages +# with --verify-config. # # Invalid configurations are tested with --verify-config, which parses # and validates the configuration. We capture its output and make sure that # it contains the error message we expect. +# +# When tor is compiled with different libraries or modules, some +# configurations may have different results. We can specify these result +# variants using additional result files. # This script looks for its test cases as individual directories in # src/test/conf_examples/. Each test may have these files: # +# Configuration Files +# # torrc -- Usually needed. This file is passed to Tor on the command line # with the "-f" flag. (If you omit it, you'll test Tor's behavior when # it receives a nonexistent configuration file.) @@ -28,22 +35,79 @@ # cmdline -- Optional. If present, it contains command-line arguments that # will be passed to Tor. # +# (included torrc files or directories) -- Optional. Additional files can be +# included in configuration, using the "%include" directive. Files or +# directories can be included in any of the config files listed above. +# Include paths should be specified relative to the test case directory. +# +# Result Files +# # expected -- If this file is present, then it should be the expected result # of "--dump-config short" for this test case. Exactly one of # "expected" or "error" must be present, or the test will fail. # +# expected_log -- Optional. If this file is present, then it contains a regex +# that must be matched by some line in the output of "--verify-config", +# which must succeed. Only used if "expected" is also present. +# # error -- If this file is present, then it contains a regex that must be # matched by some line in the output of "--verify-config", which must # fail. Exactly one of "expected" or "error" must be present, or the # test will fail. +# +# {expected,expected_log,error}_${TOR_LIBS_ENABLED}* -- If this file is +# present, then the outcome is different when some optional libraries are +# enabled. If there is no result file matching the exact list of enabled +# libraries, the script searches for result files with one or more of +# those libraries disabled. The search terminates at the standard result +# file. If expected* is present, the script also searches for +# expected_log*. +# +# For example: +# A test that succeeds, regardless of any enabled libraries: +# - expected +# A test that has a different result if the nss library is enabled +# (but the same result if any other library is enabled). We also check +# the log output in this test: +# - expected +# - expected_log +# - expected_nss +# - expected_log_nss +# A test that fails if the lzma and zstd modules are *not* enabled: +# - error +# - expected_lzma_zstd +# +# {expected,expected_log,error}*_no_${TOR_MODULES_DISABLED} -- If this file is +# present, then the outcome is different when some modules are disabled. +# If there is no result file matching the exact list of disabled modules, +# the standard result file is used. If expected* is present, the script +# also searches for expected_log*. +# +# For example: +# A test that succeeds, regardless of any disabled modules: +# - expected +# A test that has a different result if the relay module is disabled +# (but the same result if just the dirauth module is disabled): +# - expected +# - expected_no_relay_dirauth +# A test that fails if the dirauth module is disabled: +# - expected +# - error_no_dirauth +# - error_no_relay_dirauth +# (Disabling the relay module also disables dirauth module. But we don't +# want to encode that knowledge in this test script, so we supply a +# separate result file for every combination of disabled modules that +# has a different result.) umask 077 set -e +MYNAME="$0" + # emulate realpath(), in case coreutils or equivalent is not installed. abspath() { f="$*" - if [ -d "$f" ]; then + if test -d "$f"; then dir="$f" base="" else @@ -55,8 +119,8 @@ abspath() { } # find the tor binary -if [ $# -ge 1 ]; then - TOR_BINARY="${1}" +if test $# -ge 1; then + TOR_BINARY="$1" shift else TOR_BINARY="${TESTING_TOR_BINARY:-./src/app/tor}" @@ -64,6 +128,8 @@ fi TOR_BINARY="$(abspath "$TOR_BINARY")" +echo "Using Tor binary '$TOR_BINARY'." + # make a safe space for temporary files DATA_DIR=$(mktemp -d -t tor_parseconf_tests.XXXXXX) trap 'rm -rf "$DATA_DIR"' 0 @@ -90,7 +156,31 @@ else EXITCODE=1 fi -die() { echo "$1" >&2 ; exit "$EXITCODE"; } +FINAL_EXIT=0 +NEXT_TEST= + +# Log a failure message to stderr, using $@ as a printf string and arguments +# Set NEXT_TEST to "yes" and FINAL_EXIT to $EXITCODE. +fail_printf() +{ + printf "FAIL: " >&2 + # The first argument is a printf string, so this warning is spurious + # shellcheck disable=SC2059 + printf "$@" >&2 + NEXT_TEST="yes" + FINAL_EXIT=$EXITCODE +} + +# Log a failure message to stderr, using $@ as a printf string and arguments +# Exit with status $EXITCODE. +die_printf() +{ + printf "FAIL: CRITICAL error in '%s':" "$MYNAME" >&2 + # The first argument is a printf string, so this warning is spurious + # shellcheck disable=SC2059 + printf "$@" >&2 + exit $EXITCODE +} if test "$WINDOWS" = 1; then FILTER="dos2unix" @@ -98,20 +188,353 @@ else FILTER="cat" fi -touch "${DATA_DIR}/EMPTY" || die "Couldn't create empty file." +EMPTY="${DATA_DIR}/EMPTY" +touch "$EMPTY" || die_printf "Couldn't create empty file '%s'.\\n" \ + "$EMPTY" +NON_EMPTY="${DATA_DIR}/NON_EMPTY" +echo "This pattern should not match any log messages" \ + > "$NON_EMPTY" || die_printf "Couldn't create non-empty file '%s'.\\n" \ + "$NON_EMPTY" + +STANDARD_LIBS="libevent\\|openssl\\|zlib" +# Lib names are restricted to [a-z0-9]* at the moment +# We don't actually want to support foreign accents here +# shellcheck disable=SC2018,SC2019 +TOR_LIBS_ENABLED="$("$TOR_BINARY" --verify-config \ + -f "$EMPTY" --defaults-torrc "$EMPTY" \ + | sed -n 's/.* Tor .* running on .* with\(.*\)\./\1/p' \ + | tr 'A-Z' 'a-z' | tr ',' '\n' \ + | grep -v "$STANDARD_LIBS" | grep -v "n/a" \ + | sed 's/\( and\)* \(lib\)*\([a-z0-9]*\) .*/\3/' \ + | sort | tr '\n' '_')" +# Remove the last underscore, if there is one +TOR_LIBS_ENABLED=${TOR_LIBS_ENABLED%_} + +# If we ever have more than 3 optional libraries, we'll need more code here +TOR_LIBS_ENABLED_COUNT="$(echo "$TOR_LIBS_ENABLED_SEARCH" \ + | tr ' ' '\n' | wc -l)" +if test "$TOR_LIBS_ENABLED_COUNT" -gt 3; then + die_printf "Can not handle more than 3 optional libraries.\\n" +fi +# Brute-force the combinations of libraries +TOR_LIBS_ENABLED_SEARCH_3="$(echo "$TOR_LIBS_ENABLED" \ + | sed -n \ + 's/^\([^_]*\)_\([^_]*\)_\([^_]*\)$/_\1_\2 _\1_\3 _\2_\3 _\1 _\2 _\3/p')" +TOR_LIBS_ENABLED_SEARCH_2="$(echo "$TOR_LIBS_ENABLED" \ + | sed -n 's/^\([^_]*\)_\([^_]*\)$/_\1 _\2/p')" +TOR_LIBS_ENABLED_SEARCH="_$TOR_LIBS_ENABLED \ + $TOR_LIBS_ENABLED_SEARCH_3 \ + $TOR_LIBS_ENABLED_SEARCH_2" +TOR_LIBS_ENABLED_SEARCH="$(echo "$TOR_LIBS_ENABLED_SEARCH" | tr ' ' '\n' \ + | grep -v '^_*$' | tr '\n' ' ')" + +TOR_MODULES_DISABLED="$("$TOR_BINARY" --list-modules | grep ': no' \ + | cut -d ':' -f1 | sort | tr '\n' '_')" +# Remove the last underscore, if there is one +TOR_MODULES_DISABLED=${TOR_MODULES_DISABLED%_} + +echo "Tor is configured with:" +echo "Optional Libraries: ${TOR_LIBS_ENABLED:-(None)}" +if test "$TOR_LIBS_ENABLED"; then + echo "Optional Library Search List: $TOR_LIBS_ENABLED_SEARCH" +fi +echo "Disabled Modules: ${TOR_MODULES_DISABLED:-(None)}" + +# Yes, unix uses "0" for a successful command +TRUE=0 +FALSE=1 + +# Run tor --verify-config on the torrc $1, and defaults torrc $2, which may +# be $EMPTY. Pass tor the extra command line arguments $3, which will be +# passed unquoted. +# Send tor's standard output to stderr. +log_verify_config() +{ + # show the command we're about to execute + # log_verify_config() is only called when we've failed + printf "Tor --verify-config said:\\n" >&2 + printf "$ %s %s %s %s %s %s %s\\n" \ + "$TOR_BINARY" --verify-config \ + -f "$1" \ + --defaults-torrc "$2" \ + "$3" \ + >&2 + # We need cmdline unquoted + # shellcheck disable=SC2086 + "$TOR_BINARY" --verify-config \ + -f "$1" \ + --defaults-torrc "$2" \ + $3 \ + >&2 \ + || true +} + +# Run "tor --dump-config short" on the torrc $1, and defaults torrc $2, which +# may be $EMPTY. Pass tor the extra command line arguments $3, which will be +# passed unquoted. Send tor's standard output to $4. +# +# Set $FULL_TOR_CMD to the tor command line that was executed. +# +# If tor fails, fail_printf() using the file name $5, and context $6, +# which may be an empty string. Then run log_verify_config(). +dump_config() +{ + if test "$6"; then + CONTEXT=" $6" + else + CONTEXT="" + fi + + # keep the command we're about to execute, and show if it we fail + FULL_TOR_CMD=$(printf "$ %s %s %s %s %s %s %s %s" \ + "$TOR_BINARY" --dump-config short \ + -f "$1" \ + --defaults-torrc "$2" \ + "$3" + ) + # We need cmdline unquoted + # shellcheck disable=SC2086 + if ! "$TOR_BINARY" --dump-config short \ + -f "$1" \ + --defaults-torrc "$2" \ + $3 \ + > "$4"; then + fail_printf "'%s': Tor --dump-config reported an error%s:\\n%s\\n" \ + "$5" \ + "$CONTEXT" \ + "$FULL_TOR_CMD" + log_verify_config "$1" \ + "$2" \ + "$3" + fi +} + +# Run "$FILTER" on the input $1. +# Send the standard output to $2. +# If tor fails, log a failure message using the file name $3, and context $4, +# which may be an empty string. +filter() +{ + if test "$4"; then + CONTEXT=" $4" + else + CONTEXT="" + fi + + "$FILTER" "$1" \ + > "$2" \ + || fail_printf "'%s': Filter '%s' reported an error%s.\\n" \ + "$3" \ + "$FILTER" \ + "$CONTEXT" +} + +# Compare the expected file $1, and output file $2. +# +# If they are different, fail. Log the differences between the files. +# Run log_verify_config() with torrc $3, defaults torrc $4, and command +# line $5, to log Tor's error messages. +# +# If the file contents are identical, returns true. Otherwise, return false. +# +# Log failure messages using fail_printf(), with the expected file name, +# context $6, which may be an empty string, and the tor command line $7. +check_diff() +{ + if test "$6"; then + CONTEXT=" $6" + else + CONTEXT="" + fi + + if cmp "$1" "$2" > /dev/null; then + return "$TRUE" + else + fail_printf "'%s': Tor --dump-config said%s:\\n%s\\n" \ + "$1" \ + "$CONTEXT" \ + "$7" + diff -u "$1" "$2" >&2 \ + || true + log_verify_config "$3" \ + "$4" \ + "$5" + return "$FALSE" + fi +} + +# Run "tor --dump-config short" on the torrc $1, and defaults torrc $2, which +# may be $EMPTY. Pass tor the extra command line arguments $3, which will be +# passed unquoted. Send tor's standard output to $4, after running $FILTER +# on it. +# +# If tor fails, run log_verify_config(). +# +# Compare the expected file $5, and output file. If they are different, fail. +# If this is the first step that failed in this test, run log_verify_config(). +# +# If the file contents are identical, returns true. Otherwise, return false, +# and log the differences between the files. +# +# Log failure messages using fail_printf(), with the expected file name, and +# context $6, which may be an empty string. +check_dump_config() +{ + OUTPUT="$4" + OUTPUT_RAW="${OUTPUT}_raw" + + FULL_TOR_CMD= + dump_config "$1" \ + "$2" \ + "$3" \ + "$OUTPUT_RAW" \ + "$5" \ + "$6" + + filter "$OUTPUT_RAW" \ + "$OUTPUT" \ + "$5" \ + "$6" + + if check_diff "$5" \ + "$OUTPUT" \ + "$1" \ + "$2" \ + "$3" \ + "$6" \ + "$FULL_TOR_CMD"; then + return "$TRUE" + else + return "$FALSE" + fi +} + +# Check if $1 is an empty file. +# If it is, fail_printf() using $2 as the type of file. +# Returns true if the file is empty, false otherwise. +check_empty_pattern() +{ + if ! test -s "$1"; then + fail_printf "%s file '%s' is empty, and will match any output.\\n" \ + "$2" \ + "$1" + return "$TRUE" + else + return "$FALSE" + fi +} + +# Run tor --verify-config on the torrc $1, and defaults torrc $2, which may +# be $EMPTY. Pass tor the extra command line arguments $3, which will be +# passed unquoted. Send tor's standard output to $4. +# +# Set $FULL_TOR_CMD to the tor command line that was executed. +# +# If tor's exit status does not match the boolean $5, fail_printf() +# using the file name $6, and context $7, which is required. +verify_config() +{ + RESULT=$TRUE + + # keep the command we're about to execute, and show if it we fail + FULL_TOR_CMD=$(printf "$ %s %s %s %s %s %s %s" \ + "$TOR_BINARY" --verify-config \ + -f "$1" \ + --defaults-torrc "$2" \ + "$3" + ) + # We need cmdline unquoted + # shellcheck disable=SC2086 + "$TOR_BINARY" --verify-config \ + -f "$1" \ + --defaults-torrc "$2" \ + $3 \ + > "$4" || RESULT=$FALSE + + # Convert the actual and expected results to boolean, and compare + if test $((! (! RESULT))) -ne $((! (! $5))); then + fail_printf "'%s': Tor --verify-config did not %s:\\n%s\\n" \ + "$6" \ + "$7" \ + "$FULL_TOR_CMD" + cat "$4" >&2 + fi +} + +# Check for the patterns in the match file $1, in the output file $2. +# Uses grep with the entire contents of the match file as the pattern. +# (Not "grep -f".) +# +# If the pattern does not match any lines in the output file, fail. +# Log the pattern, and the entire contents of the output file. +# +# Log failure messages using fail_printf(), with the match file name, +# context $3, and tor command line $4, which are required. +check_pattern() +{ + expect_log="$(cat "$1")" + if ! grep "$expect_log" "$2" > /dev/null; then + fail_printf "Expected %s '%s':\\n%s\\n" \ + "$3" \ + "$1" \ + "$expect_log" + printf "Tor --verify-config said:\\n%s\\n" \ + "$4" >&2 + cat "$2" >&2 + fi +} + +# Run tor --verify-config on the torrc $1, and defaults torrc $2, which may +# be $EMPTY. Pass tor the extra command line arguments $3, which will be +# passed unquoted. Send tor's standard output to $4. +# +# If tor's exit status does not match the boolean $5, fail. +# +# Check for the patterns in the match file $6, in the output file. +# Uses grep with the entire contents of the match file as the pattern. +# (Not "grep -f".) The match file must not be empty. +# +# If the pattern does not match any lines in the output file, fail. +# Log the pattern, and the entire contents of the output file. +# +# Log failure messages using fail_printf(), with the match file name, +# and context $7, which is required. +check_verify_config() +{ + if check_empty_pattern "$6" "$7"; then + return + fi + + FULL_TOR_CMD= + verify_config "$1" \ + "$2" \ + "$3" \ + "$4" \ + "$5" \ + "$6" \ + "$7" + + check_pattern "$6" \ + "$4" \ + "$7" \ + "$FULL_TOR_CMD" +} for dir in "${EXAMPLEDIR}"/*; do - if ! test -d "${dir}"; then + NEXT_TEST= + + if ! test -d "$dir"; then # Only count directories. continue fi testname="$(basename "${dir}")" # We use printf since "echo -n" is not standard - printf "%s: " "$testname" + printf "%s: " \ + "$testname" PREV_DIR="$(pwd)" - cd "${dir}" + cd "$dir" if test -f "./torrc.defaults"; then DEFAULTS="./torrc.defaults" @@ -125,80 +548,108 @@ for dir in "${EXAMPLEDIR}"/*; do CMDLINE="" fi - if test -f "./expected"; then - if test -f "./error"; then - echo "FAIL: Found both ${dir}/expected and ${dir}/error." - echo "(Only one of these files should exist.)" - exit $EXITCODE - fi + EXPECTED= + EXPECTED_LOG= + ERROR= + # Search for a custom result file for any combination of enabled optional + # libraries + # The libs in the list are [A-Za-z0-9_]* and space-separated. + # shellcheck disable=SC2086 + for lib_suffix in $TOR_LIBS_ENABLED_SEARCH ""; do + # Search for a custom result file for any disabled modules + for mod_suffix in "_no_${TOR_MODULES_DISABLED}" ""; do + suffix="${lib_suffix}${mod_suffix}" - # This case should succeed: run dump-config and see if it does. + if test -f "./expected${suffix}"; then - "${TOR_BINARY}" -f "./torrc" \ - --defaults-torrc "${DEFAULTS}" \ - --dump-config short \ - ${CMDLINE} \ - | "${FILTER}" > "${DATA_DIR}/output.${testname}" \ - || die "Failure: Tor exited." + # Check for broken configs + if test -f "./error${suffix}"; then + fail_printf "Found both '%s' and '%s'.%s\\n" \ + "${dir}/expected${suffix}" \ + "${dir}/error${suffix}" \ + "(Only one of these files should exist.)" + break + fi - if cmp "./expected" "${DATA_DIR}/output.${testname}">/dev/null ; then - # Check round-trip. - "${TOR_BINARY}" -f "${DATA_DIR}/output.${testname}" \ - --defaults-torrc "${DATA_DIR}/empty" \ - --dump-config short \ - | "${FILTER}" \ - > "${DATA_DIR}/output_2.${testname}" \ - || die "Failure: Tor exited on round-trip." - - if ! cmp "${DATA_DIR}/output.${testname}" \ - "${DATA_DIR}/output_2.${testname}"; then - echo "Failure: did not match on round-trip." - exit $EXITCODE - fi + EXPECTED="./expected${suffix}" + if test -f "./expected_log${suffix}"; then + EXPECTED_LOG="./expected_log${suffix}" + fi + break - echo "OK" - else - echo "FAIL" - if test "$(wc -c < "${DATA_DIR}/output.${testname}")" = 0; then - # There was no output -- probably we failed. - "${TOR_BINARY}" -f "./torrc" \ - --defaults-torrc "${DEFAULTS}" \ - --verify-config \ - ${CMDLINE} || true + elif test -f "./error${suffix}"; then + ERROR="./error${suffix}" + break fi - diff -u "./expected" "${DATA_DIR}/output.${testname}" || /bin/true - exit $EXITCODE + done + + # Exit as soon as the inner loop finds a file, or fails + if test -f "$EXPECTED" || test -f "$ERROR" || test "$NEXT_TEST"; then + break fi + done - elif test -f "./error"; then - # This case should fail: run verify-config and see if it does. + if test "$NEXT_TEST"; then + # The test failed inside the file search loop: go to the next test + continue + elif test -f "$EXPECTED"; then + # This case should succeed: run dump-config and see if it does. + + if check_dump_config "./torrc" \ + "$DEFAULTS" \ + "$CMDLINE" \ + "${DATA_DIR}/output.${testname}" \ + "$EXPECTED" \ + ""; then + # Check round-trip. + check_dump_config "${DATA_DIR}/output.${testname}" \ + "$EMPTY" \ + "" \ + "${DATA_DIR}/output_2.${testname}" \ + "$EXPECTED" \ + "on round-trip" || true + fi - "${TOR_BINARY}" --verify-config \ - -f ./torrc \ - --defaults-torrc "${DEFAULTS}" \ - ${CMDLINE} \ - > "${DATA_DIR}/output.${testname}" \ - && die "Failure: Tor did not report an error." + if test -f "$EXPECTED_LOG"; then + # This case should succeed: run verify-config and see if it does. - expect_err="$(cat ./error)" - if grep "${expect_err}" "${DATA_DIR}/output.${testname}" >/dev/null; then - echo "OK" + check_verify_config "./torrc" \ + "$DEFAULTS" \ + "$CMDLINE" \ + "${DATA_DIR}/output_log.${testname}" \ + "$TRUE" \ + "$EXPECTED_LOG" \ + "log success" else - echo "FAIL" - echo "Expected error: ${expect_err}" - echo "Tor said:" - cat "${DATA_DIR}/output.${testname}" - exit $EXITCODE + printf "\\nNOTICE: Missing '%s_log' file:\\n" \ + "$EXPECTED" >&2 + log_verify_config "./torrc" \ + "$DEFAULTS" \ + "$CMDLINE" fi + elif test -f "$ERROR"; then + # This case should fail: run verify-config and see if it does. + + check_verify_config "./torrc" \ + "$DEFAULTS" \ + "$CMDLINE" \ + "${DATA_DIR}/output.${testname}" \ + "$FALSE" \ + "$ERROR" \ + "log error" else # This case is not actually configured with a success or a failure. # call that an error. + fail_printf "Did not find ${dir}/*expected or ${dir}/*error.\\n" + fi - echo "FAIL: Did not find ${dir}/expected or ${dir}/error." - exit $EXITCODE + if test -z "$NEXT_TEST"; then + echo "OK" fi - cd "${PREV_DIR}" + cd "$PREV_DIR" done + +exit "$FINAL_EXIT" |