1 package Test::Valgrind::Session;
8 Test::Valgrind::Session - Test::Valgrind session object.
16 our $VERSION = '1.17';
20 This class supervises the execution of the C<valgrind> process.
21 It also acts as a dispatcher between the different components.
28 use Fcntl (); # F_SETFD
30 use POSIX (); # SIGKILL, _exit()
34 use base qw<Test::Valgrind::Carp>;
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,
51 The package constructor, which takes several options :
57 All the directories from C<@search_dirs> will have F<valgrind> appended to create a list of candidates for the C<valgrind> executable.
59 Defaults to the current C<PATH> environment variable.
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.
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>.
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.
77 If C<$regen_def_supp> is true, the default suppression file associated with the tool and the command will be forcefully regenerated.
83 If C<$no_def_supp> is true, C<valgrind> won't read the default suppression file associated with the tool and the command.
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.
95 C<$extra_supps> is a reference to an array of optional suppression files that will be passed to C<valgrind>.
105 $class = ref($class) || $class;
110 my $vg = delete $args{valgrind};
111 if (defined $vg and not ref $vg) {
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';
120 $class->_croak('Empty valgrind candidates list') unless @paths;
122 my $min_version = delete $args{min_version};
123 defined and not ref and $_ = version->new($_) for $min_version;
125 my ($valgrind, $version);
128 my $ver = qx/$_ --version/;
129 if ($ver =~ /^valgrind-(\d+(\.\d+)*)/) {
131 $version = version->new($1);
132 next if $version < $min_version;
140 $class->_croak('No appropriate valgrind executable could be found')
141 unless defined $valgrind;
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;
148 valgrind => $valgrind,
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,
159 my $valgrind_path = $tvs->valgrind;
161 The path to the selected C<valgrind> executable.
165 my $valgrind_version = $tvs->version;
167 The L<version> object associated to the selected C<valgrind>.
174 my $version = $self->{version};
175 $self->{version} = $version = version->new($version) unless ref $version;
180 =head2 C<regen_def_supp>
182 my $regen_def_supp = $tvs->regen_def_supp;
184 Read-only accessor for the C<regen_def_supp> option.
188 =head2 C<no_def_supp>
190 my $no_def_supp = $tvs->no_def_supp;
192 Read-only accessor for the C<no_def_supp> option.
194 =head2 C<allow_no_supp>
196 my $allow_no_supp = $tvs->allow_no_supp;
198 Read-only accessor for the C<allow_no_supp> option.
202 eval "sub $_ { \$_[0]->{$_} }" for qw<
209 =head2 C<extra_supps>
211 my @extra_supps = $tvs->extra_supps;
213 Read-only accessor for the C<extra_supps> option.
217 sub extra_supps { @{$_[0]->{extra_supps} || []} }
227 Runs the command C<$command> through C<valgrind> with the tool C<$tool>, which will report to the action C<$action>.
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.
234 my ($self, %args) = @_;
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);
244 my $cmd = $self->command;
245 if ($cmd->isa('Test::Valgrind::Command::Aggregate')) {
246 for my $subcmd ($cmd->commands) {
247 $args{command} = $subcmd;
253 $self->report($self->report_class->new_diag(
254 'Using valgrind ' . $self->version . ' located at ' . $self->valgrind
257 my $env = $self->command->env($self);
260 if ($self->do_suppressions) {
261 push @supp_args, '--gen-suppressions=all';
263 if (!$self->no_def_supp) {
264 my $def_supp = $self->def_supp_file;
266 if ($self->regen_def_supp and -e $def_supp) {
267 1 while unlink $def_supp;
270 if (defined $def_supp and not -e $def_supp) {
271 $self->report($self->report_class->new_diag(
272 'Generating suppressions' . ($forced ? ' (forced)' : '') . '...'
274 require Test::Valgrind::Suppressions;
275 Test::Valgrind::Suppressions->generate(
277 command => $self->command,
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"
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"
294 } elsif (@supp_files) {
295 $self->report($self->report_class->new_diag(
296 "Using suppressions from $supp_files[0]"
298 } elsif ($self->allow_no_supp) {
299 $self->report($self->report_class->new_diag("No suppressions used"));
301 $self->_croak("No compatible suppressions available");
303 @supp_args = map "--suppressions=$_", @supp_files;
308 my $guard = Test::Valgrind::Session::Guard->new(sub { $self->finish });
311 pipe my $vrdr, my $vwtr or $self->_croak("pipe(\$vrdr, \$vwtr): $!");
313 my $oldfh = select $vrdr;
318 pipe my $erdr, my $ewtr or $self->_croak("pipe(\$erdr, \$ewtr): $!");
320 my $oldfh = select $erdr;
326 $self->_croak("fork(): $!") unless defined $pid;
331 eval { setpgrp(0, 0) };
334 close $erdr or POSIX::_exit(255);
338 close $vrdr or $self->_croak("close(\$vrdr): $!");
340 fcntl $vwtr, Fcntl::F_SETFD(), 0
341 or $self->_croak("fcntl(\$vwtr, F_SETFD, 0): $!");
345 $self->tool->args($self),
347 $self->parser->args($self, $vwtr),
348 $self->command->args($self),
353 exec { $args[0] } @args;
355 $self->_croak("exec @args: $!");
366 local $SIG{INT} = sub {
367 die 'valgrind analysis was interrupted';
370 close $vwtr or $self->_croak("close(\$vwtr): $!");
371 close $ewtr or $self->_croak("close(\$ewtr): $!");
374 my $sel = IO::Select->new($vrdr, $erdr);
377 while (my @ready = $sel->can_read) {
378 last SEL if @ready == 1 and fileno $ready[0] == fileno $vrdr;
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;
389 die $child_err if $child_err;
394 my $aborted = $self->parser->parse($self, $vrdr);
397 $self->report($self->report_class->new_diag("valgrind has aborted"));
404 kill -(POSIX::SIGKILL()) => $pid if kill 0 => $pid;
408 # Force the guard destructor to trigger now so that old perls don't lose $@
412 $self->{exit_code} = (waitpid($pid, 0) == $pid) ? $? >> 8 : 255;
414 close $erdr or $self->_croak("close(\$erdr): $!");
415 close $vrdr or $self->_croak("close(\$vrdr): $!");
420 die $error if $error;
425 sub Test::Valgrind::Session::Guard::new { bless \($_[1]), $_[0] }
427 sub Test::Valgrind::Session::Guard::DESTROY { ${$_[0]}->() }
431 Read-only accessor for the C<action> associated to the current run.
435 Read-only accessor for the C<tool> associated to the current run.
439 Read-only accessor for the C<parser> associated to the current tool.
443 Read-only accessor for the C<command> associated to the current run.
449 @members = qw<action tool command parser>;
451 eval "sub $_ { \@_ <= 1 ? \$_[0]->{$_} : (\$_[0]->{$_} = \$_[1]) }";
456 =head2 C<do_suppressions>
458 Forwards to C<< ->action->do_suppressions >>.
462 sub do_suppressions { $_[0]->action->do_suppressions }
464 =head2 C<parser_class>
466 Calls C<< ->tool->parser_class >> with the current session object as the unique argument.
470 sub parser_class { $_[0]->tool->parser_class($_[0]) }
472 =head2 C<report_class>
474 Calls C<< ->tool->report_class >> with the current session object as the unique argument.
478 sub report_class { $_[0]->tool->report_class($_[0]) }
480 =head2 C<def_supp_file>
482 Returns an absolute path to the default suppression file associated to the current session.
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 >>.
492 my $tool_tag = $self->tool->suppressions_tag($self);
493 return unless defined $tool_tag;
495 my $cmd_tag = $self->command->suppressions_tag($self);
496 return unless defined $cmd_tag;
498 require File::HomeDir; # So that it's not needed at configure time.
500 return File::Spec->catfile(
501 File::HomeDir->my_home,
506 "$tool_tag-$cmd_tag.supp",
510 =head2 C<suppressions>
512 my @suppressions = $tvs->suppressions;
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>.
523 unless ($self->no_def_supp) {
524 my $def_supp = $self->def_supp_file;
525 push @supps, $def_supp if defined $def_supp;
527 push @supps, $self->extra_supps;
536 Starts the action and tool associated to the current run.
537 It's automatically called at the beginning of L</run>.
544 delete @{$self}{qw<last_status exit_code>};
546 $self->tool->start($self);
547 $self->parser($self->parser_class->new)->start($self);
548 $self->action->start($self);
557 Forwards to C<< ->action->abort >> after unshifting the session object to the argument list.
564 $self->action->abort($self, @_);
569 $tvs->report($report);
571 Forwards to C<< ->action->report >> after unshifting the session object to the argument list.
576 my ($self, $report) = @_;
578 return unless defined $report;
580 for my $handler (qw<tool command>) {
581 $report = $self->$handler->filter($self, $report);
582 return unless defined $report;
585 $self->action->report($self, $report);
592 Finishes the action and tool associated to the current run.
593 It's automatically called at the end of L</run>.
600 my $action = $self->action;
602 $action->finish($self);
603 $self->parser->finish($self);
604 $self->tool->finish($self);
606 my $status = $action->status($self);
607 $self->{last_status} = defined $status ? $status : $self->{exit_code};
609 $self->$_(undef) for @members;
616 my $status = $tvs->status;
618 Returns the status code of the last run of the session.
622 sub status { $_[0]->{last_status} }
626 L<Test::Valgrind>, L<Test::Valgrind::Action>, L<Test::Valgrind::Command>, L<Test::Valgrind::Tool>, L<Test::Valgrind::Parser>.
628 L<version>, L<File::HomeDir>.
632 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
634 You can contact me by mail or on C<irc.perl.org> (vincent).
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.
643 You can find documentation for this module with the perldoc command.
645 perldoc Test::Valgrind::Session
647 =head1 COPYRIGHT & LICENSE
649 Copyright 2009,2010,2011,2013,2015 Vincent Pit, all rights reserved.
651 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
655 1; # End of Test::Valgrind::Session