File Coverage

blib/lib/Test2/Plugin/SourceDiag.pm
Criterion Covered Total %
statement 92 92 100.0
branch 39 52 75.0
condition 23 35 65.7
subroutine 12 12 100.0
pod 0 4 0.0
total 166 195 85.1


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