File Coverage

blib/lib/Test/Valgrind/Session.pm
Criterion Covered Total %
statement 190 233 81.5
branch 69 118 58.4
condition 14 33 42.4
subroutine 32 37 86.4
pod 22 22 100.0
total 327 443 73.8


line stmt bran cond sub pod time code
1             package Test::Valgrind::Session;
2              
3 8     8   3109 use strict;
  8         14  
  8         206  
4 8     8   37 use warnings;
  8         16  
  8         317  
5              
6             =head1 NAME
7              
8             Test::Valgrind::Session - Test::Valgrind session object.
9              
10             =head1 VERSION
11              
12             Version 1.17
13              
14             =cut
15              
16             our $VERSION = '1.17';
17              
18             =head1 DESCRIPTION
19              
20             This class supervises the execution of the C process.
21             It also acts as a dispatcher between the different components.
22              
23             =cut
24              
25 8     8   37 use File::Spec ();
  8         14  
  8         118  
26 8     8   37 use Scalar::Util ();
  8         13  
  8         120  
27              
28 8     8   46 use Fcntl (); # F_SETFD
  8         12  
  8         152  
29 8     8   7630 use IO::Select;
  8         15414  
  8         465  
30 8     8   7322 use POSIX (); # SIGKILL, _exit()
  8         65782  
  8         202  
31              
32 8     8   5340 use version ();
  8         16833  
  8         223  
33              
34 8     8   49 use base qw;
  8         20  
  8         10925  
35              
36             =head1 METHODS
37              
38             =head2 C
39              
40             my $tvs = Test::Valgrind::Session->new(
41             search_dirs => \@search_dirs,
42             valgrind => $valgrind, # One candidate
43             valgrind => \@valgrind, # Several candidates
44             min_version => $min_version,
45             regen_def_supp => $regen_def_supp,
46             no_def_supp => $no_def_supp,
47             allow_no_supp => $allow_no_supp,
48             extra_supps => \@extra_supps,
49             );
50              
51             The package constructor, which takes several options :
52              
53             =over 4
54              
55             =item *
56              
57             All the directories from C<@search_dirs> will have F appended to create a list of candidates for the C executable.
58              
59             Defaults to the current C environment variable.
60              
61             =item *
62              
63             If a simple scalar C<$valgrind> is passed as the value to C<'valgrind'>, it will be the only candidate.
64             C<@search_dirs> will then be ignored.
65              
66             If an array refernce C<\@valgrind> is passed, its values will be I to the list of the candidates resulting from C<@search_dirs>.
67              
68             =item *
69              
70             C<$min_version> specifies the minimal C version required.
71             The constructor will croak if it's not able to find an adequate C from the supplied candidates list and search path.
72              
73             Defaults to none.
74              
75             =item *
76              
77             If C<$regen_def_supp> is true, the default suppression file associated with the tool and the command will be forcefully regenerated.
78              
79             Defaults to false.
80              
81             =item *
82              
83             If C<$no_def_supp> is true, C won't read the default suppression file associated with the tool and the command.
84              
85             Defaults to false.
86              
87             =item *
88              
89             If C<$allow_no_supp> is true, the command will always be run into C even if no appropriate suppression file is available.
90              
91             Defaults to false.
92              
93             =item *
94              
95             C<$extra_supps> is a reference to an array of optional suppression files that will be passed to C.
96              
97             Defaults to none.
98              
99             =back
100              
101             =cut
102              
103             sub new {
104 16     16 1 13458 my $class = shift;
105 16   33     119 $class = ref($class) || $class;
106              
107 16         81 my %args = @_;
108              
109 16         30 my @paths;
110 16         40 my $vg = delete $args{valgrind};
111 16 100 100     115 if (defined $vg and not ref $vg) {
112 9         27 @paths = ($vg);
113             } else {
114 7 100 66     46 push @paths, @$vg if $vg and ref $vg eq 'ARRAY';
115 7         15 my $dirs = delete $args{search_dirs};
116 7 100       161 $dirs = [ File::Spec->path ] unless $dirs;
117 7 50       278 push @paths, map File::Spec->catfile($_, 'valgrind'), @$dirs
118             if ref $dirs eq 'ARRAY';
119             }
120 16 100       67 $class->_croak('Empty valgrind candidates list') unless @paths;
121              
122 15         41 my $min_version = delete $args{min_version};
123 15   66     197 defined and not ref and $_ = version->new($_) for $min_version;
      66        
124              
125 15         31 my ($valgrind, $version);
126 15         37 for (@paths) {
127 46 100       949 next unless -x;
128 10         69206 my $ver = qx/$_ --version/;
129 10 50       449 if ($ver =~ /^valgrind-(\d+(\.\d+)*)/) {
130 10 100       237 if ($min_version) {
131 4         85 $version = version->new($1);
132 4 100       59 next if $version < $min_version;
133             } else {
134 6         112 $version = $1;
135             }
136 8         53 $valgrind = $_;
137 8         81 last;
138             }
139             }
140 15 100       127 $class->_croak('No appropriate valgrind executable could be found')
141             unless defined $valgrind;
142              
143 8         47 my $extra_supps = delete $args{extra_supps};
144 8 50 33     94 $extra_supps = [ ] unless $extra_supps and ref $extra_supps eq 'ARRAY';
145 8 0 0     40 @$extra_supps = grep { defined && -f $_ && -r _ } @$extra_supps;
  0         0  
146              
147             bless {
148             valgrind => $valgrind,
149             version => $version,
150             regen_def_supp => delete($args{regen_def_supp}),
151             no_def_supp => delete($args{no_def_supp}),
152 8         484 allow_no_supp => delete($args{allow_no_supp}),
153             extra_supps => $extra_supps,
154             }, $class;
155             }
156              
157             =head2 C
158              
159             my $valgrind_path = $tvs->valgrind;
160              
161             The path to the selected C executable.
162              
163             =head2 C
164              
165             my $valgrind_version = $tvs->version;
166              
167             The L object associated to the selected C.
168              
169             =cut
170              
171             sub version {
172 8     8 1 25 my ($self) = @_;
173              
174 8         40 my $version = $self->{version};
175 8 100       190 $self->{version} = $version = version->new($version) unless ref $version;
176              
177 8         543 return $version;
178             }
179              
180             =head2 C
181              
182             my $regen_def_supp = $tvs->regen_def_supp;
183              
184             Read-only accessor for the C option.
185              
186             =cut
187              
188             =head2 C
189              
190             my $no_def_supp = $tvs->no_def_supp;
191              
192             Read-only accessor for the C option.
193              
194             =head2 C
195              
196             my $allow_no_supp = $tvs->allow_no_supp;
197              
198             Read-only accessor for the C option.
199              
200             =cut
201              
202 6     6 1 50 eval "sub $_ { \$_[0]->{$_} }" for qw<
  12     12 1 86  
  0     0 1 0  
  8     8 1 316  
