From ba38c97ca335e7e782e5a61ac5001ed1679c802c Mon Sep 17 00:00:00 2001 From: Mark Stosberg Date: Sat, 18 Aug 2012 13:09:36 -0400 Subject: [PATCH 1/5] Document what's going with this: map { 'ARRAY' eq ref $_ ? $_ : [ $_, $_ ] } @$tests; --- lib/TAP/Parser/Scheduler.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/TAP/Parser/Scheduler.pm b/lib/TAP/Parser/Scheduler.pm index 34cec5ab..1a5e3952 100644 --- a/lib/TAP/Parser/Scheduler.pm +++ b/lib/TAP/Parser/Scheduler.pm @@ -70,6 +70,9 @@ sub new { sub _set_rules { my ( $self, $rules, $tests ) = @_; + + # Convert all incoming tests to job objects. + # If no test description is provided use the file name as the description. my @tests = map { TAP::Parser::Scheduler::Job->new(@$_) } map { 'ARRAY' eq ref $_ ? $_ : [ $_, $_ ] } @$tests; my $schedule = $self->_rule_clause( $rules, \@tests ); From 28943fdd0649f511824d409c0c8d509f9fd2c9c4 Mon Sep 17 00:00:00 2001 From: Mark Stosberg Date: Sat, 18 Aug 2012 13:10:31 -0400 Subject: [PATCH 2/5] Only new() is a class method. The rest are instance methods. --- lib/TAP/Parser/Scheduler.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/TAP/Parser/Scheduler.pm b/lib/TAP/Parser/Scheduler.pm index 1a5e3952..a801175d 100644 --- a/lib/TAP/Parser/Scheduler.pm +++ b/lib/TAP/Parser/Scheduler.pm @@ -188,6 +188,8 @@ sub _expand { return @match; } +=head2 Instance Methods + =head3 C Get a list of all remaining tests. From a6007fb8f4122c0c93f5341fe65785f93ef7ccf7 Mon Sep 17 00:00:00 2001 From: Mark Stosberg Date: Mon, 20 Aug 2012 06:23:24 -0400 Subject: [PATCH 3/5] Attempting to complete docs for 'rules' and 'Scheduler' in TAP/* modules. --- lib/TAP/Harness.pm | 45 ++++++--- lib/TAP/Parser/Scheduler.pm | 140 +++++++++++++++++++++++++++- lib/TAP/Parser/Scheduler/Job.pm | 28 +++++- lib/TAP/Parser/Scheduler/Spinner.pm | 10 +- 4 files changed, 199 insertions(+), 24 deletions(-) diff --git a/lib/TAP/Harness.pm b/lib/TAP/Harness.pm index 79a0f877..e9812d51 100644 --- a/lib/TAP/Harness.pm +++ b/lib/TAP/Harness.pm @@ -330,20 +330,37 @@ run only one test at a time. =item * C -A reference to a hash of rules that control which tests may be -executed in parallel. This is an experimental feature and the -interface may change. - - $harness->rules( - { par => [ - { seq => '../ext/DB_File/t/*' }, - { seq => '../ext/IO_Compress_Zlib/t/*' }, - { seq => '../lib/CPANPLUS/*' }, - { seq => '../lib/ExtUtils/t/*' }, - '*' - ] - } - ); +A reference to a hash of rules that control which tests may be executed in +parallel. If no rules are declared, all tests are eligible for being run in +parallel. Here some simple examples. For the full details of the data structure +and the related glob-style pattern matching, see +L. + + # Run all tests in sequence, except those starting with "p" + $harness->rules({ + par => 't/p*.t' + }); + + # Run all tests in parallel, except those starting with "p" + $harness->rules({ + seq => [ + { seq => 't/p*.t' }, + { par => '**' }, + ], + }); + + # Run some startup tests in sequence, then some parallel tests than some + # teardown tests in sequence. + $harness->rules({ + seq => [ + { seq => 't/startup/*.t' }, + { par => ['t/a/*.t','t/b/*.t','t/c/*.t'], } + { seq => 't/shutdown/*.t' }, + ], + + }); + +This is an experimental feature and the interface may change. =item * C diff --git a/lib/TAP/Parser/Scheduler.pm b/lib/TAP/Parser/Scheduler.pm index a801175d..d037acb2 100644 --- a/lib/TAP/Parser/Scheduler.pm +++ b/lib/TAP/Parser/Scheduler.pm @@ -30,9 +30,98 @@ $VERSION = '3.25'; =head3 C - my $sched = TAP::Parser::Scheduler->new; + my $sched = TAP::Parser::Scheduler->new(tests => \@tests); + my $sched = TAP::Parser::Scheduler->new( + tests => [ ['t/test_name.t','Test Description'], ... ], + rules => \%rules, + ); + +Given 'tests' and optional 'rules' as input, returns a new +C object. Each member of C<@tests> should be either a +a test file name, or a two element arrayref, where the first element is a test +file name, and the second element is a test description. By default, we'll use +the test name as the description. + +The optional C attribute provides direction on which tests should be run +in parallel and which should be run sequentially. If no rule data structure is +provided, a default data structure is used which makes every test eligible to +be run in parallel: + + { par => '**' }, + +The rules data structure is documented more in the next section. + +=head2 Rules data structure + +The "C" data structure is the the heart of the scheduler. It allows you +to express simple rules like "run all tests in sequence" or "run all tests in +parallel except these five tests.". However, the rules structure also supports +glob-style pattern matching and recursive definitions, so you can also express +arbitarily complicated patterns. + +The rule must only have one top level key: either 'par' for "parallel" or 'seq' +for "sequence". + +Values must be either strings with possible glob-style matching, or arrayrefs +of strings or hashrefs which follow this pattern recursively. + +Every element in an arrayref directly below a 'par' key is eligible to be run +in parallel, while vavalues directly below a 'seq' key must be run in sequence. + +=head3 Rules examples + +Here are some examples: + + # All tests be run in parallel (the default rule) + { par => '**' }, + + # Run all tests in sequence, except those starting with "p" + { par => 't/p*.t' }, + + # Run all tests in parallel, except those starting with "p" + { + seq => [ + { seq => 't/p*.t' }, + { par => '**' }, + ], + } + + # Run some startup tests in sequence, then some parallel tests than some + # teardown tests in sequence. + { + seq => [ + { seq => 't/startup/*.t' }, + { par => ['t/a/*.t','t/b/*.t','t/c/*.t'], } + { seq => 't/shutdown/*.t' }, + ], + }, -Returns a new C object. + +=head3 Rules resolution + +=over4 + +=item * By default, all tests are eligible to be run in parallel. Specifying any of your own rules removes this one. + +=item * "First match wins". The first rule that matches a test will be the one that applies. + +=item * Any test which does not match a rule will be run in sequence at the end of the run. + +=item * The existence of a rule does not imply selecting a test. You must still specify the tests to run. + +=item * Specifying a rule to allow tests to run in parallel does not make the run in parallel. You still need specify the number of parallel C in your Harness object. + +=back + +=head3 Glob-style pattern matching for rules + +We implement our own glob-style pattern matching. Here are the patterns it supports: + + ** is any number of characters, including /, within a pathname + * is zero or more characters within a filename/directory name + ? is exactly one character within a filename/directory name + {foo,bar,baz} is any of foo, bar or baz. + \ is an escape character =cut @@ -212,9 +301,9 @@ sub _gather { =head3 C -Return the next available job or C if none are available. Returns -a C if the scheduler still has pending -jobs but none are available to run right now. +Return the next available job as L object or +C if none are available. Returns a L if +the scheduler still has pending jobs but none are available to run right now. =cut @@ -286,9 +375,50 @@ sub _find_next_job { =head3 C Return a human readable representation of the scheduling tree. +For example: + + my @tests = (qw{ + t/startup/foo.t + t/shutdown/foo.t + + t/a/foo.t t/b/foo.t t/c/foo.t t/d/foo.t + }); + my $sched = TAP::Parser::Scheduler->new( + tests => \@tests, + rules => { + seq => [ + { seq => 't/startup/*.t' }, + { par => ['t/a/*.t','t/b/*.t','t/c/*.t'] }, + { seq => 't/shutdown/*.t' }, + ], + }, + ); + +Produces: + + par: + seq: + par: + seq: + par: + seq: + 't/startup/foo.t' + par: + seq: + 't/a/foo.t' + seq: + 't/b/foo.t' + seq: + 't/c/foo.t' + par: + seq: + 't/shutdown/foo.t' + 't/d/foo.t' + =cut + sub as_string { my $self = shift; return $self->_as_string( $self->{schedule} ); diff --git a/lib/TAP/Parser/Scheduler/Job.pm b/lib/TAP/Parser/Scheduler/Job.pm index 6d45f7d5..eecaa5b8 100644 --- a/lib/TAP/Parser/Scheduler/Job.pm +++ b/lib/TAP/Parser/Scheduler/Job.pm @@ -31,10 +31,11 @@ Represents a single test 'job'. =head3 C my $job = TAP::Parser::Scheduler::Job->new( - $name, $desc + $filename, $description ); -Returns a new C object. +Given the filename and description of a test as scalars, returns a new +L object. =cut @@ -47,9 +48,14 @@ sub new { }, $class; } +=head2 Instance Methods + =head3 C -Register a closure to be called when this job is destroyed. + $self->on_finish(\&method). + +Register a closure to be called when this job is destroyed. The callback +will be passed the C object as it's only argument. =cut @@ -60,7 +66,10 @@ sub on_finish { =head3 C -Called when a job is complete to unlock it. + $self->finish; + +Called when a job is complete to unlock it. If a callback has been registered +with C, it calls it. Otherwise, it does nothing. =cut @@ -71,6 +80,15 @@ sub finish { } } +=head2 Attributes + + $self->filename; + $self->description; + $self->context; + +These are all "getters" which return the data set for these attributes during object construction. + + =head3 C =head3 C @@ -96,6 +114,8 @@ sub as_array_ref { =head3 C + $self->is_spinner; + Returns false indicating that this is a real job rather than a 'spinner'. Spinners are returned when the scheduler still has pending jobs but can't (because of locking) return one right now. diff --git a/lib/TAP/Parser/Scheduler/Spinner.pm b/lib/TAP/Parser/Scheduler/Spinner.pm index 6906a675..bb25fa76 100644 --- a/lib/TAP/Parser/Scheduler/Spinner.pm +++ b/lib/TAP/Parser/Scheduler/Spinner.pm @@ -34,12 +34,14 @@ return a real job. my $job = TAP::Parser::Scheduler::Spinner->new; -Returns a new C object. +Ignores any arguments and returns a new C object. =cut sub new { bless {}, shift } +=head2 Instance Methods + =head3 C Returns true indicating that is a 'spinner' job. Spinners are returned @@ -50,4 +52,10 @@ return one right now. sub is_spinner {1} +=head1 SEE ALSO + +L, L + +=cut + 1; From 0a2034f3775d41c9c34101785ab3c492eb148d41 Mon Sep 17 00:00:00 2001 From: Mark Stosberg Date: Mon, 20 Aug 2012 06:41:56 -0400 Subject: [PATCH 4/5] Document the --rules option for prove --- bin/prove | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/bin/prove b/bin/prove index e14ceba1..8d3a6347 100755 --- a/bin/prove +++ b/bin/prove @@ -70,6 +70,7 @@ Options that take arguments: -j, --jobs N Run N test jobs in parallel (try 9.) --state=opts Control prove's persistent state. --rc=rcfile Process options from rcfile + --rules Rules for parallel vs sequential processing. =head1 NOTES @@ -264,6 +265,61 @@ The C<--state> switch may be used more than once. $ prove -b --state=hot --state=all,save +=head2 --rules + +The C<--rules> option is used to control which tests are run sequentially and +which are run in parallel, if the C<--jobs> option is specified. The option may +be specified multiple times, and the order matters. + +The most practical use is likely to specify that some tests are not +"parallel-ready". Since mentioning a file with --rules doens't cause it to +selected to run as a test, you can "set and forget" some rules preferences in +your .proverc file. Then you'll be able to take maximum advantage of the +performance benefits of parallel testing, while some exceptions are still run +in parallel. + +=head3 --rules examples + + # Run all tests in parallel, except those starting with "p" + --rules='seq=t/p*.t' --rules='par=**' + + # Run all tests in sequence, except those starting with "p" + --rules='t/p*.t' + + # Run some startup tests in sequence, then some parallel tests than some + # teardown tests in sequence. + --rules='seq=t/startup/*.t' + --rules='par=t/{a,b,c}/*.t' + --rules='seq=t/shutdown/*.t' + +=head3 --rules resolution + +=over4 + +=item * By default, all tests are eligible to be run in parallel. Specifying any of your own rules removes this one. + +=item * "First match wins". The first rule that matches a test will be the one that applies. + +=item * Any test which does not match a rule will be run in sequence at the end of the run. + +=item * The existence of a rule does not imply selecting a test. You must still specify the tests to run. + +=item * Specifying a rule to allow tests to run in parallel does not make the run in parallel. You still need specify the number of parallel C in your Harness object. + +=back + +=head3 --rules Glob-style pattern matching + +We implement our own glob-style pattern matching for --rules. Here are the +supported patterns: + + ** is any number of characters, including /, within a pathname + * is zero or more characters within a filename/directory name + ? is exactly one character within a filename/directory name + {foo,bar,baz} is any of foo, bar or baz. + \ is an escape character + + =head2 @INC prove introduces a separation between "options passed to the perl which From 02d8c9e1d15a6c03f86c4ebcc645e89b716be4ba Mon Sep 17 00:00:00 2001 From: Mark Stosberg Date: Mon, 20 Aug 2012 07:21:05 -0400 Subject: [PATCH 5/5] refine rules docs - Update code comments to quit giving the impression that --rules specifiy tests to run. - Provide link to further documentation if you need it. --- bin/prove | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bin/prove b/bin/prove index 8d3a6347..40e96705 100755 --- a/bin/prove +++ b/bin/prove @@ -280,17 +280,11 @@ in parallel. =head3 --rules examples - # Run all tests in parallel, except those starting with "p" + # All tests are allowed to run in parallel, except those starting with "p" --rules='seq=t/p*.t' --rules='par=**' - # Run all tests in sequence, except those starting with "p" - --rules='t/p*.t' - - # Run some startup tests in sequence, then some parallel tests than some - # teardown tests in sequence. - --rules='seq=t/startup/*.t' - --rules='par=t/{a,b,c}/*.t' - --rules='seq=t/shutdown/*.t' + # All tests must run in sequence except those starting with "p", which should be run parallel + --rules='par=t/p*.t' =head3 --rules resolution @@ -319,6 +313,12 @@ supported patterns: {foo,bar,baz} is any of foo, bar or baz. \ is an escape character +=head3 More advance specifications for parallel vs sequence run rules + +If you need more advanced management of what runs in parallel vs in sequence, see +the associated 'rules' documentation in L and L. +If what's possible directly through C is not sufficient, you can write your own +harness to access these features directly. =head2 @INC