File Coverage

inc/Test/Base.pm
Criterion Covered Total %
statement 227 358 63.4
branch 56 172 32.5
condition 19 71 26.7
subroutine 40 59 67.8
pod 19 25 76.0
total 361 685 52.7


line stmt bran cond sub pod time code
1             #line 1
2             # TODO:
3             #
4 4     4   3274 package Test::Base;
  4         14  
  4         213  
5 4     4   2501 use 5.006001;
  4         124  
  4         30  
6 4     4   45 use Spiffy 0.30 -Base;
  4     4   6  
  4     4   144  
  4         21  
  4         7  
  4         130  
  4         27  
  4         6  
  4         23  
7             use Spiffy ':XXX';
8             our $VERSION = '0.52';
9              
10             my @test_more_exports;
11 4     4   102 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 4     4   3017  
  4         12  
  4         42  
22 4     4   29 use Test::More import => \@test_more_exports;
  4         26  
  4         2271  
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 40     40 1 73  
62 40   66     198 sub default_object {
63 40         83 $default_object ||= $default_class->new;
64             return $default_object;
65             }
66              
67             my $import_called = 0;
68 4     4   33 sub import() {
69 4 50       30 $import_called = 1;
70             my $class = (grep /^-base$/i, @_)
71             ? scalar(caller)
72 4 50       16 : $_[0];
73 4         8 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 4 50       20  
81 4         7 unless (grep /^-base$/i, @_) {
82 4         20 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 4 50       18 }
90             Test::More->import(import => \@test_more_exports, @args)
91             if @args;
92             }
93 4         15
94 4         84 _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 4     4   28 {
  4         6  
  4         15439  
102             no warnings 'redefine';
103 4     4   20 *Test::Builder::plan = sub {
104 4         35 $Have_Plan = 1;
105             goto &$plan_code;
106             };
107             }
108              
109             my $DIED = 0;
110             $SIG{__DIE__} = sub { $DIED = 1; die @_ };
111 28     28 0 33  
  28         65  
112 112     112 0 147 sub block_class { $self->find_class('Block') }
  112         259  
