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   144 use strict;
  19         47  
  19         569  
4 19     19   104 use warnings;
  19         46  
  19         1217  
5              
6             our $VERSION = '1.001';
7             my $version = $VERSION;
8             $VERSION = eval $VERSION;
9              
10 19     19   8741 use TAP::Parser::Scheduler::Job;
  19         7401  
  19         586  
11 19     19   7927 use TAP::Parser::Scheduler::Spinner;
  19         3226  
  19         7667  
12              
13             # This acts like a TAP Scheduler, but uses the test dispenser
14             # to give out jobs.
15             # This happens according to eligibility after continually
16             # toposorting and removing completed jobs, making
17             # it possible to parallelize tests efficiently
18             #
19             sub new
20             {
21 26     26 0 81 my $class = shift;
22 26         55 my $dispenser = shift;
23 26         77 my @testPairs = @_;
24              
25             # create
26             #
27 26         342 my $self = bless
28             (
29             {
30             pez => $dispenser,
31             jobs => undef,
32             queue => undef,
33             finished => [],
34             spinner => TAP::Parser::Scheduler::Spinner->new()
35             },
36             $class
37             );
38              
39             # create a list of job objects which is what
40             # the harness wants
41             # also hook them up to a closure to be called when they finish
42             #
43 26         248 my %jobs;
44 26         103 foreach my $pair (@testPairs)
45             {
46 48         368 my $job = TAP::Parser::Scheduler::Job->new(@$pair);
47 48     48   616 $job->on_finish( sub { $self->__finish(@_) } );
  48         24368  
48 48         391 $jobs{$job->description()} = $job;
49             }
50 26         181 $self->{jobs} = \%jobs;
51            
52 26         94 return $self;
53             }
54              
55             # list of all remaining tests (jobs)
56             #
57             sub get_all
58             {
59 25     25 0 708 my $self = shift;
60              
61 25         56 return values(%{$self->{jobs}});
  25         137  
62             }
63              
64             # return the next eligible job
65             # - if all jobs completed, return undef
66             # - if no job is currently eligible (due to dependencies etc), return a 'spinner' job
67             #
68             sub get_job
69             {
70 85     85 0 353169 my $self = shift;
71              
72             # maintain a queue of eligible tests here since we should deliver just one job
73             # at a time but the dispenser may 'free up' multiple at a time
74             #
75 85 100 66     916 if (!$self->{queue} || ($self->{queue} && !@{$self->{queue}}))
  59   100     538  
76             {
77             # if there is no queue right now (first call after creation)
78             # or the queue is empty, attempt to retrieve a new queue from the
79             # dispenser. The dispenser is called with the current list of finished
80             # jobs, so it may recompute and see what, if anything, is now free to run
81             #
82 81         804 $self->{queue} = $self->{pez}->getEligibleTests($self->{finished});
83 81         261 $self->{finished} = [];
84             }
85            
86             # the dispenser will return undef when there are no more tests, which means we
87             # will return undef too
88             #
89 85         232 my $job;
90 85 100       278 if ($self->{queue})
91             {
92 59 100       111 if (@{$self->{queue}})
  59         192  
93             {
94             # if there are tests in the queue, pop the first
95             # and translate it to a job object
96             #
97 48         81 my $t = shift(@{$self->{queue}});
  48         158  
98 48         126 $job = $self->{jobs}->{$t};
99             }
100             else
101             {
102             # the queue is empty but still active, so spin our wheels a bit...
103             #
104 11         51 $job = $self->{spinner};
105             }
106             }
107              
108 85         371 return $job;
109             }
110              
111             sub __finish
112             {
113 48     48   268 my $self = shift;
114 48         151 my $job = shift;
115            
116 48         230 my $t = $job->description();
117 48         250 push(@{$self->{finished}}, $t);
  48         246  
118 48         2082 delete($self->{jobs}->{$t});
119             }
120              
121             1;