File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 225 357 63.0
branch 54 172 31.4
condition 25 71 35.2
subroutine 39 59 66.1
pod 19 25 76.0
total 362 684 52.9


line stmt bran cond sub pod time code
1             #line 1
2             # TODO:
3             #
4 16     16   8160 package Test::Base;
  16         46  
5 16     16   7000 use 5.006001;
  16         395  
  16         94  
6 16     16   122 use Spiffy 0.30 -Base;
  16     16   24  
  16     16   365  
  16         69  
  16         17  
  16         463  
  16         66  
  16         20  
  16         64  
7             use Spiffy ':XXX';
8             our $VERSION = '0.52';
9              
10             my @test_more_exports;
11 16     16   606 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 16     16   7610  
  16         36  
  16         152  
22 16     16   93 use Test::More import => \@test_more_exports;
  16         25  
  16         7272  
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 72     72 1 90  
62 72   66     352 sub default_object {
63 72         93 $default_object ||= $default_class->new;
64             return $default_object;
65             }
66              
67             my $import_called = 0;
68 16     16   107 sub import() {
69 16 50       101 $import_called = 1;
70             my $class = (grep /^-base$/i, @_)
71             ? scalar(caller)
72 16 50       51 : $_[0];
73 16         24 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 16 50       69  
81 16         20 unless (grep /^-base$/i, @_) {
82 16         62 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 16 50       64 }
90             Test::More->import(import => \@test_more_exports, @args)
91             if @args;
92             }
93 16         49
94 16         313 _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 16     16   87 {
  16         21  
  16         47234  
102             no warnings 'redefine';
103 16     16   30 *Test::Builder::plan = sub {
104 16         83 $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 48  
  38         86  
112 300     300 0 274 sub block_class { $self->find_class('Block') }
  300         393  
113             sub filter_class { $self->find_class('Filter') }
114 338     338 0 276  
115 338         272 sub find_class {
116 338         513 my $suffix = shift;
117 338 100       947 my $class = ref($self) . "::$suffix";
118 15         56 return $class if $class->can('new');
119 15 50       96 $class = __PACKAGE__ . "::$suffix";
120 15         881 return $class if $class->can('new');
121 15 50       119 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 72 50   72 0 269 sub find_my_self() {
135             my $self = ref($_[0]) eq $default_class
136             ? splice(@_, 0, 1)
137 72         213 : default_object();
138             return $self, @_;
139             }
140              
141 15     15 1 130 sub blocks() {
142             (my ($self), @_) = find_my_self(@_);
143 15 50       63  
144             croak "Invalid arguments passed to 'blocks'"
145 15 50 33     67 if @_ > 1;
146             croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
147             if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
148 15         475  
149             my $blocks = $self->block_list;
150 15   50     121
151             my $section_name = shift || '';
152 15 50       71 my @blocks = $section_name
  0         0  
153             ? (grep { exists $_->{$section_name} } @$blocks)
154             : (@$blocks);
155 15 50       134  
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 15     15 1 52 sub filters() {
219 15 50       68 (my ($self), @_) = find_my_self(@_);
220 15         420 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 15         32 }
227             return $self;
228             }
229              
230 0     0 1 0 sub filter_arguments() {
231             $Test::Base::Filter::arguments;
232             }
233 1     1 0 3  
234 1 50 33     1 sub have_text_diff {
  1         599  
  1         7130  
235             eval { require Text::Diff; 1 } &&
236             $Text::Diff::VERSION >= 0.35 &&
237             $Algorithm::Diff::VERSION >= 1.15;
238             }
239              
240 27     27 1 94 sub is($$;$) {
241 27         52 (my ($self), @_) = find_my_self(@_);
242 27         41 my ($actual, $expected, $name) = @_;
243 27 50 66     428 local $Test::Builder::Level = $Test::Builder::Level + 1;
      66        
      100        
      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 27         174 ) {
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 15     15 1 62 sub run(&;$) {
261 15         43 (my ($self), @_) = find_my_self(@_);
262 15         28 my $callback = shift;
  15         296  
263 38 50       839 for my $block (@{$self->block_list}) {
264 38         66 $block->run_filters unless $block->is_filtered;
  38         98  
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 16     16   46  
286 16 0 33     110 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 15     15   32  
367 15         30 sub _pre_eval {
368 15 50       114 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 15     15   29  
377 15         288 sub _block_list_init {
378 15         65 my $spec = $self->spec;
379 15         336 $spec = $self->_pre_eval($spec);
380 15         846 my $cd = $self->block_delim;
381 15         75 my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
382 15         349 my $blocks = $self->_choose_blocks(@hunks);
383 15         25 $self->block_list($blocks); # Need to set early for possible filter use
384 15         57 my $seq = 1;
385 38         685 for my $block (@$blocks) {
386 38         634 $block->blocks_object($self);
387             $block->seq_num($seq++);
388 15         105 }
389             return $blocks;
390             }
391 15     15   25  
392 15         30 sub _choose_blocks {
393 15         44 my $blocks = [];
394 38         83 for my $hunk (@_) {
395 38 50       90 my $block = $self->_make_block($hunk);
396 0         0 if (exists $block->{ONLY}) {
397             return [$block];
398 38 50       91 }
399 38         71 next if exists $block->{SKIP};
400 38 50       138 push @$blocks, $block;
401 0         0 if (exists $block->{LAST}) {
402             return $blocks;
403             }
404 15         28 }
405             return $blocks;
406             }
407 112     112   109  
408 112         101 sub _check_reserved {
409             my $id = shift;
410 112 50 33     482 croak "'$id' is a reserved name. Use something else.\n"
411             if $reserved_section_names->{$id} or
412             $id =~ /^_/;
413             }
414 38     38   51  
415 38         46 sub _make_block {
416 38         759 my $hunk = shift;
417 38         633 my $cd = $self->block_delim;
418 38         105 my $dd = $self->data_delim;
419 38 50       526 my $block = $self->block_class->new;
420 38         85 $hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
421 38         648 my $name = $1;
422 38         62 my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
423 38   50     150 my $description = shift @parts;
424 38 50       82 $description ||= '';
425 38         51 unless ($description =~ /\S/) {
426             $description = $name;
427 38         135 }
428 38         112 $description =~ s/\s*\z//;
429             $block->set_value(description => $description);
430 38         52
431 38         52 my $section_map = {};
432 38         87 my $section_order = [];
433 112         200 while (@parts) {
434 112         174 my ($type, $filters, $value) = splice(@parts, 0, 3);
435 112 50       199 $self->_check_reserved($type);
436 112 50       162 $value = '' unless defined $value;
437 112 100       186 $filters = '' unless defined $filters;
438 8 50       15 if ($filters =~ /:(\s|\z)/) {
439             croak "Extra lines not allowed in '$type' section"
440 8         29 if $value =~ /\S/;
441 8 50       17 ($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
442 8         37 $value = '' unless defined $value;
443             $value =~ s/^\s*(.*?)\s*$/$1/;
444 112         265 }
445             $section_map->{$type} = {
446             filters => $filters,
447 112         146 };
448 112         160 push @$section_order, $type;
449             $block->set_value($type, $value);
450 38         80 }
451 38         59 $block->set_value(name => $name);
452 38         74 $block->set_value(_section_map => $section_map);
453 38         86 $block->set_value(_section_order => $section_order);
454             return $block;
455             }
456 15     15   34  
457 15 50       297 sub _spec_init {
458             return $self->_spec_string
459 15         49 if $self->_spec_string;
460 15         27 local $/;
461 15 50       285 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 = ;
465             close FILE;
466             }
467 15         26 else {
468             $spec = do {
469 16     16   108 package main;
  16         26  
  16         7671  
470 15         266 no warnings 'once';
471             ;
472             };
473 15         87 }
474             return $spec;
475             }
476              
477 16     16   80 sub _strict_warnings() {
478 16         24 require Filter::Util::Call;
479             my $done = 0;
480             Filter::Util::Call::filter_add(
481 16 50   16   57 sub {
482 16         36 return 0 if $done;
483 16         135 my ($data, $end) = ('', '');
484 365 50       440 while (my $status = Filter::Util::Call::filter_read()) {
485 365 100       513 return $status if $status < 0;
486 16         31 if (/^__(?:END|DATA)__\r?$/) {
487 16         30 $end = $_;
488             last;
489 349         357 }
490 349         639 $data .= $_;
491             $_ = '';
492 16         53 }
493 16         402 $_ = "use strict;use warnings;$data$end";
494             $done = 1;
495 16         108 }
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 111     111   128 sub block_accessor() {
535 16     16   91 my $accessor = shift;
  16         18  
  16         2774  
536 111 50       354 no strict 'refs';
537             return if defined &$accessor;
538 344     344   565 *$accessor = sub {
539 344 50       579 my $self = shift;
540 0         0 if (@_) {
541             Carp::croak "Not allowed to set values for '$accessor'";
542 344 50       274 }
  344         1023  
543             my @list = @{$self->{$accessor} || []};
544 344 100       1327 return wantarray
545             ? (@list)
546 111         526 : $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 574     574   470  
557 16     16   86 sub set_value {
  16         21  
  16         3105  
558 574         479 no strict 'refs';
559 574 100       1286 my $accessor = shift;
560             block_accessor $accessor
561 574         1654 unless defined &$accessor;
562             $self->{$accessor} = [@_];
563             }
564 38     38   54  
565 38         83 sub run_filters {
566 38         82 my $map = $self->_section_map;
567 38 50       653 my $order = $self->_section_order;
568             Carp::croak "Attempt to filter a block twice"
569 38         73 if $self->is_filtered;
570 112         204 for my $type (@$order) {
571 112         263 my $filters = $map->{$type}{filters};
572 112         1917 my @value = $self->$type;
573 112         223 $self->original_values->{$type} = $value[0];
574 310 50       500 for my $filter ($self->_get_filters($type, $filters)) {
575             $Test::Base::Filter::arguments =
576 310         364 $filter =~ s/=(.*)$// ? $1 : undef;
577 16     16   89 my $function = "main::$filter";
  16         18  
  16         8245  
578 310 100       1025 no strict 'refs';
579 10         17 if (defined &$function) {
580 10         22 $_ = join '', @value;
581 10 50 33     91 @value = &$function(@value);
      33        
582             if (not(@value) or
583             @value == 1 and $value[0] =~ /\A(\d+|)\z/
584 10         14 ) {
585             @value = ($_);
586             }
587             }
588 300         4833 else {
589 300 50       490 my $filter_object = $self->blocks_object->filter_class->new;
590             die "Can't find a function or method for '$filter' filter\n"
591 300         4928 unless $filter_object->can($filter);
592 300         684 $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 310         52086 # introspecting.
597             $self->set_value($type, @value);
598             }
599 38         699 }
600             $self->is_filtered(1);
601             }
602 112     112   118  
603 112         106 sub _get_filters {
604 112   50     341 my $type = shift;
605 112         388 my $string = shift || '';
606 112         157 $string =~ s/\s*(.*?)\s*/$1/;
607 112   100     1811 my @filters = ();
608 112 100       277 my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
609 112         133 $map_filters = [ $map_filters ] unless ref $map_filters;
610 112         98 my @append = ();
611 112         1688 for (
612             @{$self->blocks_object->_filters},
613             @$map_filters,
614             split(/\s+/, $string),
615 310         262 ) {
616 310 50       409 my $filter = $_;
617 310 50       560 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 310         352 else {
625             push @filters, $filter;
626             }
627 112         270 }
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__