#!/usr/bin/perl # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # This program is distributed with GNU Go, a Go program. # # # # Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ # # for more information. # # # # Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 # # and 2008 by the Free Software Foundation. # # # # This program is free software; you can redistribute it and/or # # modify it under the terms of the GNU General Public License # # as published by the Free Software Foundation - version 3 # # or (at your option) any later version. # # # # This program is distributed in the hope that it will be # # useful, but WITHOUT ANY WARRANTY; without even the implied # # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # # PURPOSE. See the GNU General Public License in file COPYING # # for more details. # # # # You should have received a copy of the GNU General Public # # License along with this program; if not, write to the Free # # Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # # Boston, MA 02111, USA. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Here is a perlscript regress.pl. Its purpose is to run # the regression tests that are currently implemented with # shells and awk scripts. # # Run with: # # regress.pl --help # package REGRESS; use IPC::Open3; use IO::Handle; use Getopt::Long; use FileHandle; use FindBin; use strict; use warnings; use Carp; STDOUT->autoflush(1); my $helpstring = " Run with: regress.pl --goprog \' --mode gtp [program options]\' \\ --testfile \'\' \\ --all_batches Ignores --testfile, gets test files from Makefile.in --numbers \'regexp of test numbers the next test after which won\'t be run\' [options] Possible options: --verbose 0 (very quiet) --verbose 1 (to list moves) or --verbose 2 (to draw board) [FIXME: verbose levels not well defined.] --html 0 (to not generate html) or --html 1 (default - generate html file w/ results) "; my %categories = ("JOSEKI_DATABASE", "", "JOSEKI_PATTERN", "", "FUSEKI_CONCEPT", "", "DYNAMIC_CONNECTION", "Dynamic Connection Reading", "TACTICAL_READING", "", "OWL_TUNING", "", "PATTERN_TUNING", "", "CONNECTION_TUNING", "", "MOVE_VALUATION", "", "ATARI_ATARI", "", "SEMEAI_MODULE", "", "KO_READING", "" ); my $trace_output=""; my $cur_passed; my $result; my $correct_re; my $bang; my $top_moves; my $handicap_stones; my $sgfmove; my $vertex; my @vertices; my $first; my $second; my $resultb; my $resultw; my $scriptfile; my $pidt; my $pidg; my $testdir; my $goprog; my $verbose = 1; my $old_whole_gtp = ""; my $html_whole_gtp = ""; my $testfile; my $num; my $filepos; my $goprog_in ; # stdin of computer player my $goprog_out; # stdout of computer player my $goprog_err; # stderr of computer player my $passes; my $unexpected_pass; my $failures; my $unexpected_fail; my $numbers = ""; my $boardsize = 19; #current boardsize my $testfile_out; my $all_batches; my $make_images; my $cputime; my $generate_sgf = 1; my $goprog_name = "unknown"; my $goprog_version = "0"; my $goprog_timestamp = 0; my $do_topmove = 0; my $one_gg_process = 0; my @failed_links; my @FAILED_links; my @counters = qw/connection_node owl_node reading_node trymove/; my %counters; my $next_cmd = ""; my $prev_cmd = ""; my $problem_set; my $wantshelp; GetOptions( "goprog|g=s" => \$goprog, "verbose|v=i" => \$verbose, "numbers|n=s" => \$numbers, "all_batches|all-batches|a=i" => \$all_batches, "make_images|m=i" => \$make_images, "problemset|ps|p=s" => \$problem_set, "help" => \$wantshelp, "sgf|sgf|s=i" => \$generate_sgf, ); if ($make_images) { make_images(); exit; } my $s = (lc ($^O) eq 'mswin32') ? '\\' : '/'; if (!$goprog) { $goprog = "..${s}interface${s}gnugo"; } if ($goprog !~ / /) { $goprog .= " --mode gtp --quiet -t -w -d0x101840 --showtime"; } die $helpstring unless defined $goprog; if ($wantshelp) { print $helpstring; exit; } if (!-e "html") { mkdir "html"; } # if $numbers matches the current test number, then read it to mean: # "inhibit all gtp commands AFTER the matching number, until the next # numbered test, then resume." if ($numbers) { $numbers = "^($numbers)\$"; } use File::stat; # create FileHandles $goprog_in = new FileHandle; # stdin of computer player $goprog_out = new FileHandle; # stdout of computer player $goprog_err = new FileHandle; # stdout of computer player print "Go program: $goprog\n" if $verbose > 1; $pidg = open3($goprog_in, $goprog_out, $goprog_err, $goprog) or die "Couldn't launch GNU Go: $!"; print "goprog pid: $pidg\n" if $verbose > 1; my ($goprog_exe) = split (" ", $goprog); -e $goprog_exe or ($goprog_exe = "$goprog_exe.exe") && -e $goprog_exe or die "Couldn't locate go program: $goprog_exe"; $goprog_timestamp = (stat $goprog_exe)->mtime; go_command("name"); $_ = <$goprog_out>; if (/^=\s*(.*)/) { ($goprog_name = $1) =~ s/\s*$//; } <$goprog_out>; go_command("version"); $_ = <$goprog_out>; if (/^=\s*(.*)/) { ($goprog_version = $1) =~ s/\s*$//; } <$goprog_out>; print "Name: " . $goprog_name ." ". $goprog_version . "\n" if $verbose > 1; if ($one_gg_process) { go_command("quit"); print "waiting\n" if $verbose > 2; waitpid $pidg, 0; print "done waiting\n" if $verbose > 2; } if ($problem_set) { open(F, $problem_set) or confess "can't open problem set: $problem_set"; my %filehash; while () { next if ($_ =~ /^\s*(#.*)?$/); last if ($_ =~ /DONE|STOP/); my ($filename, $probnum) = $_ =~ /^([^:]*):(\d+)/; if (!defined $filename) { warn "Unexpected line: $_"; last; } $filename =~ s/(\.tst)$//; push @{$filehash{$filename}}, $probnum; } close F; open(F, $problem_set) or confess "can't open problem set: $problem_set"; while () { next if ($_ =~ /^\s*(#.*)?$/); my ($filename, $probnum) = $_ =~ /^(.*):(\d+)/; last unless defined $filename; $filename =~ s/(\.tst)$//; if (exists ($filehash{$filename}) ){ regress_file ("$filename.tst", @{$filehash{$filename}}); delete $filehash{$filename}; } } close F; } else { if ($all_batches) { @ARGV = allTargets(); } my $curtstfile = ""; my $file_count = 0; while ($file_count <= $#ARGV) { $curtstfile = $ARGV[$file_count]; #unlink "html/index.html"; unlink "html/$curtstfile/index.html"; print "regressing file $ARGV[$file_count]\n" if $verbose > 1; unlink "html/$curtstfile/index.html"; regress_file ($ARGV[$file_count]); $file_count++; @failed_links = @FAILED_links = (); }; } if (!$one_gg_process) { go_command("quit"); print "waiting\n" if $verbose > 1; waitpid $pidg, 0; print "done waiting\n" if $verbose > 1; } #readline(*STDIN); exit; my $g_curtestfile; sub regress_chunk { my @lines = @_; } sub regress_file { $testfile = shift; my @problist = sort {$a<=>$b} @_; if ($verbose) { print "$testfile"; print ": ", join (" ", @problist), "\n" if @problist; print "\n"; } ($g_curtestfile) = $testfile =~ /(.*)\.tst$/ or confess "Unparsable test file: $testfile"; -e "html" or mkdir "html" or die "Couldn't create html"; -e "html/$testfile" or mkdir "html/$testfile" or die "Couldn't create html/$testfile"; my $childpid; unless ($one_gg_process) { $goprog_in = new FileHandle; # stdin of computer player $goprog_out = new FileHandle; # stdout of computer player $goprog_err = new FileHandle; # stderr of computer player $pidg = open3($goprog_in, $goprog_out, $goprog_err, $goprog); print "goprog pid: $pidg\n" if $verbose > 1; unless ($childpid = fork) { #Child. chdir "html/$testfile" ; open (TRACER, ">tracer.ttt"); while (defined(my $t = <$goprog_err>)) { last if $t =~ /^ALL DONE/; print TRACER $t; print "ERR: $t" if $verbose > 2; if ($t =~ /^\s*FINISHED PROBLEM:\s*$/ or $t =~ /^\s*SKIPPED PROBLEM:\s*$/) { my $num = <$goprog_err>; print TRACER $num; $num += 0; close TRACER or die "Couldn't close temp trace file"; print "closed trace file\n" if $verbose > 2; if ($t =~ /^\s*FINISHED PROBLEM:\s*$/) { rename "tracer.ttt", "$num.trace" or die "Couldn't rename tracer: $testfile, $num"; } open (TRACER, ">tracer.ttt"); } } close TRACER; exit; } } foreach (@counters) { go_command("reset_${_}_counter"); eat(); } #main bit. $pidt = open ($testfile_out,"<$testfile") or confess "Can't open $testfile"; print "testfile pid: $pidt\n" if $verbose > 1; my $negate; my $ignore; my $fail; $passes=0; $unexpected_pass=0; $failures=0; $unexpected_fail=0; $result = ""; $next_cmd = ""; $num = 0; $filepos = 0; go_command("cputime"); $cputime = <$goprog_out>; print "cputime: $cputime\n" if $verbose > 1; ($cputime) = ($cputime =~ /((\d|\.)+)/); <$goprog_out>; my $skipping; while (defined($next_cmd)) { $filepos++; my $force_read = 1; while ($force_read) { $prev_cmd = $next_cmd; $next_cmd = <$testfile_out>; $force_read = 0; if (defined($next_cmd)) { chop($next_cmd); print "NEXT_CMD: '$next_cmd'\n" if ($verbose > 1); if (($next_cmd =~ /^\s*#\?\s+\[(\!*)(.*)\]\s*(\*)*(\&)*\s*$/)) { $bang = $1; if ($1) { $negate = 1} else {$negate = 0}; $correct_re = $2; if ($3) { $fail = 1} else { $fail = 0}; if ($4) {$ignore = 1} else {$ignore = 0}; $skipping = (@problist && eval {foreach my $i (@problist) { return 0 if $i == $num} return 1;} ); if ($skipping) { go_command("echo_err SKIPPED PROBLEM:\n"); } else { go_command("echo_err FINISHED PROBLEM:\n"); } eat(); #ignore output! go_command("echo_err $num\n"); eat(); #ignore output! if ($skipping) { print "$g_curtestfile:$num skipped.\n" if $verbose > 1; tally_result ($num, "skipped", " ", " "); } else { print "TST:$negate - $correct_re - $fail - $ignore\n" if $verbose>1; if (!$ignore) { my $match_result = $result =~ /^$correct_re$/ ; if ($negate) { $match_result = ! $match_result; } if ($match_result) { if ($fail) { tally_result ($num,"PASSED","$bang$correct_re","$result"); } else { tally_result ($num,"passed","$bang$correct_re","$result"); } } else { if (!$fail) { tally_result ($num,"FAILED","$bang$correct_re","$result"); } else { tally_result ($num,"failed","$bang$correct_re","$result"); } } } } $old_whole_gtp = $html_whole_gtp; $html_whole_gtp = ""; } else { if (!($next_cmd =~ /^\s*$/)) { $html_whole_gtp .= " " . html_encode($next_cmd) . "
\n"; } } $next_cmd =~ s/^\s*$//; $next_cmd =~ s/^#.*$//; $force_read = $next_cmd eq "" } } if (defined($next_cmd)) { my ($this_number) = $next_cmd =~ /^([0-9]+)/; $skipping = (defined($this_number) && (@problist && eval {foreach my $i (@problist) {return 0 if $i == $this_number} return 1;} )); if ($skipping) { #print "SKIPPING: $next_cmd ($this_number)\n"; } else { #print "NOT SKIPPING: $next_cmd\n"; $top_moves = ""; if ($do_topmove) { if ($next_cmd =~ /reg_genmove\s+([blackwhite])+/) { $next_cmd =~ s/reg_genmove\s+([blackwhite]+)/top_moves_$1/; $top_moves = 1; } } if (defined($this_number) && $next_cmd =~ /attack|defend/ && $generate_sgf) { go_command("start_sgftrace"); eat(); #ignore output } go_command($next_cmd); if ($top_moves) { $top_moves = eat_one(); if ($top_moves) { ($result, $_) = split(/ /, $top_moves, 2); } else { $result = "PASS"; $top_moves = ""; } print "TopMoves:$top_moves\n" if $verbose > 1; } else { $result = eat_one(); if (!defined($result)) {$result="";} } print "RES: $result\n" if $verbose > 1; if (defined($this_number) && $next_cmd =~ /attack|defend/) { if ($generate_sgf) { go_command("finish_sgftrace html$s$testfile$s$this_number.sgf"); eat(); #ignore output } else { unlink "html$s$testfile$s$this_number.sgf"; } } } if (defined $this_number) {$num = $this_number;} } } my $pass_string; my $fail_string; if ($unexpected_pass == 1) { $pass_string = "pass"; } else { $pass_string = "passes"; } if ($unexpected_fail == 1) { $fail_string = "failure"; } else { $fail_string = "failures"; } print "Summary: $passes/" . ($passes + $failures) . " passes. $unexpected_pass unexpected $pass_string, " . "$unexpected_fail unexpected $fail_string\n"; unless ($one_gg_process) { go_command("echo_err ALL DONE"); print "waiting on child\n" if $verbose > 1; waitpid $childpid, 0; print "done waiting on child\n" if $verbose > 1; go_command("quit"); print "waiting\n" if $verbose > 1; waitpid $pidg, 0; print "done waiting\n" if $verbose > 1; } } sub tally_result { (my $number, my $status, my $correct, my $incorrect) = @_; my $showboard = $status ne "skipped"; $passes++ if $status eq "passed"; $unexpected_pass++ if $status eq "PASSED"; $failures++ if $status eq "failed"; $unexpected_fail++ if $status eq "FAILED"; if (($verbose and $status ne "skipped") or (!$verbose and ($status eq "PASSED" or $status eq "FAILED")) ) { print "$g_curtestfile:$number: $status: correct: $correct answer: $incorrect\n"; } $cur_passed = ($status =~ /pass/i); if ($showboard) { mkdir ("html/$testfile");# die quietly - probably already exists. my $brd = new FileHandle; open ($brd, "> html/$testfile/$num.xml") || die "ERROR: couldn't crate xml board: $!\n"; my $brdout = eat_board(); print $brd "\n"; print $brd qq@\n@; print $brd "$correct\n"; print $brd "$incorrect\n"; if ($html_whole_gtp !~ /^\s*loadsgf/m) { $old_whole_gtp .= $html_whole_gtp; $html_whole_gtp = $old_whole_gtp; } print $brd "\n$html_whole_gtp\n"; foreach my $listval ("DESCRIPTION", "CATEGORY", "SEVERITY") { my $astxt; $html_whole_gtp =~ /$listval=(.*?)
/; if (defined($1)) {$astxt = $1;} else {$astxt = "";}; print $brd "<$listval>$astxt\n"; } print $brd "\n"; go_command("cputime"); my $new_cputime = <$goprog_out>; ($new_cputime) = ($new_cputime =~ /((\d|\.)+)/); print "cputime: ".$new_cputime."\n" if $verbose > 1; <$goprog_out>; print $brd "
\n"; close $brd; } } sub html_encode { my $r = shift; $r =~ s/&/&/g; $r =~ s//>/g; return $r; } sub eat_board { go_command("query_boardsize"); my $line = eat(); (undef, $boardsize) = split(' ', $line, 2); $boardsize = $boardsize + 0; my $linesleft = $boardsize + 2; my $xboard = ""; my $cur_point = 0; my $cur_color = 0; my $cur_matcher_status = 0; my $cur_dragon_status=0; my $cur_owl_status=0; my $cur_color_letter=0; my %dragons; my $white_letter = chr(ord('z')+1); my $black_letter = chr(ord('A')-1); my $iline = 1; my $no_dragon_data = 0; my %stones; if ($prev_cmd =~ /reg_genmove/) { #FIXME: There may be other commands that won't require dragon_data #to be regenerated. Better might be to provide a way to query the #engine whether dragon_data is currently available w/out regenerating. go_command("dragon_data\n"); while ($iline) { $iline = $_ = <$goprog_out>; if ($iline =~ /^\?(.*)/) { $no_dragon_data = $1; $iline = $_ = <$goprog_out>; last; } $iline =~ s/\s*$//mg; if ($iline =~ /^=?\s*([A-Z][0-9][0-9]?):\s*$/ || !$iline) { if ($cur_point) { if ($cur_color eq "white") { $_ = $white_letter = chr(ord($white_letter)-1); $cur_color_letter = "O"; } elsif ($cur_color eq "black" || die "invalid color $cur_color") { $_ = $black_letter = chr(ord($black_letter)+1); $cur_color_letter = "X"; } $dragons{$cur_point} = $_ . ";status=" . $cur_dragon_status . ";owl_status=" . $cur_owl_status . ";color_letter=" . $cur_color_letter. ";"; $cur_color = 0; $cur_matcher_status = 0; $cur_dragon_status=0; $cur_owl_status=0; $cur_color_letter=0; } $cur_point = $1; } elsif ($iline =~ /^color:?\s+([blackwhite]*)\s*$/) { $cur_color = $1; } elsif ($iline =~ /^matcher_status:?\s+(\S*)\s*$/) { $cur_matcher_status = $1; } elsif ($iline =~ /^status:?\s+(\S*)\s*$/) { $cur_dragon_status = $1; } elsif ($iline =~ /^owl_status:?\s+(\S*)\s*$/) { $cur_owl_status = $1; } else { #we ignore lots of dragon data! } } } else { $no_dragon_data=1; foreach $cur_color ("white", "black") { $iline = 1; go_command("worm_stones $cur_color"); if ($cur_color eq "white") { $cur_color_letter = "O"; } elsif ($cur_color eq "black" || die "invalid color $cur_color") { $cur_color_letter = "X"; } while ($iline) { $iline = <$goprog_out>; my $splitline = $iline; $splitline =~ s/^[=]\s*//; $splitline =~ s/\s*$//mg; foreach (split (/\s+/,$splitline)) { $stones{$_} =";color_letter=" . $cur_color_letter. ";"; } $iline =~ s/\s*$//mg; } } } if ($prev_cmd =~ /^[0-9]*\s*reg_genmove/) { if (! ($next_cmd =~ /^#\?\s*\[(!)?\(?(.*)\)?\]\*?\s*$/)) { print "BAD TEST: $next_cmd\n"; } #$1 and $2 are just $bang and $correct_re, right? #print "Genmove test:\n"; #print " $1;$2\n"; foreach (split(/\|/,$2)) { if ($1) { $stones{$_} .= ";known_wrong;"; } else { $stones{$_} .= ";known_right;"; } } if ($cur_passed) { $stones{$result} .= ";try_right;"; } else { $stones{$result} .= ";try_wrong;"; } } else { # Experimental - should work for reg_genmove too! if (! ($next_cmd =~ /^#\?\s*\[(!)?\(?(.*)\)?\]\*?\s*$/)) { print "BAD TEST: $next_cmd\n"; } #see commend on this regex above. my $known = $2; #Here, look for something that looks like a move! while ($known =~ s/([A-Z]\d\d?)//) { if ($bang) { $stones{$1} .= ";known_wrong;"; } else { $stones{$1} .= ";known_right;"; } } my $try = $result; while ($try =~ s/([A-Z]\d\d?)//) { if ($cur_passed) { $stones{$1} .= ";try_right;"; } else { $stones{$1} .= ";try_wrong;"; } } } { my $pc = $prev_cmd; while ($pc =~ s/([A-Z]\d\d?)//) { $stones{$1} .= ";question;"; } } unless ($no_dragon_data) { #FIXME: This data is available via the strings line from dragon_data. go_command("dragon_stones"); $iline = 1; while ($iline) { $iline = <$goprog_out>; $iline =~ s/\s*$//mg; $iline =~ s/^=?\s*//; $iline = " " . $iline . " "; foreach (keys(%dragons)) { my $k = $_; my $label = $dragons{$k}; if ($iline =~ (" ".$k." ")) { $iline =~ s/^\s*//; $iline =~ s/\s*$//; foreach (split(/ /,$iline)) { $stones{$_} = $label; } } } $iline =~ s/\s*//mg; } } my %tmarr; if ($prev_cmd =~ /.*reg_genmove\s+([whiteblack]+)/) { go_command ("top_moves"); my $top_moves = <$goprog_out>; <$goprog_out>; if ($top_moves) { $top_moves =~ s/^=\s*//; $top_moves =~ s/\s*$//mg; print "TOP_MOVES:'$top_moves'\n" if $verbose > 1; if ($top_moves =~ /^\s*(.*)\s*/) { #i.e. always! my $t = $1; %tmarr = split(/\s+/,$t); foreach my $k (keys(%tmarr)) { $stones{$k} .= ";move_value=$tmarr{$k};"; } } } } my $j; my $i; for ($j = $boardsize; $j > 0; $j--) { my $jA = $j; if ($j <= 9) { $jA .= " "; } for ($i = 1; $i <= $boardsize; $i++) { my $iA = ord('A') + $i - 1; if ($iA >= ord('I')) { $iA++; } $iA = chr($iA); my $point = ""; if ($stones{$iA.$j}) { $point .= qq/ coord="$iA$j"\n/; my $status = $stones{$iA.$j}; if ($status =~ /(.).*;owl_status=([^;]*);/) { $point .= qq/ owl_status="$2"\n/; } if ($status =~ /(.).*;status=([^;]*);/) { $point .= qq/ dragon_letter="$1"\n/; $point .= qq/ dragon_status="$2"\n/; } if ($status =~ /;color_letter=([^;]*);/) { $point .= qq/ stone="/ . (($1 eq 'X') ? 'black' : 'white') . qq/"\n/; } if ($status =~ /;move_value=([^;]*);/) { $point .= qq/ move_value="$1"\n/; } $point .= qq/ known="wrong"\n/ if ($status =~ /;known_wrong;/); $point .= qq/ known="right"\n/ if ($status =~ /;known_right;/); $point .= qq/ try="right"\n/ if ($status =~ /;try_right;/); $point .= qq/ try="wrong"\n/ if ($status =~ /;try_wrong;/); $point .= qq/ question="1"\n/ if ($status =~/;question;/); } if ($point) { $xboard .= " \n"; } } } return "\n" . $xboard . "\n"; } sub eat() { # ignore empty lines my $line = ""; while ($line eq "") { chop($line = <$goprog_out>) or confess "No response!"; $line =~ s/\s*$//smg; } <$goprog_out>; return $line; } sub eat_one { my ($equals, $move) = split(' ', eat(), 2); return $move; } sub go_command { my $cmd = shift; print $goprog_in "$cmd\n"; print "CMD:$cmd\n" if $verbose > 1; foreach (@counters) { if ($cmd =~ /reset_${_}_counter/) { $counters{$_} = 0; } } } my %images; sub extract_images { my $line = shift; #i.e.: if ($line =~ /SRC=.*images.(.*)\"><.TD>.*/) { if ($verbose) { print " found: $1\n" unless ($images{$1}); } $images{$1} = 1; } } our $curdir; our $curfile; our $CURDIR; sub extract_image_dir { local $curdir = shift; local $CURDIR; opendir $CURDIR, $curdir; while (local $curfile = readdir $CURDIR) { $_ = "$curdir/$curfile"; #print -d."\n"; #print "X:".($curfile=~/^\.+$/)."\n"; if ((-d ) && !($curfile=~/^\.{1,2}$/)) { print "diving into: $curdir/$curfile\n" if $verbose>2; extract_image_dir ("$curdir/$curfile"); } elsif (($curfile =~ /\.html$/) && ($curdir =~ /d2/)) { print "processing: $curdir/$curfile\n" if $verbose; open IMGFILE, "<$curdir/$curfile" or die "Couldn't open: $curdir/$curfile" ; while () { extract_images($_); } close IMGFILE; } else { #print "no match: $curdir/$curfile\n" if $verbose; } } closedir CURDIR; } sub make_images { print "Starting processing\n" if $verbose; extract_image_dir (".") ; print "Processed files, generated ".((scalar keys(%images))/2) ." unique images:\n" if $verbose; foreach (keys(%images)) { parseFileName($_); } print "Done.\n" if $verbose; } sub allTargets { open (MAKEFILE, "< Makefile.in"); my @targets = ""; while () { if (s/^all_batches://) { @targets = split; last; } } my $target_reg = "^" . join ("|", @targets) . ":" ; close MAKEFILE; open (MAKEFILE, "< Makefile.in"); my @files; while () { if ($_ =~ $target_reg) { chop($_ = ); while ($_) { push @files, $_ =~ /\s+(\w+\.tst)/; chop if defined($_ = ); } } } close MAKEFILE; return @files; }