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