113             sub filter_class { $self->find_class('Filter') }
114 140     140 0 266  
115 140         178 sub find_class {
116 140         309 my $suffix = shift;
117 140 100       710 my $class = ref($self) . "::$suffix";
118 4         14 return $class if $class->can('new');
119 4 50       27 $class = __PACKAGE__ . "::$suffix";
120 4         334 return $class if $class->can('new');
121 4 50       45 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 40 50   40 0 223 sub find_my_self() {
135             my $self = ref($_[0]) eq $default_class
136             ? splice(@_, 0, 1)
137 40         161 : default_object();
138             return $self, @_;
139             }
140              
141 4     4 1 44 sub blocks() {
142             (my ($self), @_) = find_my_self(@_);
143 4 50       23  
144             croak "Invalid arguments passed to 'blocks'"
145 4 50 33     24 if @_ > 1;
146             croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
147             if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
148 4         164  
149             my $blocks = $self->block_list;
150 4   50     39
151 0         0 my $section_name = shift || '';
152 4 50       21 my @blocks = $section_name
153             ? (grep { exists $_->{$section_name} } @$blocks)
154             : (@$blocks);
155 4 50       252  
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 4     4 1 17 sub filters() {
219 4 50       21 (my ($self), @_) = find_my_self(@_);
220 4         158 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 4         15 }
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 28     28 1 114 sub is($$;$) {
241 28         73 (my ($self), @_) = find_my_self(@_);
242 28         74 my ($actual, $expected, $name) = @_;
243 28 50 33     431 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 28         150 ) {
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 0     0 1 0 sub run(&;$) {
261 0         0 (my ($self), @_) = find_my_self(@_);
262 0         0 my $callback = shift;
  0         0  
263 0 0       0 for my $block (@{$self->block_list}) {
264 0         0 $block->run_filters unless $block->is_filtered;
  0         0  
265             &{$callback}($block);
266             }
267             }
268              
269 4     4   9 my $name_error = "Can't determine section names";
270 4 50       22 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 4     4   10  
282 4 50       16 sub _assert_plan {
283             plan('no_plan') unless $Have_Plan;
284             }
285 4     4   14  
286 4 0 33     37 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 4     4 1 15 sub run_is() {
313 4         24 (my ($self), @_) = find_my_self(@_);
314 4         21 $self->_assert_plan;
315 4         13 my ($x, $y) = $self->_section_names(@_);
316 4         8 local $Test::Builder::Level = $Test::Builder::Level + 1;
  4         123  
317 28 50 33     216 for my $block (@{$self->block_list}) {
318 28 50       1031 next unless exists($block->{$x}) and exists($block->{$y});
319 28 50       107 $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 4     4   10  
367 4         9 sub _pre_eval {
368 4 50       39 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 4     4   10  
377 4         110 sub _block_list_init {
378 4         25 my $spec = $self->spec;
379 4         116 $spec = $self->_pre_eval($spec);
380 4         356 my $cd = $self->block_delim;
381 4         22 my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
382 4         122 my $blocks = $self->_choose_blocks(@hunks);
383 4         7 $self->block_list($blocks); # Need to set early for possible filter use
384 4         13 my $seq = 1;
385 28         719 for my $block (@$blocks) {
386 28         670 $block->blocks_object($self);
387             $block->seq_num($seq++);
388 4         29 }
389             return $blocks;
390             }
391 4     4   10  
392 4         12 sub _choose_blocks {
393 4         14 my $blocks = [];
394 28         82 for my $hunk (@_) {
395 28 50       76 my $block = $self->_make_block($hunk);
396 0         0 if (exists $block->{ONLY}) {
397             return [$block];
398 28 50       67 }
399 28         40 next if exists $block->{SKIP};
400 28 50       177 push @$blocks, $block;
401 0         0 if (exists $block->{LAST}) {
402             return $blocks;
403             }
404 4         41 }
405             return $blocks;
406             }
407 56     56   67  
408 56         66 sub _check_reserved {
409 56 50 33     297 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 28     28   34  
415 28         43 sub _make_block {
416 28         733 my $hunk = shift;
417 28         694 my $cd = $self->block_delim;
418 28         81 my $dd = $self->data_delim;
419 28 50       304 my $block = $self->block_class->new;
420 28         61 $hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
421 28         376 my $name = $1;
422 28         60 my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
423 28   50     115 my $description = shift @parts;
424 28 50       63 $description ||= '';
425 28         42 unless ($description =~ /\S/) {
426             $description = $name;
427 28         90 }
428 28         71 $description =~ s/\s*\z//;
429             $block->set_value(description => $description);
430 28         75
431 28         44 my $section_map = {};
432 28         61 my $section_order = [];
433 56         122 while (@parts) {
434 56         122 my ($type, $filters, $value) = splice(@parts, 0, 3);
435 56 100       191 $self->_check_reserved($type);
436 56 50       98 $value = '' unless defined $value;
437 56 50       203 $filters = '' unless defined $filters;
438 56 50       121 if ($filters =~ /:(\s|\z)/) {
439             croak "Extra lines not allowed in '$type' section"
440 56         228 if $value =~ /\S/;
441 56 50       138 ($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
442 56         436 $value = '' unless defined $value;
443             $value =~ s/^\s*(.*?)\s*$/$1/;
444 56         188 }
445             $section_map->{$type} = {
446             filters => $filters,
447 56         104 };
448 56         112 push @$section_order, $type;
449             $block->set_value($type, $value);
450 28         62 }
451 28         58 $block->set_value(name => $name);
452 28         58 $block->set_value(_section_map => $section_map);
453 28         83 $block->set_value(_section_order => $section_order);
454             return $block;
455             }
456 4     4   8  
457 4 50       118 sub _spec_init {
458             return $self->_spec_string
459 4         18 if $self->_spec_string;
460 4         10 local $/;
461 4 50       127 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 4         9 else {
468             $spec = do {
469 4     4   43 package main;
  4         9  
  4         2412  
470 4         120 no warnings 'once';
471             ;
472             };
473 4         27 }
474             return $spec;
475             }
476              
477 4     4   23 sub _strict_warnings() {
478 4         8 require Filter::Util::Call;
479             my $done = 0;
480             Filter::Util::Call::filter_add(
481 4 50   4   16 sub {
482 4         11 return 0 if $done;
483 4         66 my ($data, $end) = ('', '');
484 96 50       162 while (my $status = Filter::Util::Call::filter_read()) {
485 96 100       218 return $status if $status < 0;
486 4         8 if (/^__(?:END|DATA)__\r?$/) {
487 4         11 $end = $_;
488             last;
489 92         108 }
490 92         264 $data .= $_;
491             $_ = '';
492 4         17 }
493 4         123 $_ = "use strict;use warnings;$data$end";
494             $done = 1;
495 4         31 }
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 24     24   45 sub block_accessor() {
535 4     4   29 my $accessor = shift;
  4         8  
  4         892  
536 24 50       100 no strict 'refs';
537             return if defined &$accessor;
538 196     196   263 *$accessor = sub {
539 196 50       426 my $self = shift;
540 0         0 if (@_) {
541             Carp::croak "Not allowed to set values for '$accessor'";
542 196 50       214 }
  196         744  
543             my @list = @{$self->{$accessor} || []};
544 196 100       812 return wantarray
545             ? (@list)
546 24         190 : $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 326     326   431  
557 4     4   25 sub set_value {
  4         9  
  4         981  
558 326         424 no strict 'refs';
559 326 100       921 my $accessor = shift;
560             block_accessor $accessor
561 326         1557 unless defined &$accessor;
562             $self->{$accessor} = [@_];
563             }
564 28     28   50  
565 28         108 sub run_filters {
566 28         79 my $map = $self->_section_map;
567 28 50       1251 my $order = $self->_section_order;
568             Carp::croak "Attempt to filter a block twice"
569 28         69 if $self->is_filtered;
570 56         192 for my $type (@$order) {
571 56         218 my $filters = $map->{$type}{filters};
572 56         1930 my @value = $self->$type;
573 56         208 $self->original_values->{$type} = $value[0];
574 158 50       400 for my $filter ($self->_get_filters($type, $filters)) {
575             $Test::Base::Filter::arguments =
576 158         266 $filter =~ s/=(.*)$// ? $1 : undef;
577 4     4   28 my $function = "main::$filter";
  4         7  
  4         2490  
578 158 100       757 no strict 'refs';
579 46         108 if (defined &$function) {
580 46         184 $_ = join '', @value;
581 46 50 33     2104 @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 112         3084 else {
589 112 50       452 my $filter_object = $self->blocks_object->filter_class->new;
590             die "Can't find a function or method for '$filter' filter\n"
591 112         2963 unless $filter_object->can($filter);
592 112         437 $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 158         487 # introspecting.
597             $self->set_value($type, @value);
598             }
599 28         786 }
600             $self->is_filtered(1);
601             }
602 56     56   88  
603 56         82 sub _get_filters {
604 56   50     254 my $type = shift;
605 56         281 my $string = shift || '';
606 56         115 $string =~ s/\s*(.*?)\s*/$1/;
607 56   100     1715 my @filters = ();
608 56 50       164 my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
609 56         102 $map_filters = [ $map_filters ] unless ref $map_filters;
610 56         79 my @append = ();
611 56         1427 for (
612             @{$self->blocks_object->_filters},
613             @$map_filters,
614             split(/\s+/, $string),
615 158         208 ) {
616 158 50       311 my $filter = $_;
617 158 50       577 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 158         339 else {
625             push @filters, $filter;
626             }
627 56         211 }
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__