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