203             valgrind
204             regen_def_supp
205             no_def_supp
206             allow_no_supp
207             >;
208              
209             =head2 C
210              
211             my @extra_supps = $tvs->extra_supps;
212              
213             Read-only accessor for the C option.
214              
215             =cut
216              
217 6 50   6 1 16 sub extra_supps { @{$_[0]->{extra_supps} || []} }
  6         66  
218              
219             =head2 C
220              
221             $tvs->run(
222             action => $action,
223             tool => $tool,
224             command => $command,
225             );
226              
227             Runs the command C<$command> through C with the tool C<$tool>, which will report to the action C<$action>.
228              
229             If the command is a L object, the action and the tool will be initialized once before running all the aggregated commands.
230              
231             =cut
232              
233             sub run {
234 6     6 1 3504 my ($self, %args) = @_;
235              
236 6         22 for (qw) {
237 18         78 my $base = 'Test::Valgrind::' . ucfirst;
238 18         46 my $value = $args{$_};
239 18 50 33     464 $self->_croak("Invalid $_") unless Scalar::Util::blessed($value)
240             and $value->isa($base);
241 18         1392 $self->$_($args{$_})
242             }
243              
244 6         276 my $cmd = $self->command;
245 6 50       106 if ($cmd->isa('Test::Valgrind::Command::Aggregate')) {
246 0         0 for my $subcmd ($cmd->commands) {
247 0         0 $args{command} = $subcmd;
248 0         0 $self->run(%args);
249             }
250 0         0 return;
251             }
252              
253 6         58 $self->report($self->report_class->new_diag(
254             'Using valgrind ' . $self->version . ' located at ' . $self->valgrind
255             ));
256              
257 6         358 my $env = $self->command->env($self);
258              
259 6         558 my @supp_args;
260 6 50       24 if ($self->do_suppressions) {
261 0         0 push @supp_args, '--gen-suppressions=all';
262             } else {
263 6 50       274 if (!$self->no_def_supp) {
264 0         0 my $def_supp = $self->def_supp_file;
265 0         0 my $forced;
266 0 0 0     0 if ($self->regen_def_supp and -e $def_supp) {
267 0         0 1 while unlink $def_supp;
268 0         0 $forced = 1;
269             }
270 0 0 0     0 if (defined $def_supp and not -e $def_supp) {
271 0 0       0 $self->report($self->report_class->new_diag(
272             'Generating suppressions' . ($forced ? ' (forced)' : '') . '...'
273             ));
274 0         0 require Test::Valgrind::Suppressions;
275 0         0 Test::Valgrind::Suppressions->generate(
276             tool => $self->tool,
277             command => $self->command,
278             target => $def_supp,
279             );
280 0 0       0 $self->_croak('Couldn\'t generate suppressions') unless -e $def_supp;
281 0         0 $self->report($self->report_class->new_diag(
282             "Suppressions for this perl stored in $def_supp"
283             ));
284             }
285             }
286             my @supp_files = grep {
287 6 0       62 -e $_ and $self->command->check_suppressions_file($_)
  0         0  
288             } $self->suppressions;
289 6 50       276 if (@supp_files > 1) {
    50          
    100          
290 0         0 my $files_list = join "\n", map " $_", @supp_files;
291 0         0 $self->report($self->report_class->new_diag(
292             "Using suppressions from:\n$files_list"
293             ));
294             } elsif (@supp_files) {
295 0         0 $self->report($self->report_class->new_diag(
296             "Using suppressions from $supp_files[0]"
297             ));
298             } elsif ($self->allow_no_supp) {
299 4         44 $self->report($self->report_class->new_diag("No suppressions used"));
300             } else {
301 2         32 $self->_croak("No compatible suppressions available");
302             }
303 4         60 @supp_args = map "--suppressions=$_", @supp_files;
304             }
305              
306 4         16 my $error;
307             GUARDED: {
308 4     2   16 my $guard = Test::Valgrind::Session::Guard->new(sub { $self->finish });
  4         118  
  2         28  
309 4         28 $self->start;
310              
311 4 50       196 pipe my $vrdr, my $vwtr or $self->_croak("pipe(\$vrdr, \$vwtr): $!");
312             {
313 4         10 my $oldfh = select $vrdr;
  4         24  
314 4         38 $|++;
315 4         16 select $oldfh;
316             }
317              
318 4 50       128 pipe my $erdr, my $ewtr or $self->_croak("pipe(\$erdr, \$ewtr): $!");
319             {
320 4         14 my $oldfh = select $erdr;
  4         24  
321 4         20 $|++;
322 4         32 select $oldfh;
323             }
324              
325 4         5563 my $pid = fork;
326 4 50       316 $self->_croak("fork(): $!") unless defined $pid;
327              
328 4 100       85 if ($pid == 0) {
329             {
330 2         18 local $@;
  2         48  
331 2         34 eval { setpgrp(0, 0) };
  2         171  
332             }
333              
334 2 50       136 close $erdr or POSIX::_exit(255);
335              
336 2         18 local $@;
337 2         14 eval {
338 2 50       53 close $vrdr or $self->_croak("close(\$vrdr): $!");
339              
340 2 50       42 fcntl $vwtr, Fcntl::F_SETFD(), 0
341             or $self->_croak("fcntl(\$vwtr, F_SETFD, 0): $!");
342              
343 2         811 my @args = (
344             $self->valgrind,
345             $self->tool->args($self),
346             @supp_args,
347             $self->parser->args($self, $vwtr),
348             $self->command->args($self),
349             );
350              
351             {
352 8     8   48 no warnings 'exec';
  8         16  
  8         6005  
  2         18  
353 2         6 exec { $args[0] } @args;
  2         0  
354             }
355 0         0 $self->_croak("exec @args: $!");
356             };
357              
358 0         0 print $ewtr $@;
359 0         0 close $ewtr;
360              
361 0         0 POSIX::_exit(255);
362             }
363              
364 2         45 local $@;
365             eval {
366             local $SIG{INT} = sub {
367 0     0   0 die 'valgrind analysis was interrupted';
368 2         335 };
369              
370 2 50       88 close $vwtr or $self->_croak("close(\$vwtr): $!");
371 2 50       84 close $ewtr or $self->_croak("close(\$ewtr): $!");
372              
373             SEL: {
374 2         27 my $sel = IO::Select->new($vrdr, $erdr);
  2         180  
375              
376 2         415 my $child_err;
377 2         36 while (my @ready = $sel->can_read) {
378 4 100 66     1477613 last SEL if @ready == 1 and fileno $ready[0] == fileno $vrdr;
379              
380 3         16 my $buf;
381 3         70 my $bytes_read = sysread $erdr, $buf, 4096;
382 3 50       89 if (not defined $bytes_read) {
    100          
383 0         0 $self->_croak("sysread(\$erdr): $!");
384             } elsif ($bytes_read) {
385 1 50       20 $sel->remove($vrdr) unless $child_err;
386 1         113 $child_err .= $buf;
387             } else {
388 2         21 $sel->remove($erdr);
389 2 100       194 die $child_err if $child_err;
390             }
391             }
392             }
393              
394 1         140 my $aborted = $self->parser->parse($self, $vrdr);
395              
396 1 50       15 if ($aborted) {
397 0         0 $self->report($self->report_class->new_diag("valgrind has aborted"));
398 0         0 return 0;
399             }
400              
401 1         25 1;
402 2 100       68 } or do {
403 1         4 $error = $@;
404 1 50       40 kill -(POSIX::SIGKILL()) => $pid if kill 0 => $pid;
405 1         50 close $erdr;
406 1         9 close $vrdr;
407 1         2429 waitpid $pid, 0;
408             # Force the guard destructor to trigger now so that old perls don't lose $@
409 1         36 last GUARDED;
410             };
411              
412 1 50       66 $self->{exit_code} = (waitpid($pid, 0) == $pid) ? $? >> 8 : 255;
413              
414 1 50       20 close $erdr or $self->_croak("close(\$erdr): $!");
415 1 50       14 close $vrdr or $self->_croak("close(\$vrdr): $!");
416              
417 1         32 return;
418             }
419              
420 1 50       21 die $error if $error;
421              
422 0         0 return;
423             }
424              
425 4     4   18 sub Test::Valgrind::Session::Guard::new { bless \($_[1]), $_[0] }
426              
427 2     2   6 sub Test::Valgrind::Session::Guard::DESTROY { ${$_[0]}->() }
  2         78  
