File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 228 358 63.6
branch 53 172 30.8
condition 17 71 23.9
subroutine 41 60 68.3
pod 19 25 76.0
total 358 686 52.1


line stmt bran cond sub pod time code
1             #line 1
2             # TODO:
3             #
4 1     1   636 package Test::Base;
  1         3  
  1         51  
5 1     1   678 use 5.006001;
  1         31  
  1         7  
6 1     1   11 use Spiffy 0.30 -Base;
  1     1   2  
  1     1   34  
  1         5  
  1         3  
  1         32  
  1         6  
  1         2  
  1         5  
7             use Spiffy ':XXX';
8             our $VERSION = '0.52';
9              
10             my @test_more_exports;
11 1     1   27 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 1     1   798  
  1         3  
  1         11  
22 1     1   6 use Test::More import => \@test_more_exports;
  1         2  
  1         536  
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 18     18 1 35  
62 18   66     66 sub default_object {
63 18         37 $default_object ||= $default_class->new;
64             return $default_object;
65             }
66              
67             my $import_called = 0;
68 1     1   11 sub import() {
69 1 50       7 $import_called = 1;
70             my $class = (grep /^-base$/i, @_)
71             ? scalar(caller)
72 1 50       5 : $_[0];
73 1         2 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 1 50       6  
81 1         1 unless (grep /^-base$/i, @_) {
82 1         5 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 1 50       3 }
90             Test::More->import(import => \@test_more_exports, @args)
91             if @args;
92             }
93 1         4
94 1         40 _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 1     1   6 {
  1         2  
  1         4224  
102             no warnings 'redefine';
103 1     1   2 *Test::Builder::plan = sub {
104 1         6 $Have_Plan = 1;
105             goto &$plan_code;
106             };
107             }
108              
109             my $DIED = 0;
110             $SIG{__DIE__} = sub { $DIED = 1; die @_ };
111 14     14 0 17  
  14         29  
112 84     84 0 116 sub block_class { $self->find_class('Block') }
  84         163  
