File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 214 364 58.7
branch 49 178 27.5
condition 15 74 20.2
subroutine 37 60 61.6
pod 19 26 73.0
total 334 702 47.5


line stmt bran cond sub pod time code
1             #line 1
2             # TODO:
3             #
4 5     5   11729 package Test::Base;
  5         17  
  5         286  
5 5     5   3088 use 5.006001;
  5         154  
  5         38  
6 5     5   57 use Spiffy 0.30 -Base;
  5     5   9  
  5     5   192  
  5         31  
  5         12  
  5         173  
  5         33  
  5         10  
  5         32  
7             use Spiffy ':XXX';
8             our $VERSION = '0.54';
9              
10             my @test_more_exports;
11 5     5   140 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 5     5   4567  
  5         21  
  5         62  
22 5     5   35 use Test::More import => \@test_more_exports;
  5         8  
  5         2880  
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 27     27 1 46  
63 27   66     158 sub default_object {
64 27         53 $default_object ||= $default_class->new;
65             return $default_object;
66             }
67              
68             my $import_called = 0;
69 5     5   48 sub import() {
70 5 50       38 $import_called = 1;
71             my $class = (grep /^-base$/i, @_)
72             ? scalar(caller)
73 5 50       22 : $_[0];
74 5         11 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 5 50       24  
82 5         9 unless (grep /^-base$/i, @_) {
83 5         26 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 5 50       19 }
91             Test::More->import(import => \@test_more_exports, @args)
92             if @args;
93             }
94 5         17
95 5         107 _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 5     5   30 {
  5         10  
  5         22896  
103             no warnings 'redefine';
104 5     5   14 *Test::Builder::plan = sub {
105 5         33 $Have_Plan = 1;
106             goto &$plan_code;
107             };
108             }
109              
110             my $DIED = 0;
111             $SIG{__DIE__} = sub { $DIED = 1; die @_ };
112 14     14 0 22  
  14         41  
113 56     56 0 77 sub block_class { $self->find_class('Block') }
  56         111  
