File Coverage

blib/lib/Test/Valgrind/Session.pm
Criterion Covered Total %
statement 198 241 82.1
branch 68 118 57.6
condition 15 33 45.4
subroutine 34 39 87.1
pod 22 22 100.0
total 337 453 74.3


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