]> git.vpit.fr Git - perl/modules/Test-Valgrind.git/blob - lib/Test/Valgrind/Session.pm
b1710e2947acf86b0bf17922ce713fd86dd4462e
[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.11
13
14 =cut
15
16 our $VERSION = '1.11';
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 POSIX (); # SIGKILL
30
31 use version ();
32
33 use base qw/Test::Valgrind::Carp/;
34
35 =head1 METHODS
36
37 =head2 C<< new search_dirs => \@search_dirs, valgrind => [ $valgrind | \@valgrind ], min_version => $min_version, no_def_supp => $no_def_supp, extra_supps => \@extra_supps >>
38
39 The package constructor, which takes several options :
40
41 =over 4
42
43 =item *
44
45 All the directories from C<@search_dirs> will have F<valgrind> appended to create a list of candidates for the C<valgrind> executable.
46
47 Defaults to the current C<PATH> environment variable.
48
49 =item *
50
51 If a simple scalar C<$valgrind> is passed as the value to C<'valgrind'>, it will be the only candidate.
52 C<@search_dirs> will then be ignored.
53
54 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>.
55
56 =item *
57
58 C<$min_version> specifies the minimal C<valgrind> version required.
59 The constructor will croak if it's not able to find an adequate C<valgrind> from the supplied candidates list and search path.
60
61 Defaults to none.
62
63 =item *
64
65 If C<$no_def_supp> is false, C<valgrind> won't read the default suppression file associated with the tool and the command.
66
67 Defaults to false.
68
69 =item *
70
71 C<$extra_supps> is a reference to an array of optional suppression files that will be passed to C<valgrind>.
72
73 Defaults to none.
74
75 =back
76
77 =cut
78
79 sub new {
80  my $class = shift;
81  $class = ref($class) || $class;
82
83  my %args = @_;
84
85  my @paths;
86  my $vg = delete $args{valgrind};
87  if (defined $vg and not ref $vg) {
88   @paths = ($vg);
89  } else {
90   push @paths, @$vg if $vg and ref $vg eq 'ARRAY';
91   my $dirs = delete $args{search_dirs};
92   $dirs = [ File::Spec->path ] unless $dirs;
93   push @paths, map File::Spec->catfile($_, 'valgrind'), @$dirs
94                                                         if ref $dirs eq 'ARRAY';
95  }
96  $class->_croak('Empty valgrind candidates list') unless @paths;
97
98  my $min_version = delete $args{min_version};
99  defined and not ref and $_ = version->new($_) for $min_version;
100
101  my ($valgrind, $version);
102  for (@paths) {
103   next unless -x;
104   my $ver = qx/$_ --version/;
105   if ($ver =~ /^valgrind-(\d+(\.\d+)*)/) {
106    if ($min_version) {
107     $version = version->new($1);
108     next if $version < $min_version;
109    } else {
110     $version = $1;
111    }
112    $valgrind = $_;
113    last;
114   }
115  }
116  $class->_croak('No appropriate valgrind executable could be found')
117                                                        unless defined $valgrind;
118
119  my $extra_supps = delete $args{extra_supps};
120  $extra_supps    = [ ] unless $extra_supps and ref $extra_supps eq 'ARRAY';
121  @$extra_supps   = grep { defined && -f $_ && -r _ } @$extra_supps;
122
123  bless {
124   valgrind    => $valgrind,
125   version     => $version,
126   no_def_supp => delete($args{no_def_supp}),
127   extra_supps => $extra_supps,
128  }, $class;
129 }
130
131 =head2 C<valgrind>
132
133 The path to the selected C<valgrind> executable.
134
135 =head2 C<version>
136
137 The L<version> object associated to the selected C<valgrind>.
138
139 =cut
140
141 sub version {
142  my ($self) = @_;
143
144  my $version = $self->{version};
145  $self->{version} = $version = version->new($version) unless ref $version;
146
147  return $version;
148 }
149
150 =head2 C<no_def_supp>
151
152 Read-only accessor for the C<no_def_supp> option.
153
154 =cut
155
156 eval "sub $_ { \$_[0]->{$_} }" for qw/valgrind no_def_supp/;
157
158 =head2 C<extra_supps>
159
160 Read-only accessor for the C<extra_supps> option.
161
162 =cut
163
164 sub extra_supps { @{$_[0]->{extra_supps} || []} }
165
166 =head2 C<< run action => $action, tool => $tool, command => $command >>
167
168 Runs the command C<$command> through C<valgrind> with the tool C<$tool>, which will report to the action C<$action>.
169
170 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.
171
172 =cut
173
174 sub run {
175  my $self = shift;
176
177  my %args = @_;
178
179  $self->start(%args);
180  my $guard = bless sub { $self->finish } => 'Test::Valgrind::Session::Guard';
181
182  $self->_run($args{command});
183 }
184
185 sub _run {
186  my ($self, $cmd) = @_;
187
188  if ($cmd->isa('Test::Valgrind::Command::Aggregate')) {
189   $self->_run($_) for $cmd->commands;
190   return;
191  }
192
193  $self->command($cmd);
194
195  $self->report($self->report_class->new_diag(
196   'Using valgrind ' . $self->version . ' located at ' . $self->valgrind
197  ));
198
199  my $env = $self->command->env($self);
200
201  my @supp_args;
202  if ($self->do_suppressions) {
203   push @supp_args, '--gen-suppressions=all';
204  } elsif (not $self->no_def_supp) {
205   my $def_supp = $self->def_supp_file;
206   if (defined $def_supp and not -e $def_supp) {
207    $self->report($self->report_class->new_diag(
208     "Generating suppressions..."
209    ));
210    require Test::Valgrind::Suppressions;
211    Test::Valgrind::Suppressions->generate(
212     tool    => $self->tool,
213     command => $self->command,
214     target  => $def_supp,
215    );
216    $self->_croak('Couldn\'t generate suppressions') unless -e $def_supp;
217    $self->report($self->report_class->new_diag(
218     "Suppressions for this perl stored in $def_supp"
219    ));
220   }
221   push @supp_args, '--suppressions=' . $_ for $self->suppressions;
222  }
223
224  pipe my $vrdr, my $vwtr or $self->_croak("pipe(\$vrdr, \$vwtr): $!");
225  {
226   my $oldfh = select $vrdr;
227   $|++;
228   select $oldfh;
229  }
230
231  my $pid = fork;
232  $self->_croak("fork(): $!") unless defined $pid;
233
234  if ($pid == 0) {
235   eval 'setpgrp 0, 0';
236   close $vrdr or $self->_croak("close(\$vrdr): $!");
237   fcntl $vwtr, Fcntl::F_SETFD(), 0
238                               or $self->_croak("fcntl(\$vwtr, F_SETFD, 0): $!");
239
240   my @args = (
241    $self->valgrind,
242    $self->tool->args($self),
243    @supp_args,
244    $self->parser->args($self, $vwtr),
245    $self->command->args($self),
246   );
247
248 #  $self->report($self->report_class->new_diag("@args"));
249
250   exec { $args[0] } @args or $self->_croak("exec @args: $!");
251  }
252
253  local $SIG{INT} = sub {
254   kill -(POSIX::SIGKILL()) => $pid;
255   waitpid $pid, 0;
256   die 'interrupted';
257  };
258
259  close $vwtr or $self->_croak("close(\$vwtr): $!");
260
261  $self->parser->parse($self, $vrdr);
262
263  $self->{exit_code} = (waitpid($pid, 0) == $pid) ? $? >> 8 : 255;
264
265  close $vrdr or $self->_croak("close(\$vrdr): $!");
266
267  return;
268 }
269
270 sub Test::Valgrind::Session::Guard::DESTROY { $_[0]->() }
271
272 =head2 C<action>
273
274 Read-only accessor for the C<action> associated to the current run.
275
276 =head2 C<tool>
277
278 Read-only accessor for the C<tool> associated to the current run.
279
280 =head2 C<parser>
281
282 Read-only accessor for the C<parser> associated to the current tool.
283
284 =head2 C<command>
285
286 Read-only accessor for the C<command> associated to the current run.
287
288 =cut
289
290 my @members;
291 BEGIN {
292  @members = qw/action tool command parser/;
293  for (@members) {
294   eval "sub $_ { \@_ <= 1 ? \$_[0]->{$_} : (\$_[0]->{$_} = \$_[1]) }";
295   die if $@;
296  }
297 }
298
299 =head2 C<do_suppressions>
300
301 Forwards to C<< ->action->do_suppressions >>.
302
303 =cut
304
305 sub do_suppressions { $_[0]->action->do_suppressions }
306
307 =head2 C<parser_class>
308
309 Calls C<< ->tool->parser_class >> with the current session object as the unique argument.
310
311 =cut
312
313 sub parser_class { $_[0]->tool->parser_class($_[0]) }
314
315 =head2 C<report_class>
316
317 Calls C<< ->tool->report_class >> with the current session object as the unique argument.
318
319 =cut
320
321 sub report_class { $_[0]->tool->report_class($_[0]) }
322
323 =head2 C<def_supp_file>
324
325 Returns an absolute path to the default suppression file associated to the current session.
326
327 C<undef> will be returned as soon as any of C<< ->command->suppressions_tag >> or C<< ->tool->suppressions_tag >> are also C<undef>.
328 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 >>.
329
330 =cut
331
332 sub def_supp_file {
333  my ($self) = @_;
334
335  my $tool_tag = $self->tool->suppressions_tag($self);
336  return unless defined $tool_tag;
337
338  my $cmd_tag = $self->command->suppressions_tag($self);
339  return unless defined $cmd_tag;
340
341  require File::HomeDir; # So that it's not needed at configure time.
342
343  return File::Spec->catfile(
344   File::HomeDir->my_home,
345   '.perl',
346   'Test-Valgrind',
347   'suppressions',
348   $VERSION,
349   "$tool_tag-$cmd_tag.supp",
350  );
351 }
352
353 =head2 C<suppressions>
354
355 Returns the list of all the suppressions that will be passed to C<valgrind>.
356 Honors L</no_def_supp> and L</extra_supps>.
357
358 =cut
359
360 sub suppressions {
361  my ($self) = @_;
362
363  my @supps;
364  unless ($self->no_def_supp) {
365   my $def_supp = $self->def_supp_file;
366   push @supps, $def_supp if defined $def_supp;
367  }
368  push @supps, $self->extra_supps;
369
370  return @supps;
371 }
372
373 =head2 C<start>
374
375 Starts the action and tool associated to the current run.
376 It's automatically called at the beginning of L</run>.
377
378 =cut
379
380 sub start {
381  my $self = shift;
382
383  my %args = @_;
384
385  for (qw/action tool command/) {
386   my $base = 'Test::Valgrind::' . ucfirst;
387   my $value = $args{$_};
388   $self->_croak("Invalid $_") unless Scalar::Util::blessed($value)
389                                                          and $value->isa($base);
390   $self->$_($args{$_})
391  }
392
393  delete @{$self}{qw/last_status exit_code/};
394
395  $self->tool->start($self);
396  $self->parser($self->parser_class->new)->start($self);
397  $self->action->start($self);
398
399  return;
400 }
401
402 =head2 C<abort $msg>
403
404 Forwards to C<< ->action->abort >> after unshifting the session object to the argument list.
405
406 =cut
407
408 sub abort {
409  my $self = shift;
410
411  $self->action->abort($self, @_);
412 }
413
414 =head2 C<report $report>
415
416 Forwards to C<< ->action->report >> after unshifting the session object to the argument list.
417
418 =cut
419
420 sub report {
421  my ($self, $report) = @_;
422
423  return unless defined $report;
424
425  for my $handler (qw/tool command/) {
426   $report = $self->$handler->filter($self, $report);
427   return unless defined $report;
428  }
429
430  $self->action->report($self, $report);
431 }
432
433 =head2 C<finish>
434
435 Finishes the action and tool associated to the current run.
436 It's automatically called at the end of L</run>.
437
438 =cut
439
440 sub finish {
441  my ($self) = @_;
442
443  my $action = $self->action;
444
445  $action->finish($self);
446  $self->parser->finish($self);
447  $self->tool->finish($self);
448
449  my $status = $action->status($self);
450  $self->{last_status} = defined $status ? $status : $self->{exit_code};
451
452  $self->$_(undef) for @members;
453
454  return;
455 }
456
457 =head2 C<status>
458
459 Returns the status code of the last run of the session.
460
461 =cut
462
463 sub status { $_[0]->{last_status} }
464
465 =head1 SEE ALSO
466
467 L<Test::Valgrind>, L<Test::Valgrind::Action>, L<Test::Valgrind::Command>, L<Test::Valgrind::Tool>, L<Test::Valgrind::Parser>.
468
469 L<version>, L<File::HomeDir>.
470
471 =head1 AUTHOR
472
473 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
474
475 You can contact me by mail or on C<irc.perl.org> (vincent).
476
477 =head1 BUGS
478
479 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>.
480 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
481
482 =head1 SUPPORT
483
484 You can find documentation for this module with the perldoc command.
485
486     perldoc Test::Valgrind::Session
487
488 =head1 COPYRIGHT & LICENSE
489
490 Copyright 2009 Vincent Pit, all rights reserved.
491
492 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
493
494 =cut
495
496 1; # End of Test::Valgrind::Session