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   1924 use strict;
  8         10  
  8         176  
4 8     8   24 use warnings;
  8         8  
  8         250  
5              
6             =head1 NAME
7              
8             Test::Valgrind::Session - Test::Valgrind session object.
9              
10             =head1 VERSION
11              
12             Version 1.19
13              
14             =cut
15              
16             our $VERSION = '1.19';
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   20 use Config ();
  8         8  
  8         77  
26 8     8   20 use File::Spec ();
  8         9  
  8         71  
27 8     8   3425 use ExtUtils::MM (); # MM->maybe_command()
  8         713493  
  8         252  
28 8     8   76 use Scalar::Util ();
  8         13  
  8         101  
29              
30 8     8   27 use Fcntl (); # F_SETFD
  8         11  
  8         126  
31 8     8   4198 use IO::Select;
  8         9492  
  8         401  
32 8     8   3621 use POSIX (); # SIGKILL, _exit()
  8         38052  
  8         223  
33              
34 8     8   59 use base qw;
  8         12  
  8         1054  
35              
36 8     8   3304 use Test::Valgrind::Version;
  8         18  
  8         7241  
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 10429 my $class = shift;
107 16   33     85 $class = ref($class) || $class;
108              
109 16         59 my %args = @_;
110              
111 16         21 my @paths;
112 16         28 my $vg = delete $args{valgrind};
113 16 100 100     90 if (defined $vg and not ref $vg) {
114 9         20 @paths = ($vg);
115             } else {
116 7 100 66     26 push @paths, @$vg if defined $vg and ref $vg eq 'ARRAY';
117 7         18 my $dirs = delete $args{search_dirs};
118 7 100       132 $dirs = [ File::Spec->path ] unless defined $dirs;
119 7         14 my $exe_name = 'valgrind';
120 7 50       121 $exe_name .= $Config::Config{exe_ext} if defined $Config::Config{exe_ext};
121 7 50       196 push @paths, map File::Spec->catfile($_, $exe_name), @$dirs
122             if ref $dirs eq 'ARRAY';
123             }
124 16 100       55 $class->_croak('Empty valgrind candidates list') unless @paths;
125              
126 15         28 my $min_version = delete $args{min_version};
127 15 100       37 if (defined $min_version) {
128 8         46 $min_version = Test::Valgrind::Version->new(string => $min_version);
129             }
130              
131 15         20 my ($valgrind, $version);
132 15         30 for my $path (@paths) {
133 46 100 66     885 next unless defined($path) and MM->maybe_command($path);
134 10         35216 my $output = qx/$path --version/;
135 10         94 my $ver = do {
136 10         131 local $@;
137 10         38 eval { Test::Valgrind::Version->new(command_output => $output) };
  10         222  
138             };
139 10 50       45 if (defined $ver) {
140 10 100 100     130 next if defined $min_version and $ver < $min_version;
141 8         34 $valgrind = $path;
142 8         149 $version = $ver;
143 8         52 last;
144             }
145             }
146 15 100       160 $class->_croak('No appropriate valgrind executable could be found')
147             unless defined $valgrind;
148              
149 8         28 my $extra_supps = delete $args{extra_supps};
150 8 50 33     83 $extra_supps = [ ] unless $extra_supps and ref $extra_supps eq 'ARRAY';
151 8 0 0     31 @$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         224 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 32 eval "sub $_ { \$_[0]->{$_} }" for qw<
  12     12 1 48  
  0     0 1 0  
  8     8 1 220  
  8     8 1 273  
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 8 sub extra_supps { @{$_[0]->{extra_supps} || []} }
  6         28  
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 5258 my ($self, %args) = @_;
231              
232 6         24 for (qw) {
233 18         68 my $base = 'Test::Valgrind::' . ucfirst;
234 18         170 my $value = $args{$_};
235 18 50 33     354 $self->_croak("Invalid $_") unless Scalar::Util::blessed($value)
236             and $value->isa($base);
237 18         766 $self->$_($args{$_})
238             }
239              
240 6         146 my $cmd = $self->command;
241 6 50       60 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         34 $self->report($self->report_class->new_diag(
250             'Using valgrind ' . $self->version . ' located at ' . $self->valgrind
251             ));
252              
253 6         352 my $env = $self->command->env($self);
254              
255 6         564 my @supp_args;
256 6 50       32 if ($self->do_suppressions) {
257 0         0 push @supp_args, '--gen-suppressions=all';
258             } else {
259 6 50       156 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       22 -e $_ and $self->command->check_suppressions_file($_)
  0         0  
284             } $self->suppressions;
285 6 50       194 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         38 $self->report($self->report_class->new_diag("No suppressions used"));
296             } else {
297 2         14 $self->_croak("No compatible suppressions available");
298             }
299 4         56 @supp_args = map "--suppressions=$_", @supp_files;
300             }
301              
302 4         12 my $error;
303             GUARDED: {
304 4     2   14 my $guard = Test::Valgrind::Session::Guard->new(sub { $self->finish });
  4         80  
  2         23  
305 4         32 $self->start;
306              
307 4 50       184 pipe my $vrdr, my $vwtr or $self->_croak("pipe(\$vrdr, \$vwtr): $!");
308             {
309 4         12 my $oldfh = select $vrdr;
  4         32  
310 4         32 $|++;
311 4         20 select $oldfh;
312             }
313              
314 4 50       82 pipe my $erdr, my $ewtr or $self->_croak("pipe(\$erdr, \$ewtr): $!");
315             {
316 4         8 my $oldfh = select $erdr;
  4         18  
317 4         14 $|++;
318 4         12 select $oldfh;
319             }
320              
321 4         4008 my $pid = fork;
322 4 50       146 $self->_croak("fork(): $!") unless defined $pid;
323              
324 4 100       61 if ($pid == 0) {
325             {
326 2         23 local $@;
  2         70  
327 2         23 eval { setpgrp(0, 0) };
  2         309  
328             }
329              
330 2 50       71 close $erdr or POSIX::_exit(255);
331              
332 2         11 local $@;
333 2         9 eval {
334 2 50       33 close $vrdr or $self->_croak("close(\$vrdr): $!");
335              
336 2 50       27 fcntl $vwtr, Fcntl::F_SETFD(), 0
337             or $self->_croak("fcntl(\$vwtr, F_SETFD, 0): $!");
338              
339 2         465 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   46 no warnings 'exec';
  8         13  
  8         4103  
  2         9  
349 2         3 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         39 local $@;
361             eval {
362             local $SIG{INT} = sub {
363 0     0   0 die 'valgrind analysis was interrupted';
364 2         143 };
365              
366 2 50       280 close $vwtr or $self->_croak("close(\$vwtr): $!");
367 2 50       38 close $ewtr or $self->_croak("close(\$ewtr): $!");
368              
369             SEL: {
370 2         4 my $sel = IO::Select->new($vrdr, $erdr);
  2         124  
371              
372 2         393 my $child_err;
373 2         36 while (my @ready = $sel->can_read) {
374 4 100 66     1131607 last SEL if @ready == 1 and fileno $ready[0] == fileno $vrdr;
375              
376 3         8 my $buf;
377 3         42 my $bytes_read = sysread $erdr, $buf, 4096;
378 3 50       27 if (not defined $bytes_read) {
    100          
379 0         0 $self->_croak("sysread(\$erdr): $!");
380             } elsif ($bytes_read) {
381 1 50       12 $sel->remove($vrdr) unless $child_err;
382 1         63 $child_err .= $buf;
383             } else {
384 2         19 $sel->remove($erdr);
385 2 100       161 die $child_err if $child_err;
386             }
387             }
388             }
389              
390 1         86 my $aborted = $self->parser->parse($self, $vrdr);
391              
392 1 50       11 if ($aborted) {
393 0         0 $self->report($self->report_class->new_diag("valgrind has aborted"));
394 0         0 return 0;
395             }
396              
397 1         18 1;
398 2 100       130 } or do {
399 1         3 $error = $@;
400 1 50       25 kill -(POSIX::SIGKILL()) => $pid if kill 0 => $pid;
401 1         30 close $erdr;
402 1         8 close $vrdr;
403 1         1546 waitpid $pid, 0;
404             # Force the guard destructor to trigger now so that old perls don't lose $@
405 1         24 last GUARDED;
406             };
407              
408 1 50       32 $self->{exit_code} = (waitpid($pid, 0) == $pid) ? $? >> 8 : 255;
409              
410 1 50       18 close $erdr or $self->_croak("close(\$erdr): $!");
411 1 50       13 close $vrdr or $self->_croak("close(\$vrdr): $!");
412              
413 1         22 return;
414             }
415              
416 1 50       16 die $error if $error;
417              
418 0         0 return;
419             }
420              
421 4     4   16 sub Test::Valgrind::Session::Guard::new { bless \($_[1]), $_[0] }
422              
423 2     2   3 sub Test::Valgrind::Session::Guard::DESTROY { ${$_[0]}->() }
  2         27  
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   30 @members = qw;
446 8         18 for (@members) {
447 32 100   40 1 1371 eval "sub $_ { \@_ <= 1 ? \$_[0]->{$_} : (\$_[0]->{$_} = \$_[1]) }";
  40 100   32 1 346  
  32 100   11 1 437  
  11 100   40 1 219  
  40         1010  
448 32 50       3382 die if $@;
449             }
450             }
451              
452             =head2 C
453              
454             Forwards to C<< ->action->do_suppressions >>.
455              
456             =cut
457              
458 16     16 1 410 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 174 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 238 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 3612 my ($self) = @_;
517              
518 6         12 my @supps;
519 6 50       158 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         26 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 8 my $self = shift;
539              
540 4         18 delete @{$self}{qw};
  4         22  
541              
542 4         144 $self->tool->start($self);
543 4         20 $self->parser($self->parser_class->new)->start($self);
544 4         108 $self->action->start($self);
545              
546 4         8 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 20 my ($self, $report) = @_;
573              
574 10 50       28 return unless defined $report;
575              
576 10         24 for my $handler (qw) {
577 20         730 $report = $self->$handler->filter($self, $report);
578 20 50       56 return unless defined $report;
579             }
580              
581 10         242 $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 7 my ($self) = @_;
595              
596 2         133 my $action = $self->action;
597              
598 2         59 $action->finish($self);
599 2         58 $self->parser->finish($self);
600 2         65 $self->tool->finish($self);
601              
602 2         29 my $status = $action->status($self);
603 2 50       15 $self->{last_status} = defined $status ? $status : $self->{exit_code};
604              
605 2         68 $self->$_(undef) for @members;
606              
607 2         25 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,2016 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