File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 231 357 64.7
branch 59 172 34.3
condition 20 71 28.1
subroutine 41 59 69.4
pod 19 25 76.0
total 370 684 54.0


line stmt bran cond sub pod time code
1             #line 1
2             # TODO:
3             #
4 9     9   27204 package Test::Base;
  9         20  
5 9     9   3148 use 5.006001;
  9         161  
  9         46  
6 9     9   50 use Spiffy 0.30 -Base;
  9     9   12  
  9     9   241  
  9         29  
  9         6  
  9         196  
  9         28  
  9         8  
  9         41  
7             use Spiffy ':XXX';
8             our $VERSION = '0.52';
9              
10             my @test_more_exports;
11 9     9   199 BEGIN {
12             @test_more_exports = qw(
13             ok isnt like unlike is_deeply cmp_ok
14             skip todo_skip pass fail
15             eq_array eq_hash eq_set
16             plan can_ok isa_ok diag
17             use_ok
18             $TODO
19             );
20             }
21 9     9   3828  
  9         38  
  9         65  
22 9     9   44 use Test::More import => \@test_more_exports;
  9         13  
  9         3596  
23             use Carp;
24              
25             our @EXPORT = (@test_more_exports, qw(
26             is no_diff
27              
28             blocks next_block first_block
29             delimiters spec_file spec_string
30             filters filters_delay filter_arguments
31             run run_compare run_is run_is_deeply run_like run_unlike
32             WWW XXX YYY ZZZ
33             tie_output
34              
35             find_my_self default_object
36              
37             croak carp cluck confess
38             ));
39              
40             field '_spec_file';
41             field '_spec_string';
42             field _filters => [qw(norm trim)];
43             field _filters_map => {};
44             field spec =>
45             -init => '$self->_spec_init';
46             field block_list =>
47             -init => '$self->_block_list_init';
48             field _next_list => [];
49             field block_delim =>
50             -init => '$self->block_delim_default';
51             field data_delim =>
52             -init => '$self->data_delim_default';
53             field _filters_delay => 0;
54              
55             field block_delim_default => '===';
56             field data_delim_default => '---';
57              
58             my $default_class;
59             my $default_object;
60             my $reserved_section_names = {};
61 11     11 1 12  
62 11   66     41 sub default_object {
63 11         11 $default_object ||= $default_class->new;
64             return $default_object;
65             }
66              
67             my $import_called = 0;
68 9     9   47 sub import() {
69 9 50       48 $import_called = 1;
70             my $class = (grep /^-base$/i, @_)
71             ? scalar(caller)
72 9 50       23 : $_[0];
73 9         12 if (not defined $default_class) {
74             $default_class = $class;
75             }
76             # else {
77             # croak "Can't use $class after using $default_class"
78             # unless $default_class->isa($class);
79             # }
80 9 50       30  
81 9         9 unless (grep /^-base$/i, @_) {
82 9         27 my @args;
83 0 0       0 for (my $ii = 1; $ii <= $#_; ++$ii) {
84 0         0 if ($_[$ii] eq '-package') {
85             ++$ii;
86 0         0 } else {
87             push @args, $_[$ii];
88             }
89 9 50       31 }
90             Test::More->import(import => \@test_more_exports, @args)
91             if @args;
92             }
93 9         18
94 9         139 _strict_warnings();
95             goto &Spiffy::import;
96             }
97              
98             # Wrap Test::Builder::plan
99             my $plan_code = \&Test::Builder::plan;
100             my $Have_Plan = 0;
101 9     9   43 {
  9         27  
  9         23017  
102             no warnings 'redefine';
103 2     2   3 *Test::Builder::plan = sub {
104 2         10 $Have_Plan = 1;
105             goto &$plan_code;
106             };
107             }
108              
109             my $DIED = 0;
110             $SIG{__DIE__} = sub { $DIED = 1; die @_ };
111 18     18 0 10  
  18         33  
112 35     35 0 29 sub block_class { $self->find_class('Block') }
  35         41  
