File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 220 364 60.4
branch 50 178 28.0
condition 19 74 25.6
subroutine 39 60 65.0
pod 19 26 73.0
total 347 702 49.4


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