File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 221 357 61.9
branch 54 172 31.4
condition 20 71 28.1
subroutine 39 59 66.1
pod 19 25 76.0
total 353 684 51.6


line stmt bran cond sub pod time code
1             #line 1
2             # TODO:
3             #
4 6     6   3603 package Test::Base;
  6         13  
5 6     6   2067 use 5.006001;
  6         136  
  6         30  
6 6     6   40 use Spiffy 0.30 -Base;
  6     6   7  
  6     6   120  
  6         16  
  6         8  
  6         147  
  6         31  
  6         7  
  6         21  
7             use Spiffy ':XXX';
8             our $VERSION = '0.52';
9              
10             my @test_more_exports;
11 6     6   132 BEGIN {
12             @test_more_exports = qw(
13             ok isnt like unlike is_deeply cmp_ok
14             skip todo_skip pass fail
15             eq_array eq_hash eq_set
16             plan can_ok isa_ok diag
17             use_ok
18             $TODO
19             );
20             }
21 6     6   2628  
  6         16  
  6         40  
22 6     6   30 use Test::More import => \@test_more_exports;
  6         7  
  6         2233  
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 79     79 1 114  
62 79   66     276 sub default_object {
63 79         111 $default_object ||= $default_class->new;
64             return $default_object;
65             }
66              
67             my $import_called = 0;
68 6     6   40 sub import() {
69 6 50       37 $import_called = 1;
70             my $class = (grep /^-base$/i, @_)
71             ? scalar(caller)
72 6 50       20 : $_[0];
73 6         7 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 6 50       24  
81 6         7 unless (grep /^-base$/i, @_) {
82 6         22 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 6 50       18 }
90             Test::More->import(import => \@test_more_exports, @args)
91             if @args;
92             }
93 6         18
94 6         108 _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 6     6   26 {
  6         9  
  6         15117  
102             no warnings 'redefine';
103 6     6   14 *Test::Builder::plan = sub {
104 6         30 $Have_Plan = 1;
105             goto &$plan_code;
106             };
107             }
108              
109             my $DIED = 0;
110             $SIG{__DIE__} = sub { $DIED = 1; die @_ };
111 38     38 0 35  
  38         63  
112 258     258 0 237 sub block_class { $self->find_class('Block') }
  258         344  