114             sub filter_class { $self->find_class('Filter') }
115 70     70 0 87  
116 70         89 sub find_class {
117 70         153 my $suffix = shift;
118 70 100       323 my $class = ref($self) . "::$suffix";
119 5         17 return $class if $class->can('new');
120 5 50       34 $class = __PACKAGE__ . "::$suffix";
121 5         432 return $class if $class->can('new');
122 5 50       55 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 27 50   27 0 129 sub find_my_self() {
136             my $self = ref($_[0]) eq $default_class
137             ? splice(@_, 0, 1)
138 27         179 : default_object();
139             return $self, @_;
140             }
141              
142 5     5 1 54 sub blocks() {
143             (my ($self), @_) = find_my_self(@_);
144 5 50       30  
145             croak "Invalid arguments passed to 'blocks'"
146 5 50 33     26 if @_ > 1;
147             croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
148             if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
149 5         224  
150             my $blocks = $self->block_list;
151 5   50     55
152 0         0 my $section_name = shift || '';
153 5 50       41 my @blocks = $section_name
154             ? (grep { exists $_->{$section_name} } @$blocks)
155             : (@$blocks);
156 5 50       74  
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 0     0 1 0 sub filters() {
225 0 0       0 (my ($self), @_) = find_my_self(@_);
226 0         0 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 0         0 }
233             return $self;
234             }
235              
236 0     0 1 0 sub filter_arguments() {
237             $Test::Base::Filter::arguments;
238             }
239 0     0 0 0  
240 0 0 0     0 sub have_text_diff {
  0         0  
  0         0  
241             eval { require Text::Diff; 1 } &&
242             $Text::Diff::VERSION >= 0.35 &&
243             $Algorithm::Diff::VERSION >= 1.15;
244             }
245              
246 17     17 1 51 sub is($$;$) {
247 17         44 (my ($self), @_) = find_my_self(@_);
248 17         31 my ($actual, $expected, $name) = @_;
249 17 50 33     241 local $Test::Builder::Level = $Test::Builder::Level + 1;
      33        
      33        
      33        
      33        
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 17         68 ) {
257             Test::More::is($actual, $expected, $name);
258             }
259 0 0       0 else {
260 0         0 $name = '' unless defined $name;
261             ok $actual eq $expected,
262             $name . "\n" . Text::Diff::diff(\$expected, \$actual);
263             }
264             }
265              
266 5     5 1 25 sub run(&;$) {
267 5         16 (my ($self), @_) = find_my_self(@_);
268 5         14 my $callback = shift;
  5         257  
269 14 50       376 for my $block (@{$self->block_list}) {
270 14         22 $block->run_filters unless $block->is_filtered;
  14         46  
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 5     5   190  
292 5 0 33     49 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 5     5   40  
373 5         12 sub _pre_eval {
374 5 50       35 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 5     5   14  
383 5         210 sub _block_list_init {
384 5         28 my $spec = $self->spec;
385 5         136 $spec = $self->_pre_eval($spec);
386 5         342 my $cd = $self->block_delim;
387 5         31 my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
388 5         148 my $blocks = $self->_choose_blocks(@hunks);
389 5         9 $self->block_list($blocks); # Need to set early for possible filter use
390 5         16 my $seq = 1;
391 14         324 for my $block (@$blocks) {
392 14         317 $block->blocks_object($self);
393             $block->seq_num($seq++);
394 5         30 }
395             return $blocks;
396             }
397 5     5   13  
398 5         20 sub _choose_blocks {
399 5         19 my $blocks = [];
400 14         41 for my $hunk (@_) {
401 14 50       48 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 14 50       47 }
407 14         26 next if exists $block->{SKIP};
408 14 50       51 push @$blocks, $block;
409 0         0 if (exists $block->{LAST}) {
410             return $blocks;
411             }
412 5         17 }
413             return $blocks;
414             }
415 28     28   37  
416 28         39 sub _check_reserved {
417 28 50 33     168 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 14     14   23  
423 14         24 sub _make_block {
424 14         358 my $hunk = shift;
425 14         326 my $cd = $self->block_delim;
426 14         50 my $dd = $self->data_delim;
427 14 50       212 my $block = $self->block_class->new;
428 14         45 $hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
429 14         255 my $name = $1;
430 14         39 my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
431 14   50     68 my $description = shift @parts;
432 14 50       41 $description ||= '';
433 14         24 unless ($description =~ /\S/) {
434             $description = $name;
435 14         74 }
436 14         45 $description =~ s/\s*\z//;
437             $block->set_value(description => $description);
438 14         30
439 14         22 my $section_map = {};
440 14         41 my $section_order = [];
441 28         71 while (@parts) {
442 28         73 my ($type, $filters, $value) = splice(@parts, 0, 3);
443 28 100       65 $self->_check_reserved($type);
444 28 50       61 $value = '' unless defined $value;
445 28 50       113 $filters = '' unless defined $filters;
446 28 50       64 if ($filters =~ /:(\s|\z)/) {
447             croak "Extra lines not allowed in '$type' section"
448 28         152 if $value =~ /\S/;
449 28 50       72 ($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
450 28         159 $value = '' unless defined $value;
451             $value =~ s/^\s*(.*?)\s*$/$1/;
452 28         100 }
453             $section_map->{$type} = {
454             filters => $filters,
455 28         51 };
456 28         69 push @$section_order, $type;
457             $block->set_value($type, $value);
458 14         39 }
459 14         36 $block->set_value(name => $name);
460 14         42 $block->set_value(_section_map => $section_map);
461 14         45 $block->set_value(_section_order => $section_order);
462             return $block;
463             }
464 5     5   14  
465 5 50       146 sub _spec_init {
466             return $self->_spec_string
467 5         22 if $self->_spec_string;
468 5         12 local $/;
469 5 50       132 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 5         12 else {
476             $spec = do {
477 5     5   53 package main;
  5         10  
  5         3046  
478 5         132 no warnings 'once';
479             ;
480             };
481 5         38 }
482             return $spec;
483             }
484              
485 5     5   31 sub _strict_warnings() {
486 5         11 require Filter::Util::Call;
487             my $done = 0;
488             Filter::Util::Call::filter_add(
489 5 50   5   20 sub {
490 5         15 return 0 if $done;
491 5         109 my ($data, $end) = ('', '');
492 78 50       133 while (my $status = Filter::Util::Call::filter_read()) {
493 78 100       172 return $status if $status < 0;
494 5         10 if (/^__(?:END|DATA)__\r?$/) {
495 5         13 $end = $_;
496             last;
497 73         103 }
498 73         230 $data .= $_;
499             $_ = '';
500 5         16 }
501 5         150 $_ = "use strict;use warnings;$data$end";
502             $done = 1;
503 5         42 }
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 30     30   54 sub block_accessor() {
543 5     5   36 my $accessor = shift;
  5         11  
  5         1097  
544 30 50       131 no strict 'refs';
545             return if defined &$accessor;
546 118     118   224 *$accessor = sub {
547 118 50       237 my $self = shift;
548 0         0 if (@_) {
549             Carp::croak "Not allowed to set values for '$accessor'";
550 118 50       111 }
  118         451  
551             my @list = @{$self->{$accessor} || []};
552 118 100       453 return wantarray
553             ? (@list)
554 30         294 : $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 140     140   178  
565 5     5   27 sub set_value {
  5         12  
  5         1186  
566 140         164 no strict 'refs';
567 140 100       424 my $accessor = shift;
568             block_accessor $accessor
569 140         689 unless defined &$accessor;
570             $self->{$accessor} = [@_];
571             }
572 14     14   27  
573 14         44 sub run_filters {
574 14         38 my $map = $self->_section_map;
575 14 50       310 my $order = $self->_section_order;
576             Carp::croak "Attempt to filter a block twice"
577 14         36 if $self->is_filtered;
578 28         88 for my $type (@$order) {
579 28         78 my $filters = $map->{$type}{filters};
580 28         615 my @value = $self->$type;
581 28         102 $self->original_values->{$type} = $value[0];
582 56 50       133 for my $filter ($self->_get_filters($type, $filters)) {
583             $Test::Base::Filter::arguments =
584 56         100 $filter =~ s/=(.*)$// ? $1 : undef;
585 5     5   30 my $function = "main::$filter";
  5         12  
  5         3488  
586 56 50       242 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 56         1195 else {
601 56 50       158 my $filter_object = $self->blocks_object->filter_class->new;
602             die "Can't find a function or method for '$filter' filter\n"
603 56         1170 unless $filter_object->can($filter);
604 56         168 $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 56         181 # introspecting.
609             $self->set_value($type, @value);
610             }
611 14         347 }
612             $self->is_filtered(1);
613             }
614 28     28   42  
615 28         47 sub _get_filters {
616 28   50     144 my $type = shift;
617 28         122 my $string = shift || '';
618 28         54 $string =~ s/\s*(.*?)\s*/$1/;
619 28   50     609 my @filters = ();
620 28 50       79 my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
621 28         73 $map_filters = [ $map_filters ] unless ref $map_filters;
622 28         35 my @append = ();
623 28         585 for (
624             @{$self->blocks_object->_filters},
625             @$map_filters,
626             split(/\s+/, $string),
627 56         72 ) {
628 56 50       123 my $filter = $_;
629 56 50       165 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 56         137 else {
637             push @filters, $filter;
638             }
639 28         105 }
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__