File Coverage

blib/lib/Test/Vars.pm
Criterion Covered Total %
statement 240 250 96.0
branch 70 90 77.7
condition 31 42 73.8
subroutine 26 27 96.3
pod 3 3 100.0
total 370 412 89.8


line stmt bran cond sub pod time code
1             package Test::Vars;
2 37     37   959906 use 5.010_000;
  37         148  
3 37     37   200 use strict;
  37         52  
  37         1273  
4 37     37   185 use warnings;
  37         60  
  37         2731  
5              
6             our $VERSION = '0.013';
7              
8             our @EXPORT = qw(all_vars_ok test_vars vars_ok);
9              
10 37     37   22424 use parent qw(Test::Builder::Module);
  37         12301  
  37         208  
11              
12 37     37   2584 use B ();
  37         83  
  37         930  
13 37     37   36830 use ExtUtils::Manifest qw(maniread);
  37         501792  
  37         3431  
14 37     37   24031 use IO::Pipe;
  37         349136  
  37         1662  
15 37     37   28288 use Storable qw(freeze thaw);
  37         158981  
  37         3356  
16 37     37   306 use Symbol qw(qualify_to_ref);
  37         78  
  37         2730  
17              
18 37   50 37   576 use constant _VERBOSE => ($ENV{TEST_VERBOSE} || 0);
  37         47  
  37         3879  
19 37     37   211 use constant _OPpLVAL_INTRO => 128;
  37         61  
  37         77345  
