xref: /openbsd-src/gnu/usr.bin/perl/cpan/Test-Harness/lib/App/Prove.pm (revision 3d61058aa5c692477b6d18acfbbdb653a9930ff9)
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.48
22
23=cut
24
25our $VERSION = '3.48';
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 = map { $self->$_() ? $verb_map{$_} : () }
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 if @verb_adj;
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        if ( $class->can('load') ) {
416            $class->load( { app_prove => $self, args => [@args] } );
417        }
418    }
419    else {
420        croak "Can't load module $name";
421    }
422}
423
424sub _load_extensions {
425    my ( $self, $ext, @search ) = @_;
426    $self->_load_extension( $_, @search ) for @$ext;
427}
428
429sub _parse_source {
430    my ( $self, $handler ) = @_;
431
432    # Load any options.
433    ( my $opt_name = lc $handler ) =~ s/::/-/g;
434    local @ARGV = @{ $self->{argv} };
435    my %config;
436    Getopt::Long::GetOptions(
437        "$opt_name-option=s%" => sub {
438            my ( $name, $k, $v ) = @_;
439            if ( $v =~ /(?<!\\)=/ ) {
440
441                # It's a hash option.
442                croak "Option $name must be consistently used as a hash"
443                  if exists $config{$k} && ref $config{$k} ne 'HASH';
444                $config{$k} ||= {};
445                my ( $hk, $hv ) = split /(?<!\\)=/, $v, 2;
446                $config{$k}{$hk} = $hv;
447            }
448            else {
449                $v =~ s/\\=/=/g;
450                if ( exists $config{$k} ) {
451                    $config{$k} = [ $config{$k} ]
452                      unless ref $config{$k} eq 'ARRAY';
453                    push @{ $config{$k} } => $v;
454                }
455                else {
456                    $config{$k} = $v;
457                }
458            }
459        }
460    );
461    $self->{argv} = \@ARGV;
462    return ( $handler, \%config );
463}
464
465=head3 C<run>
466
467Perform whatever actions the command line args specified. The C<prove>
468command line tool consists of the following code:
469
470    use App::Prove;
471
472    my $app = App::Prove->new;
473    $app->process_args(@ARGV);
474    exit( $app->run ? 0 : 1 );  # if you need the exit code
475
476=cut
477
478sub run {
479    my $self = shift;
480
481    unless ( $self->state_manager ) {
482        $self->state_manager(
483            $self->state_class->new( { store => $self->statefile || STATE_FILE } ) );
484    }
485
486    if ( $self->show_help ) {
487        $self->_help(1);
488    }
489    elsif ( $self->show_man ) {
490        $self->_help(2);
491    }
492    elsif ( $self->show_version ) {
493        $self->print_version;
494    }
495    elsif ( $self->dry ) {
496        print "$_\n" for $self->_get_tests;
497    }
498    else {
499
500        $self->_load_extensions( $self->modules );
501        $self->_load_extensions( $self->plugins, PLUGINS );
502
503        local $ENV{TEST_VERBOSE} = 1 if $self->verbose;
504
505        return $self->_runtests( $self->_get_args, $self->_get_tests );
506    }
507
508    return 1;
509}
510
511sub _get_tests {
512    my $self = shift;
513
514    my $state = $self->state_manager;
515    my $ext   = $self->extensions;
516    $state->extensions($ext) if defined $ext;
517    if ( defined( my $state_switch = $self->state ) ) {
518        $state->apply_switch(@$state_switch);
519    }
520
521    my @tests = $state->get_tests( $self->recurse, @{ $self->argv } );
522
523    $self->_shuffle(@tests) if $self->shuffle;
524    @tests = reverse @tests if $self->backwards;
525
526    return @tests;
527}
528
529sub _runtests {
530    my ( $self, $args, @tests ) = @_;
531    my $harness = TAP::Harness::Env->create($args);
532
533    my $state = $self->state_manager;
534
535    $harness->callback(
536        after_test => sub {
537            $state->observe_test(@_);
538        }
539    );
540
541    $harness->callback(
542        after_runtests => sub {
543            $state->commit(@_);
544        }
545    );
546
547    my $aggregator = $harness->runtests(@tests);
548
549    return !$aggregator->has_errors;
550}
551
552sub _get_switches {
553    my $self = shift;
554    my @switches;
555
556    # notes that -T or -t must be at the front of the switches!
557    if ( $self->taint_fail ) {
558        push @switches, '-T';
559    }
560    elsif ( $self->taint_warn ) {
561        push @switches, '-t';
562    }
563    if ( $self->warnings_fail ) {
564        push @switches, '-W';
565    }
566    elsif ( $self->warnings_warn ) {
567        push @switches, '-w';
568    }
569
570    return @switches ? \@switches : ();
571}
572
573sub _get_lib {
574    my $self = shift;
575    my @libs;
576    if ( $self->lib ) {
577        push @libs, 'lib';
578    }
579    if ( $self->blib ) {
580        push @libs, 'blib/lib', 'blib/arch';
581    }
582    if ( @{ $self->includes } ) {
583        push @libs, @{ $self->includes };
584    }
585
586    #24926
587    @libs = map { File::Spec->rel2abs($_) } @libs;
588
589    # Huh?
590    return @libs ? \@libs : ();
591}
592
593sub _shuffle {
594    my $self = shift;
595
596    # Fisher-Yates shuffle
597    my $i = @_;
598    while ($i) {
599        my $j = rand $i--;
600        @_[ $i, $j ] = @_[ $j, $i ];
601    }
602    return;
603}
604
605=head3 C<require_harness>
606
607Load a harness replacement class.
608
609  $prove->require_harness($for => $class_name);
610
611=cut
612
613sub require_harness {
614    my ( $self, $for, $class ) = @_;
615
616    my ($class_name) = $class =~ /^(\w+(?:::\w+)*)/;
617
618    # Emulate Perl's -MModule=arg1,arg2 behaviour
619    $class =~ s!^(\w+(?:::\w+)*)=(.*)$!$1 split(/,/,q{$2})!;
620
621    eval("use $class;");
622    die "$class_name is required to use the --$for feature: $@" if $@;
623
624    $self->{harness_class} = $class_name;
625
626    return;
627}
628
629=head3 C<print_version>
630
631Display the version numbers of the loaded L<TAP::Harness> and the
632current Perl.
633
634=cut
635
636sub print_version {
637    my $self = shift;
638    require TAP::Harness;
639    printf(
640        "TAP::Harness v%s and Perl v%vd\n",
641        $TAP::Harness::VERSION, $^V
642    );
643
644    return;
645}
646
6471;
648
649# vim:ts=4:sw=4:et:sta
650
651__END__
652
653=head2 Attributes
654
655After command line parsing the following attributes reflect the values
656of the corresponding command line switches. They may be altered before
657calling C<run>.
658
659=over
660
661=item C<archive>
662
663=item C<argv>
664
665=item C<backwards>
666
667=item C<blib>
668
669=item C<color>
670
671=item C<directives>
672
673=item C<dry>
674
675=item C<exec>
676
677=item C<extensions>
678
679=item C<failures>
680
681=item C<comments>
682
683=item C<formatter>
684
685=item C<harness>
686
687=item C<ignore_exit>
688
689=item C<includes>
690
691=item C<jobs>
692
693=item C<lib>
694
695=item C<merge>
696
697=item C<modules>
698
699=item C<parse>
700
701=item C<plugins>
702
703=item C<quiet>
704
705=item C<really_quiet>
706
707=item C<recurse>
708
709=item C<rules>
710
711=item C<show_count>
712
713=item C<show_help>
714
715=item C<show_man>
716
717=item C<show_version>
718
719=item C<shuffle>
720
721=item C<state>
722
723=item C<state_class>
724
725=item C<taint_fail>
726
727=item C<taint_warn>
728
729=item C<test_args>
730
731=item C<timer>
732
733=item C<verbose>
734
735=item C<warnings_fail>
736
737=item C<warnings_warn>
738
739=item C<tapversion>
740
741=item C<trap>
742
743=back
744
745=head1 PLUGINS
746
747C<App::Prove> provides support for 3rd-party plugins.  These are currently
748loaded at run-time, I<after> arguments have been parsed (so you can not
749change the way arguments are processed, sorry), typically with the
750C<< -PI<plugin> >> switch, eg:
751
752  prove -PMyPlugin
753
754This will search for a module named C<App::Prove::Plugin::MyPlugin>, or failing
755that, C<MyPlugin>.  If the plugin can't be found, C<prove> will complain & exit.
756
757You can pass an argument to your plugin by appending an C<=> after the plugin
758name, eg C<-PMyPlugin=foo>.  You can pass multiple arguments using commas:
759
760  prove -PMyPlugin=foo,bar,baz
761
762These are passed in to your plugin's C<load()> class method (if it has one),
763along with a reference to the C<App::Prove> object that is invoking your plugin:
764
765  sub load {
766      my ($class, $p) = @_;
767
768      my @args = @{ $p->{args} };
769      # @args will contain ( 'foo', 'bar', 'baz' )
770      $p->{app_prove}->do_something;
771      ...
772  }
773
774=head2 Sample Plugin
775
776Here's a sample plugin, for your reference:
777
778  package App::Prove::Plugin::Foo;
779
780  # Sample plugin, try running with:
781  # prove -PFoo=bar -r -j3
782  # prove -PFoo -Q
783  # prove -PFoo=bar,My::Formatter
784
785  use strict;
786  use warnings;
787
788  sub load {
789      my ($class, $p) = @_;
790      my @args = @{ $p->{args} };
791      my $app  = $p->{app_prove};
792
793      print "loading plugin: $class, args: ", join(', ', @args ), "\n";
794
795      # turn on verbosity
796      $app->verbose( 1 );
797
798      # set the formatter?
799      $app->formatter( $args[1] ) if @args > 1;
800
801      # print some of App::Prove's state:
802      for my $attr (qw( jobs quiet really_quiet recurse verbose )) {
803          my $val = $app->$attr;
804          $val    = 'undef' unless defined( $val );
805          print "$attr: $val\n";
806      }
807
808      return 1;
809  }
810
811  1;
812
813=head1 SEE ALSO
814
815L<prove>, L<TAP::Harness>
816
817=cut
818