113             sub filter_class { $self->find_class('Filter') }
114 296     296 0 238  
115 296         246 sub find_class {
116 296         458 my $suffix = shift;
117 296 100       763 my $class = ref($self) . "::$suffix";
118 6         19 return $class if $class->can('new');
119 6 50       25 $class = __PACKAGE__ . "::$suffix";
120 6         380 return $class if $class->can('new');
121 6 50       41 eval "require $class";
122 0         0 return $class if $class->can('new');
123             die "Can't find a class for $suffix";
124             }
125 0     0 0 0  
126 0 0       0 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 79 50   79 0 336 sub find_my_self() {
135             my $self = ref($_[0]) eq $default_class
136             ? splice(@_, 0, 1)
137 79         265 : default_object();
138             return $self, @_;
139             }
140              
141 5     5 1 156406 sub blocks() {
142             (my ($self), @_) = find_my_self(@_);
143 5 50       24  
144             croak "Invalid arguments passed to 'blocks'"
145 5 50 33     25 if @_ > 1;
146             croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
147             if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
148 5         145  
149             my $blocks = $self->block_list;
150 5   50     32
151             my $section_name = shift || '';
152 5 50       25 my @blocks = $section_name
  0         0  
153             ? (grep { exists $_->{$section_name} } @$blocks)
154             : (@$blocks);
155 5 50       51  
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 0     0 1 0 sub spec_file() {
205 0         0 (my ($self), @_) = find_my_self(@_);
206 0         0 $self->check_late;
207 0         0 $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 5     5 1 21 sub filters() {
219 5 50       19 (my ($self), @_) = find_my_self(@_);
220 5         127 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 5         11 }
227             return $self;
228             }
229              
230 0     0 1 0 sub filter_arguments() {
231             $Test::Base::Filter::arguments;
232             }
233 16     16 0 25  
234 16 50 33     22 sub have_text_diff {
  16         940  
  16         11833  
235             eval { require Text::Diff; 1 } &&
236             $Text::Diff::VERSION >= 0.35 &&
237             $Algorithm::Diff::VERSION >= 1.15;
238             }
239              
240 63     63 1 6097 sub is($$;$) {
241 63         186 (my ($self), @_) = find_my_self(@_);
242 63         105 my ($actual, $expected, $name) = @_;
243 63 50 33     833 local $Test::Builder::Level = $Test::Builder::Level + 1;
      33        
      66        
      66        
      66        
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 63         236 ) {
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 6     6 1 38 sub run(&;$) {
261 6         12 (my ($self), @_) = find_my_self(@_);
262 6         12 my $callback = shift;
  6         204  
263 28 50       687 for my $block (@{$self->block_list}) {
264 27         33 $block->run_filters unless $block->is_filtered;
  27         71  
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             or croak $name_error;
274 0         0 my @names = grep {
275 0 0       0 $_ !~ /^(ONLY|LAST|SKIP)$/;
  0         0  
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 6     6   261  
286 6 0 33     59 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 6     6   12  
367 6         13 sub _pre_eval {
368 6 50       37 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 6     6   12  
377 6         123 sub _block_list_init {
378 6         37 my $spec = $self->spec;
379 6         138 $spec = $self->_pre_eval($spec);
380 6         822 my $cd = $self->block_delim;
381 6         31 my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
382 6         153 my $blocks = $self->_choose_blocks(@hunks);
383 6         9 $self->block_list($blocks); # Need to set early for possible filter use
384 6         17 my $seq = 1;
385 38         642 for my $block (@$blocks) {
386 38         622 $block->blocks_object($self);
387             $block->seq_num($seq++);
388 6         29 }
389             return $blocks;
390             }
391 6     6   11  
392 6         11 sub _choose_blocks {
393 6         19 my $blocks = [];
394 38         73 for my $hunk (@_) {
395 38 50       75 my $block = $self->_make_block($hunk);
396 0         0 if (exists $block->{ONLY}) {
397             return [$block];
398 38 50       63 }
399 38         47 next if exists $block->{SKIP};
400 38 50       71 push @$blocks, $block;
401 0         0 if (exists $block->{LAST}) {
402             return $blocks;
403             }
404 6         12 }
405             return $blocks;
406             }
407 152     152   114  
408 152         117 sub _check_reserved {
409             my $id = shift;
410 152 50 33     539 croak "'$id' is a reserved name. Use something else.\n"
411             if $reserved_section_names->{$id} or
412             $id =~ /^_/;
413             }
414 38     38   52  
415 38         41 sub _make_block {
416 38         803 my $hunk = shift;
417 38         645 my $cd = $self->block_delim;
418 38         72 my $dd = $self->data_delim;
419 38 50       344 my $block = $self->block_class->new;
420 38         70 $hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
421 38         471 my $name = $1;
422 38         48 my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
423 38   50     120 my $description = shift @parts;
424 38 50       77 $description ||= '';
425 38         41 unless ($description =~ /\S/) {
426             $description = $name;
427 38         132 }
428 38         77 $description =~ s/\s*\z//;
429             $block->set_value(description => $description);
430 38         51
431 38         45 my $section_map = {};
432 38         69 my $section_order = [];
433 152         207 while (@parts) {
434 152         199 my ($type, $filters, $value) = splice(@parts, 0, 3);
435 152 100       200 $self->_check_reserved($type);
436 152 50       186 $value = '' unless defined $value;
437 152 100       394 $filters = '' unless defined $filters;
438 137 50       211 if ($filters =~ /:(\s|\z)/) {
439             croak "Extra lines not allowed in '$type' section"
440 137         347 if $value =~ /\S/;
441 137 50       199 ($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
442 137         608 $value = '' unless defined $value;
443             $value =~ s/^\s*(.*?)\s*$/$1/;
444 152         316 }
445             $section_map->{$type} = {
446             filters => $filters,
447 152         161 };
448 152         179 push @$section_order, $type;
449             $block->set_value($type, $value);
450 38         57 }
451 38         51 $block->set_value(name => $name);
452 38         55 $block->set_value(_section_map => $section_map);
453 38         155 $block->set_value(_section_order => $section_order);
454             return $block;
455             }
456 6     6   10  
457 6 50       123 sub _spec_init {
458             return $self->_spec_string
459 6         30 if $self->_spec_string;
460 6         9 local $/;
461 6 50       121 my $spec;
462 0 0       0 if (my $spec_file = $self->_spec_file) {
463 0         0 open FILE, $spec_file or die $!;
464 0         0 $spec = <FILE>;
465             close FILE;
466             }
467 6         9 else {
468             $spec = do {
469 6     6   37 package main;
  6         11  
  6         2198  
470 6         167 no warnings 'once';
471             <DATA>;
472             };
473 6         35 }
474             return $spec;
475             }
476              
477 6     6   28 sub _strict_warnings() {
478 6         7 require Filter::Util::Call;
479             my $done = 0;
480             Filter::Util::Call::filter_add(
481 6 50   6   20 sub {
482 6         12 return 0 if $done;
483 6         52 my ($data, $end) = ('', '');
484 293 50       317 while (my $status = Filter::Util::Call::filter_read()) {
485 293 100       360 return $status if $status < 0;
486 6         10 if (/^__(?:END|DATA)__\r?$/) {
487 6         11 $end = $_;
488             last;
489 287         214 }
490 287         467 $data .= $_;
491             $_ = '';
492 6         26 }
493 6         156 $_ = "use strict;use warnings;$data$end";
494             $done = 1;
495 6         39 }
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 48     48   51 sub block_accessor() {
535 6     6   32 my $accessor = shift;
  6         12  
  6         911  
536 48 50       136 no strict 'refs';
537             return if defined &$accessor;
538 320     320   2628 *$accessor = sub {
539 320 50       539 my $self = shift;
540 0         0 if (@_) {
541             Carp::croak "Not allowed to set values for '$accessor'";
542 320 100       256 }
  320         1085  
543             my @list = @{$self->{$accessor} || []};
544 320 100       1275 return wantarray
545             ? (@list)
546 48         226 : $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 561     561   449  
557 6     6   25 sub set_value {
  6         11  
  6         941  
558 561         422 no strict 'refs';
559 561 100       1176 my $accessor = shift;
560             block_accessor $accessor
561 561         1481 unless defined &$accessor;
562             $self->{$accessor} = [@_];
563             }
564 28     28   54  
565 28         66 sub run_filters {
566 28         66 my $map = $self->_section_map;
567 28 50       591 my $order = $self->_section_order;
568             Carp::croak "Attempt to filter a block twice"
569 28         65 if $self->is_filtered;
570 115         194 for my $type (@$order) {
571 115         271 my $filters = $map->{$type}{filters};
572 115         2140 my @value = $self->$type;
573 115         214 $self->original_values->{$type} = $value[0];
574 258 50       438 for my $filter ($self->_get_filters($type, $filters)) {
575             $Test::Base::Filter::arguments =
576 258         313 $filter =~ s/=(.*)$// ? $1 : undef;
577 6     6   31 my $function = "main::$filter";
  6         9  
  6         2596  
578 258 50       815 no strict 'refs';
579 0         0 if (defined &$function) {
580 0         0 $_ = join '', @value;
581 0 0 0     0 @value = &$function(@value);
      0        
582             if (not(@value) or
583             @value == 1 and $value[0] =~ /\A(\d+|)\z/
584 0         0 ) {
585             @value = ($_);
586             }
587             }
588 258         4375 else {
589 258 50       433 my $filter_object = $self->blocks_object->filter_class->new;
590             die "Can't find a function or method for '$filter' filter\n"
591 258         4544 unless $filter_object->can($filter);
592 258         592 $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 257         429 # introspecting.
597             $self->set_value($type, @value);
598             }
599 27         504 }
600             $self->is_filtered(1);
601             }
602 115     115   109  
603 115         121 sub _get_filters {
604 115   50     334 my $type = shift;
605 115         392 my $string = shift || '';
606 115         146 $string =~ s/\s*(.*?)\s*/$1/;
607 115   100     1952 my @filters = ();
608 115 100       235 my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
609 115         128 $map_filters = [ $map_filters ] unless ref $map_filters;
610 115         99 my @append = ();
611 115         1825 for (
612             @{$self->blocks_object->_filters},
613             @$map_filters,
614             split(/\s+/, $string),
615 258         230 ) {
616 258 50       336 my $filter = $_;
617 258 50       474 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 258         307 else {
625             push @filters, $filter;
626             }
627 115         236 }
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__
638              
639             #line 1298