20              
21             #use Devel::Peek;
22             #use Data::Dumper;
23             #$Data::Dumper::Indent = 1;
24              
25             sub all_vars_ok {
26 2     2 1 22 my(%args) = @_;
27              
28 2         42 my $builder = __PACKAGE__->builder;
29              
30 2 50       86 if(not -f $ExtUtils::Manifest::MANIFEST){
31 0         0 $builder->plan(skip_all => "No $ExtUtils::Manifest::MANIFEST ready");
32             }
33 2         18 my $manifest = maniread();
34 2         1922 my @libs = grep{ m{\A lib/ .* [.]pm \z}xms } keys %{$manifest};
  96         500  
  2         26  
35              
36 2 50       24 if (! @libs) {
37 0         0 $builder->plan(skip_all => "not lib/");
38             }
39              
40 2         22 $builder->plan(tests => scalar @libs);
41              
42 2         1596 local $Test::Builder::Level = $Test::Builder::Level + 1;
43 2         6 my $fail = 0;
44 2         8 foreach my $lib(@libs){
45 2 50       14 _vars_ok(\&_results_as_tests, $lib, \%args) or $fail++;
46             }
47              
48 1         53 return $fail == 0;
49             }
50              
51             sub _results_as_tests {
52 127     127   1178 my($file, $exit_code, $results) = @_;
53              
54 127         675 local $Test::Builder::Level = $Test::Builder::Level + 1;
55              
56 127         2775 my $builder = __PACKAGE__->builder;
57 127         4247 my $is_ok = $builder->ok($exit_code == 0, $file);
58              
59 127         70261 for my $result (@$results) {
60 159         1109 my ($method, $message) = @$result;
61 159         891 $builder->$method($message);
62             }
63              
64 127         17328 return $is_ok;
65             }
66              
67             sub test_vars {
68 9     9 1 12934 my($lib, $result_handler, %args) = @_;
69 9         52 return _vars_ok($result_handler, $lib, \%args);
70             }
71              
72             sub vars_ok {
73 150     150 1 91214 my($lib, %args) = @_;
74 150         363 local $Test::Builder::Level = $Test::Builder::Level + 1;
75 150         677 return _vars_ok(\&_results_as_tests, $lib, \%args);
76             }
77              
78             sub _vars_ok {
79 161     161   298 my($result_handler, $file, $args) = @_;
80              
81             # Perl sometimes produces Unix style paths even on Windows, which can lead
82             # to us producing error messages with a path like "lib\foo/bar.pm", which
83             # is really confusing. It's simpler to just use Unix style everywhere
84             # internally.
85 161         702 $file =~ s{\\}{/}g;
86              
87 161         2052 my $pipe = IO::Pipe->new;
88 161         146090 my $pid = fork();
89 161 50       5581 if(defined $pid){
90 161 100       1142 if($pid != 0) { # self
91 133         5126 $pipe->reader;
92 133         617849 my $results = thaw(join('', <$pipe>));
93 133         38687162 waitpid $pid, 0;
94              
95 133         1636 return $result_handler->($file, $?, $results);
96             }
97             else { # child
98 28         2077 $pipe->writer;
99 28         5571 exit !_check_vars($file, $args, $pipe);
100             }
101             }
102             else {
103 0         0 die "fork failed: $!";
104             }
105             }
106              
107             sub _check_vars {
108 28     28   330 my($file, $args, $pipe) = @_;
109              
110 28         214 my @results;
111              
112 28         291 my $package = $file;
113              
114             # Looks like a file name. Turn it into a package name.
115 28 50       1046 if($file =~ /\./){
116 28         1024 $package =~ s{\A .* \b lib/ }{}xms;
117 28         287 $package =~ s{[.]pm \z}{}xms;
118 28         353 $package =~ s{/}{::}g;
119             }
120              
121             # Looks like a package name. Make a file name from it.
122             else{
123 0         0 $file .= '.pm';
124 0         0 $file =~ s{::}{/}g;
125             }
126              
127 28 100       505 if(ref $args->{ignore_vars} eq 'ARRAY'){
128 1         6 $args->{ignore_vars} = { map{ $_ => 1 } @{$args->{ignore_vars}} };
  1         20  
  1         21  
129             }
130              
131 28 50       1268 if(not exists $args->{ignore_vars}{'$self'}){
132 28         209 $args->{ignore_vars}{'$self'}++;
133             }
134              
135             # ensure library loaded
136             {
137 28     0   160 local $SIG{__WARN__} = sub{ }; # ignore warnings
  28         1837  
138              
139             # set PERLDB flags; see also perlvar
140 28         862 local $^P = $^P | 0x200; # NAMEANON
141              
142 28         1530 local @INC = @INC;
143 28 50       481 if($file =~ s{\A (.*\b lib)/}{}xms){
144 28         677 unshift @INC, $1;
145             }
146 28         264 eval { require $file };
  28         26687  
147              
148 28 100       8327 if($@){
149 2         29 $@ =~ s/\n .*//xms;
150 2         23 push @results, [diag => "Test::Vars ignores $file because: $@"];
151 2         46 _pipe_results($pipe, @results);
152 2         366 return 1;
153             }
154             }
155              
156 26         303 push @results, [note => "checking $package in $file ..."];
157             my $check_result = _check_into_stash(
158 26         160 *{qualify_to_ref('', $package)}{HASH}, $file, $args, \@results);
  26         797  
159              
160 26         204 _pipe_results($pipe, @results);
161 26         4862 return $check_result;
162             }
163              
164             sub _check_into_stash {
165 26     26   1249 my($stash, $file, $args, $results) = @_;
166 26         58 my $fail = 0;
167              
168 26         96 foreach my $key(sort keys %{$stash}){
  26         621  
169 101         259 my $ref = \$stash->{$key};
170              
171 101 50       291 next if ref($ref) ne 'GLOB';
172              
173 101         956 my $gv = B::svref_2object($ref);
174              
175 101         112 my $hashref = *{$ref}{HASH};
  101         231  
176 101         102 my $coderef = *{$ref}{CODE};
  101         154  
177              
178 101 100 66     2440 if(($hashref || $coderef) && $gv->FILE =~ /\Q$file\E\z/xms){
      100        
179 44 50 33     302 if($hashref && B::svref_2object($hashref)->NAME){ # stash
    50          
180 0 0       0 if(not _check_into_stash(
181             $hashref, $file, $args, $results)){
182 0         0 $fail++;
183             }
184             }
185             elsif($coderef){
186 44 100       137 if(not _check_into_code($coderef, $args, $results)){
187 9         24 $fail++;
188             }
189             }
190             }
191             }
192              
193 26         123 return $fail == 0;
194             }
195              
196             sub _check_into_code {
197 44     44   96 my($coderef, $args, $results) = @_;
198              
199 44         393 my $cv = B::svref_2object($coderef);
200              
201             # If ROOT is null then the sub is a stub, and has no body for us to check.
202 44 50 33     2311 if($cv->XSUB || $cv->ROOT->isa('B::NULL')){
203 0         0 return 1;
204             }
205              
206 44         134 my %info;
207 44         363 _count_padvars($cv, \%info, $results);
208              
209 44         100 my $fail = 0;
210              
211 44         168 foreach my $cv_info(map { $info{$_} } sort keys %info){
  50         237  
212 50         78 my $pad = $cv_info->{pad};
213              
214 50         49 push @$results, [note => "looking into $cv_info->{name}"] if _VERBOSE > 1;
215              
216 50         67 foreach my $p(@{$pad}){
  50         256  
217 527 100 100     1592 next if !( defined $p && !$p->{outside} );
218              
219 191 100       370 if(! $p->{count}){
220 15 100       89 next if $args->{ignore_vars}{$p->{name}};
221              
222 12 100       39 if(my $cb = $args->{ignore_if}){
223 2         5 local $_ = $p->{name};
224 2 50       19 next if $cb->($_);
225             }
226              
227 10   50     38 my $c = $p->{context} || '';
228 10         113 push @$results, [diag => "$p->{name} is used once in $cv_info->{name} $c"];
229 10         67 $fail++;
230             }
231 0         0 elsif(_VERBOSE > 1){
232             push @$results, [note => "$p->{name} is used $p->{count} times"];
233             }
234             }
235             }
236              
237 44         298 return $fail == 0;
238              
239             }
240              
241             sub _pipe_results {
242 28     28   332 my ($pipe, @messages) = @_;
243 28         478 print $pipe freeze(\@messages);
244 28         6144 close $pipe;
245             }
246              
247             my @padops;
248             my $op_anoncode;
249             my $op_enteriter;
250             my $op_entereval; # string eval
251             my @op_svusers;
252             BEGIN{
253 37     37   107 foreach my $op(qw(padsv padav padhv match multideref subst)){
254 222         928 $padops[B::opnumber($op)]++;
255             }
256             # blead commit 93bad3fd55489cbd split aelemfast into two ops.
257             # Prior to that, 'aelemfast' handled lexicals too.
258 37         157 my $aelemfast = B::opnumber('aelemfast_lex');
259 37 50       146 $padops[$aelemfast == -1 ? B::opnumber('aelemfast') : $aelemfast]++;
260              
261 37         73 $op_anoncode = B::opnumber('anoncode');
262 37         79 $padops[$op_anoncode]++;
263              
264 37         175 $op_enteriter = B::opnumber('enteriter');
265 37         108 $padops[$op_enteriter]++;
266              
267 37         232 $op_entereval = B::opnumber('entereval');
268 37         181 $padops[$op_entereval]++;
269              
270 37         60 foreach my $op(qw(srefgen refgen sassign aassign)){
271 148         17008 $op_svusers[B::opnumber($op)]++;
272             }
273             }
274              
275             sub _count_padvars {
276 50     50   79 my($cv, $global_info, $results) = @_;
277              
278 50         62 my %info;
279              
280 50         478 my $padlist = $cv->PADLIST;
281              
282 50         327 my $padvars = $padlist->ARRAYelt(1);
283              
284 50         68 my @pad;
285 50         79 my $ix = 0;
286 50         647 foreach my $padname($padlist->ARRAYelt(0)->ARRAY){
287 673 50       1909 if($padname->can('PVX')){
288 673         1594 my $pv = $padname->PVX;
289              
290             # Under Perl 5.22.0+, $pv can end up as undef in some cases. With
291             # a threaded Perl, instead of undef we see an empty string.
292             #
293             # $pv can also end up as just '$' or '&'.
294 673 100 66     3795 if(defined $pv && length $pv && $pv ne '&' && $pv ne '$' && !($padname->FLAGS & B::SVpad_OUR)){
      100        
      66        
      100        
295 226         198 my %p;
296              
297 226         524 $p{name} = $pv;
298 226 100       569 $p{outside} = $padname->FLAGS & B::SVf_FAKE ? 1 : 0;
299 226 100       401 if($p{outside}){
300 38         115 $p{outside_padix} = $padname->PARENT_PAD_INDEX;
301             }
302 226         628 $p{padix} = $ix;
303              
304 226         475 $pad[$ix] = \%p;
305             }
306             }
307 673         643 $ix++;
308             }
309              
310 50         356 my $cop;
311              
312 50         149 local *B::COP::_scan_unused_vars;
313             *B::COP::_scan_unused_vars = sub{
314 463     463   1333 ($cop) = @_;
315 50         736 };
316              
317 50         80 my $stringy_eval_seen = 0;
318              
319 50         130 local *B::OP::_scan_unused_vars;
320             *B::OP::_scan_unused_vars = sub {
321 3386     3386   2835 my($op) = @_;
322              
323 3386 100       4371 return if $stringy_eval_seen;
324              
325 3383         5939 my $optype = $op->type;
326 3383 100       9608 return if !defined $padops[ $optype ];
327              
328             # stringy eval could refer all the my variables
329 638 100       917 if($optype == $op_entereval){
330 1         11 foreach my $p(@pad){
331 5         26 $p->{count}++;
332             }
333 1         2 $stringy_eval_seen = 1;
334 1         5 return;
335             }
336              
337             # In Perl 5.22+, pad variables can be referred to in ops like
338             # MULTIDEREF, which show up as a B::UNOP_AUX object. This object can
339             # refer to multiple pad variables.
340 637 100       2215 if($op->isa('B::UNOP_AUX')) {
341 74         185 foreach my $i(grep {!ref}$ op->aux_list($cv)) {
  226         341  
342             # There is a bug in 5.24 with multideref aux_list where it can
343             # contain a value which is completely broken. It numifies to
344             # undef when used as an array index but "defined $i" will be
345             # true! We can detect it by comparing its stringified value to
346             # an empty string. This has been fixed in blead.
347 154 50       88 next unless do {
348 37     37   246 no warnings;
  37         52  
  37         26566  
349 154         308 "$i" ne q{};
350             };
351 154 100       281 $pad[$i]{count}++
352             if $pad[$i];
353             }
354 74         226 return;
355             }
356              
357 563         964 my $targ = $op->targ;
358 563 100       893 return if $targ == 0; # maybe foreach (...)
359              
360 555         440 my $p = $pad[$targ];
361              
362 555   100     1627 $p->{count} ||= 0;
363              
364 555 100 100     3321 if($optype == $op_anoncode){
    100          
    100          
365 6         17 my $anon_cv = $padvars->ARRAYelt($targ);
366 6 50       27 if($anon_cv->CvFLAGS & B::CVf_CLONE){
367 6         33 my $my_info = _count_padvars($anon_cv, $global_info, $results);
368              
369 6         15 $my_info->{outside} = \%info;
370              
371 6         8 foreach my $p(@{$my_info->{pad}}){
  6         16  
372 112 100 66     252 if(defined $p && $p->{outside_padix}){
373 28         44 $pad[ $p->{outside_padix} ]{count}++;
374             }
375             }
376             }
377 6         54 return;
378             }
379             elsif($optype == $op_enteriter or ($op->flags & B::OPf_WANT) == B::OPf_WANT_VOID){
380             # if $op is in void context, it is considered "not used"
381              
382 59 100       202 if(_ckwarn_once($cop)){
383 57         528 $p->{context} = sprintf 'at %s line %d', $cop->file, $cop->line;
384 57         241 return; # skip
385             }
386             }
387             elsif($op->private & _OPpLVAL_INTRO){
388             # my($var) = @_;
389             # ^^^^ padsv/non-void context
390             # ^ sassign/void context
391 139   66     319 for(my $o = $op->next; ${$o} && ref($o) ne 'B::COP'; $o = $o->next){
  261         984  
392 259 100       973 next if !$op_svusers[ $o->type ];
393 139 100       386 next if( ($o->flags & B::OPf_WANT ) != B::OPf_WANT_VOID );
394              
395 137 50       190 if(_ckwarn_once($cop)){
396 137         822 $p->{context} = sprintf 'at %s line %d',
397             $cop->file, $cop->line;
398 137         606 return; # unused, but ok
399             }
400             }
401             }
402              
403 355         1186 $p->{count}++;
404 50         1368 }; # end _scan_unused_vars()
405              
406 50         1112 my $name = sprintf('&%s::%s', $cv->GV->STASH->NAME, $cv->GV->NAME);
407              
408 50         172 my $root = $cv->ROOT;
409 50 50       74 if(${$root}){
  50         316  
410 50         561 B::walkoptree($root, '_scan_unused_vars');
411             }
412             else{
413 0         0 push @$results, [note => "NULL body subroutine $name found"];
414             }
415              
416 50         943 %info = (
417             pad => \@pad,
418             name => $name,
419             );
420              
421 50         188 return $global_info->{ ${$cv} } = \%info;
  50         754  
422             }
423              
424             sub _ckwarn_once {
425 196     196   200 my($cop) = @_;
426              
427 196         717 my $w = $cop->warnings;
428 196 100       392 if(ref($w) eq 'B::SPECIAL'){
429 186         174 return $B::specialsv_name[ ${$w} ] !~ /WARN_NONE/;
  186         869  
430             }
431             else {
432 10         13 my $bits = ${$w->object_2svref};
  10         137  
433             # see warnings::__chk() and warnings::enabled()
434 10         74 return vec($bits, $warnings::Offsets{once}, 1);
435             }
436             }
437              
438             1;
439             __END__