Test::Valgrind::Session - Test::Valgrind session object.


Test-Valgrind documentation Contained in the Test-Valgrind distribution.

Index


Code Index:

NAME

Top

Test::Valgrind::Session - Test::Valgrind session object.

VERSION

Top

Version 1.12

DESCRIPTION

Top

This class supervises the execution of the valgrind process. It also acts as a dispatcher between the different components.

METHODS

Top

new search_dirs => \@search_dirs, valgrind => [ $valgrind | \@valgrind ], min_version => $min_version, no_def_supp => $no_def_supp, extra_supps => \@extra_supps

The package constructor, which takes several options :

valgrind

The path to the selected valgrind executable.

version

The version object associated to the selected valgrind.

no_def_supp

Read-only accessor for the no_def_supp option.

extra_supps

Read-only accessor for the extra_supps option.

run action => $action, tool => $tool, command => $command

Runs the command $command through valgrind with the tool $tool, which will report to the action $action.

If the command is a Test::Valgrind::Command::Aggregate object, the action and the tool will be initialized once before running all the aggregated commands.

action

Read-only accessor for the action associated to the current run.

tool

Read-only accessor for the tool associated to the current run.

parser

Read-only accessor for the parser associated to the current tool.

command

Read-only accessor for the command associated to the current run.

do_suppressions

Forwards to ->action->do_suppressions.

parser_class

Calls ->tool->parser_class with the current session object as the unique argument.

report_class

Calls ->tool->report_class with the current session object as the unique argument.

def_supp_file

Returns an absolute path to the default suppression file associated to the current session.

undef will be returned as soon as any of ->command->suppressions_tag or ->tool->suppressions_tag are also undef. Otherwise, the file part of the name is builded by joining those two together, and the directory part is roughly File::HomeDir->my_home / .perl / Test-Valgrind / suppressions / $VERSION.

suppressions

Returns the list of all the suppressions that will be passed to valgrind. Honors no_def_supp and extra_supps.

start

Starts the action and tool associated to the current run. It's automatically called at the beginning of run.

abort $msg

Forwards to ->action->abort after unshifting the session object to the argument list.

report $report

Forwards to ->action->report after unshifting the session object to the argument list.

finish

Finishes the action and tool associated to the current run. It's automatically called at the end of run.

status

Returns the status code of the last run of the session.

SEE ALSO

Top

Test::Valgrind, Test::Valgrind::Action, Test::Valgrind::Command, Test::Valgrind::Tool, Test::Valgrind::Parser.

version, File::HomeDir.

AUTHOR

Top

Vincent Pit, <perl at profvince.com>, http://www.profvince.com.

You can contact me by mail or on irc.perl.org (vincent).

BUGS

Top

Please report any bugs or feature requests to bug-test-valgrind at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Valgrind. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

Top

You can find documentation for this module with the perldoc command.

    perldoc Test::Valgrind::Session

COPYRIGHT & LICENSE

Top


Test-Valgrind documentation Contained in the Test-Valgrind distribution.
package Test::Valgrind::Session;

use strict;
use warnings;

our $VERSION = '1.12';

use File::Spec   ();
use Scalar::Util ();

use Fcntl (); # F_SETFD
use POSIX (); # SIGKILL

use version ();

use base qw/Test::Valgrind::Carp/;

sub new {
 my $class = shift;
 $class = ref($class) || $class;

 my %args = @_;

 my @paths;
 my $vg = delete $args{valgrind};
 if (defined $vg and not ref $vg) {
  @paths = ($vg);
 } else {
  push @paths, @$vg if $vg and ref $vg eq 'ARRAY';
  my $dirs = delete $args{search_dirs};
  $dirs = [ File::Spec->path ] unless $dirs;
  push @paths, map File::Spec->catfile($_, 'valgrind'), @$dirs
                                                        if ref $dirs eq 'ARRAY';
 }
 $class->_croak('Empty valgrind candidates list') unless @paths;

 my $min_version = delete $args{min_version};
 defined and not ref and $_ = version->new($_) for $min_version;

 my ($valgrind, $version);
 for (@paths) {
  next unless -x;
  my $ver = qx/$_ --version/;
  if ($ver =~ /^valgrind-(\d+(\.\d+)*)/) {
   if ($min_version) {
    $version = version->new($1);
    next if $version < $min_version;
   } else {
    $version = $1;
   }
   $valgrind = $_;
   last;
  }
 }
 $class->_croak('No appropriate valgrind executable could be found')
                                                       unless defined $valgrind;

 my $extra_supps = delete $args{extra_supps};
 $extra_supps    = [ ] unless $extra_supps and ref $extra_supps eq 'ARRAY';
 @$extra_supps   = grep { defined && -f $_ && -r _ } @$extra_supps;

 bless {
  valgrind    => $valgrind,
  version     => $version,
  no_def_supp => delete($args{no_def_supp}),
  extra_supps => $extra_supps,
 }, $class;
}

sub version {
 my ($self) = @_;

 my $version = $self->{version};
 $self->{version} = $version = version->new($version) unless ref $version;

 return $version;
}

eval "sub $_ { \$_[0]->{$_} }" for qw/valgrind no_def_supp/;

sub extra_supps { @{$_[0]->{extra_supps} || []} }

sub run {
 my $self = shift;

 my %args = @_;

 $self->start(%args);
 my $guard = bless sub { $self->finish } => 'Test::Valgrind::Session::Guard';

 $self->_run($args{command});
}

