]> git.vpit.fr Git - perl/modules/Test-Valgrind.git/blob - lib/Test/Valgrind/Session.pm
cefd752aadd761e79734a16e68877dda6c64c93d
[perl/modules/Test-Valgrind.git] / lib / Test / Valgrind / Session.pm
1 package Test::Valgrind::Session;
2
3 use strict;
4 use warnings;
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<valgrind> process.
21 It also acts as a dispatcher between the different components.
22
23 =cut
24
25 use Config       ();
26 use File::Spec   ();
27 use ExtUtils::MM (); # MM->maybe_command()
28 use Scalar::Util ();
29
30 use Fcntl       (); # F_SETFD
31 use IO::Select;
32 use POSIX       (); # SIGKILL, _exit()
33
34 use base qw<Test::Valgrind::Carp>;
35
36 use Test::Valgrind::Version;
37
38 =head1 METHODS
39
40 =head2 C<new>
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<valgrind> appended to create a list of candidates for the C<valgrind> executable.
60
61 Defaults to the current C<PATH> 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<prepended> to the list of the candidates resulting from C<@search_dirs>.
69
70 =item *
71
72 C<$min_version> specifies the minimal C<valgrind> version required.
73 The constructor will croak if it's not able to find an adequate C<valgrind> 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<valgrind> 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<valgrind> 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<valgrind>.
98
99 Defaults to none.
100
101 =back
102
103 =cut
104
105 sub new {
106  my $class = shift;
107  $class = ref($class) || $class;
108
109  my %args = @_;
110
111  my @paths;
112  my $vg = delete $args{valgrind};
113  if (defined $vg and not ref $vg) {
114   @paths = ($vg);
115  } else {
116   push @paths, @$vg if defined $vg and ref $vg eq 'ARRAY';
117   my $dirs = delete $args{search_dirs};
118   $dirs = [ File::Spec->path ] unless defined $dirs;
119   my $exe_name = 'valgrind';
120   $exe_name   .= $Config::Config{exe_ext} if defined $Config::Config{exe_ext};
121   push @paths, map File::Spec->catfile($_, $exe_name), @$dirs
122                                                         if ref $dirs eq 'ARRAY';
123  }
124  $class->_croak('Empty valgrind candidates list') unless @paths;
125
126  my $min_version = delete $args{min_version};
127  if (defined $min_version) {
128   $min_version = Test::Valgrind::Version->new(string => $min_version);
129  }
130
131  my ($valgrind, $version);
132  for my $path (@paths) {
133   next unless defined($path) and MM->maybe_command($path);
134   my $output = qx/$path --version/;
135   my $ver    = do {
136    local $@;
137    eval { Test::Valgrind::Version->new(command_output => $output) };
138   };
139   if (defined $ver) {
140    next if defined $min_version and $ver < $min_version;
141    $valgrind = $path;
142    $version  = $ver;
143    last;
144   }
145  }
146  $class->_croak('No appropriate valgrind executable could be found')
147                                                        unless defined $valgrind;
148
149  my $extra_supps = delete $args{extra_supps};
150  $extra_supps    = [ ] unless $extra_supps and ref $extra_supps eq 'ARRAY';
151  @$extra_supps   = grep { defined && -f $_ && -r _ } @$extra_supps;
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   allow_no_supp  => delete($args{allow_no_supp}),
159   extra_supps    => $extra_supps,
160  }, $class;
161 }
162
163 =head2 C<valgrind>
164
165     my $valgrind_path = $tvs->valgrind;
166
167 The path to the selected C<valgrind> executable.
168
169 =head2 C<version>
170
171     my $valgrind_version = $tvs->version;
172
173 The L<Test::Valgrind::Version> object associated to the selected C<valgrind>.
174
175 =head2 C<regen_def_supp>
176
177     my $regen_def_supp = $tvs->regen_def_supp;
178
179 Read-only accessor for the C<regen_def_supp> option.
180
181 =cut
182
183 =head2 C<no_def_supp>
184
185     my $no_def_supp = $tvs->no_def_supp;
186
187 Read-only accessor for the C<no_def_supp> option.
188
189 =head2 C<allow_no_supp>
190
191     my $allow_no_supp = $tvs->allow_no_supp;
192
193 Read-only accessor for the C<allow_no_supp> option.
194
195 =cut
196
197 eval "sub $_ { \$_[0]->{$_} }" for qw<
198  valgrind
199  version
200  regen_def_supp
201  no_def_supp
202  allow_no_supp
203 >;
204
205 =head2 C<extra_supps>
206
207     my @extra_supps = $tvs->extra_supps;
208
209 Read-only accessor for the C<extra_supps> option.
210
211 =cut
212
213 sub extra_supps { @{$_[0]->{extra_supps} || []} }
214
215 =head2 C<run>
216
217     $tvs->run(
218      action  => $action,
219      tool    => $tool,
220      command => $command,
221     );
222
223 Runs the command C<$command> through C<valgrind> with the tool C<$tool>, which will report to the action C<$action>.
224
225 If the command is a L<Test::Valgrind::Command::Aggregate> object, the action and the tool will be initialized once before running all the aggregated commands.
226
227 =cut
228
229 sub run {
230  my ($self, %args) = @_;
231
232  for (qw<action tool command>) {
233   my $base = 'Test::Valgrind::' . ucfirst;
234   my $value = $args{$_};
235   $self->_croak("Invalid $_") unless Scalar::Util::blessed($value)
236                                                          and $value->isa($base);
237   $self->$_($args{$_})
238  }
239
240  my $cmd = $self->command;
241  if ($cmd->isa('Test::Valgrind::Command::Aggregate')) {
242   for my $subcmd ($cmd->commands) {
243    $args{command} = $subcmd;
244    $self->run(%args);
245   }
246   return;
247  }
248
249  $self->report($self->report_class->new_diag(
250   'Using valgrind ' . $self->version . ' located at ' . $self->valgrind
251  ));
252
253  my $env = $self->command->env($self);
254
255  my @supp_args;
256  if ($self->do_suppressions) {
257   push @supp_args, '--gen-suppressions=all';
258  } else {
259   if (!$self->no_def_supp) {
260    my $def_supp = $self->def_supp_file;
261    my $forced;
262    if ($self->regen_def_supp and -e $def_supp) {
263     1 while unlink $def_supp;
264     $forced = 1;
265    }
266    if (defined $def_supp and not -e $def_supp) {
267     $self->report($self->report_class->new_diag(
268      'Generating suppressions' . ($forced ? ' (forced)' : '') . '...'
269     ));
270     require Test::Valgrind::Suppressions;
271     Test::Valgrind::Suppressions->generate(
272      tool    => $self->tool,
273      command => $self->command,
274      target  => $def_supp,
275     );
276     $self->_croak('Couldn\'t generate suppressions') unless -e $def_supp;
277     $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    -e $_ and $self->command->check_suppressions_file($_)
284   } $self->suppressions;
285   if (@supp_files > 1) {
286    my $files_list = join "\n", map "    $_", @supp_files;
287    $self->report($self->report_class->new_diag(
288     "Using suppressions from:\n$files_list"
289    ));
290   } elsif (@supp_files) {
291    $self->report($self->report_class->new_diag(
292     "Using suppressions from $supp_files[0]"
293    ));
294   } elsif ($self->allow_no_supp) {
295    $self->report($self->report_class->new_diag("No suppressions used"));
296   } else {
297    $self->_croak("No compatible suppressions available");
298   }
299   @supp_args = map "--suppressions=$_", @supp_files;
300  }
301
302  my $error;
303  GUARDED: {
304   my $guard = Test::Valgrind::Session::Guard->new(sub { $self->finish });
305   $self->start;
306
307   pipe my $vrdr, my $vwtr or $self->_croak("pipe(\$vrdr, \$vwtr): $!");
308   {
309    my $oldfh = select $vrdr;
310    $|++;
311    select $oldfh;
312   }
313
314   pipe my $erdr, my $ewtr or $self->_croak("pipe(\$erdr, \$ewtr): $!");
315   {
316    my $oldfh = select $erdr;
317    $|++;
318    select $oldfh;
319   }
320
321   my $pid = fork;
322   $self->_croak("fork(): $!") unless defined $pid;
323
324   if ($pid == 0) {
325    {
326     local $@;
327     eval { setpgrp(0, 0) };
328    }
329
330    close $erdr or POSIX::_exit(255);
331
332    local $@;
333    eval {
334     close $vrdr or $self->_croak("close(\$vrdr): $!");
335
336     fcntl $vwtr, Fcntl::F_SETFD(), 0
337                               or $self->_croak("fcntl(\$vwtr, F_SETFD, 0): $!");
338
339     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      no warnings 'exec';
349      exec { $args[0] } @args;
350     }
351     $self->_croak("exec @args: $!");
352    };
353
354    print $ewtr $@;
355    close $ewtr;
356
357    POSIX::_exit(255);
358   }
359
360   local $@;
361   eval {
362    local $SIG{INT} = sub {
363     die 'valgrind analysis was interrupted';
364    };
365
366    close $vwtr or $self->_croak("close(\$vwtr): $!");
367    close $ewtr or $self->_croak("close(\$ewtr): $!");
368
369    SEL: {
370     my $sel = IO::Select->new($vrdr, $erdr);
371
372     my $child_err;
373     while (my @ready = $sel->can_read) {
374      last SEL if @ready == 1 and fileno $ready[0] == fileno $vrdr;
375
376      my $buf;
377      my $bytes_read = sysread $erdr, $buf, 4096;
378      if (not defined $bytes_read) {
379       $self->_croak("sysread(\$erdr): $!");
380      } elsif ($bytes_read) {
381       $sel->remove($vrdr) unless $child_err;
382       $child_err .= $buf;
383      } else {
384       $sel->remove($erdr);
385       die $child_err if $child_err;
386      }
387     }
388    }
389
390    my $aborted = $self->parser->parse($self, $vrdr);
391
392    if ($aborted) {
393     $self->report($self->report_class->new_diag("valgrind has aborted"));
394     return 0;
395    }
396
397    1;
398   } or do {
399    $error = $@;
400    kill -(POSIX::SIGKILL()) => $pid if kill 0 => $pid;
401    close $erdr;
402    close $vrdr;
403    waitpid $pid, 0;
404    # Force the guard destructor to trigger now so that old perls don't lose $@
405    last GUARDED;
406   };
407
408   $self->{exit_code} = (waitpid($pid, 0) == $pid) ? $? >> 8 : 255;
409
410   close $erdr or $self->_croak("close(\$erdr): $!");
411   close $vrdr or $self->_croak("close(\$vrdr): $!");
412
413   return;
414  }
415
416  die $error if $error;
417
418  return;
419 }
420
421 sub Test::Valgrind::Session::Guard::new     { bless \($_[1]), $_[0] }
422
423 sub Test::Valgrind::Session::Guard::DESTROY { ${$_[0]}->() }
424
425 =head2 C<action>
426
427 Read-only accessor for the C<action> associated to the current run.
428
429 =head2 C<tool>
430
431 Read-only accessor for the C<tool> associated to the current run.
432
433 =head2 C<parser>
434
435 Read-only accessor for the C<parser> associated to the current tool.
436
437 =head2 C<command>
438
439 Read-only accessor for the C<command> associated to the current run.
440
441 =cut
442
443 my @members;
444 BEGIN {
445  @members = qw<action tool command parser>;
446  for (@members) {
447   eval "sub $_ { \@_ <= 1 ? \$_[0]->{$_} : (\$_[0]->{$_} = \$_[1]) }";
448   die if $@;
449  }
450 }
451
452 =head2 C<do_suppressions>
453
454 Forwards to C<< ->action->do_suppressions >>.
455
456 =cut
457
458 sub do_suppressions { $_[0]->action->do_suppressions }
459
460 =head2 C<parser_class>
461
462 Calls C<< ->tool->parser_class >> with the current session object as the unique argument.
463
464 =cut
465
466 sub parser_class { $_[0]->tool->parser_class($_[0]) }
467
468 =head2 C<report_class>
469
470 Calls C<< ->tool->report_class >> with the current session object as the unique argument.
471
472 =cut
473
474 sub report_class { $_[0]->tool->report_class($_[0]) }
475
476 =head2 C<def_supp_file>
477
478 Returns an absolute path to the default suppression file associated to the current session.
479
480 C<undef> will be returned as soon as any of C<< ->command->suppressions_tag >> or C<< ->tool->suppressions_tag >> are also C<undef>.
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  my ($self) = @_;
487
488  my $tool_tag = $self->tool->suppressions_tag($self);
489  return unless defined $tool_tag;
490
491  my $cmd_tag = $self->command->suppressions_tag($self);
492  return unless defined $cmd_tag;
493
494  require File::HomeDir; # So that it's not needed at configure time.
495
496  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<suppressions>
507
508     my @suppressions = $tvs->suppressions;
509
510 Returns the list of all the suppressions that will be passed to C<valgrind>.
511 Honors L</no_def_supp> and L</extra_supps>.
512
513 =cut
514
515 sub suppressions {
516  my ($self) = @_;
517
518  my @supps;
519  unless ($self->no_def_supp) {
520   my $def_supp = $self->def_supp_file;
521   push @supps, $def_supp if defined $def_supp;
522  }
523  push @supps, $self->extra_supps;
524
525  return @supps;
526 }
527
528 =head2 C<start>
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</run>.
534
535 =cut
536
537 sub start {
538  my $self = shift;
539
540  delete @{$self}{qw<last_status exit_code>};
541
542  $self->tool->start($self);
543  $self->parser($self->parser_class->new)->start($self);
544  $self->action->start($self);
545
546  return;
547 }
548
549 =head2 C<abort>
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  my $self = shift;
559
560  $self->action->abort($self, @_);
561 }
562
563 =head2 C<report>
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  my ($self, $report) = @_;
573
574  return unless defined $report;
575
576  for my $handler (qw<tool command>) {
577   $report = $self->$handler->filter($self, $report);
578   return unless defined $report;
579  }
580
581  $self->action->report($self, $report);
582 }
583
584 =head2 C<finish>
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</run>.
590
591 =cut
592
593 sub finish {
594  my ($self) = @_;
595
596  my $action = $self->action;
597
598  $action->finish($self);
599  $self->parser->finish($self);
600  $self->tool->finish($self);
601
602  my $status = $action->status($self);
603  $self->{last_status} = defined $status ? $status : $self->{exit_code};
604
605  $self->$_(undef) for @members;
606
607  return;
608 }
609
610 =head2 C<status>
611
612     my $status = $tvs->status;
613
614 Returns the status code of the last run of the session.
615
616 =cut
617
618 sub status { $_[0]->{last_status} }
619
620 =head1 SEE ALSO
621
622 L<Test::Valgrind>, L<Test::Valgrind::Action>, L<Test::Valgrind::Command>, L<Test::Valgrind::Tool>, L<Test::Valgrind::Parser>.
623
624 L<File::HomeDir>.
625
626 =head1 AUTHOR
627
628 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
629
630 You can contact me by mail or on C<irc.perl.org> (vincent).
631
632 =head1 BUGS
633
634 Please report any bugs or feature requests to C<bug-test-valgrind at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Valgrind>.
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