File Coverage

blib/lib/Test2/Plugin/SourceDiag.pm
Criterion Covered Total %
statement 88 88 100.0
branch 37 50 74.0
condition 23 35 65.7
subroutine 12 12 100.0
pod 0 4 0.0
total 160 189 84.6


line stmt bran cond sub pod time code
1             package Test2::Plugin::SourceDiag;
2 4     4   670152 use strict;
  4         10  
  4         94  
3 4     4   19 use warnings;
  4         7  
  4         133  
4              
5             our $VERSION = '0.000004';
6              
7 4     4   21 use Test2::Event::Diag;
  4         9  
  4         101  
8              
9 4     4   18 use Scalar::Util();
  4         7  
  4         71  
10              
11 4         3753 use Test2::API qw{
12             test2_add_callback_post_load
13             test2_stack
14 4     4   19 };
  4         8  
15              
16             my %SEEN;
17              
18             sub import {
19 10     10   1628 my $class = shift;
20 10         41 my %params = @_;
21              
22 10 100       51 $params{show_source} = 1 unless defined $params{show_source};
23              
24             test2_add_callback_post_load(
25             sub {
26 10     10   15882 my $hub = test2_stack()->top;
27 10 100 100     225 $hub->filter(\&filter, inherit => 1) if $params{show_source} || $params{inject_name};
28 10 100 100     268 $hub->listen(\&listener, inherit => 1) if $params{show_source} || $params{show_args};
29 10 100       188 $hub->add_context_init(\&context_init) if $params{show_args};
30             }
31 10         72 );
32             }
33              
34             sub context_init {
35 3     3 0 298 my $ctx = shift;
36              
37             package DB;
38              
39 3         38 my @caller = caller(1);
40 3         14 my %args = @DB::args;
41 3   50     23 my $level = $args{level} || 1;
42              
43 3         34 @caller = caller(1 + $level);
44              
45 3   33     12 $ctx->trace->{args} = [grep { !Scalar::Util::blessed($_) || !$_->isa('Test::Builder')} @DB::args];
  8         65  
46             }
47              
48             sub filter {
49 37     37 0 30319 my ($hub, $event) = @_;
50 37 100       147 return $event unless $event->causes_fail;
51              
52 7 50       75 my $trace = $event->trace or return $event;
53 7 50       59 my $code = get_assert_code($trace) or return $event;
54              
55 7 100 66     155 if ($event->can('name') && !$event->name && $event->can('set_name')) {
      66        
56 3         54 my $text = join "\n" => @{$code->{source}};
  3         18  
57 3         20 $text =~ s/^\s*//;
58 3         27 $event->set_name($text);
59             }
60             else {
61 4         45 my $start = $code->{start};
62 4         12 my $end = $code->{end};
63 4         15 my $len = length("$end");
64 4         18 my $text = join "\n" => map { sprintf("% ${len}s: %s", $start++, $_) } @{$code->{source}};
  15         58  
  4         12  
65 4         43 $event->meta(__PACKAGE__, {})->{code} = $text;
66             }
67              
68 7         177 return $event;
69             }
70              
71             sub listener {
72 37     37 0 9891 my ($hub, $event) = @_;
73              
74 37 100       126 return unless $event->causes_fail;
75              
76 6         59 my $trace = $event->trace;
77 6         71 my $meta = $event->get_meta(__PACKAGE__);
78 6 100       107 my $code = $meta ? $meta->{code} : undef;
79 6 50       33 my $args = $trace ? $trace->{args} : undef;
80              
81 6 50 66     30 return unless $code || $args;
82              
83 6         14 my $msg = '';
84              
85 6 100       28 $msg .= "Failure source code:\n------------\n$code\n------------\n"
86             if $code;
87              
88 6 50       22 $msg .= "Failure Arguments: (" . join(', ', map { defined($_) ? "'$_'" : 'undef' } @$args) . ")"
  8 100       36  
89             if $args;
90              
91 6         49 $hub->send(
92             Test2::Event::Diag->new(
93             trace => $trace,
94             message => $msg,
95             )
96             );
97             }
98              
99             my %CACHE;
100              
101             sub get_assert_code {
102 7     7 0 22 my ($trace) = @_;
103              
104 7 50       36 my $file = $trace->file or return;
105 7 50       66 my $line = $trace->line or return;
106 7 50       57 my $sub = $trace->subname or return;
107 7         44 my $short_sub = $sub;
108 7         49 $short_sub =~ s/^.*:://;
109 7 50       30 return if $short_sub eq '__ANON__';
110              
111 7         38 my %subs = ($sub => 1, $short_sub => 1);
112              
113 7         1530 require PPI::Document;
114 7   66     356369 my $pd = $CACHE{$file} ||= PPI::Document->new($file);
115 7         126225 $pd->index_locations;
116              
117 7 100   1940   26075 my $it = $pd->find(sub { !$_[1]->isa('PPI::Token::Whitespace') && $_[1]->logical_line_number == $line }) or return;
  1940 50       43888  
118              
119 7 50       159 my $found = $it->[0] or return;
120              
121 7         20 my $thing = $found;
122 7         33 while ($thing) {
123 10 100 100     145 if (($thing->can('children') && $subs{($thing->children)[0]->content}) || $subs{$thing->content}) {
      66        
124 7         114 $found = $thing;
125 7         19 last;
126             }
127              
128 3         581 $thing = $thing->parent;
129             }
130              
131 7         18 my @source;
132              
133 7         37 push @source => split /\r?\n/, $found->content;
134              
135             # Add in any indentation we may have cut off.
136 7         702 my $prefix = $thing->previous_sibling;
137 7 50 33     657 if ($prefix && $prefix->isa('PPI::Token::Whitespace') && $prefix->content ne "\n") {
      33        
138 7         95 my $space = $prefix->content;
139 7         60 $space =~ s/^.*\n//s;
140 7 50       48 $source[0] = $space . $source[0] if length($space);
141             }
142              
143 7         32 my $start = $found->logical_line_number;
144             return {
145 7         251 start => $start,
146             end => $start + $#source,
147             source => \@source,
148             };
149             }
150              
151             1;
152              
153             __END__