1#!/usr/bin/perl 2# $OpenBSD: th,v 1.2 2017/10/23 17:11:07 anton Exp $ 3 4 5# 6# Test harness for pdksh tests. 7# 8# Example test: 9# name: a-test 10# description: 11# a test to show how tests are done 12# arguments: !-x!-f! 13# stdin: 14# echo -n * 15# false 16# expected-stdout: ! 17# * 18# expected-stderr: 19# + echo -n * 20# + false 21# expected-exit: 1 22# --- 23# This runs the test-program (eg, pdksh) with the arguments -x and -f, 24# standard input is a file containing "echo hi*\nfalse\n". The program 25# is expected to produce "hi*" (no trailing newline) on standard output, 26# "+ echo hi*\n+false\n" on standard error, and an exit code of 1. 27# 28# 29# Format of test files: 30# - blank lines and lines starting with # are ignored 31# - a test file contains a series of tests 32# - a test is a series of tag:value pairs ended with a "---" line 33# (leading/trailing spaces are stripped from the first line of value) 34# - test tags are: 35# Tag Flag Description 36# ----- ---- ----------- 37# name r The name of the test; should be unique 38# description m What test does 39# arguments M Arguments to pass to the program; 40# default is no arguments. 41# script m Value is written to a file which 42# is passed as an argument to the program 43# (after the arguments arguments) 44# stdin m Value is written to a file which is 45# used as standard-input for the program; 46# default is to use /dev/null. 47# perl-setup m Value is a perl script which is executed 48# just before the test is run. Try to 49# avoid using this... 50# perl-cleanup m Value is a perl script which is executed 51# just after the test is run. Try to 52# avoid using this... 53# env-setup M Value is a list of NAME=VALUE elements 54# which are put in the environment before 55# the test is run. If the =VALUE is 56# missing, NAME is removed from the 57# environment. Programs are run with 58# the following minimal environment: 59# USER, LOGNAME, HOME, PATH, SHELL, 60# MALLOC_OPTIONS=S 61# (values from the current environment 62# takes higher precedence). 63# file-setup mps Used to create files, directories 64# and symlinks. First word is either 65# file, dir or symlink; second word is 66# permissions; this is followed by a 67# quoted word that is the name of the 68# file; the end-quote should be followed 69# by a newline, then the file data 70# (if any). The first word may be 71# preceded by a ! to strip the trailing 72# newline in a symlink. 73# file-result mps Used to verify a file, symlink or 74# directory is created correctly. 75# The first word is either 76# file, dir or symlink; second word is 77# expected permissions; third word 78# is user-id; fourth is group-id; 79# fifth is "exact" or "pattern" 80# indicating whether the file contents 81# which follow is to be matched exactly 82# or if it is a regular expression. 83# The fifth argument is the quoted name 84# of the file that should be created. 85# The end-quote should be followed 86# by a newline, then the file data 87# (if any). The first word may be 88# preceded by a ! to strip the trailing 89# newline in the file contents. 90# The permissions, user and group fields 91# may be * meaning accept any value. 92# time-limit Time limit - the program is sent a 93# SIGKILL N seconds. Default is no 94# limit. 95# expected-fail `yes' if the test is expected to fail. 96# expected-exit expected exit code. Can be a number, 97# or a C expression using the variables 98# e, s and w (exit code, termination 99# signal, and status code). 100# expected-stdout m What the test should generate on stdout; 101# default is to expect no output. 102# expected-stdout-pattern m A perl pattern which matches the 103# expected output. 104# expected-stderr m What the test should generate on stderr; 105# default is to expect no output. 106# expected-stderr-pattern m A perl pattern which matches the 107# expected standard error. 108# category m Specify a comma separated list of 109# `categories' of program that the test 110# is to be run for. A category can be 111# negated by prefixing the name with a !. 112# The idea is that some tests in a 113# test suite may apply to a particular 114# program version and shouldn't be run 115# on other versions. The category(s) of 116# the program being tested can be 117# specified on the command line. 118# One category os:XXX is predefined 119# (XXX is the operating system name, 120# eg, linux, dec_osf). 121# Flag meanings: 122# r tag is required (eg, a test must have a name tag). 123# m value can be multiple lines. Lines must be prefixed with 124# a tab. If the value part of the initial tag:value line is 125# - empty: the initial blank line is stripped. 126# - a lone !: the last newline in the value is stripped; 127# M value can be multiple lines (prefixed by a tab) and consists 128# of multiple fields, delimited by a field separator character. 129# The value must start and end with the f-s-c. 130# p tag takes parameters (used with m). 131# s tag can be used several times. 132# 133 134use POSIX qw(EINTR); 135use Getopt::Std; 136use File::Temp qw/ :mktemp /; 137 138$os = defined $^O ? $^O : 'unknown'; 139 140($prog = $0) =~ s#.*/##; 141 142$Usage = <<EOF ; 143Usage: $prog [-s test-set] [-C category] [-p prog] [-v] [-e e=v] test-name ... 144 -p p Use p as the program to test 145 -C c Specify the comma separated list of categories the program 146 belongs to (see category field). 147 -s s Read tests from file s; if s is a directory, it is recursively 148 scaned for test files (which end in .t). 149 -t t Use t as default time limit for tests (default is unlimited) 150 -T dir Use dir instead of /tmp to hold temporary files 151 -P program (-p) string has multiple words, and the program is in 152 the path (kludge option) 153 -v Verbose mode: print reason test failed. 154 -e e=v Set the environment variable e to v for all tests 155 (if no =v is given, the current value is used) 156 test-name(s) specifies the name of the test(s) to run; if none are 157 specified, all tests are run. 158EOF 159 160# 161# See comment above for flag meanings 162# 163%test_fields = ( 164 'name', 'r', 165 'description', 'm', 166 'arguments', 'M', 167 'script', 'm', 168 'stdin', 'm', 169 'perl-setup', 'm', 170 'perl-cleanup', 'm', 171 'env-setup', 'M', 172 'file-setup', 'mps', 173 'file-result', 'mps', 174 'time-limit', '', 175 'expected-fail', '', 176 'expected-exit', '', 177 'expected-stdout', 'm', 178 'expected-stdout-pattern', 'm', 179 'expected-stderr', 'm', 180 'expected-stderr-pattern', 'm', 181 'category', 'm', 182 ); 183# Filled in by read_test() 184%internal_test_fields = ( 185 ':full-name', 1, # file:name 186 ':long-name', 1, # dir/file:lineno:name 187 ); 188 189# Categories of the program under test. Provide the current 190# os by default. 191%categories = ( 192# (defined $^O ? "os:$^O" : "os:unknown"), '1' 193 "os:$os", '1' 194 ); 195 196$nfailed = 0; 197$nxfailed = 0; 198$npassed = 0; 199$nxpassed = 0; 200 201%known_tests = (); 202 203if (!getopts('C:p:Ps:t:T:ve:')) { 204 print STDERR $Usage; 205 exit 1; 206} 207 208die "$prog: no program specified (use -p)\n" if !defined $opt_p; 209die "$prog: no test set specified (use -s)\n" if !defined $opt_s; 210$test_prog = $opt_p; 211$verbose = defined $opt_v && $opt_v; 212$test_set = $opt_s; 213$temp_dir = $opt_T || "/tmp"; 214if (defined $opt_t) { 215 die "$prog: bad -t argument (should be number > 0): $opt_t\n" 216 if $opt_t !~ /^\d+$/ || $opt_t <= 0; 217 $default_time_limit = $opt_t; 218} 219$program_kludge = defined $opt_P ? $opt_P : 0; 220 221if (defined $opt_C) { 222 foreach $c (split(',', $opt_C)) { 223 $c =~ s/\s+//; 224 die "$prog: categories can't be negated on the command line\n" 225 if ($c =~ /^!/); 226 $categories{$c} = 1; 227 } 228} 229 230# Note which tests are to be run. 231%do_test = (); 232grep($do_test{$_} = 1, @ARGV); 233$all_tests = @ARGV == 0; 234 235# Set up a very minimal environment 236%new_env = ( 237 MALLOC_OPTIONS => 'S', 238); 239foreach $env (('USER', 'LOGNAME', 'HOME', 'PATH', 'SHELL', 'MALLOC_OPTIONS')) { 240 $new_env{$env} = $ENV{$env} if defined $ENV{$env}; 241} 242if (defined $opt_e) { 243 # XXX need a way to allow many -e arguments... 244 if ($opt_e =~ /^([a-zA-Z_]\w*)(|=(.*))$/) { 245 $new_env{$1} = $2 eq '' ? $ENV{$1} : $3; 246 } else { 247 die "$0: bad -e argument: $opt_e\n"; 248 } 249} 250%old_env = %ENV; 251 252# The following doesn't work with perl5... Need to do it explicitly - yuck. 253#%ENV = %new_env; 254foreach $k (keys(%ENV)) { 255 delete $ENV{$k}; 256} 257$ENV{$k} = $v while ($k,$v) = each %new_env; 258 259chop($pwd = `pwd 2> /dev/null`); 260die "$prog: couldn't get current working directory\n" if $pwd eq ''; 261die "$prog: couldn't cd to $pwd - $!\n" if !chdir($pwd); 262 263if (!$program_kludge) { 264 $test_prog = "$pwd/$test_prog" if substr($test_prog, 0, 1) ne '/'; 265 die "$prog: $test_prog is not executable - bye\n" 266 if (! -x $test_prog && $os ne 'os2'); 267} 268 269@trap_sigs = ('TERM', 'QUIT', 'INT', 'PIPE', 'HUP'); 270@SIG{@trap_sigs} = ('cleanup_exit') x @trap_sigs; 271$child_kill_ok = 0; 272$SIG{'ALRM'} = 'catch_sigalrm'; 273 274$| = 1; 275 276# Create temp files 277($fh, $temps) = mkstemp("${temp_dir}/rts.XXXXXXXX"); 278close($fh); 279($fh, $tempi) = mkstemp("${temp_dir}/rti.XXXXXXXX"); 280close($fh); 281($fh, $tempo) = mkstemp("${temp_dir}/rto.XXXXXXXX"); 282close($fh); 283($fh, $tempe) = mkstemp("${temp_dir}/rte.XXXXXXXX"); 284close($fh); 285$tempdir = mkdtemp("${temp_dir}/rtd.XXXXXXXX"); 286 287if (-d $test_set) { 288 $file_prefix_skip = length($test_set) + 1; 289 $ret = &process_test_dir($test_set); 290} else { 291 $file_prefix_skip = 0; 292 $ret = &process_test_file($test_set); 293} 294&cleanup_exit() if !defined $ret; 295 296$tot_failed = $nfailed + $nxfailed; 297$tot_passed = $npassed + $nxpassed; 298if ($tot_failed || $tot_passed) { 299 print "Total failed: $tot_failed"; 300 print " ($nxfailed unexpected)" if $nxfailed; 301 print " (as expected)" if $nfailed && !$nxfailed; 302 print "\nTotal passed: $tot_passed"; 303 print " ($nxpassed unexpected)" if $nxpassed; 304 print "\n"; 305} 306 307&cleanup_exit($nxfailed ? '' : 'ok'); 308 309sub 310cleanup_exit 311{ 312 local($sig, $exitcode) = ('', 1); 313 314 if ($_[0] eq 'ok') { 315 $exitcode = 0; 316 } elsif ($_[0] ne '') { 317 $sig = $_[0]; 318 } 319 320 unlink($tempi, $tempo, $tempe, $temps); 321 &scrub_dir($tempdir) if defined $tempdir; 322 rmdir($tempdir) if defined $tempdir; 323 324 if ($sig) { 325 $SIG{$sig} = 'DEFAULT'; 326 kill $sig, $$; 327 return; 328 } 329 exit $exitcode; 330} 331 332sub 333catch_sigalrm 334{ 335 $SIG{'ALRM'} = 'catch_sigalrm'; 336 kill(9, $child_pid) if $child_kill_ok; 337 $child_killed = 1; 338} 339 340sub 341process_test_dir 342{ 343 local($dir) = @_; 344 local($ret, $file); 345 local(@todo) = (); 346 347 if (!opendir(DIR, $dir)) { 348 print STDERR "$prog: can't open directory $dir - $!\n"; 349 return undef; 350 } 351 while (defined ($file = readdir(DIR))) { 352 push(@todo, $file) if $file =~ /^[^.].*\.t$/; 353 } 354 closedir(DIR); 355 356 foreach $file (@todo) { 357 $file = "$dir/$file"; 358 if (-d $file) { 359 $ret = &process_test_dir($file); 360 } elsif (-f _) { 361 $ret = &process_test_file($file); 362 } 363 last if !defined $ret; 364 } 365 366 return $ret; 367} 368 369sub 370process_test_file 371{ 372 local($file) = @_; 373 local($ret); 374 375 if (!open(IN, $file)) { 376 print STDERR "$prog: can't open $file - $!\n"; 377 return undef; 378 } 379 while (1) { 380 $ret = &read_test($file, IN, *test); 381 last if !defined $ret || !$ret; 382 next if !$all_tests && !$do_test{$test{'name'}}; 383 next if !&category_check(*test); 384 $ret = &run_test(*test); 385 last if !defined $ret; 386 } 387 close(IN); 388 389 return $ret; 390} 391 392sub 393run_test 394{ 395 local(*test) = @_; 396 local($name) = $test{':full-name'}; 397 398 #print "Running test $name...\n" if $verbose; 399 400 return undef if !&scrub_dir($tempdir); 401 402 if (defined $test{'stdin'}) { 403 return undef if !&write_file($tempi, $test{'stdin'}); 404 $ifile = $tempi; 405 } else { 406 $ifile = '/dev/null'; 407 } 408 409 if (defined $test{'script'}) { 410 return undef if !&write_file($temps, $test{'script'}); 411 } 412 413 if (!chdir($tempdir)) { 414 print STDERR "$prog: couldn't cd to $tempdir - $!\n"; 415 return undef; 416 } 417 418 if (defined $test{'file-setup'}) { 419 local($i); 420 local($type, $perm, $rest, $c, $len, $name); 421 422 for ($i = 0; $i < $test{'file-setup'}; $i++) { 423 $val = $test{"file-setup:$i"}; 424 # 425 # format is: type perm "name" 426 # 427 ($type, $perm, $rest) = 428 split(' ', $val, 3); 429 $c = substr($rest, 0, 1); 430 $len = index($rest, $c, 1) - 1; 431 $name = substr($rest, 1, $len); 432 $rest = substr($rest, 2 + $len); 433 $perm = oct($perm) if $perm =~ /^\d+$/; 434 if ($type eq 'file') { 435 return undef if !&write_file($name, $rest); 436 if (!chmod($perm, $name)) { 437 print STDERR 438 "$prog:$test{':long-name'}: can't chmod $perm $name - $!\n"; 439 return undef; 440 } 441 } elsif ($type eq 'dir') { 442 if (!mkdir($name, $perm)) { 443 print STDERR 444 "$prog:$test{':long-name'}: can't mkdir $perm $name - $!\n"; 445 return undef; 446 } 447 } elsif ($type eq 'symlink') { 448 local($oumask) = umask($perm); 449 local($ret) = symlink($rest, $name); 450 umask($oumask); 451 if (!$ret) { 452 print STDERR 453 "$prog:$test{':long-name'}: couldn't create symlink $name - $!\n"; 454 return undef; 455 } 456 } 457 } 458 } 459 460 if (defined $test{'perl-setup'}) { 461 eval $test{'perl-setup'}; 462 if ($@ ne '') { 463 print STDERR "$prog:$test{':long-name'}: error running perl-setup - $@\n"; 464 return undef; 465 } 466 } 467 468 $pid = fork; 469 if (!defined $pid) { 470 print STDERR "$prog: can't fork - $!\n"; 471 return undef; 472 } 473 if (!$pid) { 474 @SIG{@trap_sigs} = ('DEFAULT') x @trap_sigs; 475 $SIG{'ALRM'} = 'DEFAULT'; 476 if (defined $test{'env-setup'}) { 477 local($var, $val, $i); 478 479 foreach $var (split(substr($test{'env-setup'}, 0, 1), 480 $test{'env-setup'})) 481 { 482 $i = index($var, '='); 483 next if $i == 0 || $var eq ''; 484 if ($i < 0) { 485 delete $ENV{$var}; 486 } else { 487 $ENV{substr($var, 0, $i)} = substr($var, $i + 1); 488 } 489 } 490 } 491 if (!open(STDIN, "< $ifile")) { 492 print STDERR "$prog: couldn't open $ifile in child - $!\n"; 493 kill('TERM', $$); 494 } 495 if (!open(STDOUT, "> $tempo")) { 496 print STDERR "$prog: couldn't open $tempo in child - $!\n"; 497 kill('TERM', $$); 498 } 499 if (!open(STDERR, "> $tempe")) { 500 print STDOUT "$prog: couldn't open $tempe in child - $!\n"; 501 kill('TERM', $$); 502 } 503 if ($program_kludge) { 504 @argv = split(' ', $test_prog); 505 } else { 506 @argv = ($test_prog); 507 } 508 if (defined $test{'arguments'}) { 509 push(@argv, 510 split(substr($test{'arguments'}, 0, 1), 511 substr($test{'arguments'}, 1))); 512 } 513 push(@argv, $temps) if defined $test{'script'}; 514 exec(@argv); 515 print STDERR "$prog: couldn't execute $test_prog - $!\n"; 516 kill('TERM', $$); 517 exit(95); 518 } 519 $child_pid = $pid; 520 $child_killed = 0; 521 $child_kill_ok = 1; 522 alarm($test{'time-limit'}) if defined $test{'time-limit'}; 523 while (1) { 524 $xpid = waitpid($pid, 0); 525 $child_kill_ok = 0; 526 if ($xpid < 0) { 527 next if $! == EINTR; 528 print STDERR "$prog: error waiting for child - $!\n"; 529 return undef; 530 } 531 last; 532 } 533 $status = $?; 534 alarm(0) if defined $test{'time-limit'}; 535 536 $failed = 0; 537 $why = ''; 538 539 if ($child_killed) { 540 $failed = 1; 541 $why .= "\ttest timed out (limit of $test{'time-limit'} seconds)\n"; 542 } 543 544 $ret = &eval_exit($test{'long-name'}, $status, $test{'expected-exit'}); 545 return undef if !defined $ret; 546 if (!$ret) { 547 local($expl); 548 549 $failed = 1; 550 if (($status & 0xff) == 0x7f) { 551 $expl = "stopped"; 552 } elsif (($status & 0xff)) { 553 $expl = "signal " . ($status & 0x7f); 554 } else { 555 $expl = "exit-code " . (($status >> 8) & 0xff); 556 } 557 $why .= 558 "\tunexpected exit status $status ($expl), expected $test{'expected-exit'}\n"; 559 } 560 561 $tmp = &check_output($test{'long-name'}, $tempo, 'stdout', 562 $test{'expected-stdout'}, $test{'expected-stdout-pattern'}); 563 return undef if !defined $tmp; 564 if ($tmp ne '') { 565 $failed = 1; 566 $why .= $tmp; 567 } 568 569 $tmp = &check_output($test{'long-name'}, $tempe, 'stderr', 570 $test{'expected-stderr'}, $test{'expected-stderr-pattern'}); 571 return undef if !defined $tmp; 572 if ($tmp ne '') { 573 $failed = 1; 574 $why .= $tmp; 575 } 576 577 $tmp = &check_file_result(*test); 578 return undef if !defined $tmp; 579 if ($tmp ne '') { 580 $failed = 1; 581 $why .= $tmp; 582 } 583 584 if (defined $test{'perl-cleanup'}) { 585 eval $test{'perl-cleanup'}; 586 if ($@ ne '') { 587 print STDERR "$prog:$test{':long-name'}: error running perl-cleanup - $@\n"; 588 return undef; 589 } 590 } 591 592 if (!chdir($pwd)) { 593 print STDERR "$prog: couldn't cd to $pwd - $!\n"; 594 return undef; 595 } 596 597 if ($failed) { 598 if (!$test{'expected-fail'}) { 599 print "FAIL $name\n"; 600 $nxfailed++; 601 } else { 602 print "fail $name (as expected)\n"; 603 $nfailed++; 604 } 605 $why = "\tDescription" 606 . &wrap_lines($test{'description'}, " (missing)\n") 607 . $why; 608 } elsif ($test{'expected-fail'}) { 609 print "PASS $name (unexpectedly)\n"; 610 $nxpassed++; 611 } else { 612 print "pass $name\n"; 613 $npassed++; 614 } 615 print $why if $verbose; 616 return 0; 617} 618 619sub 620category_check 621{ 622 local(*test) = @_; 623 local($c); 624 625 return 1 if (!defined $test{'category'}); 626 local($ok) = 0; 627 foreach $c (split(',', $test{'category'})) { 628 $c =~ s/\s+//; 629 if ($c =~ /^!/) { 630 $c = $'; 631 return 0 if (defined $categories{$c}); 632 } else { 633 $ok = 1 if (defined $categories{$c}); 634 } 635 } 636 return $ok; 637} 638 639sub 640scrub_dir 641{ 642 local($dir) = @_; 643 local(@todo) = (); 644 local($file); 645 646 if (!opendir(DIR, $dir)) { 647 print STDERR "$prog: couldn't open directory $dir - $!\n"; 648 return undef; 649 } 650 while (defined ($file = readdir(DIR))) { 651 push(@todo, $file) if $file ne '.' && $file ne '..'; 652 } 653 closedir(DIR); 654 foreach $file (@todo) { 655 $file = "$dir/$file"; 656 if (-d $file) { 657 return undef if !&scrub_dir($file); 658 if (!rmdir($file)) { 659 print STDERR "$prog: couldn't rmdir $file - $!\n"; 660 return undef; 661 } 662 } else { 663 if (!unlink($file)) { 664 print STDERR "$prog: couldn't unlink $file - $!\n"; 665 return undef; 666 } 667 } 668 } 669 return 1; 670} 671 672sub 673write_file 674{ 675 local($file, $str) = @_; 676 677 if (!open(TEMP, "> $file")) { 678 print STDERR "$prog: can't open $file - $!\n"; 679 return undef; 680 } 681 print TEMP $str; 682 if (!close(TEMP)) { 683 print STDERR "$prog: error writing $file - $!\n"; 684 return undef; 685 } 686 return 1; 687} 688 689sub 690check_output 691{ 692 local($name, $file, $what, $expect, $expect_pat) = @_; 693 local($got) = ''; 694 local($why) = ''; 695 local($ret); 696 697 if (!open(TEMP, "< $file")) { 698 print STDERR "$prog:$name($what): couldn't open $file after running program - $!\n"; 699 return undef; 700 } 701 while (<TEMP>) { 702 $got .= $_; 703 } 704 close(TEMP); 705 return compare_output($name, $what, $expect, $expect_pat, $got); 706} 707 708sub 709compare_output 710{ 711 local($name, $what, $expect, $expect_pat, $got) = @_; 712 local($why) = ''; 713 714 if (defined $expect_pat) { 715 $_ = $got; 716 $ret = eval "$expect_pat"; 717 if ($@ ne '') { 718 print STDERR "$prog:$name($what): error evaluating $what pattern: $expect_pat - $@\n"; 719 return undef; 720 } 721 if (!$ret) { 722 $why = "\tunexpected $what - wanted pattern"; 723 $why .= &wrap_lines($expect_pat); 724 $why .= "\tgot"; 725 $why .= &wrap_lines($got); 726 } 727 } else { 728 $expect = '' if !defined $expect; 729 if ($got ne $expect) { 730 $why .= "\tunexpected $what - " . &first_diff($expect, $got) . "\n"; 731 $why .= "\twanted"; 732 $why .= &wrap_lines($expect); 733 $why .= "\tgot"; 734 $why .= &wrap_lines($got); 735 } 736 } 737 return $why; 738} 739 740sub 741wrap_lines 742{ 743 local($str, $empty) = @_; 744 local($nonl) = substr($str, -1, 1) ne "\n"; 745 746 return (defined $empty ? $empty : " nothing\n") if $str eq ''; 747 substr($str, 0, 0) = ":\n"; 748 $str =~ s/\n/\n\t\t/g; 749 if ($nonl) { 750 $str .= "\n\t[incomplete last line]\n"; 751 } else { 752 chop($str); 753 chop($str); 754 } 755 return $str; 756} 757 758sub 759first_diff 760{ 761 local($exp, $got) = @_; 762 local($lineno, $char) = (1, 1); 763 local($i, $exp_len, $got_len); 764 local($ce, $cg); 765 766 $exp_len = length($exp); 767 $got_len = length($got); 768 if ($exp_len != $got_len) { 769 if ($exp_len < $got_len) { 770 if (substr($got, 0, $exp_len) eq $exp) { 771 return "got too much output"; 772 } 773 } elsif (substr($exp, 0, $got_len) eq $got) { 774 return "got too little output"; 775 } 776 } 777 for ($i = 0; $i < $exp_len; $i++) { 778 $ce = substr($exp, $i, 1); 779 $cg = substr($got, $i, 1); 780 last if $ce ne $cg; 781 $char++; 782 if ($ce eq "\n") { 783 $lineno++; 784 $char = 1; 785 } 786 } 787 return "first difference: line $lineno, char $char (wanted '" 788 . &format_char($ce) . "', got '" 789 . &format_char($cg) . "'"; 790} 791 792sub 793format_char 794{ 795 local($ch, $s); 796 797 $ch = ord($_[0]); 798 if ($ch == 10) { 799 return '\n'; 800 } elsif ($ch == 13) { 801 return '\r'; 802 } elsif ($ch == 8) { 803 return '\b'; 804 } elsif ($ch == 9) { 805 return '\t'; 806 } elsif ($ch > 127) { 807 $ch -= 127; 808 $s = "M-"; 809 } else { 810 $s = ''; 811 } 812 if ($ch < 32) { 813 $s .= '^'; 814 $ch += ord('@'); 815 } elsif ($ch == 127) { 816 return $s . "^?"; 817 } 818 return $s . sprintf("%c", $ch); 819} 820 821sub 822eval_exit 823{ 824 local($name, $status, $expect) = @_; 825 local($expr); 826 local($w, $e, $s) = ($status, ($status >> 8) & 0xff, $status & 0x7f); 827 828 $e = -1000 if $status & 0xff; 829 $s = -1000 if $s == 0x7f; 830 if (!defined $expect) { 831 $expr = '$w == 0'; 832 } elsif ($expect =~ /^(|-)\d+$/) { 833 $expr = "\$e == $expect"; 834 } else { 835 $expr = $expect; 836 $expr =~ s/\b([wse])\b/\$$1/g; 837 $expr =~ s/\b(SIG[A-Z0-9]+)\b/&$1/g; 838 } 839 $w = eval $expr; 840 if ($@ ne '') { 841 print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $expect ($@)\n"; 842 return undef; 843 } 844 return $w; 845} 846 847sub 848read_test 849{ 850 local($file, $in, *test) = @_; 851 local($field, $val, $flags, $do_chop, $need_redo, $start_lineno); 852 local(%cnt, $sfield); 853 854 %test = (); 855 %cnt = (); 856 while (<$in>) { 857 next if /^\s*$/; 858 next if /^ *#/; 859 last if /^\s*---\s*$/; 860 $start_lineno = $. if !defined $start_lineno; 861 if (!/^([-\w]+):\s*(|\S|\S.*\S)\s*$/) { 862 print STDERR "$prog:$file:$.: unrecognized line\n"; 863 return undef; 864 } 865 ($field, $val) = ($1, $2); 866 $sfield = $field; 867 $flags = $test_fields{$field}; 868 if (!defined $flags) { 869 print STDERR "$prog:$file:$.: unrecognized field \"$field\"\n"; 870 return undef; 871 } 872 if ($flags =~ /s/) { 873 local($cnt) = $cnt{$field}++; 874 $test{$field} = $cnt{$field}; 875 $cnt = 0 if $cnt eq ''; 876 $sfield .= ":$cnt"; 877 } elsif (defined $test{$field}) { 878 print STDERR "$prog:$file:$.: multiple \"$field\" fields\n"; 879 return undef; 880 } 881 $do_chop = $flags !~ /m/; 882 $need_redo = 0; 883 if ($val eq '' || $val eq '!' || $flags =~ /p/) { 884 if ($flags =~ /[Mm]/) { 885 if ($flags =~ /p/) { 886 if ($val =~ /^!/) { 887 $do_chop = 1; 888 $val = $'; 889 } else { 890 $do_chop = 0; 891 } 892 if ($val eq '') { 893 print STDERR 894 "$prog:$file:$.: no parameters given for field \"$field\"\n"; 895 return undef; 896 } 897 } else { 898 if ($val eq '!') { 899 $do_chop = 1; 900 } 901 $val = ''; 902 } 903 while (<$in>) { 904 last if !/^\t/; 905 $val .= $'; 906 } 907 chop $val if $do_chop; 908 $do_chop = 1; 909 $need_redo = 1; 910 # 911 # Syntax check on fields that can several instances 912 # (can give useful line numbers this way) 913 # 914 if ($field eq 'file-setup') { 915 local($type, $perm, $rest, $c, $len, $name); 916 # 917 # format is: type perm "name" 918 # 919 if ($val !~ /^[ \t]*(\S+)[ \t]+(\S+)[ \t]+([^ \t].*)/) { 920 print STDERR 921 "$prog:$file:$.: bad parameter line for file-setup field\n"; 922 return undef; 923 } 924 ($type, $perm, $rest) = ($1, $2, $3); 925 if ($type !~ /^(file|dir|symlink)$/) { 926 print STDERR 927 "$prog:$file:$.: bad file type for file-setup: $type\n"; 928 return undef; 929 } 930 if ($perm !~ /^\d+$/) { 931 print STDERR 932 "$prog:$file:$.: bad permissions for file-setup: $type\n"; 933 return undef; 934 } 935 $c = substr($rest, 0, 1); 936 if (($len = index($rest, $c, 1) - 1) <= 0) { 937 print STDERR 938 "$prog:$file:$.: missing end quote for file name in file-setup: $rest\n"; 939 return undef; 940 } 941 $name = substr($rest, 1, $len); 942 if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { 943 # Note: this is not a security thing - just a sanity 944 # check - a test can still use symlinks to get at files 945 # outside the test directory. 946 print STDERR 947"$prog:$file:$.: file name in file-setup is absolute or contains ..: $name\n"; 948 return undef; 949 } 950 } 951 if ($field eq 'file-result') { 952 local($type, $perm, $uid, $gid, $matchType, 953 $rest, $c, $len, $name); 954 # 955 # format is: type perm uid gid matchType "name" 956 # 957 if ($val !~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)/) { 958 print STDERR 959 "$prog:$file:$.: bad parameter line for file-result field\n"; 960 return undef; 961 } 962 ($type, $perm, $uid, $gid, $matchType, $rest) 963 = ($1, $2, $3, $4, $5, $6); 964 if ($type !~ /^(file|dir|symlink)$/) { 965 print STDERR 966 "$prog:$file:$.: bad file type for file-result: $type\n"; 967 return undef; 968 } 969 if ($perm !~ /^\d+$/ && $perm ne '*') { 970 print STDERR 971 "$prog:$file:$.: bad permissions for file-result: $perm\n"; 972 return undef; 973 } 974 if ($uid !~ /^\d+$/ && $uid ne '*') { 975 print STDERR 976 "$prog:$file:$.: bad user-id for file-result: $uid\n"; 977 return undef; 978 } 979 if ($gid !~ /^\d+$/ && $gid ne '*') { 980 print STDERR 981 "$prog:$file:$.: bad group-id for file-result: $gid\n"; 982 return undef; 983 } 984 if ($matchType !~ /^(exact|pattern)$/) { 985 print STDERR 986 "$prog:$file:$.: bad match type for file-result: $matchType\n"; 987 return undef; 988 } 989 $c = substr($rest, 0, 1); 990 if (($len = index($rest, $c, 1) - 1) <= 0) { 991 print STDERR 992 "$prog:$file:$.: missing end quote for file name in file-result: $rest\n"; 993 return undef; 994 } 995 $name = substr($rest, 1, $len); 996 if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { 997 # Note: this is not a security thing - just a sanity 998 # check - a test can still use symlinks to get at files 999 # outside the test directory. 1000 print STDERR 1001"$prog:$file:$.: file name in file-result is absolute or contains ..: $name\n"; 1002 return undef; 1003 } 1004 } 1005 } elsif ($val eq '') { 1006 print STDERR 1007 "$prog:$file:$.: no value given for field \"$field\"\n"; 1008 return undef; 1009 } 1010 } 1011 $val .= "\n" if !$do_chop; 1012 $test{$sfield} = $val; 1013 redo if $need_redo; 1014 } 1015 if ($_ eq '') { 1016 if (%test) { 1017 print STDERR 1018 "$prog:$file:$start_lineno: end-of-file while reading test\n"; 1019 return undef; 1020 } 1021 return 0; 1022 } 1023 1024 while (($field, $val) = each %test_fields) { 1025 if ($val =~ /r/ && !defined $test{$field}) { 1026 print STDERR 1027 "$prog:$file:$start_lineno: required field \"$field\" missing\n"; 1028 return undef; 1029 } 1030 } 1031 1032 $test{':full-name'} = substr($file, $file_prefix_skip) . ":$test{'name'}"; 1033 $test{':long-name'} = "$file:$start_lineno:$test{'name'}"; 1034 1035 # Syntax check on specific fields 1036 if (defined $test{'expected-fail'}) { 1037 if ($test{'expected-fail'} !~ /^(yes|no)$/) { 1038 print STDERR 1039 "$prog:$test{':long-name'}: bad value for expected-fail field\n"; 1040 return undef; 1041 } 1042 $test{'expected-fail'} = $1 eq 'yes'; 1043 } else { 1044 $test{'expected-fail'} = 0; 1045 } 1046 if (defined $test{'arguments'}) { 1047 local($firstc) = substr($test{'arguments'}, 0, 1); 1048 1049 if (substr($test{'arguments'}, -1, 1) ne $firstc) { 1050 print STDERR "$prog:$test{':long-name'}: arguments field doesn't start and end with the same character\n"; 1051 return undef; 1052 } 1053 } 1054 if (defined $test{'env-setup'}) { 1055 local($firstc) = substr($test{'env-setup'}, 0, 1); 1056 1057 if (substr($test{'env-setup'}, -1, 1) ne $firstc) { 1058 print STDERR "$prog:$test{':long-name'}: env-setup field doesn't start and end with the same character\n"; 1059 return undef; 1060 } 1061 } 1062 if (defined $test{'expected-exit'}) { 1063 local($val) = $test{'expected-exit'}; 1064 1065 if ($val =~ /^(|-)\d+$/) { 1066 if ($val < 0 || $val > 255) { 1067 print STDERR "$prog:$test{':long-name'}: expected-exit value $val not in 0..255\n"; 1068 return undef; 1069 } 1070 } elsif ($val !~ /^([\s<>+-=*%\/&|!()]|\b[wse]\b|\bSIG[A-Z0-9]+\b)+$/) { 1071 print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $val\n"; 1072 return undef; 1073 } 1074 } else { 1075 $test{'expected-exit'} = 0; 1076 } 1077 if (defined $test{'expected-stdout'} 1078 && defined $test{'expected-stdout-pattern'}) 1079 { 1080 print STDERR "$prog:$test{':long-name'}: can't use both expected-stdout and expected-stdout-pattern\n"; 1081 return undef; 1082 } 1083 if (defined $test{'expected-stderr'} 1084 && defined $test{'expected-stderr-pattern'}) 1085 { 1086 print STDERR "$prog:$test{':long-name'}: can't use both expected-stderr and expected-stderr-pattern\n"; 1087 return undef; 1088 } 1089 if (defined $test{'time-limit'}) { 1090 if ($test{'time-limit'} !~ /^\d+$/ || $test{'time-limit'} == 0) { 1091 print STDERR 1092 "$prog:$test{':long-name'}: bad value for time-limit field\n"; 1093 return undef; 1094 } 1095 } elsif (defined $default_time_limit) { 1096 $test{'time-limit'} = $default_time_limit; 1097 } 1098 1099 if (defined $known_tests{$test{'name'}}) { 1100 print STDERR "$prog:$test{':long-name'}: warning: duplicate test name ${test{'name'}}\n"; 1101 } 1102 $known_tests{$test{'name'}} = 1; 1103 1104 return 1; 1105} 1106 1107sub 1108tty_msg 1109{ 1110 local($msg) = @_; 1111 1112 open(TTY, "> /dev/tty") || return 0; 1113 print TTY $msg; 1114 close(TTY); 1115 return 1; 1116} 1117 1118sub 1119never_called_funcs 1120{ 1121 return 0; 1122 &tty_msg("hi\n"); 1123 &never_called_funcs(); 1124 &catch_sigalrm(); 1125 $old_env{'foo'} = 'bar'; 1126 $internal_test_fields{'foo'} = 'bar'; 1127} 1128 1129sub 1130check_file_result 1131{ 1132 local(*test) = @_; 1133 1134 return '' if (!defined $test{'file-result'}); 1135 1136 local($why) = ''; 1137 local($i); 1138 local($type, $perm, $uid, $gid, $rest, $c, $len, $name); 1139 local(@stbuf); 1140 1141 for ($i = 0; $i < $test{'file-result'}; $i++) { 1142 $val = $test{"file-result:$i"}; 1143 # 1144 # format is: type perm "name" 1145 # 1146 ($type, $perm, $uid, $gid, $matchType, $rest) = 1147 split(' ', $val, 6); 1148 $c = substr($rest, 0, 1); 1149 $len = index($rest, $c, 1) - 1; 1150 $name = substr($rest, 1, $len); 1151 $rest = substr($rest, 2 + $len); 1152 $perm = oct($perm) if $perm =~ /^\d+$/; 1153 1154 @stbuf = lstat($name); 1155 if (!@stbuf) { 1156 $why .= "\texpected $type \"$name\" not created\n"; 1157 next; 1158 } 1159 if ($perm ne '*' && ($stbuf[2] & 07777) != $perm) { 1160 $why .= "\t$type \"$name\" has unexpected permissions\n"; 1161 $why .= sprintf("\t\texpected 0%o, found 0%o\n", 1162 $perm, $stbuf[2] & 07777); 1163 } 1164 if ($uid ne '*' && $stbuf[4] != $uid) { 1165 $why .= "\t$type \"$name\" has unexpected user-id\n"; 1166 $why .= sprintf("\t\texpected %d, found %d\n", 1167 $uid, $stbuf[4]); 1168 } 1169 if ($gid ne '*' && $stbuf[5] != $gid) { 1170 $why .= "\t$type \"$name\" has unexpected group-id\n"; 1171 $why .= sprintf("\t\texpected %d, found %d\n", 1172 $gid, $stbuf[5]); 1173 } 1174 1175 if ($type eq 'file') { 1176 if (-l _ || ! -f _) { 1177 $why .= "\t$type \"$name\" is not a regular file\n"; 1178 } else { 1179 local $tmp = &check_output($test{'long-name'}, $name, 1180 "$type contents in \"$name\"", 1181 $matchType eq 'exact' ? $rest : undef 1182 $matchType eq 'pattern' ? $rest : undef); 1183 return undef if (!defined $tmp); 1184 $why .= $tmp; 1185 } 1186 } elsif ($type eq 'dir') { 1187 if ($rest !~ /^\s*$/) { 1188 print STDERR "$prog:$test{':long-name'}: file-result test for directory $name should not have content specified\n"; 1189 return undef; 1190 } 1191 if (-l _ || ! -d _) { 1192 $why .= "\t$type \"$name\" is not a directory\n"; 1193 } 1194 } elsif ($type eq 'symlink') { 1195 if (!-l _) { 1196 $why .= "\t$type \"$name\" is not a symlink\n"; 1197 } else { 1198 local $content = readlink($name); 1199 if (!defined $content) { 1200 print STDERR "$prog:$test{':long-name'}: file-result test for $type $name failed - could not readlink - $!\n"; 1201 return undef; 1202 } 1203 local $tmp = &compare_output($test{'long-name'}, 1204 "$type contents in \"$name\"", 1205 $matchType eq 'exact' ? $rest : undef 1206 $matchType eq 'pattern' ? $rest : undef); 1207 return undef if (!defined $tmp); 1208 $why .= $tmp; 1209 } 1210 } 1211 } 1212 1213 return $why; 1214} 1215 1216sub HELP_MESSAGE 1217{ 1218 print STDERR $Usage; 1219 exit 0; 1220} 1221