File Coverage

blib/lib/App/TestOnTap/Scheduler.pm
Criterion Covered Total %
statement 47 47 100.0
branch 6 6 100.0
condition 5 6 83.3
subroutine 9 9 100.0
pod 0 3 0.0
total 67 71 94.3


line stmt bran cond sub pod time code
1             package App::TestOnTap::Scheduler;
2              
3 19     19   149 use strict;
  19         54  
  19         589  
4 19     19   123 use warnings;
  19         49  
  19         578  
5              
6 19     19   8748 use TAP::Parser::Scheduler::Job;
  19         7602  
  19         581  
7 19     19   8584 use TAP::Parser::Scheduler::Spinner;
  19         3485  
  19         7324  
8              
9             # This acts like a TAP Scheduler, but uses the test dispenser
10             # to give out jobs.
11             # This happens according to eligibility after continually
12             # toposorting and removing completed jobs, making
13             # it possible to parallelize tests efficiently
14             #
15             sub new
16             {
17 26     26 0 88 my $class = shift;
18 26         59 my $dispenser = shift;
19 26         91 my @testPairs = @_;
20              
21             # create
22             #
23 26         322 my $self = bless
24             (
25             {
26             pez => $dispenser,
27             jobs => undef,
28             queue => undef,
29             finished => [],
30             spinner => TAP::Parser::Scheduler::Spinner->new()
31             },
32             $class
33             );
34              
35             # create a list of job objects which is what
36             # the harness wants
37             # also hook them up to a closure to be called when they finish
38             #
39 26         247 my %jobs;
40 26         96 foreach my $pair (@testPairs)
41             {
42 48         357 my $job = TAP::Parser::Scheduler::Job->new(@$pair);
43 48     48   693 $job->on_finish( sub { $self->__finish(@_) } );
  48         33607  
44 48         407 $jobs{$job->description()} = $job;
45             }
46 26         204 $self->{jobs} = \%jobs;
47            
48 26         100 return $self;
49             }
50              
51             # list of all remaining tests (jobs)
52             #
53             sub get_all
54             {
55 25     25 0 663 my $self = shift;
56              
57 25         56 return values(%{$self->{jobs}});
  25         154  
58             }
59              
60             # return the next eligible job
61             # - if all jobs completed, return undef
62             # - if no job is currently eligible (due to dependencies etc), return a 'spinner' job
63             #
64             sub get_job
65             {
66 85     85 0 326355 my $self = shift;
67              
68             # maintain a queue of eligible tests here since we should deliver just one job
69             # at a time but the dispenser may 'free up' multiple at a time
70             #
71 85 100 66     1068 if (!$self->{queue} || ($self->{queue} && !@{$self->{queue}}))
  59   100     573  
72             {
73             # if there is no queue right now (first call after creation)
74             # or the queue is empty, attempt to retrieve a new queue from the
75             # dispenser. The dispenser is called with the current list of finished
76             # jobs, so it may recompute and see what, if anything, is now free to run
77             #
78 81         1003 $self->{queue} = $self->{pez}->getEligibleTests($self->{finished});
79 81         312 $self->{finished} = [];
80             }
81            
82             # the dispenser will return undef when there are no more tests, which means we
83             # will return undef too
84             #
85 85         272 my $job;
86 85 100       348 if ($self->{queue})
87             {
88 59 100       145 if (@{$self->{queue}})
  59         250  
89             {
90             # if there are tests in the queue, pop the first
91             # and translate it to a job object
92             #
93 48         93 my $t = shift(@{$self->{queue}});
  48         163  
94 48         156 $job = $self->{jobs}->{$t};
95             }
96             else
97             {
98             # the queue is empty but still active, so spin our wheels a bit...
99             #
100 11         39 $job = $self->{spinner};
101             }
102             }
103              
104 85         454 return $job;
105             }
106              
107             sub __finish
108             {
109 48     48   293 my $self = shift;
110 48         141 my $job = shift;
111            
112 48         260 my $t = $job->description();
113 48         281 push(@{$self->{finished}}, $t);
  48         289  
114 48         2244 delete($self->{jobs}->{$t});
115             }
116              
117             1;