2 # Copyright (c) 1996-2012 Sullivan Beck. All rights reserved.
3 # This program is free software; you can redistribute it and/or modify it
4 # under the same terms as Perl itself.
7 ###############################################################################
10 # 1996-??-?? Wrote initial version for Date::Manip module
12 # 1996-2001 Numerous changes
14 # 2001-03-29 Rewrote to make it easier to drop in for any module.
16 # 2001-06-19 Modifications to make space delimited stuff work better.
18 # 2001-08-23 Added support for undef args.
20 # 2007-08-14 Better support for undef/blank args.
22 # 2008-01-02 Better handling of $runtests.
24 # 2008-01-24 Better handling of undef/blank args when arguements are
25 # entered as lists instead of strings.
27 # 2008-01-25 Created a global $testnum variable to store the test number
30 # 2008-11-05 Slightly better handling of blank/undef in returned values.
32 # 2009-09-01 Added "-l" value to $runtests.
34 # 2009-09-30 Much better support for references.
36 # 2010-02-05 Fixed bug in passing tests as lists
38 # 2010-04-05 Renamed to testfunc.pl to avoid being called in a core module
40 ###############################################################################
42 use Storable qw(dclone);
44 # Usage: test_Func($funcref,$tests,$runtests,@extra)=@_;
46 # This takes a series of tests, runs them, compares the output of the tests
47 # with expected output, and reports any differences. Each test consists of
49 # a function passed in as a reference ($funcref)
50 # a series of arguments to be passed to the function
51 # the expected output from the function call
53 # Tests may be passed in in two methods: as a string, or as a reference.
55 # Using the string case, $tests is a newline delimited string. Each test
56 # takes one or more lines of the string. Tests are separated from each
57 # other by a blank line.
59 # Arguments and return value(s) may be written as a single line:
60 # ARG1 ARG2 ... ARGn ~ VAL1 VAL2 ... VALm
61 # or as multiple lines:
72 # If any of the arguments OR values have spaces in them, only the multiline
75 # If there is exactly one return value, the separating tilde is
77 # ARG1 ARG2 ... ARGn VAL1
85 # It is valid to have a function with no arguments or with no return
86 # value (or both). The "~" must be used:
88 # ARG1 ARG2 ... ARGn ~
90 # ~ VAL1 VAL2 ... VALm
94 # Leading and trailing space is ignored in the multi-line format.
96 # If desired, any of the ARGs or VALs may be the word "_undef_" which
97 # will be strictly interpreted as the perl undef value. The word "_blank_"
98 # may also be used to designate a defined but empty string.
100 # They may also be (in the multiline format) of the form:
102 # \ STRING : a string reference
104 # [] LIST : a list reference (where LIST is a
105 # comma separated list)
107 # [SEP] LIST : a list reference (where SEP is a
108 # single character separator)
110 # {} HASH : a hash reference (where HASH is
111 # a comma separated list)
113 # {SEP} HASH : a hash reference (where SEP is a
114 # single character separator)
116 # Alternately, the tests can be passed in as a list reference:
128 # @extra are extra arguments which are added to the function call.
130 # There are several ways to run the tests, depending on the value of
133 # If $runtests is 0, the tests are run in a non-interactive way suitable
134 # for running as part of a "make test".
136 # If $runtests is a positive number, it runs runs all tests starting at
137 # that value in a way suitable for running interactively.
139 # If $runtests is a negative number, it runs all tests starting at that
140 # value, but providing feedback at each test.
142 # If $runtests is a string "=N" (where N is a number), it runs only
145 # If $runtests is the string "-l", it lists the tests and the expected
146 # output without running any.
149 my($funcref,$tests,$runtests,@extra)=@_;
152 $runtests = 0 if (! $runtests);
153 my($starttest,$feedback,$endtest,$runtest);
154 if ($runtests eq "0" or $runtests eq "-0") {
159 } elsif ($runtests =~ /^\d+$/){
160 $starttest = $runtests;
164 } elsif ($runtests =~ /^-(\d+)$/) {
169 } elsif ($runtests =~ /^=(\d+)$/) {
174 } elsif ($runtests eq "-l") {
180 die "ERROR: unknown argument(s): $runtests";
183 my($tests_as_list) = 0;
184 if (ref($tests) eq "ARRAY") {
192 my(@lines)=split(/\n/,$tests);
195 my $line = shift(@lines);
198 next if ($line =~ /^$comment/);
206 push(@tests,[ @test ]);
211 push(@tests,[ @test ]);
214 # Get arg/val lists for each test.
216 foreach my $test (@tests) {
221 @tmp = split(/\s+/,$tmp[0]) if ($#tmp == 0);
225 for ($i=0; $i<=$#tmp; $i++) {
226 if ($tmp[$i] eq "~") {
236 @arg=@tmp[0..($sep-1)];
237 @val=@tmp[($sep+1)..$#tmp];
239 $test = [ [@arg],[@val] ];
243 my($ntest)=$#tests + 1;
244 print "1..$ntest\n" if ($feedback && $runtest);
248 @t = ($starttest..$endtest);
250 @t = ($starttest..$ntest);
257 if ($tests_as_list) {
258 @arg = @{ $tests[$t-1][0] };
260 my $arg = dclone($tests[$t-1][0]);
262 print_to_vals(\@arg);
265 my $argprt = dclone(\@arg);
266 my @argprt = @$argprt;
267 vals_to_print(\@argprt);
269 my $exp = dclone($tests[$t-1][1]);
271 print_to_vals(\@exp);
272 vals_to_print(\@exp);
278 @ans = &$funcref(@arg,@extra);
280 vals_to_print(\@ans);
282 # Compare the results
284 foreach my $arg (@arg) {
285 $arg = "_undef_" if (! defined $arg);
286 $arg = "_blank_" if ($arg eq "");
288 $arg = join("\n ",@argprt,@extra);
289 $ans = join("\n ",@ans);
290 $exp = join("\n ",@exp);
293 print "########################\n";
295 print "Args = $arg\n";
296 print "Expected = $exp\n";
297 } elsif ($ans ne $exp) {
299 warn "########################\n";
300 warn "Args = $arg\n";
301 warn "Expected = $exp\n";
303 warn "########################\n";
305 print "ok $t\n" if ($feedback);
310 # The following is similar but it takes input from an input file and
311 # sends output to an output file.
313 # $files is a reference to a list of tests. If one of the tests is named
314 # "foobar", the input is from "foobar.in", output is to "foobar.out", and
315 # the expected output is in "foobar.exp".
317 # The function stored in $funcref is called as:
318 # &$funcref($in,$out,@extra)
319 # where $in is the name of the input file, $out is the name of the output
320 # file, and @extra are any additional arguments that are required.
322 # The function should return 0 on success, or an error message.
325 my($funcref,$files,$runtests,@extra)=@_;
328 $runtests=0 if (! $runtests);
330 my($ntest)=$#files + 1;
331 print "1..$ntest\n" if (! $runtests);
335 @t = ($runtests..$ntest);
336 } elsif ($runtests < 0) {
344 my $test = $files[$t-1];
345 my $expf = "$test.exp";
346 my $outf = "$test.out";
348 if (! -f $test || ! -f $expf) {
350 warn "Test: $test: missing input/outpuf information\n";
354 my $err = &$funcref($test,$outf,@extra);
357 warn "Test: $test: $err\n";
362 open(FH,$expf) || do {
364 warn "Test: $test: $!\n";
369 my $exp = join("",@exp);
370 open(FH,$outf) || do {
372 warn "Test: $test: $!\n";
377 my $out = join("",@out);
381 warn "Test: $test: output differs from expected value\n";
385 print "ok $t\n" if (! $runtests);
389 # Converts a printable version of arguments to actual arguments
393 foreach my $arg (@$listref) {
394 next if (! defined($arg));
395 if ($arg eq "_undef_") {
398 } elsif ($arg eq "_blank_") {
401 } elsif ($arg =~ /^\\\s*(.*)/) {
405 } elsif ($arg =~ /^\[(.?)\]\s*(.*)/) {
406 my($sep,$str) = ($1,$2);
407 $sep = "," if (! $sep);
408 my @list = split(/\Q$sep\E/,$str);
409 foreach my $e (@list) {
410 $e = "" if ($e eq "_blank_");
411 $e = undef if ($e eq "_undef_");
415 } elsif ($arg =~ /^\{(.?)\}\s*(.*)/) {
416 my($sep,$str) = ($1,$2);
417 $sep = "," if (! $sep);
418 my %hash = split(/\Q$sep\E/,$str);
419 foreach my $key (keys %hash) {
420 my $val = $hash{$key};
421 $hash{$key} = undef if ($val eq "_undef_");
422 $hash{$key} = "" if ($val eq "_blank_");
429 # Converts arguments to a printable version.
433 foreach my $arg (@$listref) {
434 if (! defined $arg) {
437 } elsif (! ref($arg)) {
438 $arg = "_blank_" if ($arg eq "");
442 if ($ref eq "SCALAR") {
445 } elsif ($ref eq "ARRAY") {
447 foreach my $e (@list) {
448 $e = "_undef_", next if (! defined($e));
449 $e = "_blank_" if ($e eq "");
451 $arg = join(" ","[",join(", ",@list),"]");
453 } elsif ($ref eq "HASH") {
455 foreach my $key (keys %hash) {
456 my $val = $hash{$key};
457 $hash{$key} = "_undef_", next if (! defined($val));
458 $hash{$key} = "_blank_" if ($val eq "_blank_");
461 join(", ",map { "$_ => $hash{$_}" }
462 (sort keys %hash)), "}");
472 # indent-tabs-mode: nil
473 # cperl-indent-level: 3
474 # cperl-continued-statement-offset: 2
475 # cperl-continued-brace-offset: 0
476 # cperl-brace-offset: 0
477 # cperl-brace-imaginary-offset: 0
478 # cperl-label-offset: -2