1 #! /usr/bin/perl 2 ##--------------------------------------------------------------------## 3 ##--- Valgrind regression testing script vg_regtest ---## 4 ##--------------------------------------------------------------------## 5 6 # This file is part of Valgrind, a dynamic binary instrumentation 7 # framework. 8 # 9 # Copyright (C) 2003 Nicholas Nethercote 10 # njn (at] valgrind.org 11 # 12 # This program is free software; you can redistribute it and/or 13 # modify it under the terms of the GNU General Public License as 14 # published by the Free Software Foundation; either version 2 of the 15 # License, or (at your option) any later version. 16 # 17 # This program is distributed in the hope that it will be useful, but 18 # WITHOUT ANY WARRANTY; without even the implied warranty of 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 # General Public License for more details. 21 # 22 # You should have received a copy of the GNU General Public License 23 # along with this program; if not, write to the Free Software 24 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 25 # 02111-1307, USA. 26 # 27 # The GNU General Public License is contained in the file COPYING. 28 29 #---------------------------------------------------------------------------- 30 # usage: vg_regtest [options] <dirs | files> 31 # 32 # Options: 33 # --all: run tests in all subdirs 34 # --valgrind: valgrind launcher to use. Default is ./coregrind/valgrind. 35 # (This option should probably only be used in conjunction with 36 # --valgrind-lib.) 37 # --valgrind-lib: valgrind libraries to use. Default is $tests_dir/.in_place. 38 # (This option should probably only be used in conjunction with 39 # --valgrind.) 40 # 41 # The easiest way is to run all tests in valgrind/ with (assuming you installed 42 # in $PREFIX): 43 # 44 # $PREFIX/bin/vg_regtest --all 45 # 46 # You can specify individual files to test, or whole directories, or both. 47 # Directories are traversed recursively, except for ones named, for example, 48 # CVS/ or docs/. 49 # 50 # Each test is defined in a file <test>.vgtest, containing one or more of the 51 # following lines, in any order: 52 # - prog: <prog to run> (compulsory) 53 # - args: <args for prog> (default: none) 54 # - vgopts: <Valgrind options> (default: none; 55 # multiple are allowed) 56 # - stdout_filter: <filter to run stdout through> (default: none) 57 # - stderr_filter: <filter to run stderr through> (default: ./filter_stderr) 58 # - prereq: <prerequisite command> (default: none) 59 # - post: <post-test check command> (default: none) 60 # - cleanup: <post-test cleanup cmd> (default: none) 61 # 62 # Note that filters are necessary for stderr results to filter out things that 63 # always change, eg. process id numbers. 64 # 65 # Expected stdout (filtered) is kept in <test>.stdout.exp* (can be more 66 # than one expected output). It can be missing if it would be empty. Expected 67 # stderr (filtered) is kept in <test>.stderr.exp*. There must be at least 68 # one stderr.exp* file. Any .exp* file that ends in '~' or '#' is ignored; 69 # this is because Emacs creates temporary files of these names. 70 # 71 # If results don't match, the output can be found in <test>.std<strm>.out, 72 # and the diff between expected and actual in <test>.std<strm>.diff*. 73 # 74 # The prerequisite command, if present, works like this: 75 # - if it returns 0 the test is run 76 # - if it returns 1 the test is skipped 77 # - if it returns anything else the script aborts. 78 # The idea here is results other than 0 or 1 are likely to be due to 79 # problems with the commands, and you don't want to conflate them with the 1 80 # case, which would happen if you just tested for zero or non-zero. 81 # 82 # The post-test command, if present, must return 0 and its stdout must match 83 # the expected stdout which is kept in <test>.post.exp*. 84 # 85 # Sometimes it is useful to run all the tests at a high sanity check 86 # level or with arbitrary other flags. To make this simple, extra 87 # options, applied to all tests run, are read from $EXTRA_REGTEST_OPTS, 88 # and handed to valgrind prior to any other flags specified by the 89 # .vgtest file. 90 # 91 # Some more notes on adding regression tests for a new tool are in 92 # docs/xml/manual-writing-tools.xml. 93 #---------------------------------------------------------------------------- 94 95 use warnings; 96 use strict; 97 98 #---------------------------------------------------------------------------- 99 # Global vars 100 #---------------------------------------------------------------------------- 101 my $usage="\n" 102 . "Usage:\n" 103 . " vg_regtest [--all, --valgrind, --valgrind-lib]\n" 104 . " Use EXTRA_REGTEST_OPTS to supply extra args for all tests\n" 105 . "\n"; 106 107 my $tmp="vg_regtest.tmp.$$"; 108 109 # Test variables 110 my $vgopts; # valgrind options 111 my $prog; # test prog 112 my $args; # test prog args 113 my $stdout_filter; # filter program to run stdout results file through 114 my $stderr_filter; # filter program to run stderr results file through 115 my $prereq; # prerequisite test to satisfy before running test 116 my $post; # check command after running test 117 my $cleanup; # cleanup command to run 118 119 my @failures; # List of failed tests 120 121 my $num_tests_done = 0; 122 my %num_failures = (stderr => 0, stdout => 0, post => 0); 123 124 # Default valgrind to use is this build tree's (uninstalled) one 125 my $valgrind = "./coregrind/valgrind"; 126 127 chomp(my $tests_dir = `pwd`); 128 129 my $valgrind_lib = "$tests_dir/.in_place"; 130 131 # default filter is the one named "filter_stderr" in the test's directory 132 my $default_stderr_filter = "filter_stderr"; 133 134 135 #---------------------------------------------------------------------------- 136 # Process command line, setup 137 #---------------------------------------------------------------------------- 138 139 # If $prog is a relative path, it prepends $dir to it. Useful for two reasons: 140 # 141 # 1. Can prepend "." onto programs to avoid trouble with users who don't have 142 # "." in their path (by making $dir = ".") 143 # 2. Can prepend the current dir to make the command absolute to avoid 144 # subsequent trouble when we change directories. 145 # 146 # Also checks the program exists and is executable. 147 sub validate_program ($$$$) 148 { 149 my ($dir, $prog, $must_exist, $must_be_executable) = @_; 150 151 # If absolute path, leave it alone. If relative, make it 152 # absolute -- by prepending current dir -- so we can change 153 # dirs and still use it. 154 $prog = "$dir/$prog" if ($prog !~ /^\//); 155 if ($must_exist) { 156 (-f $prog) or die "vg_regtest: `$prog' not found or not a file ($dir)\n"; 157 } 158 if ($must_be_executable) { 159 (-x $prog) or die "vg_regtest: `$prog' not executable ($dir)\n"; 160 } 161 162 return $prog; 163 } 164 165 sub process_command_line() 166 { 167 my $alldirs = 0; 168 my @fs; 169 170 for my $arg (@ARGV) { 171 if ($arg =~ /^-/) { 172 if ($arg =~ /^--all$/) { 173 $alldirs = 1; 174 } elsif ($arg =~ /^--valgrind=(.*)$/) { 175 $valgrind = $1; 176 } elsif ($arg =~ /^--valgrind-lib=(.*)$/) { 177 $valgrind_lib = $1; 178 } else { 179 die $usage; 180 } 181 } else { 182 push(@fs, $arg); 183 } 184 } 185 $valgrind = validate_program($tests_dir, $valgrind, 1, 0); 186 187 if ($alldirs) { 188 @fs = (); 189 foreach my $f (glob "*") { 190 push(@fs, $f) if (-d $f); 191 } 192 } 193 194 (0 != @fs) or die "No test files or directories specified\n"; 195 196 return @fs; 197 } 198 199 #---------------------------------------------------------------------------- 200 # Read a .vgtest file 201 #---------------------------------------------------------------------------- 202 sub read_vgtest_file($) 203 { 204 my ($f) = @_; 205 206 # Defaults. 207 ($vgopts, $prog, $args) = ("", undef, ""); 208 ($stdout_filter, $stderr_filter) = (undef, undef); 209 ($prereq, $post, $cleanup) = (undef, undef, undef); 210 211 # Every test directory must have a "filter_stderr" 212 $stderr_filter = validate_program(".", $default_stderr_filter, 1, 1); 213 214 open(INPUTFILE, "< $f") || die "File $f not openable\n"; 215 216 while (my $line = <INPUTFILE>) { 217 if ($line =~ /^\s*#/ || $line =~ /^\s*$/) { 218 next; 219 } elsif ($line =~ /^\s*vgopts:\s*(.*)$/) { 220 my $addvgopts = $1; 221 $addvgopts =~ s/\${PWD}/$ENV{PWD}/g; 222 $vgopts = $vgopts . " " . $addvgopts; # Nb: Make sure there's a space! 223 } elsif ($line =~ /^\s*prog:\s*(.*)$/) { 224 $prog = validate_program(".", $1, 0, 0); 225 } elsif ($line =~ /^\s*args:\s*(.*)$/) { 226 $args = $1; 227 } elsif ($line =~ /^\s*stdout_filter:\s*(.*)$/) { 228 $stdout_filter = validate_program(".", $1, 1, 1); 229 } elsif ($line =~ /^\s*stderr_filter:\s*(.*)$/) { 230 $stderr_filter = validate_program(".", $1, 1, 1); 231 } elsif ($line =~ /^\s*prereq:\s*(.*)$/) { 232 $prereq = $1; 233 } elsif ($line =~ /^\s*post:\s*(.*)$/) { 234 $post = $1; 235 } elsif ($line =~ /^\s*cleanup:\s*(.*)$/) { 236 $cleanup = $1; 237 } else { 238 die "Bad line in $f: $line\n"; 239 } 240 } 241 close(INPUTFILE); 242 243 if (!defined $prog) { 244 $prog = ""; # allow no prog for testing error and --help cases 245 } 246 } 247 248 #---------------------------------------------------------------------------- 249 # Do one test 250 #---------------------------------------------------------------------------- 251 # Since most of the program time is spent in system() calls, need this to 252 # propagate a Ctrl-C enabling us to quit. 253 sub mysystem($) 254 { 255 my $exit_code = system($_[0]); 256 ($exit_code == 2) and exit 1; # 2 is SIGINT 257 return $exit_code; 258 } 259 260 # from a directory name like "/foo/cachesim/tests/" determine the tool name 261 sub determine_tool() 262 { 263 my $dir = `pwd`; 264 $dir =~ /.*\/([^\/]+)\/tests.*/; # foo/tool_name/tests/foo 265 return $1; 266 } 267 268 # Compare output against expected output; it should match at least one of 269 # them. 270 sub do_diffs($$$$) 271 { 272 my ($fullname, $name, $mid, $f_exps) = @_; 273 274 for my $f_exp (@$f_exps) { 275 (-r $f_exp) or die "Could not read `$f_exp'\n"; 276 277 # Emacs produces temporary files that end in '~' and '#'. We ignore 278 # these. 279 if ($f_exp !~ /[~#]$/) { 280 # $n is the (optional) suffix after the ".exp"; we tack it onto 281 # the ".diff" file. 282 my $n = ""; 283 if ($f_exp =~ /.*\.exp(.*)$/) { 284 $n = $1; 285 } else { 286 $n = ""; 287 ($f_exp eq "/dev/null") or die "Unexpected .exp file: $f_exp\n"; 288 } 289 290 mysystem("diff -u $f_exp $name.$mid.out > $name.$mid.diff$n"); 291 292 if (not -s "$name.$mid.diff$n") { 293 # A match; remove .out and any previously created .diff files. 294 unlink("$name.$mid.out"); 295 unlink(<$name.$mid.diff*>); 296 return; 297 } 298 } 299 } 300 # If we reach here, none of the .exp files matched. 301 print "*** $name failed ($mid) ***\n"; 302 push(@failures, sprintf("%-40s ($mid)", "$fullname")); 303 $num_failures{$mid}++; 304 } 305 306 sub do_one_test($$) 307 { 308 my ($dir, $vgtest) = @_; 309 $vgtest =~ /^(.*)\.vgtest/; 310 my $name = $1; 311 my $fullname = "$dir/$name"; 312 313 # Pull any extra options (for example, --sanity-level=4) 314 # from $EXTRA_REGTEST_OPTS. 315 my $maybe_extraopts = $ENV{"EXTRA_REGTEST_OPTS"}; 316 my $extraopts = $maybe_extraopts ? $maybe_extraopts : ""; 317 318 read_vgtest_file($vgtest); 319 320 if (defined $prereq) { 321 my $prereq_res = system("$prereq"); 322 if (0 == $prereq_res) { 323 # Do nothing (ie. continue with the test) 324 } elsif (256 == $prereq_res) { 325 # Nb: weird Perl-ism -- exit code of '1' is seen by Perl as 256... 326 # Prereq failed, skip. 327 printf("%-16s (skipping, prereq failed: $prereq)\n", "$name:"); 328 return; 329 } else { 330 # Bad prereq; abort. 331 $prereq_res /= 256; 332 die "prereq returned $prereq_res: $prereq\n"; 333 } 334 } 335 336 printf("%-16s valgrind $extraopts $vgopts $prog $args\n", "$name:"); 337 338 # Pass the appropriate --tool option for the directory (can be overridden 339 # by an "args:" line, though). Set both VALGRIND_LIB and 340 # VALGRIND_LIB_INNER in case this Valgrind was configured with 341 # --enable-inner. 342 my $tool=determine_tool(); 343 mysystem("VALGRIND_LIB=$valgrind_lib VALGRIND_LIB_INNER=$valgrind_lib " 344 . "$valgrind --command-line-only=yes --memcheck:leak-check=no " 345 . "--tool=$tool $extraopts $vgopts " 346 . "$prog $args > $name.stdout.out 2> $name.stderr.out"); 347 348 # Filter stdout 349 if (defined $stdout_filter) { 350 mysystem("$stdout_filter < $name.stdout.out > $tmp"); 351 rename($tmp, "$name.stdout.out"); 352 } 353 # Find all the .stdout.exp files. If none, use /dev/null. 354 my @stdout_exps = <$name.stdout.exp*>; 355 @stdout_exps = ( "/dev/null" ) if (0 == scalar @stdout_exps); 356 do_diffs($fullname, $name, "stdout", \@stdout_exps); 357 358 # Filter stderr 359 mysystem("$stderr_filter < $name.stderr.out > $tmp"); 360 rename($tmp, "$name.stderr.out"); 361 # Find all the .stderr.exp files. At least one must exist. 362 my @stderr_exps = <$name.stderr.exp*>; 363 (0 != scalar @stderr_exps) or die "Could not find `$name.stderr.exp*'\n"; 364 do_diffs($fullname, $name, "stderr", \@stderr_exps); 365 366 # Maybe do post-test check 367 if (defined $post) { 368 if (mysystem("$post > $name.post.out") != 0) { 369 print("post check failed: $post\n"); 370 $num_failures{"post"}++; 371 } else { 372 # Find all the .post.exp files. If none, use /dev/null. 373 my @post_exps = <$name.post.exp*>; 374 @post_exps = ( "/dev/null" ) if (0 == scalar @post_exps); 375 do_diffs($fullname, $name, "post", \@post_exps); 376 } 377 } 378 379 if (defined $cleanup) { 380 (system("$cleanup") == 0) or 381 print("(cleanup operation failed: $cleanup)\n"); 382 } 383 384 $num_tests_done++; 385 } 386 387 #---------------------------------------------------------------------------- 388 # Test one directory (and any subdirs) 389 #---------------------------------------------------------------------------- 390 sub test_one_dir($$); # forward declaration 391 392 sub test_one_dir($$) 393 { 394 my ($dir, $prev_dirs) = @_; 395 $dir =~ s/\/$//; # trim a trailing '/' 396 397 # Ignore dirs into which we should not recurse. 398 if ($dir =~ /^(BitKeeper|CVS|SCCS|docs|doc)$/) { return; } 399 400 (-x "$tests_dir/tests/arch_test") or die 401 "vg_regtest: 'arch_test' is missing. Did you forget to 'make check'?\n"; 402 403 # Ignore any dir whose name matches that of an architecture which is not 404 # the architecture we are running on. Eg. when running on x86, ignore 405 # ppc/ directories ('arch_test' returns 1 for this case). Likewise for 406 # the OS and platform. 407 # Nb: weird Perl-ism -- exit code of '1' is seen by Perl as 256... 408 if (256 == system("$tests_dir/tests/arch_test $dir")) { return; } 409 if (256 == system("$tests_dir/tests/os_test $dir")) { return; } 410 if ($dir =~ /(\w+)-(\w+)/ && 411 256 == system("sh $tests_dir/tests/platform_test $1 $2")) { return; } 412 413 chdir($dir) or die "Could not change into $dir\n"; 414 415 # Nb: Don't prepend a '/' to the base directory 416 my $full_dir = $prev_dirs . ($prev_dirs eq "" ? "" : "/") . $dir; 417 my $dashes = "-" x (50 - length $full_dir); 418 419 my @fs = glob "*"; 420 my $found_tests = (0 != (grep { $_ =~ /\.vgtest$/ } @fs)); 421 422 if ($found_tests) { 423 print "-- Running tests in $full_dir $dashes\n"; 424 } 425 foreach my $f (@fs) { 426 if (-d $f) { 427 test_one_dir($f, $full_dir); 428 } elsif ($f =~ /\.vgtest$/) { 429 do_one_test($full_dir, $f); 430 } 431 } 432 if ($found_tests) { 433 print "-- Finished tests in $full_dir $dashes\n"; 434 } 435 436 chdir(".."); 437 } 438 439 #---------------------------------------------------------------------------- 440 # Summarise results 441 #---------------------------------------------------------------------------- 442 sub plural($) 443 { 444 return ( $_[0] == 1 ? "" : "s" ); 445 } 446 447 sub summarise_results 448 { 449 my $x = ( $num_tests_done == 1 ? "test" : "tests" ); 450 451 printf("\n== %d test%s, %d stderr failure%s, %d stdout failure%s, " 452 . "%d post failure%s ==\n", 453 $num_tests_done, plural($num_tests_done), 454 $num_failures{"stderr"}, plural($num_failures{"stderr"}), 455 $num_failures{"stdout"}, plural($num_failures{"stdout"}), 456 $num_failures{"post"}, plural($num_failures{"post"})); 457 458 foreach my $failure (@failures) { 459 print "$failure\n"; 460 } 461 print "\n"; 462 } 463 464 #---------------------------------------------------------------------------- 465 # main(), sort of 466 #---------------------------------------------------------------------------- 467 sub warn_about_EXTRA_REGTEST_OPTS() 468 { 469 print "WARNING: \$EXTRA_REGTEST_OPTS is set. You probably don't want\n"; 470 print "to run the regression tests with it set, unless you are doing some\n"; 471 print "strange experiment, and/or you really know what you are doing.\n"; 472 print "\n"; 473 } 474 475 # nuke VALGRIND_OPTS 476 $ENV{"VALGRIND_OPTS"} = ""; 477 478 if ($ENV{"EXTRA_REGTEST_OPTS"}) { 479 print "\n"; 480 warn_about_EXTRA_REGTEST_OPTS(); 481 } 482 483 my @fs = process_command_line(); 484 foreach my $f (@fs) { 485 if (-d $f) { 486 test_one_dir($f, ""); 487 } else { 488 # Allow the .vgtest suffix to be given or omitted 489 if ($f =~ /.vgtest$/ && -r $f) { 490 # do nothing 491 } elsif (-r "$f.vgtest") { 492 $f = "$f.vgtest"; 493 } else { 494 die "`$f' neither a directory nor a readable test file/name\n" 495 } 496 my $dir = `dirname $f`; chomp $dir; 497 my $file = `basename $f`; chomp $file; 498 chdir($dir) or die "Could not change into $dir\n"; 499 do_one_test($dir, $file); 500 chdir($tests_dir); 501 } 502 } 503 summarise_results(); 504 505 if ($ENV{"EXTRA_REGTEST_OPTS"}) { 506 warn_about_EXTRA_REGTEST_OPTS(); 507 } 508 509 if (0 == $num_failures{"stdout"} && 510 0 == $num_failures{"stderr"} && 511 0 == $num_failures{"post"}) { 512 exit 0; 513 } else { 514 exit 1; 515 } 516 517 ##--------------------------------------------------------------------## 518 ##--- end vg_regtest ---## 519 ##--------------------------------------------------------------------## 520