113             sub filter_class { $self->find_class('Filter') }
114 98     98 0 129  
115 98         119 sub find_class {
116 98         192 my $suffix = shift;
117 98 100       351 my $class = ref($self) . "::$suffix";
118 1         4 return $class if $class->can('new');
119 1 50       8 $class = __PACKAGE__ . "::$suffix";
120 1         69 return $class if $class->can('new');
121 1 50       12 eval "require $class";
122 0         0 return $class if $class->can('new');
123             die "Can't find a class for $suffix";
124             }
125 1     1 0 3  
126 1 50       5 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 18 50   18 0 104 sub find_my_self() {
135             my $self = ref($_[0]) eq $default_class
136             ? splice(@_, 0, 1)
137 18         78 : default_object();
138             return $self, @_;
139             }
140              
141 1     1 1 6 sub blocks() {
142             (my ($self), @_) = find_my_self(@_);
143 1 50       5  
144             croak "Invalid arguments passed to 'blocks'"
145 1 50 33     8 if @_ > 1;
146             croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
147             if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
148 1         31  
149             my $blocks = $self->block_list;
150 1   50     7
151 0         0 my $section_name = shift || '';
152 1 50       6 my @blocks = $section_name
153             ? (grep { exists $_->{$section_name} } @$blocks)
154             : (@$blocks);
155 1 50       12  
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 1     1 1 7 sub spec_file() {
205 1         6 (my ($self), @_) = find_my_self(@_);
206 1         30 $self->check_late;
207 1         4 $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 1     1 1 47 sub filters() {
219 1 50       9 (my ($self), @_) = find_my_self(@_);
220 1         56 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 1         4 }
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 14     14 1 46 sub is($$;$) {
241 14         37 (my ($self), @_) = find_my_self(@_);
242 14         30 my ($actual, $expected, $name) = @_;
243 14 50 33     179 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 14         483 ) {
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         28  
263 14 50       448 for my $block (@{$self->block_list}) {
264 14         22 $block->run_filters unless $block->is_filtered;
  14         59  
265             &{$callback}($block);
266             }
267             }
268              
269 0     0   0 my $name_error = "Can't determine section names";
270 0 0       0 sub _section_names {
271 0 0       0 return @_ if @_ == 2;
272             my $block = $self->first_block
273 0         0 or croak $name_error;
274 0 0       0 my @names = grep {
275 0         0 $_ !~ /^(ONLY|LAST|SKIP)$/;
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 0     0   0  
282 0 0       0 sub _assert_plan {
283             plan('no_plan') unless $Have_Plan;
284             }
285 1     1   3  
286 1 0 33     12 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 0     0 1 0 sub run_is() {
313 0         0 (my ($self), @_) = find_my_self(@_);
314 0         0 $self->_assert_plan;
315 0         0 my ($x, $y) = $self->_section_names(@_);
316 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
  0         0  
317 0 0 0     0 for my $block (@{$self->block_list}) {
318 0 0       0 next unless exists($block->{$x}) and exists($block->{$y});
319 0 0       0 $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 1     1   2  
367 1         2 sub _pre_eval {
368 1 50       10 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 1     1   3  
377 1         28 sub _block_list_init {
378 1         6 my $spec = $self->spec;
379 1         30 $spec = $self->_pre_eval($spec);
380 1         167 my $cd = $self->block_delim;
381 1         6 my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
382 1         32 my $blocks = $self->_choose_blocks(@hunks);
383 1         2 $self->block_list($blocks); # Need to set early for possible filter use
384 1         3 my $seq = 1;
385 14         334 for my $block (@$blocks) {
386 14         413 $block->blocks_object($self);
387             $block->seq_num($seq++);
388 1         8 }
389             return $blocks;
390             }
391 1     1   2  
392 1         4 sub _choose_blocks {
393 1         3 my $blocks = [];
394 14         39 for my $hunk (@_) {
395 14 50       49 my $block = $self->_make_block($hunk);
396 0         0 if (exists $block->{ONLY}) {
397             return [$block];
398 14 50       31 }
399 14         24 next if exists $block->{SKIP};
400 14 50       41 push @$blocks, $block;
401 0         0 if (exists $block->{LAST}) {
402             return $blocks;
403             }
404 1         3 }
405             return $blocks;
406             }
407 28     28   37  
408 28         34 sub _check_reserved {
409 28 50 33     149 my $id = shift;
410             croak "'$id' is a reserved name. Use something else.\n"
411             if $reserved_section_names->{$id} or
412             $id =~ /^_/;
413             }
414 14     14   19  
415 14         18 sub _make_block {
416 14         387 my $hunk = shift;
417 14         379 my $cd = $self->block_delim;
418 14         36 my $dd = $self->data_delim;
419 14 50       121 my $block = $self->block_class->new;
420 14         30 $hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
421 14         197 my $name = $1;
422 14         28 my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
423 14   50     54 my $description = shift @parts;
424 14 50       27 $description ||= '';
425 14         20 unless ($description =~ /\S/) {
426             $description = $name;
427 14         61 }
428 14         37 $description =~ s/\s*\z//;
429             $block->set_value(description => $description);
430 14         25
431 14         17 my $section_map = {};
432 14         35 my $section_order = [];
433 28         68 while (@parts) {
434 28         62 my ($type, $filters, $value) = splice(@parts, 0, 3);
435 28 50       54 $self->_check_reserved($type);
436 28 50       58 $value = '' unless defined $value;
437 28 50       104 $filters = '' unless defined $filters;
438 28 50       94 if ($filters =~ /:(\s|\z)/) {
439             croak "Extra lines not allowed in '$type' section"
440 28         122 if $value =~ /\S/;
441 28 50       63 ($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
442 28         152 $value = '' unless defined $value;
443             $value =~ s/^\s*(.*?)\s*$/$1/;
444 28         100 }
445             $section_map->{$type} = {
446             filters => $filters,
447 28         54 };
448 28         62 push @$section_order, $type;
449             $block->set_value($type, $value);
450 14         33 }
451 14         37 $block->set_value(name => $name);
452 14         27 $block->set_value(_section_map => $section_map);
453 14         41 $block->set_value(_section_order => $section_order);
454             return $block;
455             }
456 1     1   3  
457 1 50       29 sub _spec_init {
458             return $self->_spec_string
459 1         3 if $self->_spec_string;
460 1         3 local $/;
461 1 50       26 my $spec;
462 1 50       56 if (my $spec_file = $self->_spec_file) {
463 1         33 open FILE, $spec_file or die $!;
464 1         12 $spec = ;
465             close FILE;
466             }
467 0         0 else {
468             $spec = do {
469 1     1   9 package main;
  1         2  
  1         742  
470 0         0 no warnings 'once';
471             ;
472             };
473 1         9 }
474             return $spec;
475             }
476              
477 1     1   6 sub _strict_warnings() {
478 1         1 require Filter::Util::Call;
479             my $done = 0;
480             Filter::Util::Call::filter_add(
481 1 50   1   4 sub {
482 1         3 return 0 if $done;
483 1         13 my ($data, $end) = ('', '');
484 21 50       36 while (my $status = Filter::Util::Call::filter_read()) {
485 21 100       42 return $status if $status < 0;
486 1         3 if (/^__(?:END|DATA)__\r?$/) {
487 1         2 $end = $_;
488             last;
489 20         29 }
490 20         63 $data .= $_;
491             $_ = '';
492 1         4 }
493 1         26 $_ = "use strict;use warnings;$data$end";
494             $done = 1;
495 1         9 }
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 6     6   64 sub block_accessor() {
535 1     1   8 my $accessor = shift;
  1         3  
  1         207  
536 6 50       32 no strict 'refs';
537             return if defined &$accessor;
538 98     98   183 *$accessor = sub {
        98      
539 98 50       204 my $self = shift;
540 0         0 if (@_) {
541             Carp::croak "Not allowed to set values for '$accessor'";
542 98 50       101 }
  98         521  
543             my @list = @{$self->{$accessor} || []};
544 98 100       367 return wantarray
545             ? (@list)
546 6         60 : $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 182     182   218  
557 1     1   6 sub set_value {
  1         2  
  1         216  
558 182         223 no strict 'refs';
559 182 100       474 my $accessor = shift;
560             block_accessor $accessor
561 182         770 unless defined &$accessor;
562             $self->{$accessor} = [@_];
563             }
564 14     14   22  
565 14         35 sub run_filters {
566 14         39 my $map = $self->_section_map;
567 14 50       376 my $order = $self->_section_order;
568             Carp::croak "Attempt to filter a block twice"
569 14         35 if $self->is_filtered;
570 28         70 for my $type (@$order) {
571 28         81 my $filters = $map->{$type}{filters};
572 28         758 my @value = $self->$type;
573 28         91 $self->original_values->{$type} = $value[0];
574 98 50       210 for my $filter ($self->_get_filters($type, $filters)) {
575             $Test::Base::Filter::arguments =
576 98         143 $filter =~ s/=(.*)$// ? $1 : undef;
577 1     1   7 my $function = "main::$filter";
  1         2  
  1         817  
578 98 100       426 no strict 'refs';
579 14         32 if (defined &$function) {
580 14         58 $_ = join '', @value;
581 14 50 33     176 @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 84         2166 else {
589 84 50       291 my $filter_object = $self->blocks_object->filter_class->new;
590             die "Can't find a function or method for '$filter' filter\n"
591 84         2189 unless $filter_object->can($filter);
592 84         280 $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 98         259 # introspecting.
597             $self->set_value($type, @value);
598             }
599 14         377 }
600             $self->is_filtered(1);
601             }
602 28     28   48  
603 28         37 sub _get_filters {
604 28   50     116 my $type = shift;
605 28         121 my $string = shift || '';
606 28         50 $string =~ s/\s*(.*?)\s*/$1/;
607 28   50     751 my @filters = ();
608 28 50       77 my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
609 28         39 $map_filters = [ $map_filters ] unless ref $map_filters;
610 28         35 my @append = ();
611 28         686 for (
612             @{$self->blocks_object->_filters},
613             @$map_filters,
614             split(/\s+/, $string),
615 98         126 ) {
616 98 50       187 my $filter = $_;
617 98 50       299 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 98         209 else {
625             push @filters, $filter;
626             }
627 28         107 }
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__