xref: /openbsd-src/gnu/usr.bin/perl/cpan/Test-Harness/lib/App/Prove.pm (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1package App::Prove;
2
3use strict;
4use vars qw($VERSION @ISA);
5
6use TAP::Object ();
7use TAP::Harness;
8use TAP::Parser::Utils qw( split_shell );
9use File::Spec;
10use Getopt::Long;
11use App::Prove::State;
12use Carp;
13
14=head1 NAME
15
16App::Prove - Implements the C<prove> command.
17
18=head1 VERSION
19
20Version 3.26
21
22=cut
23
24$VERSION = '3.26';
25
26=head1 DESCRIPTION
27
28L<Test::Harness> provides a command, C<prove>, which runs a TAP based
29test suite and prints a report. The C<prove> command is a minimal
30wrapper around an instance of this module.
31
32=head1 SYNOPSIS
33
34    use App::Prove;
35
36    my $app = App::Prove->new;
37    $app->process_args(@ARGV);
38    $app->run;
39
40=cut
41
42use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
43use constant IS_VMS => $^O eq 'VMS';
44use constant IS_UNIXY => !( IS_VMS || IS_WIN32 );
45
46use constant STATE_FILE => IS_UNIXY ? '.prove'   : '_prove';
47use constant RC_FILE    => IS_UNIXY ? '.proverc' : '_proverc';
48
49use constant PLUGINS => 'App::Prove::Plugin';
50
51my @ATTR;
52
53BEGIN {
54    @ISA = qw(TAP::Object);
55
56    @ATTR = qw(
57      archive argv blib show_count color directives exec failures comments
58      formatter harness includes modules plugins jobs lib merge parse quiet
59      really_quiet recurse backwards shuffle taint_fail taint_warn timer
60      verbose warnings_fail warnings_warn show_help show_man show_version
61      state_class test_args state dry extensions ignore_exit rules state_manager
62      normalize sources tapversion trap
63    );
64    __PACKAGE__->mk_methods(@ATTR);
65}
66
67=head1 METHODS
68
69=head2 Class Methods
70
71=head3 C<new>
72
73Create a new C<App::Prove>. Optionally a hash ref of attribute
74initializers may be passed.
75
76=cut
77
78# new() implementation supplied by TAP::Object
79
80sub _initialize {
81    my $self = shift;
82    my $args = shift || {};
83
84    my @is_array = qw(
85      argv rc_opts includes modules state plugins rules sources
86    );
87
88    # setup defaults:
89    for my $key (@is_array) {
90        $self->{$key} = [];
91    }
92    $self->{harness_class} = 'TAP::Harness';
93
94    for my $attr (@ATTR) {
95        if ( exists $args->{$attr} ) {
96
97            # TODO: Some validation here
98            $self->{$attr} = $args->{$attr};
99        }
100    }
101
102    my %env_provides_default = (
103        HARNESS_TIMER => 'timer',
104    );
105
106    while ( my ( $env, $attr ) = each %env_provides_default ) {
107        $self->{$attr} = 1 if $ENV{$env};
108    }
109    $self->state_class('App::Prove::State');
110    return $self;
111}
112
113=head3 C<state_class>
114
115Getter/setter for the name of the class used for maintaining state.  This
116class should either subclass from C<App::Prove::State> or provide an identical
117interface.
118
119=head3 C<state_manager>
120
121Getter/setter for the instance of the C<state_class>.
122
123=cut
124
125=head3 C<add_rc_file>
126
127    $prove->add_rc_file('myproj/.proverc');
128
129Called before C<process_args> to prepend the contents of an rc file to
130the options.
131
132=cut
133
134sub add_rc_file {
135    my ( $self, $rc_file ) = @_;
136
137    local *RC;
138    open RC, "<$rc_file" or croak "Can't read $rc_file ($!)";
139    while ( defined( my $line = <RC> ) ) {
140        push @{ $self->{rc_opts} },
141          grep { defined and not /^#/ }
142          $line =~ m{ ' ([^']*) ' | " ([^"]*) " | (\#.*) | (\S+) }xg;
143    }
144    close RC;
145}
146
147=head3 C<process_args>
148
149    $prove->process_args(@args);
150
151Processes the command-line arguments. Attributes will be set
152appropriately. Any filenames may be found in the C<argv> attribute.
153
154Dies on invalid arguments.
155
156=cut
157
158sub process_args {
159    my $self = shift;
160
161    my @rc = RC_FILE;
162    unshift @rc, glob '~/' . RC_FILE if IS_UNIXY;
163
164    # Preprocess meta-args.
165    my @args;
166    while ( defined( my $arg = shift ) ) {
167        if ( $arg eq '--norc' ) {
168            @rc = ();
169        }
170        elsif ( $arg eq '--rc' ) {
171            defined( my $rc = shift )
172              or croak "Missing argument to --rc";
173            push @rc, $rc;
174        }
175        elsif ( $arg =~ m{^--rc=(.+)$} ) {
176            push @rc, $1;
177        }
178        else {
179            push @args, $arg;
180        }
181    }
182
183    # Everything after the arisdottle '::' gets passed as args to
184    # test programs.
185    if ( defined( my $stop_at = _first_pos( '::', @args ) ) ) {
186        my @test_args = splice @args, $stop_at;
187        shift @test_args;
188        $self->{test_args} = \@test_args;
189    }
190
191    # Grab options from RC files
192    $self->add_rc_file($_) for grep -f, @rc;
193    unshift @args, @{ $self->{rc_opts} };
194
195    if ( my @bad = map {"-$_"} grep {/^-(man|help)$/} @args ) {
196        die "Long options should be written with two dashes: ",
197          join( ', ', @bad ), "\n";
198    }
199
200    # And finally...
201
202    {
203        local @ARGV = @args;
204        Getopt::Long::Configure(qw(no_ignore_case bundling pass_through));
205
206        # Don't add coderefs to GetOptions
207        GetOptions(
208            'v|verbose'  => \$self->{verbose},
209            'f|failures' => \$self->{failures},
210            'o|comments' => \$self->{comments},
211            'l|lib'      => \$self->{lib},
212            'b|blib'     => \$self->{blib},
213            's|shuffle'  => \$self->{shuffle},
214            'color!'     => \$self->{color},
215            'colour!'    => \$self->{color},
216            'count!'     => \$self->{show_count},
217            'c'          => \$self->{color},
218            'D|dry'      => \$self->{dry},
219            'ext=s@'     => sub {
220                my ( $opt, $val ) = @_;
221
222                # Workaround for Getopt::Long 2.25 handling of
223                # multivalue options
224                push @{ $self->{extensions} ||= [] }, $val;
225            },
226            'harness=s'    => \$self->{harness},
227            'ignore-exit'  => \$self->{ignore_exit},
228            'source=s@'    => $self->{sources},
229            'formatter=s'  => \$self->{formatter},
230            'r|recurse'    => \$self->{recurse},
231            'reverse'      => \$self->{backwards},
232            'p|parse'      => \$self->{parse},
233            'q|quiet'      => \$self->{quiet},
234            'Q|QUIET'      => \$self->{really_quiet},
235            'e|exec=s'     => \$self->{exec},
236            'm|merge'      => \$self->{merge},
237            'I=s@'         => $self->{includes},
238            'M=s@'         => $self->{modules},
239            'P=s@'         => $self->{plugins},
240            'state=s@'     => $self->{state},
241            'directives'   => \$self->{directives},
242            'h|help|?'     => \$self->{show_help},
243            'H|man'        => \$self->{show_man},
244            'V|version'    => \$self->{show_version},
245            'a|archive=s'  => \$self->{archive},
246            'j|jobs=i'     => \$self->{jobs},
247            'timer'        => \$self->{timer},
248            'T'            => \$self->{taint_fail},
249            't'            => \$self->{taint_warn},
250            'W'            => \$self->{warnings_fail},
251            'w'            => \$self->{warnings_warn},
252            'normalize'    => \$self->{normalize},
253            'rules=s@'     => $self->{rules},
254            'tapversion=s' => \$self->{tapversion},
255            'trap'         => \$self->{trap},
256        ) or croak('Unable to continue');
257
258        # Stash the remainder of argv for later
259        $self->{argv} = [@ARGV];
260    }
261
262    return;
263}
264
265sub _first_pos {
266    my $want = shift;
267    for ( 0 .. $#_ ) {
268        return $_ if $_[$_] eq $want;
269    }
270    return;
271}
272
273sub _help {
274    my ( $self, $verbosity ) = @_;
275
276    eval('use Pod::Usage 1.12 ()');
277    if ( my $err = $@ ) {
278        die 'Please install Pod::Usage for the --help option '
279          . '(or try `perldoc prove`.)'
280          . "\n ($@)";
281    }
282
283    Pod::Usage::pod2usage( { -verbose => $verbosity } );
284
285    return;
286}
287
288sub _color_default {
289    my $self = shift;
290
291    return -t STDOUT && !$ENV{HARNESS_NOTTY} && !IS_WIN32;
292}
293
294sub _get_args {
295    my $self = shift;
296
297    my %args;
298
299    $args{trap} = 1 if $self->trap;
300
301    if ( defined $self->color ? $self->color : $self->_color_default ) {
302        $args{color} = 1;
303    }
304    if ( !defined $self->show_count ) {
305        $args{show_count} = 1;
306    }
307    else {
308        $args{show_count} = $self->show_count;
309    }
310
311    if ( $self->archive ) {
312        $self->require_harness( archive => 'TAP::Harness::Archive' );
313        $args{archive} = $self->archive;
314    }
315
316    if ( my $jobs = $self->jobs ) {
317        $args{jobs} = $jobs;
318    }
319
320    if ( my $harness_opt = $self->harness ) {
321        $self->require_harness( harness => $harness_opt );
322    }
323
324    if ( my $formatter = $self->formatter ) {
325        $args{formatter_class} = $formatter;
326    }
327
328    for my $handler ( @{ $self->sources } ) {
329        my ( $name, $config ) = $self->_parse_source($handler);
330        $args{sources}->{$name} = $config;
331    }
332
333    if ( $self->ignore_exit ) {
334        $args{ignore_exit} = 1;
335    }
336
337    if ( $self->taint_fail && $self->taint_warn ) {
338        die '-t and -T are mutually exclusive';
339    }
340
341    if ( $self->warnings_fail && $self->warnings_warn ) {
342        die '-w and -W are mutually exclusive';
343    }
344
345    for my $a (qw( lib switches )) {
346        my $method = "_get_$a";
347        my $val    = $self->$method();
348        $args{$a} = $val if defined $val;
349    }
350
351    # Handle verbose, quiet, really_quiet flags
352    my %verb_map = ( verbose => 1, quiet => -1, really_quiet => -2, );
353
354    my @verb_adj = grep {$_} map { $self->$_() ? $verb_map{$_} : 0 }
355      keys %verb_map;
356
357    die "Only one of verbose, quiet or really_quiet should be specified\n"
358      if @verb_adj > 1;
359
360    $args{verbosity} = shift @verb_adj || 0;
361
362    for my $a (qw( merge failures comments timer directives normalize )) {
363        $args{$a} = 1 if $self->$a();
364    }
365
366    $args{errors} = 1 if $self->parse;
367
368    # defined but zero-length exec runs test files as binaries
369    $args{exec} = [ split( /\s+/, $self->exec ) ]
370      if ( defined( $self->exec ) );
371
372    $args{version} = $self->tapversion if defined( $self->tapversion );
373
374    if ( defined( my $test_args = $self->test_args ) ) {
375        $args{test_args} = $test_args;
376    }
377
378    if ( @{ $self->rules } ) {
379        my @rules;
380        for ( @{ $self->rules } ) {
381            if (/^par=(.*)/) {
382                push @rules, $1;
383            }
384            elsif (/^seq=(.*)/) {
385                push @rules, { seq => $1 };
386            }
387        }
388        $args{rules} = { par => [@rules] };
389    }
390
391    return ( \%args, $self->{harness_class} );
392}
393
394sub _find_module {
395    my ( $self, $class, @search ) = @_;
396
397    croak "Bad module name $class"
398      unless $class =~ /^ \w+ (?: :: \w+ ) *$/x;
399
400    for my $pfx (@search) {
401        my $name = join( '::', $pfx, $class );
402        eval "require $name";
403        return $name unless $@;
404    }
405
406    eval "require $class";
407    return $class unless $@;
408    return;
409}
410
411sub _load_extension {
412    my ( $self, $name, @search ) = @_;
413
414    my @args = ();
415    if ( $name =~ /^(.*?)=(.*)/ ) {
416        $name = $1;
417        @args = split( /,/, $2 );
418    }
419
420    if ( my $class = $self->_find_module( $name, @search ) ) {
421        $class->import(@args);
422        if ( $class->can('load') ) {
423            $class->load( { app_prove => $self, args => [@args] } );
424        }
425    }
426    else {
427        croak "Can't load module $name";
428    }
429}
430
431sub _load_extensions {
432    my ( $self, $ext, @search ) = @_;
433    $self->_load_extension( $_, @search ) for @$ext;
434}
435
436sub _parse_source {
437    my ( $self, $handler ) = @_;
438
439    # Load any options.
440    ( my $opt_name = lc $handler ) =~ s/::/-/g;
441    local @ARGV = @{ $self->{argv} };
442    my %config;
443    Getopt::Long::GetOptions(
444        "$opt_name-option=s%" => sub {
445            my ( $name, $k, $v ) = @_;
446            if ( $v =~ /(?<!\\)=/ ) {
447
448                # It's a hash option.
449                croak "Option $name must be consistently used as a hash"
450                  if exists $config{$k} && ref $config{$k} ne 'HASH';
451                $config{$k} ||= {};
452                my ( $hk, $hv ) = split /(?<!\\)=/, $v, 2;
453                $config{$k}{$hk} = $hv;
454            }
455            else {
456                $v =~ s/\\=/=/g;
457                if ( exists $config{$k} ) {
458                    $config{$k} = [ $config{$k} ]
459                      unless ref $config{$k} eq 'ARRAY';
460                    push @{ $config{$k} } => $v;
461                }
462                else {
463                    $config{$k} = $v;
464                }
465            }
466        }
467    );
468    $self->{argv} = \@ARGV;
469    return ( $handler, \%config );
470}
471
472=head3 C<run>
473
474Perform whatever actions the command line args specified. The C<prove>
475command line tool consists of the following code:
476
477    use App::Prove;
478
479    my $app = App::Prove->new;
480    $app->process_args(@ARGV);
481    exit( $app->run ? 0 : 1 );  # if you need the exit code
482
483=cut
484
485sub run {
486    my $self = shift;
487
488    unless ( $self->state_manager ) {
489        $self->state_manager(
490            $self->state_class->new( { store => STATE_FILE } ) );
491    }
492
493    if ( $self->show_help ) {
494        $self->_help(1);
495    }
496    elsif ( $self->show_man ) {
497        $self->_help(2);
498    }
499    elsif ( $self->show_version ) {
500        $self->print_version;
501    }
502    elsif ( $self->dry ) {
503        print "$_\n" for $self->_get_tests;
504    }
505    else {
506
507        $self->_load_extensions( $self->modules );
508        $self->_load_extensions( $self->plugins, PLUGINS );
509
510        local $ENV{TEST_VERBOSE} = 1 if $self->verbose;
511
512        return $self->_runtests( $self->_get_args, $self->_get_tests );
513    }
514
515    return 1;
516}
517
518sub _get_tests {
519    my $self = shift;
520
521    my $state = $self->state_manager;
522    my $ext   = $self->extensions;
523    $state->extensions($ext) if defined $ext;
524    if ( defined( my $state_switch = $self->state ) ) {
525        $state->apply_switch(@$state_switch);
526    }
527
528    my @tests = $state->get_tests( $self->recurse, @{ $self->argv } );
529
530    $self->_shuffle(@tests) if $self->shuffle;
531    @tests = reverse @tests if $self->backwards;
532
533    return @tests;
534}
535
536sub _runtests {
537    my ( $self, $args, $harness_class, @tests ) = @_;
538    my $harness = $harness_class->new($args);
539
540    my $state = $self->state_manager;
541
542    $harness->callback(
543        after_test => sub {
544            $state->observe_test(@_);
545        }
546    );
547
548    $harness->callback(
549        after_runtests => sub {
550            $state->commit(@_);
551        }
552    );
553
554    my $aggregator = $harness->runtests(@tests);
555
556    return !$aggregator->has_errors;
557}
558
559sub _get_switches {
560    my $self = shift;
561    my @switches;
562
563    # notes that -T or -t must be at the front of the switches!
564    if ( $self->taint_fail ) {
565        push @switches, '-T';
566    }
567    elsif ( $self->taint_warn ) {
568        push @switches, '-t';
569    }
570    if ( $self->warnings_fail ) {
571        push @switches, '-W';
572    }
573    elsif ( $self->warnings_warn ) {
574        push @switches, '-w';
575    }
576
577    push @switches, split_shell( $ENV{HARNESS_PERL_SWITCHES} );
578
579    return @switches ? \@switches : ();
580}
581
582sub _get_lib {
583    my $self = shift;
584    my @libs;
585    if ( $self->lib ) {
586        push @libs, 'lib';
587    }
588    if ( $self->blib ) {
589        push @libs, 'blib/lib', 'blib/arch';
590    }
591    if ( @{ $self->includes } ) {
592        push @libs, @{ $self->includes };
593    }
594
595    #24926
596    @libs = map { File::Spec->rel2abs($_) } @libs;
597
598    # Huh?
599    return @libs ? \@libs : ();
600}
601
602sub _shuffle {
603    my $self = shift;
604
605    # Fisher-Yates shuffle
606    my $i = @_;
607    while ($i) {
608        my $j = rand $i--;
609        @_[ $i, $j ] = @_[ $j, $i ];
610    }
611    return;
612}
613
614=head3 C<require_harness>
615
616Load a harness replacement class.
617
618  $prove->require_harness($for => $class_name);
619
620=cut
621
622sub require_harness {
623    my ( $self, $for, $class ) = @_;
624
625    my ($class_name) = $class =~ /^(\w+(?:::\w+)*)/;
626
627    # Emulate Perl's -MModule=arg1,arg2 behaviour
628    $class =~ s!^(\w+(?:::\w+)*)=(.*)$!$1 split(/,/,q{$2})!;
629
630    eval("use $class;");
631    die "$class_name is required to use the --$for feature: $@" if $@;
632
633    $self->{harness_class} = $class_name;
634
635    return;
636}
637
638=head3 C<print_version>
639
640Display the version numbers of the loaded L<TAP::Harness> and the
641current Perl.
642
643=cut
644
645sub print_version {
646    my $self = shift;
647    printf(
648        "TAP::Harness v%s and Perl v%vd\n",
649        $TAP::Harness::VERSION, $^V
650    );
651
652    return;
653}
654
6551;
656
657# vim:ts=4:sw=4:et:sta
658
659__END__
660
661=head2 Attributes
662
663After command line parsing the following attributes reflect the values
664of the corresponding command line switches. They may be altered before
665calling C<run>.
666
667=over
668
669=item C<archive>
670
671=item C<argv>
672
673=item C<backwards>
674
675=item C<blib>
676
677=item C<color>
678
679=item C<directives>
680
681=item C<dry>
682
683=item C<exec>
684
685=item C<extensions>
686
687=item C<failures>
688
689=item C<comments>
690
691=item C<formatter>
692
693=item C<harness>
694
695=item C<ignore_exit>
696
697=item C<includes>
698
699=item C<jobs>
700
701=item C<lib>
702
703=item C<merge>
704
705=item C<modules>
706
707=item C<parse>
708
709=item C<plugins>
710
711=item C<quiet>
712
713=item C<really_quiet>
714
715=item C<recurse>
716
717=item C<rules>
718
719=item C<show_count>
720
721=item C<show_help>
722
723=item C<show_man>
724
725=item C<show_version>
726
727=item C<shuffle>
728
729=item C<state>
730
731=item C<state_class>
732
733=item C<taint_fail>
734
735=item C<taint_warn>
736
737=item C<test_args>
738
739=item C<timer>
740
741=item C<verbose>
742
743=item C<warnings_fail>
744
745=item C<warnings_warn>
746
747=item C<tapversion>
748
749=item C<trap>
750
751=back
752
753=head1 PLUGINS
754
755C<App::Prove> provides support for 3rd-party plugins.  These are currently
756loaded at run-time, I<after> arguments have been parsed (so you can not
757change the way arguments are processed, sorry), typically with the
758C<< -PI<plugin> >> switch, eg:
759
760  prove -PMyPlugin
761
762This will search for a module named C<App::Prove::Plugin::MyPlugin>, or failing
763that, C<MyPlugin>.  If the plugin can't be found, C<prove> will complain & exit.
764
765You can pass an argument to your plugin by appending an C<=> after the plugin
766name, eg C<-PMyPlugin=foo>.  You can pass multiple arguments using commas:
767
768  prove -PMyPlugin=foo,bar,baz
769
770These are passed in to your plugin's C<load()> class method (if it has one),
771along with a reference to the C<App::Prove> object that is invoking your plugin:
772
773  sub load {
774      my ($class, $p) = @_;
775
776      my @args = @{ $p->{args} };
777      # @args will contain ( 'foo', 'bar', 'baz' )
778      $p->{app_prove}->do_something;
779      ...
780  }
781
782Note that the user's arguments are also passed to your plugin's C<import()>
783function as a list, eg:
784
785  sub import {
786      my ($class, @args) = @_;
787      # @args will contain ( 'foo', 'bar', 'baz' )
788      ...
789  }
790
791This is for backwards compatibility, and may be deprecated in the future.
792
793=head2 Sample Plugin
794
795Here's a sample plugin, for your reference:
796
797  package App::Prove::Plugin::Foo;
798
799  # Sample plugin, try running with:
800  # prove -PFoo=bar -r -j3
801  # prove -PFoo -Q
802  # prove -PFoo=bar,My::Formatter
803
804  use strict;
805  use warnings;
806
807  sub load {
808      my ($class, $p) = @_;
809      my @args = @{ $p->{args} };
810      my $app  = $p->{app_prove};
811
812      print "loading plugin: $class, args: ", join(', ', @args ), "\n";
813
814      # turn on verbosity
815      $app->verbose( 1 );
816
817      # set the formatter?
818      $app->formatter( $args[1] ) if @args > 1;
819
820      # print some of App::Prove's state:
821      for my $attr (qw( jobs quiet really_quiet recurse verbose )) {
822          my $val = $app->$attr;
823          $val    = 'undef' unless defined( $val );
824          print "$attr: $val\n";
825      }
826
827      return 1;
828  }
829
830  1;
831
832=head1 SEE ALSO
833
834L<prove>, L<TAP::Harness>
835
836=cut
837