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