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