3 ############################################################################
5 # Privoxy-Regression-Test
7 # A regression test "framework" for Privoxy. For documentation see:
8 # perldoc privoxy-regression-test.pl
12 # - Update documentation
13 # - Validate HTTP times.
14 # - Implement a HTTP_VERSION directive or allow to
15 # specify whole request lines.
16 # - Support filter regression tests.
17 # - Document magic Expect Header values
18 # - Internal fuzz support?
20 # Copyright (c) 2007-2020 Fabian Keil <fk@fabiankeil.de>
22 # Permission to use, copy, modify, and distribute this software for any
23 # purpose with or without fee is hereby granted, provided that the above
24 # copyright notice and this permission notice appear in all copies.
26 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
27 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
28 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
29 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
30 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
31 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
32 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
34 ############################################################################
41 PRT_VERSION => 'Privoxy-Regression-Test 0.7.2',
50 # The reason for a maximum test level is explained in the
51 # perldoc section TEST LEVELS near the end of this file.
56 PRIVOXY_ADDRESS => 'http://127.0.0.1:8118/',
57 PRIVOXY_CGI_URL => 'http://p.p/',
58 FELLATIO_URL => 'http://127.0.0.1:8080/',
59 LEADING_LOG_DATE => 1,
60 LEADING_LOG_TIME => 1,
62 DEBUG_LEVEL_FILE_LOADING => 0,
63 DEBUG_LEVEL_PAGE_FETCHING => 0,
64 DEBUG_LEVEL_VERBOSE_FAILURE => 1,
65 # XXX: Only partly implemented and mostly useless.
66 DEBUG_LEVEL_VERBOSE_SUCCESS => 0,
67 DEBUG_LEVEL_STATUS => 1,
69 # Internal use, don't modify
70 # Available debug bits:
72 LL_VERBOSE_FAILURE => 2,
73 LL_PAGE_FETCHING => 4,
75 LL_VERBOSE_SUCCESS => 16,
78 CLIENT_HEADER_TEST => 1,
79 SERVER_HEADER_TEST => 2,
82 STICKY_ACTIONS_TEST => 5,
83 TRUSTED_CGI_REQUEST => 6,
88 sub init_our_variables() {
90 our $leading_log_time = LEADING_LOG_TIME;
91 our $leading_log_date = LEADING_LOG_DATE;
92 our $privoxy_cgi_url = PRIVOXY_CGI_URL;
93 our $log_level = get_default_log_level();
94 our $proxy = defined $ENV{'http_proxy'} ? $ENV{'http_proxy'} : PRIVOXY_ADDRESS;
97 sub get_default_log_level() {
101 $log_level |= LL_FILE_LOADING if DEBUG_LEVEL_FILE_LOADING;
102 $log_level |= LL_PAGE_FETCHING if DEBUG_LEVEL_PAGE_FETCHING;
103 $log_level |= LL_VERBOSE_FAILURE if DEBUG_LEVEL_VERBOSE_FAILURE;
104 $log_level |= LL_VERBOSE_SUCCESS if DEBUG_LEVEL_VERBOSE_SUCCESS;
105 $log_level |= LL_STATUS if DEBUG_LEVEL_STATUS;
107 # This one is supposed to be always on.
108 $log_level |= LL_SOFT_ERROR;
113 ############################################################################
115 # File loading functions
117 ############################################################################
125 # Unescape brackets and dots
126 $tag =~ s@\\(?=[{}().+])@@g;
128 # log_message("Parsed tag: " . $tag);
130 check_for_forbidden_characters($tag);
135 sub check_for_forbidden_characters($) {
138 my $allowed = '[-=\dA-Za-z~{}\[\]:./();\t ,+@"_%?&*^|]';
140 unless ($string =~ m/^$allowed*$/o) {
141 my $forbidden = $string;
142 $forbidden =~ s@^$allowed*(.).*@$1@;
144 log_and_die("'" . $string . "' contains character '" . $forbidden. "' which is unacceptable.");
148 sub load_regression_tests() {
149 if (cli_option_is_set('local-test-file')) {
150 load_regression_tests_from_file(get_cli_option('local-test-file'));
152 load_regression_tests_through_privoxy();
156 # XXX: Contains a lot of code duplicated from load_action_files()
157 # that should be factored out.
158 sub load_regression_tests_from_file($) {
159 my $action_file = shift;
163 our @regression_tests;
165 my $si = 0; # Section index
166 my $ri = -1; # Regression test index
171 my $sticky_actions = undef;
173 l(LL_STATUS, "Gathering regression tests from local file " . $action_file);
175 open(my $ACTION_FILE, "<", $action_file)
176 or log_and_die("Failed to open $action_file: $!");
178 while (<$ACTION_FILE>) {
182 my ($token, $value) = tokenize($_);
184 next unless defined $token;
186 # Load regression tests
188 if (token_starts_new_test($token)) {
190 # Beginning of new regression test.
193 enlist_new_test(\@regression_tests, $token, $value, $si, $ri, $count);
194 $no_checks = 1; # Already validated by enlist_new_test().
197 if ($token =~ /level\s+(\d+)/i) {
200 register_dependency($level, $value);
203 if ($token eq 'sticky actions') {
205 # Will be used by each following Sticky URL.
206 $sticky_actions = $value;
207 if ($sticky_actions =~ /{[^}]*\s/) {
208 log_and_die("'Sticky Actions' with whitespace inside the " .
209 "action parameters are currently unsupported.");
213 if ($si == -1 || $ri == -1) {
214 # No beginning of a test detected yet,
215 # so we don't care about any other test
220 if ($token eq 'expect header') {
222 l(LL_FILE_LOADING, "Detected expectation: " . $value);
223 $regression_tests[$si][$ri]{'expect-header'} = $value;
225 } elsif ($token eq 'tag') {
229 my $tag = parse_tag($value);
231 # We already checked in parse_tag() after filtering
234 l(LL_FILE_LOADING, "Detected TAG: " . $tag);
236 # Save tag for all tests in this section
238 $regression_tests[$si][$ri]{'tag'} = $tag;
244 } elsif ($token eq 'ignore' && $value =~ /Yes/i) {
246 l(LL_FILE_LOADING, "Ignoring section: " . test_content_as_string($regression_tests[$si][$ri]));
247 $regression_tests[$si][$ri]{'ignore'} = 1;
250 } elsif ($token eq 'expect status code') {
252 l(LL_FILE_LOADING, "Expecting status code: " . $value);
253 $regression_tests[$si][$ri]{'expected-status-code'} = $value;
255 } elsif ($token eq 'level') { # XXX: stupid name
257 $value =~ s@(\d+).*@$1@;
258 l(LL_FILE_LOADING, "Level: " . $value);
259 $regression_tests[$si][$ri]{'level'} = $value;
261 } elsif ($token eq 'method') {
263 l(LL_FILE_LOADING, "Method: " . $value);
264 $regression_tests[$si][$ri]{'method'} = $value;
266 } elsif ($token eq 'redirect destination') {
268 l(LL_FILE_LOADING, "Redirect destination: " . $value);
269 $regression_tests[$si][$ri]{'redirect destination'} = $value;
271 } elsif ($token eq 'url') {
273 if (defined $sticky_actions) {
274 die "WTF? Attempted to overwrite Sticky Actions"
275 if defined ($regression_tests[$si][$ri]{'sticky-actions'});
277 l(LL_FILE_LOADING, "Sticky actions: " . $sticky_actions);
278 $regression_tests[$si][$ri]{'sticky-actions'} = $sticky_actions;
280 log_and_die("Sticky URL without Sticky Actions in $action_file: $value");
285 # We don't use it, so we don't need
287 l(LL_STATUS, "Enabling no_checks for $token") unless $no_checks;
291 unless ($no_checks) {
292 check_for_forbidden_characters($value);
293 check_for_forbidden_characters($token);
297 l(LL_FILE_LOADING, "Done loading " . $count . " regression tests."
298 . " Of which " . $ignored. " will be ignored)\n");
303 sub load_regression_tests_through_privoxy() {
305 our $privoxy_cgi_url;
307 our %privoxy_features;
312 my $privoxy_version = '(Unknown version!)';
314 $curl_url .= $privoxy_cgi_url;
315 $curl_url .= 'show-status';
317 l(LL_STATUS, "Asking Privoxy for the number of action files available ...");
319 # Dear Privoxy, please reload the config file if necessary ...
320 get_cgi_page_or_else($curl_url);
322 # ... so we get the latest one here.
323 foreach (@{get_cgi_page_or_else($curl_url)}) {
326 if (/<td>(.*?)<\/td><td class=\"buttons\"><a href=\"\/show-status\?file=actions&index=(\d+)\">/) {
328 my $url = $privoxy_cgi_url . 'show-status?file=actions&index=' . $2;
329 $actionfiles[$file_number++] = $url;
331 } elsif (m@config\.html#.*\">([^<]*)</a>\s+(.*)<br>@) {
333 my $directive = $1 . " " . $2;
334 push (@privoxy_config, $directive);
336 } elsif (m@<td><code>([^<]*)</code></td>@) {
340 } elsif (m@<td> (Yes|No) </td>@) {
342 $privoxy_features{$feature} = $1 if defined $feature;
345 } elsif (m@This is <a href="https?://www.privoxy.org/">Privoxy</a> (\d+\.\d+\.\d+) on@) {
346 $privoxy_version = $1;
350 l(LL_STATUS, "Gathering regression tests from " .
351 @actionfiles . " action file(s) delivered by Privoxy $privoxy_version.");
353 load_action_files(\@actionfiles);
356 sub token_starts_new_test($) {
359 my @new_test_directives = ('set header', 'fetch test',
360 'trusted cgi request', 'request header', 'method test',
361 'blocked url', 'url', 'redirected url');
363 foreach my $new_test_directive (@new_test_directives) {
364 return 1 if $new_test_directive eq $token;
372 my ($token, $value) = (undef, undef);
374 # Remove leading and trailing white space and a
375 # a leading <pre> which is part of the first line.
379 # Reverse HTML-encoding
380 # XXX: Seriously incomplete.
385 if (/^\#\s*([^=:#]*?)\s*[=]\s*([^#]+)(?:#.*)?$/) {
390 $token =~ s@\s\s+@ @g;
391 $token =~ tr/[A-Z]/[a-z]/;
393 } elsif (/^TAG\s*:(.*)$/) {
399 return ($token, $value);
402 sub enlist_new_test($$$$$$) {
404 my ($regression_tests, $token, $value, $si, $ri, $number) = @_;
408 if ($token eq 'set header') {
410 l(LL_FILE_LOADING, "Header to set: " . $value);
411 $type = CLIENT_HEADER_TEST;
412 $executor = \&execute_client_header_regression_test;
414 } elsif ($token eq 'request header') {
416 l(LL_FILE_LOADING, "Header to request: " . $value);
417 $type = SERVER_HEADER_TEST;
418 $executor = \&execute_server_header_regression_test;
419 $$regression_tests[$si][$ri]{'expected-status-code'} = 200;
421 } elsif ($token eq 'trusted cgi request') {
423 l(LL_FILE_LOADING, "CGI URL to test in a dumb way: " . $value);
424 $type = TRUSTED_CGI_REQUEST;
425 $executor = \&execute_dumb_fetch_test;
426 $$regression_tests[$si][$ri]{'expected-status-code'} = 200;
428 } elsif ($token eq 'fetch test') {
430 l(LL_FILE_LOADING, "URL to test in a dumb way: " . $value);
431 $type = DUMB_FETCH_TEST;
432 $executor = \&execute_dumb_fetch_test;
433 $$regression_tests[$si][$ri]{'expected-status-code'} = 200;
435 } elsif ($token eq 'method test') {
437 l(LL_FILE_LOADING, "Method to test: " . $value);
439 $executor = \&execute_method_test;
440 $$regression_tests[$si][$ri]{'expected-status-code'} = 200;
442 } elsif ($token eq 'blocked url') {
444 l(LL_FILE_LOADING, "URL to block-test: " . $value);
445 $executor = \&execute_block_test;
448 } elsif ($token eq 'url') {
450 l(LL_FILE_LOADING, "Sticky URL to test: " . $value);
451 $type = STICKY_ACTIONS_TEST;
452 $executor = \&execute_sticky_actions_test;
454 } elsif ($token eq 'redirected url') {
456 l(LL_FILE_LOADING, "Redirected URL to test: " . $value);
457 $type = REDIRECT_TEST;
458 $executor = \&execute_redirect_test;
462 die "Incomplete '" . $token . "' support detected.";
465 $$regression_tests[$si][$ri]{'type'} = $type;
466 $$regression_tests[$si][$ri]{'level'} = $type;
467 $$regression_tests[$si][$ri]{'executor'} = $executor;
469 check_for_forbidden_characters($value);
471 $$regression_tests[$si][$ri]{'data'} = $value;
473 # For function that only get passed single tests
474 $$regression_tests[$si][$ri]{'section-id'} = $si;
475 $$regression_tests[$si][$ri]{'regression-test-id'} = $ri;
476 $$regression_tests[$si][$ri]{'number'} = $number - 1;
478 "Regression test " . $number . " (section:" . $si . "):");
481 sub mark_matching_tests_for_skipping($) {
482 my $overwrite_condition = shift;
484 our @regression_tests;
486 for (my $s = 0; $s < @regression_tests; $s++) {
490 while (defined $regression_tests[$s][$r]) {
492 if ($regression_tests[$s][$r]{'data'} eq $overwrite_condition) {
493 my $message = sprintf("Marking test %s for ignoring. Overwrite condition: %s.",
494 $regression_tests[$s][$r]{'number'}, $overwrite_condition);
496 l(LL_FILE_LOADING, $message);
498 # XXX: Should eventually get its own key so get_skip_reason()
499 # can tell about the overwrite condition.
500 $regression_tests[$s][$r]{'ignore'} = 1;
508 # XXX: Shares a lot of code with load_regression_tests_from_file()
509 # that should be factored out.
510 sub load_action_files($) {
514 our @regression_tests;
516 my $actionfiles_ref = shift;
517 my @actionfiles = @{$actionfiles_ref};
519 my $si = 0; # Section index
520 my $ri = -1; # Regression test index
525 for my $file_number (0 .. @actionfiles - 1) {
527 my $curl_url = quote($actionfiles[$file_number]);
528 my $actionfile = undef;
529 my $sticky_actions = undef;
530 my $level_offset = 0;
532 foreach (@{get_cgi_page_or_else($curl_url)}) {
537 if (/<h2>Contents of Actions File (.*?)</) {
541 next unless defined $actionfile;
545 my ($token, $value) = tokenize($_);
547 next unless defined $token;
549 # Load regression tests
550 if ($token eq 'default level offset') {
552 $level_offset = $value;
553 l(LL_FILE_LOADING, "Setting default level offset to " . $level_offset);
556 if (token_starts_new_test($token)) {
558 # Beginning of new regression test.
561 enlist_new_test(\@regression_tests, $token, $value, $si, $ri, $count);
562 $no_checks = 1; # Already validated by enlist_new_test().
563 if ($level_offset != 0) {
564 $regression_tests[$si][$ri]{'level'} += $level_offset;
568 if ($token =~ /level\s+(\d+)/i) {
571 register_dependency($level, $value);
574 if ($token eq 'sticky actions') {
576 # Will be used by each following Sticky URL.
577 $sticky_actions = $value;
578 if ($sticky_actions =~ /{[^}]*\s/) {
579 log_and_die("'Sticky Actions' with whitespace inside the " .
580 "action parameters are currently unsupported.");
584 if ($token eq 'overwrite condition') {
586 l(LL_FILE_LOADING, "Detected overwrite condition: " . $value);
587 # We can only skip matching tests that have already
588 # be loaded but that is exactly what we want anyway.
589 mark_matching_tests_for_skipping($value);
593 if ($si == -1 || $ri == -1) {
594 # No beginning of a test detected yet,
595 # so we don't care about any other test
600 if ($token eq 'expect header') {
602 l(LL_FILE_LOADING, "Detected expectation: " . $value);
603 $regression_tests[$si][$ri]{'expect-header'} = $value;
605 } elsif ($token eq 'tag') {
609 my $tag = parse_tag($value);
611 # We already checked in parse_tag() after filtering
614 l(LL_FILE_LOADING, "Detected TAG: " . $tag);
616 # Save tag for all tests in this section
618 $regression_tests[$si][$ri]{'tag'} = $tag;
624 } elsif ($token eq 'ignore' && $value =~ /Yes/i) {
626 l(LL_FILE_LOADING, "Ignoring section: " . test_content_as_string($regression_tests[$si][$ri]));
627 $regression_tests[$si][$ri]{'ignore'} = 1;
630 } elsif ($token eq 'expect status code') {
632 l(LL_FILE_LOADING, "Expecting status code: " . $value);
633 $regression_tests[$si][$ri]{'expected-status-code'} = $value;
635 } elsif ($token eq 'level') { # XXX: stupid name
637 $value =~ s@(\d+).*@$1@;
638 l(LL_FILE_LOADING, "Level: " . $value);
639 $regression_tests[$si][$ri]{'level'} = $value;
641 } elsif ($token eq 'method') {
643 l(LL_FILE_LOADING, "Method: " . $value);
644 $regression_tests[$si][$ri]{'method'} = $value;
646 } elsif ($token eq 'redirect destination') {
648 l(LL_FILE_LOADING, "Redirect destination: " . $value);
649 $regression_tests[$si][$ri]{'redirect destination'} = $value;
651 } elsif ($token eq 'url') {
653 if (defined $sticky_actions) {
654 die "WTF? Attempted to overwrite Sticky Actions"
655 if defined ($regression_tests[$si][$ri]{'sticky-actions'});
657 l(LL_FILE_LOADING, "Sticky actions: " . $sticky_actions);
658 $regression_tests[$si][$ri]{'sticky-actions'} = $sticky_actions;
660 log_and_die("Sticky URL without Sticky Actions in $actionfile: $value");
665 # We don't use it, so we don't need
667 l(LL_STATUS, "Enabling no_checks for $token") unless $no_checks;
671 unless ($no_checks) {
672 check_for_forbidden_characters($value);
673 check_for_forbidden_characters($token);
678 l(LL_FILE_LOADING, "Done loading " . $count . " regression tests."
679 . " Of which " . $ignored. " will be ignored)\n");
682 ############################################################################
684 # Regression test executing functions
686 ############################################################################
688 # Fisher Yates shuffle from Perl's "How do I shuffle an array randomly?" FAQ
689 sub fisher_yates_shuffle($) {
693 my $j = int rand($i+1);
694 @$deck[$i,$j] = @$deck[$j,$i];
698 sub execute_regression_tests() {
700 our @regression_tests;
701 my $loops = get_cli_option('loops');
703 my $all_failures = 0;
704 my $all_successes = 0;
706 unless (@regression_tests) {
708 l(LL_STATUS, "No regression tests found.");
712 l(LL_STATUS, "Executing regression tests ...");
714 while ($loops-- > 0) {
721 if (cli_option_is_set('shuffle-tests')) {
723 # Shuffle both the test sections and
724 # the tests they contain.
726 # XXX: With the current data layout, shuffling tests
727 # from different sections isn't possible.
728 # Is this worth changing the layout?
729 fisher_yates_shuffle(\@regression_tests);
730 for (my $s = 0; $s < @regression_tests; $s++) {
731 fisher_yates_shuffle($regression_tests[$s]);
735 for (my $s = 0; $s < @regression_tests; $s++) {
739 while (defined $regression_tests[$s][$r]) {
741 unless (cli_option_is_set('shuffle-tests')) {
742 die "Section id mismatch" if ($s != $regression_tests[$s][$r]{'section-id'});
743 die "Regression test id mismatch" if ($r != $regression_tests[$s][$r]{'regression-test-id'});
745 die "Internal error. Test executor missing."
746 unless defined $regression_tests[$s][$r]{executor};
748 my $number = $regression_tests[$s][$r]{'number'};
749 my $skip_reason = get_skip_reason($regression_tests[$s][$r]);
751 if (defined $skip_reason) {
753 my $message = "Skipping test " . $number . ": " . $skip_reason . ".";
754 log_message($message) if (cli_option_is_set('show-skipped-tests'));
759 my $result = $regression_tests[$s][$r]{executor}($regression_tests[$s][$r]);
761 log_result($regression_tests[$s][$r], $result, $tests);
763 $successes += $result;
765 sleep(get_cli_option('sleep-time')) if (cli_option_is_set('sleep-time'));
770 $failures = $tests - $successes;
772 log_message("Executed " . $tests . " regression tests. " .
773 'Skipped ' . $skipped . '. ' .
774 $successes . " successes, " . $failures . " failures.");
776 $all_tests += $tests;
777 $all_failures += $failures;
778 $all_successes += $successes;
781 if (get_cli_option('loops') > 1) {
782 log_message("Total: Executed " . $all_tests . " regression tests. " .
783 $all_successes . " successes, " . $all_failures . " failures.");
787 sub get_skip_reason($) {
789 my $skip_reason = undef;
791 if ($test->{'ignore'}) {
793 $skip_reason = "Ignore flag is set";
795 } elsif (cli_option_is_set('test-number') and
796 get_cli_option('test-number') != $test->{'number'}) {
798 $skip_reason = "Only executing test " . get_cli_option('test-number');
802 $skip_reason = level_is_unacceptable($test->{'level'});
808 sub level_is_unacceptable($) {
810 my $min_level = get_cli_option('min-level');
811 my $max_level = get_cli_option('max-level');
812 my $required_level = cli_option_is_set('level') ?
813 get_cli_option('level') : $level;
816 if ($required_level != $level) {
818 $reason = "Level doesn't match (" . $level .
819 " != " . $required_level . ")"
821 } elsif ($level < $min_level) {
823 $reason = "Level too low (" . $level . " < " . $min_level . ")";
825 } elsif ($level > $max_level) {
827 $reason = "Level too high (" . $level . " > " . $max_level . ")";
831 $reason = dependency_unsatisfied($level);
837 sub dependency_unsatisfied($) {
842 our %privoxy_features;
844 my $dependency_problem = undef;
846 if (defined ($dependencies{$level}{'config line'})) {
848 my $dependency = $dependencies{$level}{'config line'};
849 $dependency_problem = "depends on config line matching: '" . $dependency . "'";
851 foreach (@privoxy_config) {
854 $dependency_problem = undef;
861 if (defined ($dependencies{$level}{'feature status'})
862 and not defined $dependency_problem) {
864 my $dependency = $dependencies{$level}{'feature status'};
865 my ($feature, $status) = $dependency =~ /([^\s]*)\s+(Yes|No)/;
867 unless (defined($privoxy_features{$feature})
868 and ($privoxy_features{$feature} eq $status))
870 $dependency_problem = "depends on '" . $feature .
871 "' being set to '" . $status . "'";
875 return $dependency_problem;
878 sub register_dependency($$) {
881 my $dependency = shift;
884 if ($dependency =~ /config line\s+(.*)/) {
886 $dependencies{$level}{'config line'} = $1;
888 } elsif ($dependency =~ /feature status\s+(.*)/) {
890 $dependencies{$level}{'feature status'} = $1;
894 log_and_die("Didn't recognize dependency: $dependency.");
898 sub execute_method_test($) {
901 our $privoxy_cgi_url;
905 my $method = $test->{'data'};
907 my $curl_parameters = '';
908 my $expected_status_code = $test->{'expected-status-code'};
910 $curl_parameters .= '--request ' . $method . ' ';
911 # Don't complain about the 'missing' body
912 $curl_parameters .= '--head ' if ($method =~ /^HEAD$/i);
914 $curl_parameters .= $privoxy_cgi_url;
916 $buffer_ref = get_page_with_curl($curl_parameters);
917 $status_code = get_status_code($buffer_ref);
919 return check_status_code_result($status_code, $expected_status_code);
922 sub execute_redirect_test($) {
928 my $curl_parameters = '';
929 my $url = $test->{'data'};
930 my $redirect_destination;
931 my $expected_redirect_destination = $test->{'redirect destination'};
933 # XXX: Check if a redirect actually applies before doing the request.
934 # otherwise the test may hit a real server in failure cases.
936 $curl_parameters .= '--head ';
938 $curl_parameters .= quote($url);
940 $buffer_ref = get_page_with_curl($curl_parameters);
941 $status_code = get_status_code($buffer_ref);
943 if ($status_code ne "302") {
944 l(LL_VERBOSE_FAILURE,
945 "Ooops. Expected redirect to: '" . $expected_redirect_destination
946 . "' but got a response with status code: " . $status_code);
949 foreach (@{$buffer_ref}) {
950 if (/^Location: (.*)\r\n/) {
951 $redirect_destination = $1;
956 my $success = ($redirect_destination eq $expected_redirect_destination);
959 l(LL_VERBOSE_FAILURE,
960 "Ooops. Expected redirect to: '" . $expected_redirect_destination
961 . "' but the redirect leads to: '" . $redirect_destination. "'");
967 sub execute_dumb_fetch_test($) {
970 our $privoxy_cgi_url;
975 my $curl_parameters = '';
976 my $expected_status_code = $test->{'expected-status-code'};
978 if (defined $test->{method}) {
979 $curl_parameters .= '--request ' . quote($test->{method}) . ' ';
981 if ($test->{type} == TRUSTED_CGI_REQUEST) {
982 $curl_parameters .= '--referer ' . quote($privoxy_cgi_url) . ' ';
985 $curl_parameters .= quote($test->{'data'});
987 $buffer_ref = get_page_with_curl($curl_parameters);
988 $status_code = get_status_code($buffer_ref);
990 return check_status_code_result($status_code, $expected_status_code);
993 sub execute_block_test($) {
996 my $url = $test->{'data'};
997 my $final_results = get_final_results($url);
999 return defined $final_results->{'+block'};
1002 sub execute_sticky_actions_test($) {
1005 my $url = $test->{'data'};
1006 my $verified_actions = 0;
1007 # XXX: splitting currently doesn't work for actions whose parameters contain spaces.
1008 my @sticky_actions = split(/\s+/, $test->{'sticky-actions'});
1009 my $final_results = get_final_results($url);
1011 foreach my $sticky_action (@sticky_actions) {
1013 if (defined $final_results->{$sticky_action}) {
1015 $verified_actions++;
1017 } elsif ($sticky_action =~ /-.*\{/) {
1019 # Disabled multi actions aren't explicitly listed as
1020 # disabled and thus have to be checked by verifying
1021 # that they aren't enabled.
1022 $verified_actions++;
1025 l(LL_VERBOSE_FAILURE,
1026 "Ooops. '$sticky_action' is not among the final results.");
1030 return $verified_actions == @sticky_actions;
1033 sub get_final_results($) {
1036 our $privoxy_cgi_url;
1038 my $curl_parameters = '';
1039 my %final_results = ();
1040 my $final_results_reached = 0;
1042 die "Unacceptable characters in $url" if $url =~ m@[\\'"]@;
1043 # XXX: should be URL-encoded properly
1050 $curl_parameters .= quote($privoxy_cgi_url . 'show-url-info?url=' . $url);
1052 foreach (@{get_cgi_page_or_else($curl_parameters)}) {
1054 $final_results_reached = 1 if (m@<h2>Final results:</h2>@);
1056 next unless ($final_results_reached);
1059 # Privoxy versions before 3.0.16 add a space
1060 # between action name and parameters, therefore
1062 if (m@<br>([-+])<a.*>([^>]*)</a>(?: ?(\{.*\}))?@) {
1066 if (defined $parameter) {
1067 # In case the caller needs to check
1068 # the action and its parameter
1069 $final_results{$action . $parameter} = 1;
1071 # In case the action doesn't have parameters
1072 # or the caller doesn't care for the parameter.
1073 $final_results{$action} = 1;
1077 return \%final_results;
1080 sub check_status_code_result($$) {
1082 my $status_code = shift;
1083 my $expected_status_code = shift;
1086 unless (defined $status_code) {
1088 # XXX: should probably be caught earlier.
1089 l(LL_VERBOSE_FAILURE,
1090 "Ooops. We expected status code " . $expected_status_code . ", but didn't get any status code at all.");
1092 } elsif ($expected_status_code == $status_code) {
1095 l(LL_VERBOSE_SUCCESS,
1096 "Yay. We expected status code " . $expected_status_code . ", and received: " . $status_code . '.');
1098 } elsif (cli_option_is_set('fuzzer-feeding') and $status_code == 123) {
1100 l(LL_VERBOSE_FAILURE,
1101 "Oh well. Status code lost while fuzzing. Can't check if it was " . $expected_status_code . '.');
1105 l(LL_VERBOSE_FAILURE,
1106 "Ooops. We expected status code " . $expected_status_code . ", but received: " . $status_code . '.');
1112 sub execute_client_header_regression_test($) {
1118 $buffer_ref = get_show_request_with_curl($test);
1120 $header = get_header($buffer_ref, $test);
1122 return check_header_result($test, $header);
1125 sub execute_server_header_regression_test($) {
1131 $buffer_ref = get_head_with_curl($test);
1133 $header = get_server_header($buffer_ref, $test);
1135 return check_header_result($test, $header);
1138 sub interpret_result($) {
1139 my $success = shift;
1140 return $success ? "Success" : "Failure";
1143 sub check_header_result($$) {
1148 my $expect_header = $test->{'expect-header'};
1151 if ($expect_header eq 'NO CHANGE') {
1153 $success = (defined($header) and $header eq $test->{'data'});
1156 $header = "REMOVAL" unless defined $header;
1157 l(LL_VERBOSE_FAILURE,
1158 "Ooops. Got: '" . $header . "' while expecting: '" . $expect_header . "'");
1161 } elsif ($expect_header eq 'REMOVAL') {
1163 # XXX: Use more reliable check here and make sure
1164 # the header has a different name.
1165 $success = not (defined($header) and $header eq $test->{'data'});
1168 l(LL_VERBOSE_FAILURE,
1169 "Ooops. Expected removal but: '" . $header . "' is still there.");
1172 } elsif ($expect_header eq 'SOME CHANGE') {
1174 $success = (defined($header) and $header ne $test->{'data'});
1177 $header = "REMOVAL" unless defined $header;
1178 l(LL_VERBOSE_FAILURE,
1179 "Ooops. Got: '" . $header . "' while expecting: SOME CHANGE");
1184 $success = (defined($header) and $header eq $expect_header);
1187 $header = "No matching header" unless defined $header; # XXX: No header detected to be precise
1188 l(LL_VERBOSE_FAILURE,
1189 "Ooops. Got: '" . $header . "' while expecting: '" . $expect_header . "'");
1195 sub get_header_name($) {
1199 $header =~ s@(.*?: ).*@$1@;
1204 sub get_header($$) {
1206 our $filtered_request = '';
1208 my $buffer_ref = shift;
1211 my @buffer = @{$buffer_ref};
1213 my $expect_header = $test->{'expect-header'};
1215 die "get_header called with no expect header" unless defined $expect_header;
1218 my $processed_request_reached = 0;
1219 my $read_header = 0;
1220 my $processed_request = '';
1224 if ($expect_header eq 'REMOVAL'
1225 or $expect_header eq 'NO CHANGE'
1226 or $expect_header eq 'SOME CHANGE') {
1228 $expect_header = $test->{'data'};
1231 $header_to_get = get_header_name($expect_header);
1235 # Skip everything before the Processed request
1236 if (/Processed Request/) {
1237 $processed_request_reached = 1;
1240 next unless $processed_request_reached;
1242 # End loop after the Processed request
1243 last if (/<\/pre>/);
1245 # Ditch tags and leading/trailing white space.
1249 # Decode characters we care about.
1252 $filtered_request .= "\n" . $_;
1254 if (/^$header_to_get/) {
1264 sub get_server_header($$) {
1266 my $buffer_ref = shift;
1269 my @buffer = @{$buffer_ref};
1271 my $expect_header = $test->{'expect-header'};
1275 # XXX: Should be caught before starting to test.
1276 log_and_die("No expect header for test " . $test->{'number'})
1277 unless defined $expect_header;
1279 if ($expect_header eq 'REMOVAL'
1280 or $expect_header eq 'NO CHANGE'
1281 or $expect_header eq 'SOME CHANGE') {
1283 $expect_header = $test->{'data'};
1286 $header_to_get = get_header_name($expect_header);
1290 # XXX: should probably verify that the request
1291 # was actually answered by Fellatio.
1292 if (/^$header_to_get/) {
1294 $header =~ s@\s*$@@g;
1302 sub get_status_code($) {
1304 my $buffer_ref = shift;
1305 our $privoxy_cgi_url;
1307 my $skip_connection_established_response = $privoxy_cgi_url =~ m@^https://@;
1308 my @buffer = @{$buffer_ref};
1312 if ($skip_connection_established_response) {
1314 next if (m@^HTTP/1\.1 200 Connection established@);
1315 next if (m@^\r\n$@);
1316 $skip_connection_established_response = 0;
1319 if (/^HTTP\/\d\.\d (\d{3})/) {
1325 return '123' if cli_option_is_set('fuzzer-feeding');
1327 log_and_die('Unexpected buffer line: "' . $_ . '"');
1332 sub get_test_keys() {
1333 return ('tag', 'data', 'expect-header', 'ignore');
1337 sub test_content_as_string($) {
1343 foreach my $key (get_test_keys()) {
1344 $test->{$key} = 'Not set' unless (defined $test->{$key});
1347 $s .= 'Tag: ' . $test->{'tag'};
1349 $s .= 'Set header: ' . $test->{'data'}; # XXX: adjust for other test types
1351 $s .= 'Expected header: ' . $test->{'expect-header'};
1353 $s .= 'Ignore: ' . $test->{'ignore'};
1358 sub fuzz_header($) {
1360 my $white_space = int(rand(2)) - 1 ? " " : "\t";
1362 $white_space = $white_space x (1 + int(rand(5)));
1364 # Only fuzz white space before the first quoted token.
1365 # (Privoxy doesn't touch white space inside quoted tokens
1366 # and modifying it would cause the tests to fail).
1367 $header =~ s@(^[^"]*?)\s@$1$white_space@g;
1372 ############################################################################
1374 # HTTP fetch functions
1376 ############################################################################
1378 sub get_cgi_page_or_else($) {
1380 my $cgi_url = shift;
1381 my $content_ref = get_page_with_curl($cgi_url);
1382 my $status_code = get_status_code($content_ref);
1384 if (200 != $status_code) {
1386 my $log_message = "Failed to fetch Privoxy CGI page '$cgi_url'. " .
1387 "Received status code ". $status_code .
1388 " while only 200 is acceptable.";
1390 if (cli_option_is_set('fuzzer-feeding')) {
1392 $log_message .= " Ignored due to fuzzer feeding.";
1393 l(LL_SOFT_ERROR, $log_message)
1397 log_and_die($log_message);
1401 return $content_ref;
1404 # XXX: misleading name
1405 sub get_show_request_with_curl($) {
1407 our $privoxy_cgi_url;
1410 my $curl_parameters = ' ';
1411 my $header = $test->{'data'};
1413 if (cli_option_is_set('header-fuzzing')) {
1414 $header = fuzz_header($header);
1417 # Enable the action to test
1418 $curl_parameters .= '-H \'X-Privoxy-Control: ' . $test->{'tag'} . '\' ';
1420 # Add the header to filter
1421 if ($privoxy_cgi_url =~ m@^https://@ and $header =~ m@^Host:@) {
1422 $curl_parameters .= '--proxy-header \'' . $header . '\' ';
1424 $curl_parameters .= '-H \'' . $header . '\' ';
1427 $curl_parameters .= ' ';
1428 $curl_parameters .= $privoxy_cgi_url;
1429 $curl_parameters .= 'show-request';
1431 return get_cgi_page_or_else($curl_parameters);
1434 sub get_head_with_curl($) {
1436 our $fellatio_url = FELLATIO_URL;
1439 my $curl_parameters = ' ';
1441 # Enable the action to test
1442 $curl_parameters .= '-H \'X-Privoxy-Control: ' . $test->{'tag'} . '\' ';
1443 # The header to filter
1444 $curl_parameters .= '-H \'X-Gimme-Head-With: ' . $test->{'data'} . '\' ';
1445 $curl_parameters .= '--head ';
1447 $curl_parameters .= ' ';
1448 $curl_parameters .= $fellatio_url;
1450 return get_page_with_curl($curl_parameters);
1453 sub get_page_with_curl($) {
1457 my $parameters = shift;
1459 my $curl_line = CURL;
1460 my $retries_left = get_cli_option('retries') + 1;
1463 if (defined $proxy) {
1464 $curl_line .= ' --proxy ' . quote($proxy);
1466 # We want to see the HTTP status code
1467 $curl_line .= " --include ";
1468 # Let Privoxy emit two log messages less.
1469 $curl_line .= ' -H \'Proxy-Connection:\' ' unless $parameters =~ /Proxy-Connection:/;
1470 $curl_line .= ' -H \'Connection: close\' ' unless $parameters =~ /Connection:/;
1471 # We don't care about fetch statistic.
1472 $curl_line .= " -s ";
1473 # We do care about the failure reason if any.
1474 $curl_line .= " -S ";
1475 # We want to advertise ourselves
1476 $curl_line .= " --user-agent '" . PRT_VERSION . "' ";
1477 # We aren't too patient
1478 $curl_line .= " --max-time '" . get_cli_option('max-time') . "' ";
1479 # We don't want curl to treat "[]", "{}" etc. special
1480 $curl_line .= " --globoff ";
1482 $curl_line .= $parameters;
1483 # XXX: still necessary?
1484 $curl_line .= ' 2>&1';
1486 l(LL_PAGE_FETCHING, "Executing: " . $curl_line);
1489 @buffer = `$curl_line`;
1492 log_and_die("Executing '$curl_line' failed.") unless @buffer;
1493 $failure_reason = array_as_string(\@buffer);
1494 chomp $failure_reason;
1495 l(LL_SOFT_ERROR, "Fetch failure: '" . $failure_reason . $! ."'");
1497 } while ($? && --$retries_left);
1499 unless ($retries_left) {
1500 log_and_die("Running curl failed " . get_cli_option('retries') .
1501 " times in a row. Last error: '" . $failure_reason . "'.");
1508 ############################################################################
1512 ############################################################################
1514 sub array_as_string($) {
1515 my $array_ref = shift;
1518 foreach (@{$array_ref}) {
1527 log_message('Test is:' . test_content_as_string($test));
1533 my $this_level = shift;
1534 my $message = shift;
1536 log_message($message) if ($log_level & $this_level);
1539 sub log_and_die($) {
1540 my $message = shift;
1542 log_message('Oh noes. ' . $message . ' Fatal error. Exiting.');
1546 sub log_message($) {
1548 my $message = shift;
1552 our $leading_log_date;
1553 our $leading_log_time;
1555 my $time_stamp = '';
1556 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime time;
1558 if ($leading_log_date || $leading_log_time) {
1560 if ($leading_log_date) {
1563 $time_stamp = sprintf("%i-%.2i-%.2i", $year, $mon, $mday);
1566 if ($leading_log_time) {
1567 $time_stamp .= ' ' if $leading_log_date;
1568 $time_stamp.= sprintf("%.2i:%.2i:%.2i", $hour, $min, $sec);
1571 $message = $time_stamp . ": " . $message;
1574 printf("%s\n", $message);
1577 sub log_result($$) {
1579 our $filtered_request;
1585 my $message = sprintf("%s for test %d",
1586 interpret_result($result),
1589 if (cli_option_is_set('verbose')) {
1590 $message .= sprintf(" (%d/%d/%d)", $number,
1591 $test->{'section-id'},
1592 $test->{'regression-test-id'});
1597 if ($test->{'type'} == CLIENT_HEADER_TEST) {
1599 $message .= 'Header ';
1600 $message .= quote($test->{'data'});
1601 $message .= ' and tag ';
1602 $message .= quote($test->{'tag'});
1604 } elsif ($test->{'type'} == SERVER_HEADER_TEST) {
1606 $message .= 'Request Header ';
1607 $message .= quote($test->{'data'});
1608 $message .= ' and tag ';
1609 $message .= quote($test->{'tag'});
1611 } elsif ($test->{'type'} == DUMB_FETCH_TEST) {
1614 $message .= quote($test->{'data'});
1615 $message .= ' and expected status code ';
1616 $message .= quote($test->{'expected-status-code'});
1618 } elsif ($test->{'type'} == TRUSTED_CGI_REQUEST) {
1620 $message .= 'CGI URL ';
1621 $message .= quote($test->{'data'});
1622 $message .= ' and expected status code ';
1623 $message .= quote($test->{'expected-status-code'});
1625 } elsif ($test->{'type'} == METHOD_TEST) {
1627 $message .= 'HTTP method ';
1628 $message .= quote($test->{'data'});
1629 $message .= ' and expected status code ';
1630 $message .= quote($test->{'expected-status-code'});
1632 } elsif ($test->{'type'} == BLOCK_TEST) {
1634 $message .= 'Supposedly-blocked URL: ';
1635 $message .= quote($test->{'data'});
1637 } elsif ($test->{'type'} == STICKY_ACTIONS_TEST) {
1639 $message .= 'Sticky Actions: ';
1640 $message .= quote($test->{'sticky-actions'});
1641 $message .= ' and URL: ';
1642 $message .= quote($test->{'data'});
1644 } elsif ($test->{'type'} == REDIRECT_TEST) {
1646 $message .= 'Redirected URL: ';
1647 $message .= quote($test->{'data'});
1648 $message .= ' and redirect destination: ';
1649 $message .= quote($test->{'redirect destination'});
1653 die "Incomplete support for test type " . $test->{'type'} . " detected.";
1656 log_message($message) if (!$result or cli_option_is_set('verbose'));
1661 return '\'' . $s . '\'';
1664 sub print_version() {
1665 printf PRT_VERSION . "\n";
1668 sub list_test_types() {
1670 'Client header test' => CLIENT_HEADER_TEST,
1671 'Server header test' => 2,
1672 'Dumb fetch test' => 3,
1674 'Sticky action test' => 5,
1675 'Trusted CGI test' => 6,
1677 'Redirect test' => 108,
1680 print "\nThe supported test types and their default levels are:\n";
1681 foreach my $test_type (sort { $test_types{$a} <=> $test_types{$b} } keys %test_types) {
1682 printf " %-20s -> %3.d\n", $test_type, $test_types{$test_type};
1689 our $privoxy_cgi_url;
1695 Options and their default values if they have any:
1696 [--debug $cli_options{'debug'}]
1697 [--forks $cli_options{'forks'}]
1704 [--loops $cli_options{'loops'}]
1705 [--max-level $cli_options{'max-level'}]
1706 [--max-time $cli_options{'max-time'}]
1707 [--min-level $cli_options{'min-level'}]
1708 [--privoxy-address $cli_options{'privoxy-address'}]
1709 [--privoxy-cgi-prefix $privoxy_cgi_url]
1710 [--retries $cli_options{'retries'}]
1711 [--show-skipped-tests]
1713 [--sleep-time $cli_options{'sleep-time'}]
1724 Try "perldoc $0" for more information
1731 sub init_cli_options() {
1737 $cli_options{'debug'} = $log_level;
1738 $cli_options{'forks'} = CLI_FORKS;
1739 $cli_options{'loops'} = CLI_LOOPS;
1740 $cli_options{'max-level'} = CLI_MAX_LEVEL;
1741 $cli_options{'max-time'} = CLI_MAX_TIME;
1742 $cli_options{'min-level'} = CLI_MIN_LEVEL;
1743 $cli_options{'sleep-time'}= CLI_SLEEP_TIME;
1744 $cli_options{'retries'} = CLI_RETRIES;
1745 $cli_options{'privoxy-address'} = $proxy;
1748 sub parse_cli_options() {
1752 our $privoxy_cgi_url;
1757 'debug=i' => \$cli_options{'debug'},
1758 'forks=i' => \$cli_options{'forks'},
1759 'fuzzer-address=s' => \$cli_options{'fuzzer-address'},
1760 'fuzzer-feeding' => \$cli_options{'fuzzer-feeding'},
1761 'header-fuzzing' => \$cli_options{'header-fuzzing'},
1763 'level=i' => \$cli_options{'level'},
1764 'local-test-file=s' => \$cli_options{'local-test-file'},
1765 'loops=i' => \$cli_options{'loops'},
1766 'max-level=i' => \$cli_options{'max-level'},
1767 'max-time=i' => \$cli_options{'max-time'},
1768 'min-level=i' => \$cli_options{'min-level'},
1769 'privoxy-address=s' => \$cli_options{'privoxy-address'},
1770 'privoxy-cgi-prefix=s' => \$privoxy_cgi_url, # XXX: Should use cli_options()
1771 'retries=i' => \$cli_options{'retries'},
1772 'shuffle-tests' => \$cli_options{'shuffle-tests'},
1773 'show-skipped-tests' => \$cli_options{'show-skipped-tests'},
1774 'sleep-time=i' => \$cli_options{'sleep-time'},
1775 'test-number=i' => \$cli_options{'test-number'},
1776 'verbose' => \$cli_options{'verbose'},
1777 'version' => sub {print_version && exit(0)}
1779 $log_level |= $cli_options{'debug'};
1782 sub cli_option_is_set($) {
1785 my $cli_option = shift;
1787 return defined $cli_options{$cli_option};
1790 sub get_cli_option($) {
1793 my $cli_option = shift;
1795 die "Unknown CLI option: $cli_option" unless defined $cli_options{$cli_option};
1797 return $cli_options{$cli_option};
1800 sub init_proxy_settings($) {
1805 if (($choice eq 'fuzz-proxy') and cli_option_is_set('fuzzer-address')) {
1806 $proxy = get_cli_option('fuzzer-address');
1809 if ((not defined $proxy) or ($choice eq 'vanilla-proxy')) {
1811 if (cli_option_is_set('privoxy-address')) {
1812 $proxy .= get_cli_option('privoxy-address');
1817 sub start_forks($) {
1820 log_and_die("Invalid --fork value: " . $forks . ".") if ($forks < 0);
1822 foreach my $fork (1 .. $forks) {
1823 log_message("Starting fork $fork");
1825 if (defined $pid && !$pid) {
1833 init_our_variables();
1834 parse_cli_options();
1835 init_proxy_settings('vanilla-proxy');
1836 load_regression_tests();
1837 init_proxy_settings('fuzz-proxy');
1838 start_forks(get_cli_option('forks')) if cli_option_is_set('forks');
1839 execute_regression_tests();
1846 B<privoxy-regression-test> - A regression test "framework" for Privoxy.
1850 B<privoxy-regression-test> [B<--debug bitmask>] [B<--forks> forks]
1851 [B<--fuzzer-feeding>] [B<--fuzzer-feeding>] [B<--help>] [B<--level level>]
1852 [B<--local-test-file testfile>] [B<--loops count>] [B<--max-level max-level>]
1853 [B<--max-time max-time>] [B<--min-level min-level>] B<--privoxy-address proxy-address>
1854 B<--privoxy-cgi-prefix cgi-prefix> [B<--retries retries>] [B<--test-number test-number>]
1855 [B<--show-skipped-tests>] [B<--sleep-time> seconds] [B<--verbose>]
1860 Privoxy-Regression-Test is supposed to one day become
1861 a regression test suite for Privoxy. It's not quite there
1862 yet, however, and can currently only test header actions,
1863 check the returned status code for requests to arbitrary
1864 URLs and verify which actions are applied to them.
1866 Client header actions are tested by requesting
1867 B<http://p.p/show-request> and checking whether
1868 or not Privoxy modified the original request as expected.
1870 The original request contains both the header the action-to-be-tested
1871 acts upon and an additional tagger-triggering header that enables
1874 Applied actions are checked through B<http://p.p/show-url-info>.
1876 =head1 CONFIGURATION FILE SYNTAX
1878 Privoxy-Regression-Test's configuration is embedded in
1879 Privoxy action files and loaded through Privoxy's web interface.
1881 It makes testing a Privoxy version running on a remote system easier
1882 and should prevent you from updating your tests without updating Privoxy's
1883 configuration accordingly.
1885 A client-header-action test section looks like this:
1887 # Set Header = Referer: http://www.example.org.zwiebelsuppe.exit/
1888 # Expect Header = Referer: http://www.example.org/
1889 {+client-header-filter{hide-tor-exit-notation} -hide-referer}
1890 TAG:^client-header-filter\{hide-tor-exit-notation\}$
1892 The example above causes Privoxy-Regression-Test to set
1893 the header B<Referer: http://www.example.org.zwiebelsuppe.exit/>
1894 and to expect it to be modified to
1895 B<Referer: http://www.example.org/>.
1897 When testing this section, Privoxy-Regression-Test will set the header
1898 B<X-Privoxy-Control: client-header-filter{hide-tor-exit-notation}>
1899 causing the B<privoxy-control> tagger to create the tag
1900 B<client-header-filter{hide-tor-exit-notation}> which will finally
1901 cause Privoxy to enable the action section.
1903 Note that the actions itself are only used by Privoxy,
1904 Privoxy-Regression-Test ignores them and will be happy
1905 as long as the expectations are satisfied.
1907 A fetch test looks like this:
1909 # Fetch Test = http://p.p/user-manual
1910 # Expect Status Code = 302
1912 It tells Privoxy-Regression-Test to request B<http://p.p/user-manual>
1913 and to expect a response with the HTTP status code B<302>. Obviously that's
1914 not a very thorough test and mainly useful to get some code coverage
1915 for Valgrind or to verify that the templates are installed correctly.
1917 If you want to test CGI pages that require a trusted
1918 referer, you can use:
1920 # Trusted CGI Request = http://p.p/edit-actions
1922 It works like ordinary fetch tests, but sets the referer
1923 header to a trusted value.
1925 If no explicit status code expectation is set, B<200> is used.
1927 To verify that a URL is blocked, use:
1929 # Blocked URL = http://www.example.com/blocked
1931 To verify that a specific set of actions is applied to an URL, use:
1933 # Sticky Actions = +block{foo} +handle-as-empty-document -handle-as-image
1934 # URL = http://www.example.org/my-first-url
1936 The sticky actions will be checked for all URLs below it
1937 until the next sticky actions directive.
1939 To verify that requests for a URL get redirected, use:
1941 # Redirected URL = http://www.example.com/redirect-me
1942 # Redirect Destination = http://www.example.org/redirected
1944 To skip a test, add the following line:
1948 The difference between a skipped test and a removed one is that removing
1949 a test affects the numbers of the following tests, while a skipped test
1950 is still loaded and thus keeps the test numbers unchanged.
1952 Sometimes user modifications intentionally conflict with tests in the
1953 default configuration and thus cause test failures. Adding the Ignore
1954 directive to the failing tests works but is inconvenient as the directive
1955 is likely to get lost with the next update.
1957 Overwrite conditions are an alternative and can be added in any action
1958 file as long as the come after the test that is expected to fail.
1959 They cause all previous tests that match the condition to be skipped.
1961 It is recommended to put the overwrite condition below the custom Privoxy
1962 section that causes the expected test failure and before the custom test
1963 that verifies that tests the now expected behaviour. Example:
1965 # The following section is expected to overwrite a section in
1966 # default.action, whose effect is being tested. Thus also disable
1967 # the test that is now expected to fail and add a new one.
1969 {+block{Facebook makes Firefox even more unstable. Do not want.}}
1970 # Overwrite condition = http://apps.facebook.com/onthefarm/track.php?creative=&cat=friendvisit&subcat=weeds&key=a789a971dc687bee4c20c044834fabdd&next=index.php%3Fref%3Dnotif%26visitId%3D898835505
1971 # Blocked URL = http://apps.facebook.com/
1976 All tests have test levels to let the user
1977 control which ones to execute (see I<OPTIONS> below).
1978 Test levels are either set with the B<Level> directive,
1979 or implicitly through the test type.
1981 Redirect tests default to level 108, block tests to level 7,
1982 fetch tests to level 6, "Sticky Actions" tests default to
1983 level 5, tests for trusted CGI requests to level 3 and
1984 client-header-action tests to level 1.
1986 The current redirect test level is above the default
1987 max-level value as failed tests will result in outgoing
1988 connections. Use the B<--max-level> option to run them
1991 The "Default level offset" directive can be used to change
1992 the default level by a given value. This directive affects
1993 all tests located after it until the end of the file or a another
1994 "Default level offset" directive is reached. The purpose of this
1995 directive is to make it more convenient to skip similar tests in
1996 a given file without having to remove or disable the tests completely.
2000 B<--debug bitmask> Add the bitmask provided as integer
2001 to the debug settings.
2003 B<--forks forks> Number of forks to start before executing
2004 the regression tests. This is mainly useful for stress-testing.
2006 B<--fuzzer-address> Listening address used when executing
2007 the regression tests. Useful to make sure that the requests
2008 to load the regression tests don't fail due to fuzzing.
2010 B<--fuzzer-feeding> Ignore some errors that would otherwise
2011 cause Privoxy-Regression-Test to abort the test because
2012 they shouldn't happen in normal operation. This option is
2013 intended to be used if Privoxy-Regression-Test is only
2014 used to feed a fuzzer in which case there's a high chance
2015 that Privoxy gets an invalid request and returns an error
2018 B<--help> Shows available command line options.
2020 B<--header-fuzzing> Modifies linear white space in
2021 headers in a way that should not affect the test result.
2023 B<--level level> Only execute tests with the specified B<level>.
2025 B<--local-test-file test-file> Do not get the tests
2026 through Privoxy's web interface, but use a single local
2027 file. Not recommended for testing Privoxy, but can be useful
2028 to "misappropriate" Privoxy-Regression-Test to test other
2029 stuff, like webserver configurations.
2031 B<--loop count> Loop through the regression tests B<count> times.
2032 Useful to feed a fuzzer, or when doing stress tests with
2033 several Privoxy-Regression-Test instances running at the same
2036 B<--max-level max-level> Only execute tests with a B<level>
2037 below or equal to the numerical B<max-level>.
2039 B<--max-time max-time> Give Privoxy B<max-time> seconds
2040 to return data. Increasing the default may make sense when
2041 Privoxy is run through Valgrind, decreasing the default may
2042 make sense when Privoxy-Regression-Test is used to feed
2045 B<--min-level min-level> Only execute tests with a B<level>
2046 above or equal to the numerical B<min-level>.
2048 B<--privoxy-address proxy-address> Privoxy's listening address.
2049 If it's not set, the value of the environment variable http_proxy
2050 will be used unless the variable isn't set in which case
2051 http://127.0.0.1:8118/ will be used. B<proxy-address> has to
2052 be specified in http_proxy syntax.
2054 B<--privoxy-cgi-prefix privoxy-cgi-prefix> The prefix to use when
2055 building URLs that are supposed to reach Privoxy's CGI interface.
2056 If it's not set, B<http://p.p/> is used, which is supposed to work
2057 with the default Privoxy configuration.
2058 If Privoxy has been built with B<FEATURE_HTTPS_INSPECTION> enabled,
2059 and if https inspection is activated with the B<+https-inspection>
2060 action, this option can be used with
2061 B<https://p.p/> provided the system running Privoxy-Regression-Test
2062 has been configured to trust the certificate used by Privoxy.
2063 Note that there are currently two tests in the official
2064 B<regression-tests.action> file that are expected to fail when
2065 using a B<privoxy-cgi-prefix> with B<https://> and aren't automatically
2068 B<--retries retries> Retry B<retries> times.
2070 B<--test-number test-number> Only run the test with the specified
2073 B<--show-skipped-tests> Log skipped tests even if verbose mode is off.
2075 B<--shuffle-tests> Shuffle test sections and their tests before
2076 executing them. When combined with B<--forks>, this can increase
2077 the chances of detecting race conditions. Of course some problems
2078 are easier to detect without this option.
2080 B<--sleep-time seconds> Wait B<seconds> between tests. Useful when
2081 debugging issues with systems that don't log with millisecond precision.
2083 B<--verbose> Log successful tests as well. By default only
2084 the failures are logged.
2086 B<--version> Print version and exit.
2088 The second dash is optional, options can be shortened,
2089 as long as there are no ambiguities.
2091 =head1 PRIVOXY CONFIGURATION
2093 Privoxy-Regression-Test is shipped with B<regression-tests.action>
2094 which aims to test all official client-header modifying actions
2095 and can be used to verify that the templates and the user manual
2096 files are installed correctly.
2098 To use it, it has to be copied in Privoxy's configuration
2099 directory, and afterwards referenced in Privoxy's configuration
2102 actionsfile regression-tests.action
2104 In general, its tests are supposed to work without changing
2105 any other action files, unless you already added lots of
2106 taggers yourself. If you are using taggers that cause problems,
2107 you might have to temporary disable them for Privoxy's CGI pages.
2109 Some of the regression tests rely on Privoxy features that
2110 may be disabled in your configuration. Tests with a level below
2111 7 are supposed to work with all Privoxy configurations (provided
2112 you didn't build with FEATURE_GRACEFUL_TERMINATION).
2114 Tests with level 9 require Privoxy to deliver the User Manual,
2115 tests with level 12 require the CGI editor to be enabled.
2119 Expect the configuration file syntax to change with future releases.
2123 As Privoxy's B<show-request> page only shows client headers,
2124 Privoxy-Regression-Test can't use it to test Privoxy actions
2125 that modify server headers.
2127 As Privoxy-Regression-Test relies on Privoxy's tag feature to
2128 control the actions to test, it currently only works with
2129 Privoxy 3.0.7 or later.
2131 At the moment Privoxy-Regression-Test fetches Privoxy's
2132 configuration page through I<curl>(1), therefore you have to
2133 have I<curl> installed, otherwise you won't be able to run
2134 Privoxy-Regression-Test in a meaningful way.
2142 Fabian Keil <fk@fabiankeil.de>