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