sub _run {
 my ($self, $cmd) = @_;

 if ($cmd->isa('Test::Valgrind::Command::Aggregate')) {
  $self->_run($_) for $cmd->commands;
  return;
 }

 $self->command($cmd);

 $self->report($self->report_class->new_diag(
  'Using valgrind ' . $self->version . ' located at ' . $self->valgrind
 ));

 my $env = $self->command->env($self);

 my @supp_args;
 if ($self->do_suppressions) {
  push @supp_args, '--gen-suppressions=all';
 } elsif (not $self->no_def_supp) {
  my $def_supp = $self->def_supp_file;
  if (defined $def_supp and not -e $def_supp) {
   $self->report($self->report_class->new_diag(
    "Generating suppressions..."
   ));
   require Test::Valgrind::Suppressions;
   Test::Valgrind::Suppressions->generate(
    tool    => $self->tool,
    command => $self->command,
    target  => $def_supp,
   );
   $self->_croak('Couldn\'t generate suppressions') unless -e $def_supp;
   $self->report($self->report_class->new_diag(
    "Suppressions for this perl stored in $def_supp"
   ));
  }
  for ($self->suppressions) {
   next unless -e $_;
   $self->report($self->report_class->new_diag("Using suppression file $_"));
   push @supp_args, "--suppressions=$_";
  }
 }

 pipe my $vrdr, my $vwtr or $self->_croak("pipe(\$vrdr, \$vwtr): $!");
 {
  my $oldfh = select $vrdr;
  $|++;
  select $oldfh;
 }

 my $pid = fork;
 $self->_croak("fork(): $!") unless defined $pid;

 if ($pid == 0) {
  eval 'setpgrp 0, 0';
  close $vrdr or $self->_croak("close(\$vrdr): $!");
  fcntl $vwtr, Fcntl::F_SETFD(), 0
                              or $self->_croak("fcntl(\$vwtr, F_SETFD, 0): $!");

  my @args = (
   $self->valgrind,
   $self->tool->args($self),
   @supp_args,
   $self->parser->args($self, $vwtr),
   $self->command->args($self),
  );

#  $self->report($self->report_class->new_diag("@args"));

  exec { $args[0] } @args or $self->_croak("exec @args: $!");
 }

 local $SIG{INT} = sub {
  kill -(POSIX::SIGKILL()) => $pid;
  waitpid $pid, 0;
  die 'interrupted';
 };

 close $vwtr or $self->_croak("close(\$vwtr): $!");

 $self->parser->parse($self, $vrdr);

 $self->{exit_code} = (waitpid($pid, 0) == $pid) ? $? >> 8 : 255;

 close $vrdr or $self->_croak("close(\$vrdr): $!");

 return;
}

sub Test::Valgrind::Session::Guard::DESTROY { $_[0]->() }

my @members;
BEGIN {
 @members = qw/action tool command parser/;
 for (@members) {
  eval "sub $_ { \@_ <= 1 ? \$_[0]->{$_} : (\$_[0]->{$_} = \$_[1]) }";
  die if $@;
 }
}

sub do_suppressions { $_[0]->action->do_suppressions }

sub parser_class { $_[0]->tool->parser_class($_[0]) }

sub report_class { $_[0]->tool->report_class($_[0]) }

sub def_supp_file {
 my ($self) = @_;

 my $tool_tag = $self->tool->suppressions_tag($self);
 return unless defined $tool_tag;

 my $cmd_tag = $self->command->suppressions_tag($self);
 return unless defined $cmd_tag;

 require File::HomeDir; # So that it's not needed at configure time.

 return File::Spec->catfile(
  File::HomeDir->my_home,
  '.perl',
  'Test-Valgrind',
  'suppressions',
  $VERSION,
  "$tool_tag-$cmd_tag.supp",
 );
}

sub suppressions {
 my ($self) = @_;

 my @supps;
 unless ($self->no_def_supp) {
  my $def_supp = $self->def_supp_file;
  push @supps, $def_supp if defined $def_supp;
 }
 push @supps, $self->extra_supps;

 return @supps;
}

sub start {
 my $self = shift;

 my %args = @_;

 for (qw/action tool command/) {
  my $base = 'Test::Valgrind::' . ucfirst;
  my $value = $args{$_};
  $self->_croak("Invalid $_") unless Scalar::Util::blessed($value)
                                                         and $value->isa($base);
  $self->$_($args{$_})
 }

 delete @{$self}{qw/last_status exit_code/};

 $self->tool->start($self);
 $self->parser($self->parser_class->new)->start($self);
 $self->action->start($self);

 return;
}

sub abort {
 my $self = shift;

 $self->action->abort($self, @_);
}

sub report {
 my ($self, $report) = @_;

 return unless defined $report;

 for my $handler (qw/tool command/) {
  $report = $self->$handler->filter($self, $report);
  return unless defined $report;
 }

 $self->action->report($self, $report);
}

sub finish {
 my ($self) = @_;

 my $action = $self->action;

 $action->finish($self);
 $self->parser->finish($self);
 $self->tool->finish($self);

 my $status = $action->status($self);
 $self->{last_status} = defined $status ? $status : $self->{exit_code};

 $self->$_(undef) for @members;

 return;
}

sub status { $_[0]->{last_status} }

1; # End of Test::Valgrind::Session