Blame t/testfunc.pl

Packit f3e6b9
#!/usr/bin/perl -w
Packit f3e6b9
# Copyright (c) 1996-2018 Sullivan Beck. All rights reserved.
Packit f3e6b9
# This program is free software; you can redistribute it and/or modify it
Packit f3e6b9
# under the same terms as Perl itself.
Packit f3e6b9
Packit f3e6b9
# SB_TEST.PL
Packit f3e6b9
###############################################################################
Packit f3e6b9
# HISTORY
Packit f3e6b9
#
Packit f3e6b9
# 1996-??-??  Wrote initial version for Date::Manip module
Packit f3e6b9
#
Packit f3e6b9
# 1996-2001   Numerous changes
Packit f3e6b9
#
Packit f3e6b9
# 2001-03-29  Rewrote to make it easier to drop in for any module.
Packit f3e6b9
#
Packit f3e6b9
# 2001-06-19  Modifications to make space delimited stuff work better.
Packit f3e6b9
#
Packit f3e6b9
# 2001-08-23  Added support for undef args.
Packit f3e6b9
#
Packit f3e6b9
# 2007-08-14  Better support for undef/blank args.
Packit f3e6b9
#
Packit f3e6b9
# 2008-01-02  Better handling of $runtests.
Packit f3e6b9
#
Packit f3e6b9
# 2008-01-24  Better handling of undef/blank args when arguements are
Packit f3e6b9
#             entered as lists instead of strings.
Packit f3e6b9
#
Packit f3e6b9
# 2008-01-25  Created a global $testnum variable to store the test number
Packit f3e6b9
#             in.
Packit f3e6b9
#
Packit f3e6b9
# 2008-11-05  Slightly better handling of blank/undef in returned values.
Packit f3e6b9
#
Packit f3e6b9
# 2009-09-01  Added "-l" value to $runtests.
Packit f3e6b9
#
Packit f3e6b9
# 2009-09-30  Much better support for references.
Packit f3e6b9
#
Packit f3e6b9
# 2010-02-05  Fixed bug in passing tests as lists
Packit f3e6b9
#
Packit f3e6b9
# 2010-04-05  Renamed to testfunc.pl to avoid being called in a core module
Packit f3e6b9
Packit f3e6b9
###############################################################################
Packit f3e6b9
Packit f3e6b9
use Storable qw(dclone);
Packit f3e6b9
Packit f3e6b9
# Usage: test_Func($funcref,$tests,$runtests,@extra)=@_;
Packit f3e6b9
#
Packit f3e6b9
# This takes a series of tests, runs them, compares the output of the tests
Packit f3e6b9
# with expected output, and reports any differences.  Each test consists of
Packit f3e6b9
# several parts:
Packit f3e6b9
#    a function passed in as a reference ($funcref)
Packit f3e6b9
#    a series of arguments to be passed to the function
Packit f3e6b9
#    the expected output from the function call
Packit f3e6b9
#
Packit f3e6b9
# Tests may be passed in in two methods: as a string, or as a reference.
Packit f3e6b9
#
Packit f3e6b9
# Using the string case, $tests is a newline delimited string.  Each test
Packit f3e6b9
# takes one or more lines of the string.  Tests are separated from each
Packit f3e6b9
# other by a blank line.
Packit f3e6b9
#
Packit f3e6b9
# Arguments and return value(s) may be written as a single line:
Packit f3e6b9
#    ARG1 ARG2 ... ARGn ~ VAL1 VAL2 ... VALm
Packit f3e6b9
# or as multiple lines:
Packit f3e6b9
#    ARG1
Packit f3e6b9
#    ARG2
Packit f3e6b9
#    ...
Packit f3e6b9
#    ARGn
Packit f3e6b9
#    ~
Packit f3e6b9
#    VAL1
Packit f3e6b9
#    VAL2
Packit f3e6b9
#    ...
Packit f3e6b9
#    VALm
Packit f3e6b9
#
Packit f3e6b9
# If any of the arguments OR values have spaces in them, only the multiline
Packit f3e6b9
# form may be used.
Packit f3e6b9
#
Packit f3e6b9
# If there is exactly one return value, the separating tilde is
Packit f3e6b9
# optional:
Packit f3e6b9
#    ARG1 ARG2 ... ARGn VAL1
Packit f3e6b9
# or:
Packit f3e6b9
#    ARG1
Packit f3e6b9
#    ARG2
Packit f3e6b9
#    ...
Packit f3e6b9
#    ARGn
Packit f3e6b9
#    VAL
Packit f3e6b9
#
Packit f3e6b9
# It is valid to have a function with no arguments or with no return
Packit f3e6b9
# value (or both).  The "~" must be used:
Packit f3e6b9
#
Packit f3e6b9
#    ARG1 ARG2 ... ARGn ~
Packit f3e6b9
#
Packit f3e6b9
#    ~ VAL1 VAL2 ... VALm
Packit f3e6b9
#
Packit f3e6b9
#    ~
Packit f3e6b9
#
Packit f3e6b9
# Leading and trailing space is ignored in the multi-line format.
Packit f3e6b9
#
Packit f3e6b9
# If desired, any of the ARGs or VALs may be the word "_undef_" which
Packit f3e6b9
# will be strictly interpreted as the perl undef value. The word "_blank_"
Packit f3e6b9
# may also be used to designate a defined but empty string.
Packit f3e6b9
#
Packit f3e6b9
# They may also be (in the multiline format) of the form:
Packit f3e6b9
#
Packit f3e6b9
#   \ STRING           : a string reference
Packit f3e6b9
#
Packit f3e6b9
#   [] LIST            : a list reference (where LIST is a
Packit f3e6b9
#                        comma separated list)
Packit f3e6b9
#
Packit f3e6b9
#   [SEP] LIST         : a list reference (where SEP is a
Packit f3e6b9
#                        single character separator)
Packit f3e6b9
#
Packit f3e6b9
#   {} HASH            : a hash reference (where HASH is
Packit f3e6b9
#                        a comma separated list)
Packit f3e6b9
#
Packit f3e6b9
#   {SEP} HASH         : a hash reference (where SEP is a
Packit f3e6b9
#                        single character separator)
Packit f3e6b9
#
Packit f3e6b9
# Alternately, the tests can be passed in as a list reference:
Packit f3e6b9
#    $tests = [
Packit f3e6b9
#               [
Packit f3e6b9
#                 [ @ARGS1 ],
Packit f3e6b9
#                 [ @VALS1 ]
Packit f3e6b9
#               ],
Packit f3e6b9
#               [
Packit f3e6b9
#                 [ @ARGS2 ],
Packit f3e6b9
#                 [ @VALS2 ]
Packit f3e6b9
#               ], ...
Packit f3e6b9
#             ]
Packit f3e6b9
#
Packit f3e6b9
# @extra are extra arguments which are added to the function call.
Packit f3e6b9
#
Packit f3e6b9
# There are several ways to run the tests, depending on the value of
Packit f3e6b9
# $runtests.
Packit f3e6b9
#
Packit f3e6b9
# If $runtests is 0, the tests are run in a non-interactive way suitable
Packit f3e6b9
# for running as part of a "make test".
Packit f3e6b9
#
Packit f3e6b9
# If $runtests is a positive number, it runs runs all tests starting at
Packit f3e6b9
# that value in a way suitable for running interactively.
Packit f3e6b9
#
Packit f3e6b9
# If $runtests is a negative number, it runs all tests starting at that
Packit f3e6b9
# value, but providing feedback at each test.
Packit f3e6b9
#
Packit f3e6b9
# If $runtests is a string "=N" (where N is a number), it runs only
Packit f3e6b9
# that test.
Packit f3e6b9
#
Packit f3e6b9
# If $runtests is the string "-l", it lists the tests and the expected
Packit f3e6b9
# output without running any.
Packit f3e6b9
Packit f3e6b9
sub test_Func {
Packit f3e6b9
   my($funcref,$tests,$runtests,@extra)=@_;
Packit f3e6b9
   my(@tests);
Packit f3e6b9
Packit f3e6b9
   $runtests     = 0  if (! $runtests);
Packit f3e6b9
   my($starttest,$feedback,$endtest,$runtest);
Packit f3e6b9
   if      ($runtests eq "0"  or  $runtests eq "-0") {
Packit f3e6b9
      $starttest = 1;
Packit f3e6b9
      $feedback  = 1;
Packit f3e6b9
      $endtest   = 0;
Packit f3e6b9
      $runtest   = 1;
Packit f3e6b9
   } elsif ($runtests =~ /^\d+$/){
Packit f3e6b9
      $starttest = $runtests;
Packit f3e6b9
      $feedback  = 0;
Packit f3e6b9
      $endtest   = 0;
Packit f3e6b9
      $runtest   = 1;
Packit f3e6b9
   } elsif ($runtests =~ /^-(\d+)$/) {
Packit f3e6b9
      $starttest = $1;
Packit f3e6b9
      $feedback  = 1;
Packit f3e6b9
      $endtest   = 0;
Packit f3e6b9
      $runtest   = 1;
Packit f3e6b9
   } elsif ($runtests =~ /^=(\d+)$/) {
Packit f3e6b9
      $starttest = $1;
Packit f3e6b9
      $feedback  = 1;
Packit f3e6b9
      $endtest   = $1;
Packit f3e6b9
      $runtest   = 1;
Packit f3e6b9
   } elsif ($runtests eq "-l") {
Packit f3e6b9
      $starttest = 1;
Packit f3e6b9
      $feedback  = 1;
Packit f3e6b9
      $endtest   = 0;
Packit f3e6b9
      $runtest   = 0;
Packit f3e6b9
   } else {
Packit f3e6b9
      die "ERROR: unknown argument(s): $runtests";
Packit f3e6b9
   }
Packit f3e6b9
Packit f3e6b9
   my($tests_as_list) = 0;
Packit f3e6b9
   if (ref($tests) eq "ARRAY") {
Packit f3e6b9
      @tests   = @$tests;
Packit f3e6b9
      $tests_as_list = 1;
Packit f3e6b9
Packit f3e6b9
   } else {
Packit f3e6b9
      # Separate tests.
Packit f3e6b9
Packit f3e6b9
      my($comment)="#";
Packit f3e6b9
      my(@lines)=split(/\n/,$tests);
Packit f3e6b9
      my(@test);
Packit f3e6b9
      while (@lines) {
Packit f3e6b9
         my $line = shift(@lines);
Packit f3e6b9
         $line =~ s/^\s*//;
Packit f3e6b9
         $line =~ s/\s*$//;
Packit f3e6b9
         next  if ($line =~ /^$comment/);
Packit f3e6b9
Packit f3e6b9
         if ($line ne "") {
Packit f3e6b9
            push(@test,$line);
Packit f3e6b9
            next;
Packit f3e6b9
         }
Packit f3e6b9
Packit f3e6b9
         if (@test) {
Packit f3e6b9
            push(@tests,[ @test ]);
Packit f3e6b9
            @test=();
Packit f3e6b9
         }
Packit f3e6b9
      }
Packit f3e6b9
      if (@test) {
Packit f3e6b9
         push(@tests,[ @test ]);
Packit f3e6b9
      }
Packit f3e6b9
Packit f3e6b9
      # Get arg/val lists for each test.
Packit f3e6b9
Packit f3e6b9
      foreach my $test (@tests) {
Packit f3e6b9
         my(@tmp)=@$test;
Packit f3e6b9
         my(@arg,@val);
Packit f3e6b9
Packit f3e6b9
         # single line test
Packit f3e6b9
         @tmp = split(/\s+/,$tmp[0])  if ($#tmp == 0);
Packit f3e6b9
Packit f3e6b9
         my($sep)=-1;
Packit f3e6b9
         my($i);
Packit f3e6b9
         for ($i=0; $i<=$#tmp; $i++) {
Packit f3e6b9
            if ($tmp[$i] eq "~") {
Packit f3e6b9
               $sep=$i;
Packit f3e6b9
               last;
Packit f3e6b9
            }
Packit f3e6b9
         }
Packit f3e6b9
Packit f3e6b9
         if ($sep<0) {
Packit f3e6b9
            @val=pop(@tmp);
Packit f3e6b9
            @arg=@tmp;
Packit f3e6b9
         } else {
Packit f3e6b9
            @arg=@tmp[0..($sep-1)];
Packit f3e6b9
            @val=@tmp[($sep+1)..$#tmp];
Packit f3e6b9
         }
Packit f3e6b9
         $test = [ [@arg],[@val] ];
Packit f3e6b9
      }
Packit f3e6b9
   }
Packit f3e6b9
Packit f3e6b9
   my($ntest)=$#tests + 1;
Packit f3e6b9
   print "1..$ntest\n"  if ($feedback  &&  $runtest);
Packit f3e6b9
Packit f3e6b9
   my(@t);
Packit f3e6b9
   if ($endtest) {
Packit f3e6b9
      @t = ($starttest..$endtest);
Packit f3e6b9
   } else {
Packit f3e6b9
      @t = ($starttest..$ntest);
Packit f3e6b9
   }
Packit f3e6b9
Packit f3e6b9
   foreach my $t (@t) {
Packit f3e6b9
      $::testnum  = $t;
Packit f3e6b9
Packit f3e6b9
      my (@arg);
Packit f3e6b9
      if ($tests_as_list) {
Packit f3e6b9
         @arg     = @{ $tests[$t-1][0] };
Packit f3e6b9
      } else {
Packit f3e6b9
         my $arg  = dclone($tests[$t-1][0]);
Packit f3e6b9
         @arg     = @$arg;
Packit f3e6b9
         print_to_vals(\@arg);
Packit f3e6b9
      }
Packit f3e6b9
Packit f3e6b9
      my $argprt  = dclone(\@arg);
Packit f3e6b9
      my @argprt  = @$argprt;
Packit f3e6b9
      vals_to_print(\@argprt);
Packit f3e6b9
Packit f3e6b9
      my $exp     = dclone($tests[$t-1][1]);
Packit f3e6b9
      my @exp     = @$exp;
Packit f3e6b9
      print_to_vals(\@exp);
Packit f3e6b9
      vals_to_print(\@exp);
Packit f3e6b9
Packit f3e6b9
      # Run the test
Packit f3e6b9
Packit f3e6b9
      my ($ans,@ans);
Packit f3e6b9
      if ($runtest) {
Packit f3e6b9
         @ans = &$funcref(@arg,@extra);
Packit f3e6b9
      }
Packit f3e6b9
      vals_to_print(\@ans);
Packit f3e6b9
Packit f3e6b9
      # Compare the results
Packit f3e6b9
Packit f3e6b9
      foreach my $arg (@arg) {
Packit f3e6b9
         $arg = "_undef_"  if (! defined $arg);
Packit f3e6b9
         $arg = "_blank_"  if ($arg eq "");
Packit f3e6b9
      }
Packit f3e6b9
      $arg = join("\n           ",@argprt,@extra);
Packit f3e6b9
      $ans = join("\n           ",@ans);
Packit f3e6b9
      $exp = join("\n           ",@exp);
Packit f3e6b9
Packit f3e6b9
      if (! $runtest) {
Packit f3e6b9
         print "########################\n";
Packit f3e6b9
         print "Test     = $t\n";
Packit f3e6b9
         print "Args     = $arg\n";
Packit f3e6b9
         print "Expected = $exp\n";
Packit f3e6b9
      } elsif ($ans ne $exp) {
Packit f3e6b9
         print "not ok $t\n";
Packit f3e6b9
         warn "########################\n";
Packit f3e6b9
         warn "Args     = $arg\n";
Packit f3e6b9
         warn "Expected = $exp\n";
Packit f3e6b9
         warn "Got      = $ans\n";
Packit f3e6b9
         warn "########################\n";
Packit f3e6b9
      } else {
Packit f3e6b9
         print "ok $t\n"  if ($feedback);
Packit f3e6b9
      }
Packit f3e6b9
   }
Packit f3e6b9
}
Packit f3e6b9
Packit f3e6b9
# The following is similar but it takes input from an input file and
Packit f3e6b9
# sends output to an output file.
Packit f3e6b9
#
Packit f3e6b9
# $files is a reference to a list of tests.  If one of the tests is named
Packit f3e6b9
# "foobar", the input is from "foobar.in", output is to "foobar.out", and
Packit f3e6b9
# the expected output is in "foobar.exp".
Packit f3e6b9
#
Packit f3e6b9
# The function stored in $funcref is called as:
Packit f3e6b9
#    &$funcref($in,$out,@extra)
Packit f3e6b9
# where $in is the name of the input file, $out is the name of the output
Packit f3e6b9
# file, and @extra are any additional arguments that are required.
Packit f3e6b9
#
Packit f3e6b9
# The function should return 0 on success, or an error message.
Packit f3e6b9
Packit f3e6b9
sub test_File {
Packit f3e6b9
   my($funcref,$files,$runtests,@extra)=@_;
Packit f3e6b9
   my(@files)=@$files;
Packit f3e6b9
Packit f3e6b9
   $runtests=0  if (! $runtests);
Packit f3e6b9
Packit f3e6b9
   my($ntest)=$#files + 1;
Packit f3e6b9
   print "1..$ntest\n"  if (! $runtests);
Packit f3e6b9
Packit f3e6b9
   my(@t);
Packit f3e6b9
   if ($runtests > 0) {
Packit f3e6b9
      @t = ($runtests..$ntest);
Packit f3e6b9
   } elsif ($runtests < 0) {
Packit f3e6b9
      @t = (-$runtests);
Packit f3e6b9
   } else {
Packit f3e6b9
      @t = (1..$ntest);
Packit f3e6b9
   }
Packit f3e6b9
Packit f3e6b9
   foreach my $t (@t) {
Packit f3e6b9
      $::testnum = $t;
Packit f3e6b9
      my $test = $files[$t-1];
Packit f3e6b9
      my $expf = "$test.exp";
Packit f3e6b9
      my $outf = "$test.out";
Packit f3e6b9
Packit f3e6b9
      if (! -f $test  ||  ! -f $expf) {
Packit f3e6b9
         print "not ok $t\n";
Packit f3e6b9
         warn  "Test: $test: missing input/outpuf information\n";
Packit f3e6b9
         next;
Packit f3e6b9
      }
Packit f3e6b9
Packit f3e6b9
      my $err  = &$funcref($test,$outf,@extra);
Packit f3e6b9
      if ($err) {
Packit f3e6b9
         print "not ok $t\n";
Packit f3e6b9
         warn  "Test: $test: $err\n";
Packit f3e6b9
         next;
Packit f3e6b9
      }
Packit f3e6b9
Packit f3e6b9
      local *FH;
Packit f3e6b9
      open(FH,$expf)  ||  do {
Packit f3e6b9
         print "not ok $t\n";
Packit f3e6b9
         warn  "Test: $test: $!\n";
Packit f3e6b9
         next;
Packit f3e6b9
      };
Packit f3e6b9
      my @exp = <FH>;
Packit f3e6b9
      close(FH);
Packit f3e6b9
      my $exp = join("",@exp);
Packit f3e6b9
      open(FH,$outf)  ||  do {
Packit f3e6b9
         print "not ok $t\n";
Packit f3e6b9
         warn  "Test: $test: $!\n";
Packit f3e6b9
         next;
Packit f3e6b9
      };
Packit f3e6b9
      my @out = <FH>;
Packit f3e6b9
      close(FH);
Packit f3e6b9
      my $out = join("",@out);
Packit f3e6b9
Packit f3e6b9
      if ($out ne $exp) {
Packit f3e6b9
         print "not ok $t\n";
Packit f3e6b9
         warn  "Test: $test: output differs from expected value\n";
Packit f3e6b9
         next;
Packit f3e6b9
      }
Packit f3e6b9
Packit f3e6b9
      print "ok $t\n"  if (! $runtests);
Packit f3e6b9
   }
Packit f3e6b9
}
Packit f3e6b9
Packit f3e6b9
# Converts a printable version of arguments to actual arguments
Packit f3e6b9
sub print_to_vals {
Packit f3e6b9
   my($listref) = @_;
Packit f3e6b9
Packit f3e6b9
   foreach my $arg (@$listref) {
Packit f3e6b9
      next  if (! defined($arg));
Packit f3e6b9
      if ($arg eq "_undef_") {
Packit f3e6b9
         $arg = undef;
Packit f3e6b9
Packit f3e6b9
      } elsif ($arg eq "_blank_") {
Packit f3e6b9
         $arg = "";
Packit f3e6b9
Packit f3e6b9
      } elsif ($arg =~ /^\\\s*(.*)/) {
Packit f3e6b9
         $str = $1;
Packit f3e6b9
         $arg = \$str;
Packit f3e6b9
Packit f3e6b9
      } elsif ($arg =~ /^\[(.?)\]\s*(.*)/) {
Packit f3e6b9
         my($sep,$str) = ($1,$2);
Packit f3e6b9
         $sep = ","  if (! $sep);
Packit f3e6b9
         my @list = split(/\Q$sep\E/,$str);
Packit f3e6b9
         foreach my $e (@list) {
Packit f3e6b9
            $e = ""     if ($e eq "_blank_");
Packit f3e6b9
            $e = undef  if ($e eq "_undef_");
Packit f3e6b9
         }
Packit f3e6b9
         $arg = \@list;
Packit f3e6b9
Packit f3e6b9
      } elsif ($arg =~ /^\{(.?)\}\s*(.*)/) {
Packit f3e6b9
         my($sep,$str) = ($1,$2);
Packit f3e6b9
         $sep = ","  if (! $sep);
Packit f3e6b9
         my %hash = split(/\Q$sep\E/,$str);
Packit f3e6b9
         foreach my $key (keys %hash) {
Packit f3e6b9
            my $val = $hash{$key};
Packit f3e6b9
            $hash{$key} = undef  if ($val eq "_undef_");
Packit f3e6b9
            $hash{$key} = ""     if ($val eq "_blank_");
Packit f3e6b9
         }
Packit f3e6b9
         $arg = \%hash;
Packit f3e6b9
      }
Packit f3e6b9
   }
Packit f3e6b9
}
Packit f3e6b9
Packit f3e6b9
# Converts arguments to a printable version.
Packit f3e6b9
sub vals_to_print {
Packit f3e6b9
   my($listref) = @_;
Packit f3e6b9
Packit f3e6b9
   foreach my $arg (@$listref) {
Packit f3e6b9
      if (! defined $arg) {
Packit f3e6b9
         $arg = "_undef_";
Packit f3e6b9
Packit f3e6b9
      } elsif (! ref($arg)) {
Packit f3e6b9
         $arg = "_blank_"  if ($arg eq "");
Packit f3e6b9
Packit f3e6b9
      } else {
Packit f3e6b9
         my $ref = ref($arg);
Packit f3e6b9
         if      ($ref eq "SCALAR") {
Packit f3e6b9
            $arg = "\\ $$arg";
Packit f3e6b9
Packit f3e6b9
         } elsif ($ref eq "ARRAY") {
Packit f3e6b9
            my @list = @$arg;
Packit f3e6b9
            foreach my $e (@list) {
Packit f3e6b9
               $e = "_undef_", next   if (! defined($e));
Packit f3e6b9
               $e = "_blank_"         if ($e eq "");
Packit f3e6b9
            }
Packit f3e6b9
            $arg = join(" ","[",join(", ",@list),"]");
Packit f3e6b9
Packit f3e6b9
         } elsif ($ref eq "HASH") {
Packit f3e6b9
            %hash = %$arg;
Packit f3e6b9
            foreach my $key (keys %hash) {
Packit f3e6b9
               my $val = $hash{$key};
Packit f3e6b9
               $hash{$key} = "_undef_", next  if (! defined($val));
Packit f3e6b9
               $hash{$key} = "_blank_"        if ($val eq "_blank_");
Packit f3e6b9
            }
Packit f3e6b9
            $arg = join(" ","{",
Packit f3e6b9
                        join(", ",map { "$_ => $hash{$_}" }
Packit f3e6b9
                             (sort keys %hash)), "}");
Packit f3e6b9
            $arg =~ s/  +/ /g;
Packit f3e6b9
         }
Packit f3e6b9
      }
Packit f3e6b9
   }
Packit f3e6b9
}
Packit f3e6b9
Packit f3e6b9
1;
Packit f3e6b9
# Local Variables:
Packit f3e6b9
# mode: cperl
Packit f3e6b9
# indent-tabs-mode: nil
Packit f3e6b9
# cperl-indent-level: 3
Packit f3e6b9
# cperl-continued-statement-offset: 2
Packit f3e6b9
# cperl-continued-brace-offset: 0
Packit f3e6b9
# cperl-brace-offset: 0
Packit f3e6b9
# cperl-brace-imaginary-offset: 0
Packit f3e6b9
# cperl-label-offset: 0
Packit f3e6b9
# End:
Packit f3e6b9