428              
429             =head2 C
430              
431             Read-only accessor for the C associated to the current run.
432              
433             =head2 C
434              
435             Read-only accessor for the C associated to the current run.
436              
437             =head2 C
438              
439             Read-only accessor for the C associated to the current tool.
440              
441             =head2 C
442              
443             Read-only accessor for the C associated to the current run.
444              
445             =cut
446              
447             my @members;
448             BEGIN {
449 8     8   136 @members = qw;
450 8         29 for (@members) {
451 32 100   40 1 2022 eval "sub $_ { \@_ <= 1 ? \$_[0]->{$_} : (\$_[0]->{$_} = \$_[1]) }";
  40 100   32 1 535  
  32 100   11 1 482  
  11 100   40 1 361  
  40         978  
452 32 50       5175 die if $@;
453             }
454             }
455              
456             =head2 C
457              
458             Forwards to C<< ->action->do_suppressions >>.
459              
460             =cut
461              
462 16     16 1 670 sub do_suppressions { $_[0]->action->do_suppressions }
463              
464             =head2 C
465              
466             Calls C<< ->tool->parser_class >> with the current session object as the unique argument.
467              
468             =cut
469              
470 4     4 1 144 sub parser_class { $_[0]->tool->parser_class($_[0]) }
471              
472             =head2 C
473              
474             Calls C<< ->tool->report_class >> with the current session object as the unique argument.
475              
476             =cut
477              
478 10     10 1 412 sub report_class { $_[0]->tool->report_class($_[0]) }
479              
480             =head2 C
481              
482             Returns an absolute path to the default suppression file associated to the current session.
483              
484             C will be returned as soon as any of C<< ->command->suppressions_tag >> or C<< ->tool->suppressions_tag >> are also C.
485             Otherwise, the file part of the name is builded by joining those two together, and the directory part is roughly F<< File::HomeDir->my_home / .perl / Test-Valgrind / suppressions / $VERSION >>.
486              
487             =cut
488              
489             sub def_supp_file {
490 0     0 1 0 my ($self) = @_;
491              
492 0         0 my $tool_tag = $self->tool->suppressions_tag($self);
493 0 0       0 return unless defined $tool_tag;
494              
495 0         0 my $cmd_tag = $self->command->suppressions_tag($self);
496 0 0       0 return unless defined $cmd_tag;
497              
498 0         0 require File::HomeDir; # So that it's not needed at configure time.
499              
500 0         0 return File::Spec->catfile(
501             File::HomeDir->my_home,
502             '.perl',
503             'Test-Valgrind',
504             'suppressions',
505             $VERSION,
506             "$tool_tag-$cmd_tag.supp",
507             );
508             }
509              
510             =head2 C
511              
512             my @suppressions = $tvs->suppressions;
513              
514             Returns the list of all the suppressions that will be passed to C.
515             Honors L and L.
516              
517             =cut
518              
519             sub suppressions {
520 6     6 1 18 my ($self) = @_;
521              
522 6         18 my @supps;
523 6 50       242 unless ($self->no_def_supp) {
524 0         0 my $def_supp = $self->def_supp_file;
525 0 0       0 push @supps, $def_supp if defined $def_supp;
526             }
527 6         34 push @supps, $self->extra_supps;
528              
529 6         30 return @supps;
530             }
531              
532             =head2 C
533              
534             $tvs->start;
535              
536             Starts the action and tool associated to the current run.
537             It's automatically called at the beginning of L.
538              
539             =cut
540              
541             sub start {
542 4     4 1 12 my $self = shift;
543              
544 4         10 delete @{$self}{qw};
  4         18  
545              
546 4         154 $self->tool->start($self);
547 4         26 $self->parser($self->parser_class->new)->start($self);
548 4         174 $self->action->start($self);
549              
550 4         12 return;
551             }
552              
553             =head2 C
554              
555             $tvs->abort($msg);
556              
557             Forwards to C<< ->action->abort >> after unshifting the session object to the argument list.
558              
559             =cut
560              
561             sub abort {
562 0     0 1 0 my $self = shift;
563              
564 0         0 $self->action->abort($self, @_);
565             }
566              
567             =head2 C
568              
569             $tvs->report($report);
570              
571             Forwards to C<< ->action->report >> after unshifting the session object to the argument list.
572              
573             =cut
574              
575             sub report {
576 10     10 1 32 my ($self, $report) = @_;
577              
578 10 50       42 return unless defined $report;
579              
580 10         48 for my $handler (qw) {
581 20         826 $report = $self->$handler->filter($self, $report);
582 20 50       88 return unless defined $report;
583             }
584              
585 10         356 $self->action->report($self, $report);
586             }
587              
588             =head2 C
589              
590             $tvs->finish;
591              
592             Finishes the action and tool associated to the current run.
593             It's automatically called at the end of L.
594              
595             =cut
596              
597             sub finish {
598 2     2 1 10 my ($self) = @_;
599              
600 2         143 my $action = $self->action;
601              
602 2         92 $action->finish($self);
603 2         81 $self->parser->finish($self);
604 2         140 $self->tool->finish($self);
605              
606 2         76 my $status = $action->status($self);
607 2 50       19 $self->{last_status} = defined $status ? $status : $self->{exit_code};
608              
609 2         92 $self->$_(undef) for @members;
610              
611 2         38 return;
612             }
613              
614             =head2 C
615              
616             my $status = $tvs->status;
617              
618             Returns the status code of the last run of the session.
619              
620             =cut
621              
622 0     0 1   sub status { $_[0]->{last_status} }
623              
624             =head1 SEE ALSO
625              
626             L, L, L, L, L.
627              
628             L, L.
629              
630             =head1 AUTHOR
631              
632             Vincent Pit, C<< >>, L.
633              
634             You can contact me by mail or on C (vincent).
635              
636             =head1 BUGS
637              
638             Please report any bugs or feature requests to C, or through the web interface at L.
639             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
640              
641             =head1 SUPPORT
642              
643             You can find documentation for this module with the perldoc command.
644              
645             perldoc Test::Valgrind::Session
646              
647             =head1 COPYRIGHT & LICENSE
648              
649             Copyright 2009,2010,2011,2013,2015 Vincent Pit, all rights reserved.
650              
651             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
652              
653             =cut
654              
655             1; # End of Test::Valgrind::Session