File Coverage

blib/lib/Stepford/Graph.pm
Criterion Covered Total %
statement 128 134 95.5
branch 15 20 75.0
condition 3 3 100.0
subroutine 22 23 95.6
pod 0 8 0.0
total 168 188 89.3


line stmt bran cond sub pod time code
1             package Stepford::Graph;
2              
3 39     39   290 use strict;
  39         95  
  39         1399  
4 39     39   203 use warnings;
  39         84  
  39         1338  
5 39     39   224 use namespace::autoclean;
  39         69  
  39         319  
6              
7             our $VERSION = '0.006000';
8              
9 39     39   3497 use List::AllUtils qw( all none );
  39         66  
  39         2470  
10 39     39   233 use Stepford::Error;
  39         62  
  39         1259  
11 39         314 use Stepford::Types qw(
12             ArrayRef
13             Bool
14             HashRef
15             Logger
16             Maybe
17             Num
18             Step
19 39     39   221 );
  39         89  
20 39     39   288185 use Try::Tiny qw( catch try );
  39         90  
  39         2640  
21              
22 39     39   266 use Moose;
  39         65  
  39         345  
23 39     39   248869 use MooseX::StrictConstructor;
  39         82  
  39         285  
24              
25             has config => (
26             is => 'ro',
27             isa => HashRef,
28             required => 1,
29             );
30              
31             has logger => (
32             is => 'ro',
33             isa => Logger,
34             required => 1,
35             );
36              
37             has step_class => (
38             is => 'ro',
39             isa => Step,
40             required => 1,
41             );
42              
43             has _step_object => (
44             is => 'ro',
45             isa => Step,
46             lazy => 1,
47             builder => '_build_step_object',
48             );
49              
50             has last_run_time => (
51             is => 'ro',
52             isa => Maybe [Num],
53             writer => 'set_last_run_time',
54             clearer => '_clear_last_run_time',
55             lazy => 1,
56             default => sub {
57             ## no critic (RequireInitializationForLocalVars)
58             local $_;
59             shift->_step_object->last_run_time;
60             },
61             );
62              
63             has step_productions_as_hashref => (
64             is => 'ro',
65             isa => HashRef,
66             writer => 'set_step_productions_as_hashref',
67             clearer => '_clear_step_productions_as_hashref',
68             lazy => 1,
69             default => sub {
70             ## no critic (RequireInitializationForLocalVars)
71             local $_;
72             shift->_step_object->productions_as_hashref;
73             },
74             );
75              
76             has _children_graphs => (
77             traits => ['Array'],
78             init_arg => 'children_graphs',
79             is => 'ro',
80             isa => ArrayRef ['Stepford::Graph'],
81             required => 1,
82             );
83              
84             has is_being_processed => (
85             is => 'ro',
86             isa => Bool,
87             default => 0,
88             writer => 'set_is_being_processed',
89             );
90              
91             has has_been_processed => (
92             is => 'ro',
93             isa => Bool,
94             default => 0,
95             writer => 'set_has_been_processed',
96             );
97              
98             sub _build_step_object {
99 619     619   1600 my $self = shift;
100 619         2276 my $args = $self->_constructor_args_for_class;
101              
102 619         14261 $self->logger->debug( $self->step_class . '->new' );
103 619         34824 return $self->step_class->new($args);
104             }
105              
106             sub _constructor_args_for_class {
107 619     619   1301 my $self = shift;
108              
109 619         15435 my $class = $self->step_class;
110 619         16280 my $config = $self->config;
111              
112 619         1029 my %args;
113 619         7362 for my $init_arg (
114 2154         5205 grep { defined }
115 2154         66975 map { $_->init_arg } $class->meta->get_all_attributes
116             ) {
117              
118             $args{$init_arg} = $config->{$init_arg}
119 2154 100       7784 if exists $config->{$init_arg};
120             }
121              
122 619         3392 my %productions = $self->_children_productions;
123              
124 619         6004 for my $dep ( map { $_->name } $class->dependencies ) {
  469         50808  
125 469 50       1284 next if exists $args{$dep};
126              
127             # XXX - I'm not sure this error is reachable. We already check that a
128             # class's declared dependencies can be satisfied while building the
129             # graph. That said, it doesn't hurt to leave this check in here, and it
130             # might help illuminate bugs in the Runner itself.
131             Stepford::Error->throw(
132             "Cannot construct a $class object. We are missing a required production: $dep"
133 469 50       1270 ) unless exists $productions{$dep};
134              
135 469         1037 $args{$dep} = $productions{$dep};
136             }
137              
138 619         90243 $args{logger} = $self->logger;
139              
140 619         2127 return \%args;
141             }
142              
143             # Note: this is intentionally depth-first traversal as this is required for
144             # the correct sort order when running steps.
145             sub traverse {
146 1257     1257 0 2343 my $self = shift;
147 1257         1749 my $cb = shift;
148              
149 1257         1682 for my $graph ( @{ $self->_children_graphs } ) {
  1257         33406  
150 997         4458 $graph->traverse($cb);
151             }
152 1215         5217 $cb->($self);
153 1188         9015 return;
154             }
155              
156             # This checks if the step/graph is in a state where we can run it, e.g.,
157             # it isn't being processed currently, the children have been processed, it
158             # hasn't already been processed. It does not do any checks on the internal
159             # state of the step (e.g., last run times). Rather, it is intended for
160             # completely internal flow control.
161             #
162             # This is called repeatedly in a multi-process build to figure out whether we
163             # are ready to consider running the step.
164             sub can_run_step {
165 1215     1215 0 2379 my $self = shift;
166              
167             # These checks are not logged as they are part of Stepford's internal
168             # flow control and might be run many times for a single step.
169             return
170 1215   100     34907 !$self->is_being_processed
171             && $self->children_have_been_processed
172             && !$self->has_been_processed;
173             }
174              
175             # This checks whether we should run the step. It is meant to be run after we
176             # determine that the step is in a state where it _can_ run. This primarily
177             # looks at the internal state of the step, e.g., last run times.
178             #
179             # This is generally only called once, immediately before we run the step or
180             # decide never to run it.
181             #
182             # can and should are separated as they serve two different purposes in the
183             # Runner's flow control.
184             sub step_is_up_to_date {
185 619     619 0 1312 my $self = shift;
186 619         1018 my $force_step_execution = shift;
187              
188 619         15133 my $step_class = $self->step_class;
189              
190 619 100       1690 if ($force_step_execution) {
191 4         97 $self->logger->info("Force execution is enabled for $step_class.");
192 4         115 return 0;
193             }
194              
195 615 100       16372 unless ( defined $self->last_run_time ) {
196 341         8490 $self->logger->debug("No last run time for $step_class.");
197 341         12351 return 0;
198             }
199              
200 274 100       464 unless ( @{ $self->_children_graphs } ) {
  274         7311  
201 135         2972 $self->logger->debug("No previous steps for $step_class.");
202 135         4261 return 1;
203             }
204              
205 139 50       287 if ( my @missing
206 205         4606 = grep { !defined $_->last_run_time } @{ $self->_children_graphs } ) {
  139         3221  
207             $self->logger->debug(
208             "A previous step for $step_class does not have a last run time: "
209 0         0 . join ', ', map { $_->step_class } @missing );
  0         0  
210 0         0 return 0;
211             }
212              
213 139         3142 my $step_last_run_time = $self->last_run_time;
214 205         4534 my @newer_children = grep { $_->last_run_time > $step_last_run_time }
215 139         223 @{ $self->_children_graphs };
  139         3099  
216 139 100       400 unless (@newer_children) {
217 102         2447 $self->logger->info("$step_class is up to date.");
218 102         3343 return 1;
219             }
220              
221             $self->logger->info(
222             "Last run time for $step_class is "
223             . $self->last_run_time
224             . '. The following children have newer last run times: '
225             . join ', ',
226 37         886 map { $_->step_class . ' (' . $_->last_run_time . ')' }
  37         868  
227             @newer_children
228             );
229              
230 37         1146 return 0;
231             }
232              
233             sub run_step {
234 212     212 0 500 my $self = shift;
235              
236 212 50       1297 die 'Tried running '
237             . $self->step_class
238             . ' when not all children have been processed.'
239             unless $self->children_have_been_processed;
240              
241 212 50       8815 die 'Tried running '
242             . $self->step_class
243             . ' when it is currently being run'
244             if $self->is_being_processed;
245              
246 212         7127 $self->set_is_being_processed(1);
247              
248 212         5474 $self->logger->info( 'Running ' . $self->step_class );
249              
250             {
251             # We use many list functions that will modify the underlying array
252             # they are called on if $_ is modified. We localize it for safety.
253             ## no critic (RequireInitializationForLocalVars)
254 212         5947 local $_;
  212         431  
255 212         5917 $self->_step_object->run;
256             }
257 210         104244 $self->set_is_being_processed(0);
258 210         6805 $self->set_has_been_processed(1);
259 210         7039 $self->_clear_last_run_time;
260 210         7569 $self->_clear_step_productions_as_hashref;
261              
262 210         719 return;
263             }
264              
265             sub productions {
266 0     0 0 0 my $self = shift;
267              
268             return (
269             $self->_children_productions,
270 0         0 %{ $self->step_productions_as_hashref },
  0         0  
271             );
272             }
273              
274             sub _children_productions {
275 619     619   1191 my $self = shift;
276              
277             return
278 476         1074 map { %{ $_->step_productions_as_hashref } }
  476         13301  
279 619         1006 @{ $self->_children_graphs };
  619         18399  
280             }
281              
282             sub children_have_been_processed {
283 1400     1400 0 2688 my $self = shift;
284              
285 1400     1095   10123 all { $_->has_been_processed } @{ $self->_children_graphs };
  1095         31986  
  1400         39452  
286             }
287              
288             sub is_serializable {
289 201     201 0 427 my $self = shift;
290              
291             # A step can be serialized as long as it and all of its children do not
292             # implement Stepford::Role::Step::Unserializable
293             none {
294 352     352   19980 $_->step_class->does('Stepford::Role::Step::Unserializable')
295             }
296 201         2238 ( $self, @{ $self->_children_graphs } );
  201         6469  
297             }
298              
299             sub as_string {
300 152     152 0 443 my $self = shift;
301              
302             return join(
303             "\n",
304             (
305 152         502 map { sprintf( '[ %s ] --> [ %s ]', @$_ ) }
  596         3302  
306             _parent_child_pairs($self)
307             ),
308             q{}
309             );
310             }
311              
312             sub _parent_child_pairs {
313 152     152   250 my $parent = shift;
314              
315 152         269 my @pairs;
316 152         464 my @parents = ($parent);
317 152         255 my %seen;
318 152         464 while (@parents) {
319 738         1100 my $current_parent = shift @parents;
320 738         1189 my @children = @{ $current_parent->_children_graphs };
  738         17408  
321              
322 738         1333 my @children_step_classes = map { $_->step_class } @children;
  596         12969  
323 738         16382 my $current_parent_step_class = $current_parent->step_class;
324              
325             push @pairs,
326 738         1566 map { [ $current_parent_step_class, $_ ] } @children_step_classes;
  596         1363  
327              
328             push @parents,
329 738         1255 grep { !exists $seen{ $_->step_class } } @children;
  596         12735  
330              
331 738         2424 @seen{@children_step_classes} = ();
332             }
333              
334 152         490 return @pairs;
335             }
336              
337             __PACKAGE__->meta->make_immutable;
338              
339             1;
340              
341             # ABSTRACT: Contains the step dependency graph
342              
343             __END__
344              
345             =pod
346              
347             =encoding UTF-8
348              
349             =head1 NAME
350              
351             Stepford::Graph - Contains the step dependency graph
352              
353             =head1 VERSION
354              
355             version 0.006000
356              
357             =head1 DESCRIPTION
358              
359             This is an internal class and has no user-facing parts. Please do not use it.
360              
361             A C<Stepford::Graph> is a directed graph of the step dependency resolution.
362             Each step node has connections to all of the nodes for the steps that create
363             its dependencies. It is a graph rather than a tree as there may be multiple
364             paths between a step node and steps that create dependencies of dependency
365             steps. It is directed as each node only knows about the nodes for its own
366             dependencies, not the nodes that it provides dependencies for.
367              
368             =for Pod::Coverage .*
369              
370             =head1 SUPPORT
371              
372             Bugs may be submitted through L<https://github.com/maxmind/Stepford/issues>.
373              
374             =head1 AUTHOR
375              
376             Dave Rolsky <drolsky@maxmind.com>
377              
378             =head1 COPYRIGHT AND LICENSE
379              
380             This software is copyright (c) 2014 - 2019 by MaxMind, Inc.
381              
382             This is free software; you can redistribute it and/or modify it under
383             the same terms as the Perl 5 programming language system itself.
384              
385             =cut