File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 66 355 18.5
branch 11 168 6.5
condition 4 74 5.4
subroutine 19 59 32.2
pod 19 25 76.0
total 119 681 17.4


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