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   143 use strict;
  19         48  
  19         632  
4 19     19   108 use warnings;
  19         42  
  19         528  
5              
6 19     19   8621 use TAP::Parser::Scheduler::Job;
  19         7358  
  19         689  
7 19     19   8336 use TAP::Parser::Scheduler::Spinner;
  19         3369  
  19         7513  
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 78 my $class = shift;
18 26         65 my $dispenser = shift;
19 26         83 my @testPairs = @_;
20              
21             # create
22             #
23 26         293 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         234 my %jobs;
40 26         93 foreach my $pair (@testPairs)
41             {
42 48         321 my $job = TAP::Parser::Scheduler::Job->new(@$pair);
43 48     48   662 $job->on_finish( sub { $self->__finish(@_) } );
  48         30407  
44 48         400 $jobs{$job->description()} = $job;
45             }
46 26         190 $self->{jobs} = \%jobs;
47            
48 26         96 return $self;
49             }
50              
51             # list of all remaining tests (jobs)
52             #
53             sub get_all
54             {
55 25     25 0 645 my $self = shift;
56              
57 25         68 return values(%{$self->{jobs}});
  25         138  
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 330316 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     1080 if (!$self->{queue} || ($self->{queue} && !@{$self->{queue}}))
  59   100     590  
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         903 $self->{queue} = $self->{pez}->getEligibleTests($self->{finished});
79 81         303 $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         235 my $job;
86 85 100       321 if ($self->{queue})
87             {
88 59 100       151 if (@{$self->{queue}})
  59         218  
89             {
90             # if there are tests in the queue, pop the first
91             # and translate it to a job object
92             #
93 48         121 my $t = shift(@{$self->{queue}});
  48         159  
94 48         233 $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         41 $job = $self->{spinner};
101             }
102             }
103              
104 85         335 return $job;
105             }
106              
107             sub __finish
108             {
109 48     48   335 my $self = shift;
110 48         178 my $job = shift;
111            
112 48         333 my $t = $job->description();
113 48         377 push(@{$self->{finished}}, $t);
  48         283  
114 48         2303 delete($self->{jobs}->{$t});
115             }
116              
117             1;