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