| #!/usr/bin/perl |
| |
| use strict; |
| use warnings; |
| use Getopt::Long; |
| use IO::Handle; |
| use IPC::Open2 qw( open2 ); |
| use POSIX qw( WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG ); |
| |
| my $VALGRIND = 0; |
| my $EXECUTABLE = "t/harness"; |
| GetOptions( |
| 'valgrind|v+' => \$VALGRIND, |
| 'executable|e=s' => \$EXECUTABLE, |
| 'fail-early|F' => \(my $FAIL_EARLY), |
| ) or exit 1; |
| |
| my ( $hin, $hout, $hpid ); |
| { |
| my @command = $EXECUTABLE; |
| unshift @command, "valgrind", "--tool=memcheck", "--leak-check=yes", "--num-callers=25", "--log-file=valgrind.out", "--error-exitcode=126" if $VALGRIND; |
| |
| $hpid = open2 $hout, $hin, @command or die "Cannot open2 harness - $!"; |
| } |
| |
| my $exitcode = 0; |
| |
| my $command; |
| my @expect; |
| |
| my $linenum = 0; |
| |
| sub do_onetest |
| { |
| $hin->print( "$command\n" ); |
| undef $command; |
| |
| my $fail_printed = 0; |
| |
| while( my $outline = <$hout> ) { |
| last if $outline eq "DONE\n" or $outline eq "?\n"; |
| |
| chomp $outline; |
| |
| if( !@expect ) { |
| print "# line $linenum: Test failed\n" unless $fail_printed++; |
| print "# expected nothing more\n" . |
| "# Actual: $outline\n"; |
| next; |
| } |
| |
| my $expectation = shift @expect; |
| |
| next if $expectation eq $outline; |
| |
| print "# line $linenum: Test failed\n" unless $fail_printed++; |
| print "# Expected: $expectation\n" . |
| "# Actual: $outline\n"; |
| } |
| |
| if( @expect ) { |
| print "# line $linenum: Test failed\n" unless $fail_printed++; |
| print "# Expected: $_\n" . |
| "# didn't happen\n" for @expect; |
| } |
| |
| $exitcode = 1 if $fail_printed; |
| exit $exitcode if $exitcode and $FAIL_EARLY; |
| } |
| |
| sub do_line |
| { |
| my ( $line ) = @_; |
| |
| if( $line =~ m/^!(.*)/ ) { |
| do_onetest if defined $command; |
| print "> $1\n"; |
| } |
| |
| # Commands have capitals |
| elsif( $line =~ m/^([A-Z]+)/ ) { |
| # Some convenience formatting |
| if( $line =~ m/^(PUSH|ENCIN) (.*)$/ ) { |
| # we're evil |
| my $string = eval($2); |
| $line = "$1 " . unpack "H*", $string; |
| } |
| elsif( $line =~ m/^(SELECTION \d+) +(\[?)(.*?)(\]?)$/ ) { |
| # we're evil |
| my $string = eval($3); |
| $line = "$1 $2 " . unpack( "H*", $string ) . " $4"; |
| } |
| |
| do_onetest if defined $command; |
| |
| $command = $line; |
| undef @expect; |
| } |
| # Expectations have lowercase |
| elsif( $line =~ m/^([a-z]+)/ ) { |
| # Convenience formatting |
| if( $line =~ m/^(text|encout) (.*)$/ ) { |
| $line = "$1 " . join ",", map sprintf("%x", $_), eval($2); |
| } |
| elsif( $line =~ m/^(output) (.*)$/ ) { |
| $line = "$1 " . join ",", map sprintf("%x", $_), unpack "C*", eval($2); |
| } |
| elsif( $line =~ m/^control (.*)$/ ) { |
| $line = sprintf "control %02x", eval($1); |
| } |
| elsif( $line =~ m/^csi (\S+) (.*)$/ ) { |
| $line = sprintf "csi %02x %s", eval($1), $2; # TODO |
| } |
| elsif( $line =~ m/^(osc) (\[\d+)? *(.*?)(\]?)$/ ) { |
| my ( $cmd, $initial, $data, $final ) = ( $1, $2, $3, $4 ); |
| $initial //= ""; |
| $initial .= ";" if $initial =~ m/\d+/; |
| |
| $line = "$cmd $initial" . join( "", map sprintf("%02x", $_), unpack "C*", length $data ? eval($data) : "" ) . "$final"; |
| } |
| elsif( $line =~ m/^(escape|dcs|apc|pm|sos) (\[?)(.*?)(\]?)$/ ) { |
| $line = "$1 $2" . join( "", map sprintf("%02x", $_), unpack "C*", length $3 ? eval($3) : "" ) . "$4"; |
| } |
| elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) { |
| $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2"; |
| } |
| elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) { |
| # no conversion |
| } |
| elsif( $line =~ m/^(selection-set) (.*?) (\[?)(.*?)(\]?)$/ ) { |
| $line = "$1 $2 $3" . join( "", map sprintf("%02x", $_), unpack "C*", eval($4) ) . "$5"; |
| } |
| else { |
| warn "Unrecognised test expectation '$line'\n"; |
| } |
| |
| push @expect, $line; |
| } |
| # ?screen_row assertion is emulated here |
| elsif( $line =~ s/^\?screen_row\s+(\d+)\s*=\s*// ) { |
| my $row = $1; |
| my $want; |
| |
| if( $line =~ m/^"/ ) { |
| $want = eval($line); |
| } |
| else { |
| # Turn 0xDD,0xDD,... directly into bytes |
| $want = pack "C*", map { hex } split m/,/, $line; |
| } |
| |
| do_onetest if defined $command; |
| |
| $hin->print( "\?screen_chars $row\n" ); |
| my $response = <$hout>; |
| chomp $response; |
| |
| $response = pack "C*", map { hex } split m/,/, $response; |
| if( $response ne $want ) { |
| print "# line $linenum: Assert ?screen_row $row failed:\n" . |
| "# Expected: $want\n" . |
| "# Actual: $response\n"; |
| $exitcode = 1; |
| exit $exitcode if $exitcode and $FAIL_EARLY; |
| } |
| } |
| # Assertions start with '?' |
| elsif( $line =~ s/^\?([a-z]+.*?=)\s*// ) { |
| do_onetest if defined $command; |
| |
| my ( $assertion ) = $1 =~ m/^(.*)\s+=/; |
| my $expectation = $line; |
| |
| $hin->print( "\?$assertion\n" ); |
| my $response = <$hout>; defined $response or wait, die "Test harness failed - $?\n"; |
| chomp $response; $response =~ s/^\s+|\s+$//g; |
| |
| # Some convenience formatting |
| if( $assertion =~ m/^screen_chars/ and $expectation =~ m/^"/ ) { |
| $expectation = join ",", map sprintf("0x%02x", ord $_), split m//, eval($expectation); |
| } |
| |
| if( $response ne $expectation ) { |
| print "# line $linenum: Assert $assertion failed:\n" . |
| "# Expected: $expectation\n" . |
| "# Actual: $response\n"; |
| $exitcode = 1; |
| exit $exitcode if $exitcode and $FAIL_EARLY; |
| } |
| } |
| # Test controls start with '$' |
| elsif( $line =~ s/\$SEQ\s+(\d+)\s+(\d+):\s*// ) { |
| my ( $low, $high ) = ( $1, $2 ); |
| foreach my $val ( $low .. $high ) { |
| ( my $inner = $line ) =~ s/\\#/$val/g; |
| do_line( $inner ); |
| } |
| } |
| elsif( $line =~ s/\$REP\s+(\d+):\s*// ) { |
| my $count = $1; |
| do_line( $line ) for 1 .. $count; |
| } |
| else { |
| die "Unrecognised TEST line $line\n"; |
| } |
| } |
| |
| open my $test, "<", $ARGV[0] or die "Cannot open test script $ARGV[0] - $!"; |
| |
| while( my $line = <$test> ) { |
| $linenum++; |
| $line =~ s/^\s+//; |
| chomp $line; |
| |
| next if $line =~ m/^(?:#|$)/; |
| last if $line eq "__END__"; |
| |
| do_line( $line ); |
| } |
| |
| do_onetest if defined $command; |
| |
| close $hin; |
| close $hout; |
| |
| waitpid $hpid, 0; |
| if( $? ) { |
| printf STDERR "Harness exited %d\n", WEXITSTATUS($?) if WIFEXITED($?); |
| printf STDERR "Harness exit signal %d\n", WTERMSIG($?) if WIFSIGNALED($?); |
| $exitcode = WIFEXITED($?) ? WEXITSTATUS($?) : 125; |
| } |
| |
| exit $exitcode; |