TOR_VEGAS: Implement Prop#324 TOR_VEGAS.
[tor.git] / src / test / test_parseconf.sh
blobc02b8b23c0ea59f830595efdc8d1d0dcf0c6bb81
1 #!/bin/sh
2 # Copyright 2019, The Tor Project, Inc.
3 # See LICENSE for licensing information
5 # Integration test script for verifying that Tor configurations are parsed as
6 # we expect.
8 # Valid configurations are tested with --dump-config, which parses and
9 # validates the configuration before writing it out. We then make sure that
10 # the result is what we expect, before parsing and dumping it again to make
11 # sure that there is no change. Optionally, we can also test the log messages
12 # with --verify-config.
14 # Invalid configurations are tested with --verify-config, which parses
15 # and validates the configuration. We capture its output and make sure that
16 # it contains the error message we expect.
18 # When tor is compiled with different libraries or modules, some
19 # configurations may have different results. We can specify these result
20 # variants using additional result files.
22 # This script looks for its test cases as individual directories in
23 # src/test/conf_examples/. Each test may have these files:
25 # Configuration Files
27 # torrc -- Usually needed. This file is passed to Tor on the command line
28 # with the "-f" flag. (If you omit it, you'll test Tor's behavior when
29 # it receives a nonexistent configuration file.)
31 # torrc.defaults -- Optional. If present, it is passed to Tor on the command
32 # line with the --defaults-torrc option. If this file is absent, an empty
33 # file is passed instead to prevent Tor from reading the system defaults.
35 # cmdline -- Optional. If present, it contains command-line arguments that
36 # will be passed to Tor.
38 # (included torrc files or directories) -- Optional. Additional files can be
39 # included in configuration, using the "%include" directive. Files or
40 # directories can be included in any of the config files listed above.
41 # Include paths should be specified relative to the test case directory.
43 # Result Files
45 # expected -- If this file is present, then it should be the expected result
46 # of "--dump-config short" for this test case. Exactly one of
47 # "expected" or "error" must be present, or the test will fail.
49 # expected_log -- Optional. If this file is present, then it contains a regex
50 # that must be matched by some line in the output of "--verify-config",
51 # which must succeed. Only used if "expected" is also present.
53 # error -- If this file is present, then it contains a regex that must be
54 # matched by some line in the output of "--verify-config", which must
55 # fail. Exactly one of "expected" or "error" must be present, or the
56 # test will fail.
58 # {expected,expected_log,error}_${TOR_LIBS_ENABLED}* -- If this file is
59 # present, then the outcome is different when some optional libraries are
60 # enabled. If there is no result file matching the exact list of enabled
61 # libraries, the script searches for result files with one or more of
62 # those libraries disabled. The search terminates at the standard result
63 # file. If expected* is present, the script also searches for
64 # expected_log*.
66 # For example:
67 # A test that succeeds, regardless of any enabled libraries:
68 # - expected
69 # A test that has a different result if the nss library is enabled
70 # (but the same result if any other library is enabled). We also check
71 # the log output in this test:
72 # - expected
73 # - expected_log
74 # - expected_nss
75 # - expected_log_nss
76 # A test that fails if the lzma and zstd modules are *not* enabled:
77 # - error
78 # - expected_lzma_zstd
80 # {expected,expected_log,error}*_no_${TOR_MODULES_DISABLED} -- If this file is
81 # present, then the outcome is different when some modules are disabled.
82 # If there is no result file matching the exact list of disabled modules,
83 # the standard result file is used. If expected* is present, the script
84 # also searches for expected_log*.
86 # For example:
87 # A test that succeeds, regardless of any disabled modules:
88 # - expected
89 # A test that has a different result if the relay module is disabled
90 # (but the same result if just the dirauth module is disabled):
91 # - expected
92 # - expected_no_relay_dirauth
93 # A test that fails if the dirauth module is disabled:
94 # - expected
95 # - error_no_dirauth
96 # - error_no_relay_dirauth
97 # (Disabling the relay module also disables dirauth module. But we don't
98 # want to encode that knowledge in this test script, so we supply a
99 # separate result file for every combination of disabled modules that
100 # has a different result.)
102 umask 077
103 set -e
105 MYNAME="$0"
107 # emulate realpath(), in case coreutils or equivalent is not installed.
108 abspath() {
109 f="$*"
110 if test -d "$f"; then
111 dir="$f"
112 base=""
113 else
114 dir="$(dirname "$f")"
115 base="/$(basename "$f")"
117 dir="$(cd "$dir" && pwd)"
118 echo "$dir$base"
121 # find the tor binary
122 if test $# -ge 1; then
123 TOR_BINARY="$1"
124 shift
125 else
126 TOR_BINARY="${TESTING_TOR_BINARY:-./src/app/tor}"
129 TOR_BINARY="$(abspath "$TOR_BINARY")"
131 echo "Using Tor binary '$TOR_BINARY'."
133 # make a safe space for temporary files
134 DATA_DIR=$(mktemp -d -t tor_parseconf_tests.XXXXXX)
135 trap 'rm -rf "$DATA_DIR"' 0
137 # This is where we look for examples
138 EXAMPLEDIR="$(dirname "$0")"/conf_examples
140 case "$(uname -s)" in
141 CYGWIN*) WINDOWS=1;;
142 MINGW*) WINDOWS=1;;
143 MSYS*) WINDOWS=1;;
144 *) WINDOWS=0;;
145 esac
147 ####
148 # BUG WORKAROUND FOR 31757:
149 # On Appveyor, it seems that Tor sometimes randomly fails to produce
150 # output with --dump-config. Whil we are figuring this out, do not treat
151 # windows errors as hard failures.
152 ####
153 if test "$WINDOWS" = 1; then
154 EXITCODE=0
155 else
156 EXITCODE=1
159 FINAL_EXIT=0
160 NEXT_TEST=
162 # Log a failure message to stderr, using $@ as a printf string and arguments
163 # Set NEXT_TEST to "yes" and FINAL_EXIT to $EXITCODE.
164 fail_printf()
166 printf "FAIL: " >&2
167 # The first argument is a printf string, so this warning is spurious
168 # shellcheck disable=SC2059
169 printf "$@" >&2
170 NEXT_TEST="yes"
171 FINAL_EXIT=$EXITCODE
174 # Log a failure message to stderr, using $@ as a printf string and arguments
175 # Exit with status $EXITCODE.
176 die_printf()
178 printf "FAIL: CRITICAL error in '%s':" "$MYNAME" >&2
179 # The first argument is a printf string, so this warning is spurious
180 # shellcheck disable=SC2059
181 printf "$@" >&2
182 exit $EXITCODE
185 if test "$WINDOWS" = 1; then
186 FILTER="dos2unix"
187 else
188 FILTER="cat"
191 EMPTY="${DATA_DIR}/EMPTY"
192 touch "$EMPTY" || die_printf "Couldn't create empty file '%s'.\\n" \
193 "$EMPTY"
194 NON_EMPTY="${DATA_DIR}/NON_EMPTY"
195 echo "This pattern should not match any log messages" \
196 > "$NON_EMPTY" || die_printf "Couldn't create non-empty file '%s'.\\n" \
197 "$NON_EMPTY"
199 STANDARD_LIBS="libevent\\|openssl\\|zlib"
200 # Lib names are restricted to [a-z0-9]* at the moment
201 # We don't actually want to support foreign accents here
202 # shellcheck disable=SC2018,SC2019
203 TOR_LIBS_ENABLED="$("$TOR_BINARY" --verify-config \
204 -f "$EMPTY" --defaults-torrc "$EMPTY" \
205 | sed -n 's/.* Tor .* running on .* with\(.*\) and .* .* as libc\./\1/p' \
206 | tr 'A-Z' 'a-z' | tr ',' '\n' \
207 | grep -v "$STANDARD_LIBS" | grep -v "n/a" \
208 | sed 's/\( and\)* \(lib\)*\([a-z0-9]*\) .*/\3/' \
209 | sort | tr '\n' '_')"
210 # Remove the last underscore, if there is one
211 TOR_LIBS_ENABLED=${TOR_LIBS_ENABLED%_}
213 # If we ever have more than 3 optional libraries, we'll need more code here
214 TOR_LIBS_ENABLED_COUNT="$(echo "$TOR_LIBS_ENABLED_SEARCH" \
215 | tr ' ' '\n' | wc -l)"
216 if test "$TOR_LIBS_ENABLED_COUNT" -gt 3; then
217 die_printf "Can not handle more than 3 optional libraries.\\n"
219 # Brute-force the combinations of libraries
220 TOR_LIBS_ENABLED_SEARCH_3="$(echo "$TOR_LIBS_ENABLED" \
221 | sed -n \
222 's/^\([^_]*\)_\([^_]*\)_\([^_]*\)$/_\1_\2 _\1_\3 _\2_\3 _\1 _\2 _\3/p')"
223 TOR_LIBS_ENABLED_SEARCH_2="$(echo "$TOR_LIBS_ENABLED" \
224 | sed -n 's/^\([^_]*\)_\([^_]*\)$/_\1 _\2/p')"
225 TOR_LIBS_ENABLED_SEARCH="_$TOR_LIBS_ENABLED \
226 $TOR_LIBS_ENABLED_SEARCH_3 \
227 $TOR_LIBS_ENABLED_SEARCH_2"
228 TOR_LIBS_ENABLED_SEARCH="$(echo "$TOR_LIBS_ENABLED_SEARCH" | tr ' ' '\n' \
229 | grep -v '^_*$' | tr '\n' ' ')"
231 TOR_MODULES_DISABLED="$("$TOR_BINARY" --list-modules | grep ': no' \
232 | cut -d ':' -f1 | sort | tr '\n' '_')"
233 # Remove the last underscore, if there is one
234 TOR_MODULES_DISABLED=${TOR_MODULES_DISABLED%_}
236 echo "Tor is configured with:"
237 echo "Optional Libraries: ${TOR_LIBS_ENABLED:-(None)}"
238 if test "$TOR_LIBS_ENABLED"; then
239 echo "Optional Library Search List: $TOR_LIBS_ENABLED_SEARCH"
241 echo "Disabled Modules: ${TOR_MODULES_DISABLED:-(None)}"
243 # Yes, unix uses "0" for a successful command
244 TRUE=0
245 FALSE=1
247 # Run tor --verify-config on the torrc $1, and defaults torrc $2, which may
248 # be $EMPTY. Pass tor the extra command line arguments $3, which will be
249 # passed unquoted.
250 # Send tor's standard output to stderr.
251 log_verify_config()
253 # show the command we're about to execute
254 # log_verify_config() is only called when we've failed
255 printf "Tor --verify-config said:\\n" >&2
256 printf "$ %s %s %s %s %s %s %s\\n" \
257 "$TOR_BINARY" --verify-config \
258 -f "$1" \
259 --defaults-torrc "$2" \
260 "$3" \
262 # We need cmdline unquoted
263 # shellcheck disable=SC2086
264 "$TOR_BINARY" --verify-config \
265 -f "$1" \
266 --defaults-torrc "$2" \
267 $3 \
268 >&2 \
269 || true
272 # Run "tor --dump-config short" on the torrc $1, and defaults torrc $2, which
273 # may be $EMPTY. Pass tor the extra command line arguments $3, which will be
274 # passed unquoted. Send tor's standard output to $4.
276 # Set $FULL_TOR_CMD to the tor command line that was executed.
278 # If tor fails, fail_printf() using the file name $5, and context $6,
279 # which may be an empty string. Then run log_verify_config().
280 dump_config()
282 if test "$6"; then
283 CONTEXT=" $6"
284 else
285 CONTEXT=""
288 # keep the command we're about to execute, and show if it we fail
289 FULL_TOR_CMD=$(printf "$ %s %s %s %s %s %s %s %s" \
290 "$TOR_BINARY" --dump-config short \
291 -f "$1" \
292 --defaults-torrc "$2" \
293 "$3"
295 # We need cmdline unquoted
296 # shellcheck disable=SC2086
297 if ! "$TOR_BINARY" --dump-config short \
298 -f "$1" \
299 --defaults-torrc "$2" \
300 $3 \
301 > "$4"; then
302 fail_printf "'%s': Tor --dump-config reported an error%s:\\n%s\\n" \
303 "$5" \
304 "$CONTEXT" \
305 "$FULL_TOR_CMD"
306 log_verify_config "$1" \
307 "$2" \
308 "$3"
312 # Run "$FILTER" on the input $1.
313 # Send the standard output to $2.
314 # If tor fails, log a failure message using the file name $3, and context $4,
315 # which may be an empty string.
316 filter()
318 if test "$4"; then
319 CONTEXT=" $4"
320 else
321 CONTEXT=""
324 "$FILTER" "$1" \
325 > "$2" \
326 || fail_printf "'%s': Filter '%s' reported an error%s.\\n" \
327 "$3" \
328 "$FILTER" \
329 "$CONTEXT"
332 # Compare the expected file $1, and output file $2.
334 # If they are different, fail. Log the differences between the files.
335 # Run log_verify_config() with torrc $3, defaults torrc $4, and command
336 # line $5, to log Tor's error messages.
338 # If the file contents are identical, returns true. Otherwise, return false.
340 # Log failure messages using fail_printf(), with the expected file name,
341 # context $6, which may be an empty string, and the tor command line $7.
342 check_diff()
344 if test "$6"; then
345 CONTEXT=" $6"
346 else
347 CONTEXT=""
350 if cmp "$1" "$2" > /dev/null; then
351 return "$TRUE"
352 else
353 fail_printf "'%s': Tor --dump-config said%s:\\n%s\\n" \
354 "$1" \
355 "$CONTEXT" \
356 "$7"
357 diff -u "$1" "$2" >&2 \
358 || true
359 log_verify_config "$3" \
360 "$4" \
361 "$5"
362 return "$FALSE"
366 # Run "tor --dump-config short" on the torrc $1, and defaults torrc $2, which
367 # may be $EMPTY. Pass tor the extra command line arguments $3, which will be
368 # passed unquoted. Send tor's standard output to $4, after running $FILTER
369 # on it.
371 # If tor fails, run log_verify_config().
373 # Compare the expected file $5, and output file. If they are different, fail.
374 # If this is the first step that failed in this test, run log_verify_config().
376 # If the file contents are identical, returns true. Otherwise, return false,
377 # and log the differences between the files.
379 # Log failure messages using fail_printf(), with the expected file name, and
380 # context $6, which may be an empty string.
381 check_dump_config()
383 OUTPUT="$4"
384 OUTPUT_RAW="${OUTPUT}_raw"
386 FULL_TOR_CMD=
387 dump_config "$1" \
388 "$2" \
389 "$3" \
390 "$OUTPUT_RAW" \
391 "$5" \
392 "$6"
394 filter "$OUTPUT_RAW" \
395 "$OUTPUT" \
396 "$5" \
397 "$6"
399 if check_diff "$5" \
400 "$OUTPUT" \
401 "$1" \
402 "$2" \
403 "$3" \
404 "$6" \
405 "$FULL_TOR_CMD"; then
406 return "$TRUE"
407 else
408 return "$FALSE"
412 # Check if $1 is an empty file.
413 # If it is, fail_printf() using $2 as the type of file.
414 # Returns true if the file is empty, false otherwise.
415 check_empty_pattern()
417 if ! test -s "$1"; then
418 fail_printf "%s file '%s' is empty, and will match any output.\\n" \
419 "$2" \
420 "$1"
421 return "$TRUE"
422 else
423 return "$FALSE"
427 # Run tor --verify-config on the torrc $1, and defaults torrc $2, which may
428 # be $EMPTY. Pass tor the extra command line arguments $3, which will be
429 # passed unquoted. Send tor's standard output to $4.
431 # Set $FULL_TOR_CMD to the tor command line that was executed.
433 # If tor's exit status does not match the boolean $5, fail_printf()
434 # using the file name $6, and context $7, which is required.
435 verify_config()
437 RESULT=$TRUE
439 # keep the command we're about to execute, and show if it we fail
440 FULL_TOR_CMD=$(printf "$ %s %s %s %s %s %s %s" \
441 "$TOR_BINARY" --verify-config \
442 -f "$1" \
443 --defaults-torrc "$2" \
444 "$3"
446 # We need cmdline unquoted
447 # shellcheck disable=SC2086
448 "$TOR_BINARY" --verify-config \
449 -f "$1" \
450 --defaults-torrc "$2" \
451 $3 \
452 > "$4" || RESULT=$FALSE
454 # Convert the actual and expected results to boolean, and compare
455 if test $((! (! RESULT))) -ne $((! (! $5))); then
456 fail_printf "'%s': Tor --verify-config did not %s:\\n%s\\n" \
457 "$6" \
458 "$7" \
459 "$FULL_TOR_CMD"
460 cat "$4" >&2
464 # Check for the patterns in the match file $1, in the output file $2.
465 # Uses grep with the entire contents of the match file as the pattern.
466 # (Not "grep -f".)
468 # If the pattern does not match any lines in the output file, fail.
469 # Log the pattern, and the entire contents of the output file.
471 # Log failure messages using fail_printf(), with the match file name,
472 # context $3, and tor command line $4, which are required.
473 check_pattern()
475 expect_log="$(cat "$1")"
476 if ! grep "$expect_log" "$2" > /dev/null; then
477 fail_printf "Expected %s '%s':\\n%s\\n" \
478 "$3" \
479 "$1" \
480 "$expect_log"
481 printf "Tor --verify-config said:\\n%s\\n" \
482 "$4" >&2
483 cat "$2" >&2
487 # Run tor --verify-config on the torrc $1, and defaults torrc $2, which may
488 # be $EMPTY. Pass tor the extra command line arguments $3, which will be
489 # passed unquoted. Send tor's standard output to $4.
491 # If tor's exit status does not match the boolean $5, fail.
493 # Check for the patterns in the match file $6, in the output file.
494 # Uses grep with the entire contents of the match file as the pattern.
495 # (Not "grep -f".) The match file must not be empty.
497 # If the pattern does not match any lines in the output file, fail.
498 # Log the pattern, and the entire contents of the output file.
500 # Log failure messages using fail_printf(), with the match file name,
501 # and context $7, which is required.
502 check_verify_config()
504 if check_empty_pattern "$6" "$7"; then
505 return
508 FULL_TOR_CMD=
509 verify_config "$1" \
510 "$2" \
511 "$3" \
512 "$4" \
513 "$5" \
514 "$6" \
515 "$7"
517 check_pattern "$6" \
518 "$4" \
519 "$7" \
520 "$FULL_TOR_CMD"
523 for dir in "${EXAMPLEDIR}"/*; do
524 NEXT_TEST=
526 if ! test -d "$dir"; then
527 # Only count directories.
528 continue
531 testname="$(basename "${dir}")"
532 # We use printf since "echo -n" is not standard
533 printf "%s: " \
534 "$testname"
536 PREV_DIR="$(pwd)"
537 cd "$dir"
539 if test -f "./torrc.defaults"; then
540 DEFAULTS="./torrc.defaults"
541 else
542 DEFAULTS="${DATA_DIR}/EMPTY"
545 if test -f "./cmdline"; then
546 CMDLINE="$(cat ./cmdline)"
547 else
548 CMDLINE=""
551 EXPECTED=
552 EXPECTED_LOG=
553 ERROR=
554 # Search for a custom result file for any combination of enabled optional
555 # libraries
556 # The libs in the list are [A-Za-z0-9_]* and space-separated.
557 # shellcheck disable=SC2086
558 for lib_suffix in $TOR_LIBS_ENABLED_SEARCH ""; do
559 # Search for a custom result file for any disabled modules
560 for mod_suffix in "_no_${TOR_MODULES_DISABLED}" ""; do
561 suffix="${lib_suffix}${mod_suffix}"
563 if test -f "./expected${suffix}"; then
565 # Check for broken configs
566 if test -f "./error${suffix}"; then
567 fail_printf "Found both '%s' and '%s'.%s\\n" \
568 "${dir}/expected${suffix}" \
569 "${dir}/error${suffix}" \
570 "(Only one of these files should exist.)"
571 break
574 EXPECTED="./expected${suffix}"
575 if test -f "./expected_log${suffix}"; then
576 EXPECTED_LOG="./expected_log${suffix}"
578 break
580 elif test -f "./error${suffix}"; then
581 ERROR="./error${suffix}"
582 break
584 done
586 # Exit as soon as the inner loop finds a file, or fails
587 if test -f "$EXPECTED" || test -f "$ERROR" || test "$NEXT_TEST"; then
588 break
590 done
592 if test "$NEXT_TEST"; then
593 # The test failed inside the file search loop: go to the next test
594 continue
595 elif test -f "$EXPECTED"; then
596 # This case should succeed: run dump-config and see if it does.
598 if check_dump_config "./torrc" \
599 "$DEFAULTS" \
600 "$CMDLINE" \
601 "${DATA_DIR}/output.${testname}" \
602 "$EXPECTED" \
603 ""; then
604 # Check round-trip.
605 check_dump_config "${DATA_DIR}/output.${testname}" \
606 "$EMPTY" \
607 "" \
608 "${DATA_DIR}/output_2.${testname}" \
609 "$EXPECTED" \
610 "on round-trip" || true
613 if test -f "$EXPECTED_LOG"; then
614 # This case should succeed: run verify-config and see if it does.
616 check_verify_config "./torrc" \
617 "$DEFAULTS" \
618 "$CMDLINE" \
619 "${DATA_DIR}/output_log.${testname}" \
620 "$TRUE" \
621 "$EXPECTED_LOG" \
622 "log success"
623 else
624 printf "\\nNOTICE: Missing '%s_log' file:\\n" \
625 "$EXPECTED" >&2
626 log_verify_config "./torrc" \
627 "$DEFAULTS" \
628 "$CMDLINE"
631 elif test -f "$ERROR"; then
632 # This case should fail: run verify-config and see if it does.
634 check_verify_config "./torrc" \
635 "$DEFAULTS" \
636 "$CMDLINE" \
637 "${DATA_DIR}/output.${testname}" \
638 "$FALSE" \
639 "$ERROR" \
640 "log error"
641 else
642 # This case is not actually configured with a success or a failure.
643 # call that an error.
644 fail_printf "Did not find ${dir}/*expected or ${dir}/*error.\\n"
647 if test -z "$NEXT_TEST"; then
648 echo "OK"
651 cd "$PREV_DIR"
653 done
655 exit "$FINAL_EXIT"