113             sub filter_class { $self->find_class('Filter') }
114 53     53 0 40  
115 53         36 sub find_class {
116 53         74 my $suffix = shift;
117 53 100       133 my $class = ref($self) . "::$suffix";
118 2         5 return $class if $class->can('new');
119 2 50       10 $class = __PACKAGE__ . "::$suffix";
120 2         150 return $class if $class->can('new');
121 2 50       13 eval "require $class";
122 0         0 return $class if $class->can('new');
123             die "Can't find a class for $suffix";
124             }
125 0     0 0 0  
126 0 0       0 sub check_late {
127 0         0 if ($self->{block_list}) {
128 0         0 my $caller = (caller(1))[3];
129 0         0 $caller =~ s/.*:://;
130             croak "Too late to call $caller()"
131             }
132             }
133              
134 11 50   11 0 35 sub find_my_self() {
135             my $self = ref($_[0]) eq $default_class
136             ? splice(@_, 0, 1)
137 11         25 : default_object();
138             return $self, @_;
139             }
140              
141 1     1 1 7 sub blocks() {
142             (my ($self), @_) = find_my_self(@_);
143 1 50       3  
144             croak "Invalid arguments passed to 'blocks'"
145 1 50 33     4 if @_ > 1;
146             croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
147             if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
148 1         29  
149             my $blocks = $self->block_list;
150 1   50     6
151             my $section_name = shift || '';
152 1 50       3 my @blocks = $section_name
  0         0  
153             ? (grep { exists $_->{$section_name} } @$blocks)
154             : (@$blocks);
155 1 50       10  
156             return scalar(@blocks) unless wantarray;
157 0 0       0
158             return (@blocks) if $self->_filters_delay;
159 0         0  
160 0 0       0 for my $block (@blocks) {
161             $block->run_filters
162             unless $block->is_filtered;
163             }
164 0         0  
165             return (@blocks);
166             }
167              
168 0     0 1 0 sub next_block() {
169 0         0 (my ($self), @_) = find_my_self(@_);
170 0 0       0 my $list = $self->_next_list;
171 0         0 if (@$list == 0) {
  0         0  
172 0         0 $list = [@{$self->block_list}, undef];
173             $self->_next_list($list);
174 0         0 }
175 0 0 0     0 my $block = shift @$list;
176 0         0 if (defined $block and not $block->is_filtered) {
177             $block->run_filters;
178 0         0 }
179             return $block;
180             }
181              
182 0     0 1 0 sub first_block() {
183 0         0 (my ($self), @_) = find_my_self(@_);
184 0         0 $self->_next_list([]);
185             $self->next_block;
186             }
187              
188 0     0 1 0 sub filters_delay() {
189 0 0       0 (my ($self), @_) = find_my_self(@_);
190             $self->_filters_delay(defined $_[0] ? shift : 1);
191             }
192              
193 0     0 1 0 sub delimiters() {
194 0         0 (my ($self), @_) = find_my_self(@_);
195 0         0 $self->check_late;
196 0   0     0 my ($block_delimiter, $data_delimiter) = @_;
197 0   0     0 $block_delimiter ||= $self->block_delim_default;
198 0         0 $data_delimiter ||= $self->data_delim_default;
199 0         0 $self->block_delim($block_delimiter);
200 0         0 $self->data_delim($data_delimiter);
201             return $self;
202             }
203              
204 0     0 1 0 sub spec_file() {
205 0         0 (my ($self), @_) = find_my_self(@_);
206 0         0 $self->check_late;
207 0         0 $self->_spec_file(shift);
208             return $self;
209             }
210              
211 0     0 1 0 sub spec_string() {
212 0         0 (my ($self), @_) = find_my_self(@_);
213 0         0 $self->check_late;
214 0         0 $self->_spec_string(shift);
215             return $self;
216             }
217              
218 2     2 1 13 sub filters() {
219 2 50       8 (my ($self), @_) = find_my_self(@_);
220 2         49 if (ref($_[0]) eq 'HASH') {
221             $self->_filters_map(shift);
222             }
223 0         0 else {
224 0         0 my $filters = $self->_filters;
225             push @$filters, @_;
226 2         5 }
227             return $self;
228             }
229              
230 0     0 1 0 sub filter_arguments() {
231             $Test::Base::Filter::arguments;
232             }
233 0     0 0 0  
234 0 0 0     0 sub have_text_diff {
  0         0  
  0         0  
235             eval { require Text::Diff; 1 } &&
236             $Text::Diff::VERSION >= 0.35 &&
237             $Algorithm::Diff::VERSION >= 1.15;
238             }
239              
240 6     6 1 9 sub is($$;$) {
241 6         9 (my ($self), @_) = find_my_self(@_);
242 6         6 my ($actual, $expected, $name) = @_;
243 6 50 33     49 local $Test::Builder::Level = $Test::Builder::Level + 1;
      33        
      33        
      33        
      33        
244             if ($ENV{TEST_SHOW_NO_DIFFS} or
245             not defined $actual or
246             not defined $expected or
247             $actual eq $expected or
248             not($self->have_text_diff) or
249             $expected !~ /\n./s
250 6         16 ) {
251             Test::More::is($actual, $expected, $name);
252             }
253 0 0       0 else {
254 0         0 $name = '' unless defined $name;
255             ok $actual eq $expected,
256             $name . "\n" . Text::Diff::diff(\$expected, \$actual);
257             }
258             }
259              
260 1     1 1 4 sub run(&;$) {
261 1         2 (my ($self), @_) = find_my_self(@_);
262 1         2 my $callback = shift;
  1         23  
263 1 50       30 for my $block (@{$self->block_list}) {
264 0         0 $block->run_filters unless $block->is_filtered;
  0         0  
265             &{$callback}($block);
266             }
267             }
268              
269 1     1   1 my $name_error = "Can't determine section names";
270 1 50       5 sub _section_names {
271 0 0       0 return @_ if @_ == 2;
272             my $block = $self->first_block
273             or croak $name_error;
274 0         0 my @names = grep {
275 0 0       0 $_ !~ /^(ONLY|LAST|SKIP)$/;
  0         0  
276 0 0       0 } @{$block->{_section_order}[0] || []};
277             croak "$name_error. Need two sections in first block"
278 0         0 unless @names == 2;
279             return @names;
280             }
281 1     1   2  
282 1 50       3 sub _assert_plan {
283             plan('no_plan') unless $Have_Plan;
284             }
285 9     9   51  
286 9 50 66     91 sub END {
      33        
287             run_compare() unless $Have_Plan or $DIED or not $import_called;
288             }
289              
290 0     0 1 0 sub run_compare() {
291 0         0 (my ($self), @_) = find_my_self(@_);
292 0         0 $self->_assert_plan;
293 0         0 my ($x, $y) = $self->_section_names(@_);
294 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
  0         0  
295 0 0 0     0 for my $block (@{$self->block_list}) {
296 0 0       0 next unless exists($block->{$x}) and exists($block->{$y});
297 0 0       0 $block->run_filters unless $block->is_filtered;
    0          
298 0 0       0 if (ref $block->$x) {
299             is_deeply($block->$x, $block->$y,
300             $block->name ? $block->name : ());
301             }
302 0 0       0 elsif (ref $block->$y eq 'Regexp') {
303 0 0       0 my $regexp = ref $y ? $y : $block->$y;
304             like($block->$x, $regexp, $block->name ? $block->name : ());
305             }
306 0 0       0 else {
307             is($block->$x, $block->$y, $block->name ? $block->name : ());
308             }
309             }
310             }
311              
312 1     1 1 5 sub run_is() {
313 1         3 (my ($self), @_) = find_my_self(@_);
314 1         3 $self->_assert_plan;
315 1         2 my ($x, $y) = $self->_section_names(@_);
316 1         3 local $Test::Builder::Level = $Test::Builder::Level + 1;
  1         24  
317 6 50 33     22 for my $block (@{$self->block_list}) {
318 6 50       112 next unless exists($block->{$x}) and exists($block->{$y});
319 6 100       10 $block->run_filters unless $block->is_filtered;
320             is($block->$x, $block->$y,
321             $block->name ? $block->name : ()
322             );
323             }
324             }
325              
326 0     0 1 0 sub run_is_deeply() {
327 0         0 (my ($self), @_) = find_my_self(@_);
328 0         0 $self->_assert_plan;
329 0         0 my ($x, $y) = $self->_section_names(@_);
  0         0  
330 0 0 0     0 for my $block (@{$self->block_list}) {
331 0 0       0 next unless exists($block->{$x}) and exists($block->{$y});
332 0 0       0 $block->run_filters unless $block->is_filtered;
333             is_deeply($block->$x, $block->$y,
334             $block->name ? $block->name : ()
335             );
336             }
337             }
338              
339 0     0 1 0 sub run_like() {
340 0         0 (my ($self), @_) = find_my_self(@_);
341 0         0 $self->_assert_plan;
342 0         0 my ($x, $y) = $self->_section_names(@_);
  0         0  
343 0 0 0     0 for my $block (@{$self->block_list}) {
344 0 0       0 next unless exists($block->{$x}) and defined($y);
345 0 0       0 $block->run_filters unless $block->is_filtered;
346 0 0       0 my $regexp = ref $y ? $y : $block->$y;
347             like($block->$x, $regexp,
348             $block->name ? $block->name : ()
349             );
350             }
351             }
352              
353 0     0 1 0 sub run_unlike() {
354 0         0 (my ($self), @_) = find_my_self(@_);
355 0         0 $self->_assert_plan;
356 0         0 my ($x, $y) = $self->_section_names(@_);
  0         0  
357 0 0 0     0 for my $block (@{$self->block_list}) {
358 0 0       0 next unless exists($block->{$x}) and defined($y);
359 0 0       0 $block->run_filters unless $block->is_filtered;
360 0 0       0 my $regexp = ref $y ? $y : $block->$y;
361             unlike($block->$x, $regexp,
362             $block->name ? $block->name : ()
363             );
364             }
365             }
366 2     2   3  
367 2         4 sub _pre_eval {
368 2 50       12 my $spec = shift;
369             return $spec unless $spec =~
370 0         0 s/\A\s*<<<(.*?)>>>\s*$//sm;
371 0         0 my $eval_code = $1;
372 0 0       0 eval "package main; $eval_code";
373 0         0 croak $@ if $@;
374             return $spec;
375             }
376 2     2   4  
377 2         33 sub _block_list_init {
378 2         7 my $spec = $self->spec;
379 2         48 $spec = $self->_pre_eval($spec);
380 2         248 my $cd = $self->block_delim;
381 2         9 my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
382 2         41 my $blocks = $self->_choose_blocks(@hunks);
383 2         2 $self->block_list($blocks); # Need to set early for possible filter use
384 2         4 my $seq = 1;
385 18         258 for my $block (@$blocks) {
386 18         267 $block->blocks_object($self);
387             $block->seq_num($seq++);
388 2         10 }
389             return $blocks;
390             }
391 2     2   5  
392 2         2 sub _choose_blocks {
393 2         6 my $blocks = [];
394 18         24 for my $hunk (@_) {
395 18 50       36 my $block = $self->_make_block($hunk);
396 0         0 if (exists $block->{ONLY}) {
397             return [$block];
398 18 50       22 }
399 18         18 next if exists $block->{SKIP};
400 18 50       27 push @$blocks, $block;
401 0         0 if (exists $block->{LAST}) {
402             return $blocks;
403             }
404 2         4 }
405             return $blocks;
406             }
407 36     36   27  
408 36         26 sub _check_reserved {
409             my $id = shift;
410 36 50 33     123 croak "'$id' is a reserved name. Use something else.\n"
411             if $reserved_section_names->{$id} or
412             $id =~ /^_/;
413             }
414 18     18   17  
415 18         20 sub _make_block {
416 18         286 my $hunk = shift;
417 18         243 my $cd = $self->block_delim;
418 18         30 my $dd = $self->data_delim;
419 18 50       127 my $block = $self->block_class->new;
420 18         25 $hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
421 18         142 my $name = $1;
422 18         20 my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
423 18   50     49 my $description = shift @parts;
424 18 50       24 $description ||= '';
425 18         17 unless ($description =~ /\S/) {
426             $description = $name;
427 18         46 }
428 18         26 $description =~ s/\s*\z//;
429             $block->set_value(description => $description);
430 18         19
431 18         19 my $section_map = {};
432 18         26 my $section_order = [];
433 36         51 while (@parts) {
434 36         48 my ($type, $filters, $value) = splice(@parts, 0, 3);
435 36 50       42 $self->_check_reserved($type);
436 36 50       42 $value = '' unless defined $value;
437 36 100       68 $filters = '' unless defined $filters;
438 12 50       18 if ($filters =~ /:(\s|\z)/) {
439             croak "Extra lines not allowed in '$type' section"
440 12         28 if $value =~ /\S/;
441 12 50       17 ($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
442 12         32 $value = '' unless defined $value;
443             $value =~ s/^\s*(.*?)\s*$/$1/;
444 36         67 }
445             $section_map->{$type} = {
446             filters => $filters,
447 36         39 };
448 36         39 push @$section_order, $type;
449             $block->set_value($type, $value);
450 18         25 }
451 18         22 $block->set_value(name => $name);
452 18         20 $block->set_value(_section_map => $section_map);
453 18         27 $block->set_value(_section_order => $section_order);
454             return $block;
455             }
456 2     2   4  
457 2 50       39 sub _spec_init {
458             return $self->_spec_string
459 2         7 if $self->_spec_string;
460 2         2 local $/;
461 2 50       41 my $spec;
462 0 0       0 if (my $spec_file = $self->_spec_file) {
463 0         0 open FILE, $spec_file or die $!;
464 0         0 $spec = ;
465             close FILE;
466             }
467 2         3 else {
468             $spec = do {
469 9     9   49 package main;
  9         12  
  9         3397  
470 2         42 no warnings 'once';
471             ;
472             };
473 2         9 }
474             return $spec;
475             }
476              
477 9     9   40 sub _strict_warnings() {
478 9         10 require Filter::Util::Call;
479             my $done = 0;
480             Filter::Util::Call::filter_add(
481 9 50   9   27 sub {
482 9         16 return 0 if $done;
483 9         72 my ($data, $end) = ('', '');
484 249 50       275 while (my $status = Filter::Util::Call::filter_read()) {
485 249 100       321 return $status if $status < 0;
486 9         16 if (/^__(?:END|DATA)__\r?$/) {
487 9         15 $end = $_;
488             last;
489 240         182 }
490 240         422 $data .= $_;
491             $_ = '';
492 9         28 }
493 9         224 $_ = "use strict;use warnings;$data$end";
494             $done = 1;
495 9         49 }
496             );
497             }
498              
499 0     0 1 0 sub tie_output() {
500 0 0       0 my $handle = shift;
501 0         0 die "No buffer to tie" unless @_;
502             tie $handle, 'Test::Base::Handle', $_[0];
503             }
504 0     0 1 0  
505 0         0 sub no_diff {
506             $ENV{TEST_SHOW_NO_DIFFS} = 1;
507             }
508              
509             package Test::Base::Handle;
510              
511 0     0   0 sub TIEHANDLE() {
512 0         0 my $class = shift;
513             bless \ $_[0], $class;
514             }
515 0     0   0  
516 0         0 sub PRINT {
517             $$self .= $_ for @_;
518             }
519              
520             #===============================================================================
521             # Test::Base::Block
522             #
523             # This is the default class for accessing a Test::Base block object.
524             #===============================================================================
525             package Test::Base::Block;
526             our @ISA = qw(Spiffy);
527              
528             our @EXPORT = qw(block_accessor);
529 0     0   0  
530 0         0 sub AUTOLOAD {
531             return;
532             }
533              
534 26     26   26 sub block_accessor() {
535 9     9   42 my $accessor = shift;
  9         13  
  9         1344  
536 26 50       82 no strict 'refs';
537             return if defined &$accessor;
538 48     48   33 *$accessor = sub {
539 48 50       72 my $self = shift;
540 0         0 if (@_) {
541             Carp::croak "Not allowed to set values for '$accessor'";
542 48 50       29 }
  48         99  
543             my @list = @{$self->{$accessor} || []};
544 48 100       104 return wantarray
545             ? (@list)
546 26         105 : $list[0];
547             };
548             }
549              
550             block_accessor 'name';
551             block_accessor 'description';
552             Spiffy::field 'seq_num';
553             Spiffy::field 'is_filtered';
554             Spiffy::field 'blocks_object';
555             Spiffy::field 'original_values' => {};
556 160     160   118  
557 9     9   40 sub set_value {
  9         10  
  9         1510  
558 160         99 no strict 'refs';
559 160 100       265 my $accessor = shift;
560             block_accessor $accessor
561 160         363 unless defined &$accessor;
562             $self->{$accessor} = [@_];
563             }
564 7     7   6  
565 7         10 sub run_filters {
566 7         34 my $map = $self->_section_map;
567 7 50       102 my $order = $self->_section_order;
568             Carp::croak "Attempt to filter a block twice"
569 7         11 if $self->is_filtered;
570 14         18 for my $type (@$order) {
571 14         27 my $filters = $map->{$type}{filters};
572 14         244 my @value = $self->$type;
573 14         27 $self->original_values->{$type} = $value[0];
574 53 50       75 for my $filter ($self->_get_filters($type, $filters)) {
575             $Test::Base::Filter::arguments =
576 53         58 $filter =~ s/=(.*)$// ? $1 : undef;
577 9     9   40 my $function = "main::$filter";
  9         12  
  9         3891  
578 53 100       135 no strict 'refs';
579 18         27 if (defined &$function) {
580 18         34 $_ = join '', @value;
581 18 50 33     192 @value = &$function(@value);
      33        
582             if (not(@value) or
583             @value == 1 and $value[0] =~ /\A(\d+|)\z/
584 0         0 ) {
585             @value = ($_);
586             }
587             }
588 35         508 else {
589 35 50       54 my $filter_object = $self->blocks_object->filter_class->new;
590             die "Can't find a function or method for '$filter' filter\n"
591 35         516 unless $filter_object->can($filter);
592 35         74 $filter_object->current_block($self);
593             @value = $filter_object->$filter(@value);
594             }
595             # Set the value after each filter since other filters may be
596 52         73 # introspecting.
597             $self->set_value($type, @value);
598             }
599 6         98 }
600             $self->is_filtered(1);
601             }
602 14     14   12  
603 14         14 sub _get_filters {
604 14   50     46 my $type = shift;
605 14         43 my $string = shift || '';
606 14         17 $string =~ s/\s*(.*?)\s*/$1/;
607 14   100     189 my @filters = ();
608 14 50       34 my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
609 14         24 $map_filters = [ $map_filters ] unless ref $map_filters;
610 14         7 my @append = ();
611 14         188 for (
612             @{$self->blocks_object->_filters},
613             @$map_filters,
614             split(/\s+/, $string),
615 53         42 ) {
616 53 50       60 my $filter = $_;
617 53 50       83 last unless length $filter;
    50          
618 0         0 if ($filter =~ s/^-//) {
  0         0  
619             @filters = grep { $_ ne $filter } @filters;
620             }
621 0         0 elsif ($filter =~ s/^\+//) {
622             push @append, $filter;
623             }
624 53         48 else {
625             push @filters, $filter;
626             }
627 14         32 }
628             return @filters, @append;
629             }
630              
631             {
632             %$reserved_section_names = map {
633             ($_, 1);
634             } keys(%Test::Base::Block::), qw( new DESTROY );
635             }
636              
637             __DATA__