1 package Test::Valgrind::Session;
8 Test::Valgrind::Session - Test::Valgrind session object.
16 our $VERSION = '1.02';
20 This class supervises the execution of the C<valgrind> process.
21 It also acts as a dispatcher between the different components.
25 # All these modules are required at configure time.
31 require Fcntl; # F_SETFD
32 require POSIX; # SIGKILL
35 use base qw/Test::Valgrind::Carp/;
39 =head2 C<< new search_dirs => \@search_dirs, valgrind => [ $valgrind | \@valgrind ], min_version => $min_version, no_def_supp => $no_def_supp, extra_supps => \@extra_supps >>
41 The package constructor, which takes several options :
47 All the directories from C<@search_dirs> will have F<valgrind> appended to create a list of candidates for the C<valgrind> executable.
49 Defaults to the current C<PATH> environment variable.
53 If a simple scalar C<$valgrind> is passed as the value to C<'valgrind'>, it will be the only candidate.
54 C<@search_dirs> will then be ignored.
56 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>.
60 C<$min_version> specifies the minimal C<valgrind> version required.
61 The constructor will croak if it's not able to find an adequate C<valgrind> from the supplied candidates list and search path.
67 If C<$no_def_supp> is false, C<valgrind> won't read the default suppression file associated with the tool and the command.
73 C<$extra_supps> is a reference to an array of optional suppression files that will be passed to C<valgrind>.
81 my $build_version = sub {
88 $class = ref($class) || $class;
93 my $vg = delete $args{vg};
94 if (defined $vg and not ref $vg) {
97 push @paths, @$vg if $vg and ref $vg eq 'ARRAY';
98 my $dirs = delete $args{search_dirs};
99 $dirs = [ File::Spec->path ] unless $dirs;
100 push @paths, map File::Spec->catfile($_, 'valgrind'), @$dirs
101 if ref $dirs eq 'ARRAY';
103 $class->_croak('Empty valgrind candidates list') unless @paths;
105 my $min_version = delete $args{min_version};
106 defined and not ref and $_ = $build_version->($_) for $min_version;
108 my ($valgrind, $version);
111 my $ver = qx/$_ --version/;
112 if ($ver =~ /^valgrind-(\d+(\.\d+)*)/) {
114 $version = $build_version->($1);
115 next if $version < $min_version;
123 $class->_croak('No appropriate valgrind executable could be found')
124 unless defined $valgrind;
126 my $extra_supps = delete $args{extra_supps};
127 $extra_supps = [ ] unless $extra_supps and ref $extra_supps eq 'ARRAY';
128 @$extra_supps = grep { defined && -f $_ && -r _ } @$extra_supps;
131 valgrind => $valgrind,
133 no_def_supp => delete($args{no_def_supp}),
134 extra_supps => $extra_supps,
140 The path to the selected C<valgrind> executable.
144 The L<version> object associated to the selected C<valgrind>.
151 my $version = $self->{version};
152 $self->{version} = $version = $build_version->($version) unless ref $version;
157 =head2 C<no_def_supp>
159 Read-only accessor for the C<no_def_supp> option.
163 eval "sub $_ { \$_[0]->{$_} }" for qw/valgrind no_def_supp/;
165 =head2 C<extra_supps>
167 Read-only accessor for the C<extra_supps> option.
171 sub extra_supps { @{$_[0]->{extra_supps} || []} }
173 =head2 C<< run action => $action, tool => $tool, command => $command >>
175 Runs the command C<$command> through C<valgrind> with the tool C<$tool>, which will report to the action C<$action>.
177 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.
187 my $guard = bless sub { $self->finish } => 'Test::Valgrind::Session::Guard';
189 $self->_run($args{command});
193 my ($self, $cmd) = @_;
195 if ($cmd->isa('Test::Valgrind::Command::Aggregate')) {
196 $self->_run($_) for $cmd->commands;
200 $self->command($cmd);
202 $self->report($self->report_class->new_diag(
203 'Using valgrind ' . $self->version . ' located at ' . $self->valgrind
206 my $env = $self->command->env($self);
209 if ($self->do_suppressions) {
210 push @supp_args, '--gen-suppressions=all';
211 } elsif (not $self->no_def_supp) {
212 my $def_supp = $self->def_supp_file;
213 if (defined $def_supp and not -e $def_supp) {
214 $self->report($self->report_class->new_diag(
215 "Generating suppressions..."
217 require Test::Valgrind::Suppressions;
218 Test::Valgrind::Suppressions->generate(
220 command => $self->command,
223 $self->_croak('Couldn\'t generate suppressions') unless -e $def_supp;
224 $self->report($self->report_class->new_diag(
225 "Suppressions for this perl stored in $def_supp"
228 push @supp_args, '--suppressions=' . $_ for $self->suppressions;
231 pipe my $vrdr, my $vwtr or $self->_croak("pipe(\$vrdr, \$vwtr): $!");
233 my $oldfh = select $vrdr;
239 $self->_croak("fork(): $!") unless defined $pid;
243 close $vrdr or $self->_croak("close(\$vrdr): $!");
244 fcntl $vwtr, Fcntl::F_SETFD(), 0
245 or $self->_croak("fcntl(\$vwtr, F_SETFD, 0): $!");
249 '--log-fd=' . fileno($vwtr),
250 $self->tool->args($self),
252 $self->command->args($self),
255 # $self->report($self->report_class->new_diag("@args"));
257 exec { $args[0] } @args or $self->_croak("exec @args: $!");
260 local $SIG{INT} = sub {
261 kill -(POSIX::SIGKILL()) => $pid;
266 close $vwtr or $self->_croak("close(\$vwtr): $!");
268 $self->tool->parse($self, $vrdr);
270 $self->{exit_code} = (waitpid($pid, 0) == $pid) ? $? >> 8 : 255;
272 close $vrdr or $self->_croak("close(\$vrdr): $!");
277 sub Test::Valgrind::Session::Guard::DESTROY { $_[0]->() }
281 Read-only accessor for the C<action> associated to the current run.
285 Read-only accessor for the C<tool> associated to the current run.
289 Read-only accessor for the C<command> associated to the current run.
295 @members = qw/action tool command/;
297 eval "sub $_ { \@_ <= 1 ? \$_[0]->{$_} : (\$_[0]->{$_} = \$_[1]) }";
302 =head2 C<do_suppressions>
304 Forwards to C<< ->action->do_suppressions >>.
308 sub do_suppressions { $_[0]->action->do_suppressions }
310 =head2 C<report_class>
312 Calls C<< ->action->report_class >> with the current session object as the unique argument.
316 sub report_class { $_[0]->tool->report_class($_[0]) }
318 =head2 C<def_supp_file>
320 Returns an absolute path to the default suppression file associated to the current session.
322 C<undef> will be returned as soon as any of C<< ->command->suppressions_tag >> or C<< ->tool->suppressions_tag >> are also C<undef>.
323 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 >>.
330 my $tool_tag = $self->tool->suppressions_tag($self);
331 return unless defined $tool_tag;
333 my $cmd_tag = $self->command->suppressions_tag($self);
334 return unless defined $cmd_tag;
336 require File::HomeDir; # So that it's not needed at configure time.
338 return File::Spec->catfile(
339 File::HomeDir->my_home,
344 "$tool_tag-$cmd_tag.supp",
348 =head2 C<suppressions>
350 Returns the list of all the suppressions that will be passed to C<valgrind>.
351 Honors L</no_def_supp> and L</extra_supps>.
359 unless ($self->no_def_supp) {
360 my $def_supp = $self->def_supp_file;
361 push @supps, $def_supp if defined $def_supp;
363 push @supps, $self->extra_supps;
370 Starts the action and tool associated to the current run.
371 It's automatically called at the beginning of L</run>.
381 my $base = 'Test::Valgrind::' . ucfirst;
382 my $value = $args{$_};
383 $self->_croak("Invalid $_") unless Scalar::Util::blessed($value)
384 and $value->isa($base);
388 delete @{$self}{qw/last_status exit_code/};
390 $self->tool->start($self);
391 $self->action->start($self);
398 Forwards to C<< ->action->abort >> after unshifting the session object to the argument list.
404 $self->action->abort($self, @_);
407 =head2 C<report $report>
409 Forwards to C<< ->action->report >> after unshifting the session object to the argument list.
414 my ($self, $report) = @_;
416 return unless defined $report;
418 $report = $self->command->filter($self, $report);
419 return unless defined $report;
421 $self->action->report($self, $report);
426 Finishes the action and tool associated to the current run.
427 It's automatically called at the end of L</run>.
434 my $action = $self->action;
435 $action->finish($self);
436 $self->tool->finish($self);
438 my $status = $action->status($self);
439 $self->{last_status} = defined $status ? $status : $self->{exit_code};
441 $self->$_(undef) for @members;
448 Returns the status code of the last run of the session.
452 sub status { $_[0]->{last_status} }
456 L<Test::Valgrind>, L<Test::Valgrind::Action>, L<Test::Valgrind::Tool>, L<Test::Valgrind::Command>.
458 L<version>, L<File::HomeDir>.
462 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
464 You can contact me by mail or on C<irc.perl.org> (vincent).
468 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>.
469 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
473 You can find documentation for this module with the perldoc command.
475 perldoc Test::Valgrind::Session
477 =head1 COPYRIGHT & LICENSE
479 Copyright 2009 Vincent Pit, all rights reserved.
481 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
485 1; # End of Test::Valgrind::Session