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   715 package Test::Base;
  1         3  
  1         58  
5 1     1   642 use 5.006001;
  1         38  
  1         7  
6 1     1   14 use Spiffy 0.30 -Base;
  1     1   2  
  1     1   47  
  1         7  
  1         2  
  1         38  
  1         5  
  1         3  
  1         7  
7             use Spiffy ':XXX';
8             our $VERSION = '0.52';
9              
10             my @test_more_exports;
11 1     1   26 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   848  
  1         3  
  1         14  
22 1     1   9 use Test::More import => \@test_more_exports;
  1         3  
  1         555  
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 20     20 1 27  
62 20   66     89 sub default_object {
63 20         29 $default_object ||= $default_class->new;
64             return $default_object;
65             }
66              
67             my $import_called = 0;
68 1     1   10 sub import() {
69 1 50       8 $import_called = 1;
70             my $class = (grep /^-base$/i, @_)
71             ? scalar(caller)
72 1 50       4 : $_[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       4 }
90             Test::More->import(import => \@test_more_exports, @args)
91             if @args;
92             }
93 1         4
94 1         23 _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   8 {
  1         2  
  1         4038  
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 16     16 0 18  
  16         27  
112 96     96 0 107 sub block_class { $self->find_class('Block') }
  96         163  
113             sub filter_class { $self->find_class('Filter') }
114 112     112 0 121  
115 112         115 sub find_class {
116 112         194 my $suffix = shift;
117 112 100       300 my $class = ref($self) . "::$suffix";
118 1         3 return $class if $class->can('new');
119 1 50       6 $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 2  
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 20 50   20 0 67 sub find_my_self() {
135             my $self = ref($_[0]) eq $default_class
136             ? splice(@_, 0, 1)
137 20         78 : default_object();
138             return $self, @_;
139             }
140              
141 1     1 1 5 sub blocks() {
142             (my ($self), @_) = find_my_self(@_);
143 1 50       4  
144             croak "Invalid arguments passed to 'blocks'"
145 1 50 33     5 if @_ > 1;
146             croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
147             if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
148 1         21  
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       11  
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 17 sub spec_file() {
205 1         5 (my ($self), @_) = find_my_self(@_);
206 1         23 $self->check_late;
207 1         2 $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 14 sub filters() {
219 1 50       7 (my ($self), @_) = find_my_self(@_);
220 1         35 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         2 }
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 16     16 1 33 sub is($$;$) {
241 16         34 (my ($self), @_) = find_my_self(@_);
242 16         28 my ($actual, $expected, $name) = @_;
243 16 50 33     194 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 16         49 ) {
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 20 sub run(&;$) {
261 1         3 (my ($self), @_) = find_my_self(@_);
262 1         2 my $callback = shift;
  1         28  
263 16 50       411 for my $block (@{$self->block_list}) {
264 16         19 $block->run_filters unless $block->is_filtered;
  16         42  
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     9 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   4  
367 1         1 sub _pre_eval {
368 1 50       8 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   2  
377 1         21 sub _block_list_init {
378 1         4 my $spec = $self->spec;
379 1         24 $spec = $self->_pre_eval($spec);
380 1         111 my $cd = $self->block_delim;
381 1         6 my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
382 1         28 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 16         343 for my $block (@$blocks) {
386 16         313 $block->blocks_object($self);
387             $block->seq_num($seq++);
388 1         6 }
389             return $blocks;
390             }
391 1     1   2  
392 1         2 sub _choose_blocks {
393 1         3 my $blocks = [];
394 16         32 for my $hunk (@_) {
395 16 50       49 my $block = $self->_make_block($hunk);
396 0         0 if (exists $block->{ONLY}) {
397             return [$block];
398 16 50       30 }
399 16         24 next if exists $block->{SKIP};
400 16 50       41 push @$blocks, $block;
401 0         0 if (exists $block->{LAST}) {
402             return $blocks;
403             }
404 1         3 }
405             return $blocks;
406             }
407 32     32   34  
408 32         143 sub _check_reserved {
409 32 50 33     145 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 16     16   17  
415 16         22 sub _make_block {
416 16         381 my $hunk = shift;
417 16         344 my $cd = $self->block_delim;
418 16         37 my $dd = $self->data_delim;
419 16 50       111 my $block = $self->block_class->new;
420 16         28 $hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
421 16         174 my $name = $1;
422 16         26 my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
423 16   50     53 my $description = shift @parts;
424 16 50       29 $description ||= '';
425 16         20 unless ($description =~ /\S/) {
426             $description = $name;
427 16         54 }
428 16         31 $description =~ s/\s*\z//;
429             $block->set_value(description => $description);
430 16         24
431 16         18 my $section_map = {};
432 16         32 my $section_order = [];
433 32         56 while (@parts) {
434 32         55 my ($type, $filters, $value) = splice(@parts, 0, 3);
435 32 50       48 $self->_check_reserved($type);
436 32 50       55 $value = '' unless defined $value;
437 32 50       95 $filters = '' unless defined $filters;
438 32 50       53 if ($filters =~ /:(\s|\z)/) {
439             croak "Extra lines not allowed in '$type' section"
440 32         123 if $value =~ /\S/;
441 32 50       66 ($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
442 32         135 $value = '' unless defined $value;
443             $value =~ s/^\s*(.*?)\s*$/$1/;
444 32         96 }
445             $section_map->{$type} = {
446             filters => $filters,
447 32         48 };
448 32         59 push @$section_order, $type;
449             $block->set_value($type, $value);
450 16         27 }
451 16         26 $block->set_value(name => $name);
452 16         28 $block->set_value(_section_map => $section_map);
453 16         43 $block->set_value(_section_order => $section_order);
454             return $block;
455             }
456 1     1   1  
457 1 50       39 sub _spec_init {
458             return $self->_spec_string
459 1         4 if $self->_spec_string;
460 1         2 local $/;
461 1 50       26 my $spec;
462 1 50       48 if (my $spec_file = $self->_spec_file) {
463 1         32 open FILE, $spec_file or die $!;
464 1         9 $spec = ;
465             close FILE;
466             }
467 0         0 else {
468             $spec = do {
469 1     1   24 package main;
  1         2  
  1         579  
470 0         0 no warnings 'once';
471             ;
472             };
473 1         7 }
474             return $spec;
475             }
476              
477 1     1   6 sub _strict_warnings() {
478 1         2 require Filter::Util::Call;
479             my $done = 0;
480             Filter::Util::Call::filter_add(
481 1 50   1   3 sub {
482 1         3 return 0 if $done;
483 1         12 my ($data, $end) = ('', '');
484 21 50       35 while (my $status = Filter::Util::Call::filter_read()) {
485 21 100       39 return $status if $status < 0;
486 1         2 if (/^__(?:END|DATA)__\r?$/) {
487 1         2 $end = $_;
488             last;
489 20         25 }
490 20         60 $data .= $_;
491             $_ = '';
492 1         4 }
493 1         25 $_ = "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   7 my $accessor = shift;
  1         3  
  1         296  
536 6 50       24 no strict 'refs';
537             return if defined &$accessor;
538 112     112   209 *$accessor = sub {
        112      
539 112 50       188 my $self = shift;
540 0         0 if (@_) {
541             Carp::croak "Not allowed to set values for '$accessor'";
542 112 50       130 }
  112         339  
543             my @list = @{$self->{$accessor} || []};
544 112 100       319 return wantarray
545             ? (@list)
546 6         41 : $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 208     208   224  
557 1     1   7 sub set_value {
  1         1  
  1         232  
558 208         309 no strict 'refs';
559 208 100       471 my $accessor = shift;
560             block_accessor $accessor
561 208         835 unless defined &$accessor;
562             $self->{$accessor} = [@_];
563             }
564 16     16   18  
565 16         39 sub run_filters {
566 16         34 my $map = $self->_section_map;
567 16 50       320 my $order = $self->_section_order;
568             Carp::croak "Attempt to filter a block twice"
569 16         26 if $self->is_filtered;
570 32         103 for my $type (@$order) {
571 32         71 my $filters = $map->{$type}{filters};
572 32         707 my @value = $self->$type;
573 32         79 $self->original_values->{$type} = $value[0];
574 112 50       216 for my $filter ($self->_get_filters($type, $filters)) {
575             $Test::Base::Filter::arguments =
576 112         148 $filter =~ s/=(.*)$// ? $1 : undef;
577 1     1   6 my $function = "main::$filter";
  1         2  
  1         593  
578 112 100       433 no strict 'refs';
579 16         27 if (defined &$function) {
580 16         44 $_ = join '', @value;
581 16 50 33     162 @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 96         2069 else {
589 96 50       259 my $filter_object = $self->blocks_object->filter_class->new;
590             die "Can't find a function or method for '$filter' filter\n"
591 96         3739 unless $filter_object->can($filter);
592 96         281 $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 112         229 # introspecting.
597             $self->set_value($type, @value);
598             }
599 16         344 }
600             $self->is_filtered(1);
601             }
602 32     32   31  
603 32         42 sub _get_filters {
604 32   50     108 my $type = shift;
605 32         140 my $string = shift || '';
606 32         48 $string =~ s/\s*(.*?)\s*/$1/;
607 32   50     665 my @filters = ();
608 32 50       68 my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
609 32         45 $map_filters = [ $map_filters ] unless ref $map_filters;
610 32         37 my @append = ();
611 32         612 for (
612             @{$self->blocks_object->_filters},
613             @$map_filters,
614             split(/\s+/, $string),
615 112         113 ) {
616 112 50       183 my $filter = $_;
617 112 50       216 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 112         176 else {
625             push @filters, $filter;
626             }
627 32         97 }
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__