File Coverage

blib/lib/Devel/Git/MultiBisect/AllCommits.pm
Criterion Covered Total %
statement 23 70 32.8
branch 0 10 0.0
condition n/a
subroutine 8 11 72.7
pod 3 3 100.0
total 34 94 36.1


line stmt bran cond sub pod time code
1             package Devel::Git::MultiBisect::AllCommits;
2 4     4   2551 use v5.14.0;
  4         38  
3 4     4   39 use warnings;
  4         25  
  4         179  
4 4     4   2033 use parent( qw| Devel::Git::MultiBisect | );
  4         1515  
  4         33  
5 4     4   2329 use Devel::Git::MultiBisect::Opts qw( process_options );
  4         9  
  4         255  
6 4     4   34 use Carp;
  4         9  
  4         199  
7 4     4   24 use Cwd;
  4         10  
  4         200  
8 4     4   24 use File::Temp;
  4         8  
  4         279  
9 4     4   23 use List::Util qw(first sum);
  4         9  
  4         2650  
10              
11             our $VERSION = '0.19';
12             $VERSION = eval $VERSION;
13              
14             =head1 NAME
15              
16             Devel::Git::MultiBisect::AllCommits - Gather test output over an entire range of F commits
17              
18             =head1 SYNOPSIS
19              
20             use Devel::Git::MultiBisect::AllCommits;
21              
22             $self = Devel::Git::MultiBisect::AllCommits->new(\%parameters);
23              
24             $commit_range = $self->get_commits_range();
25              
26             $full_targets = $self->set_targets(\@target_args);
27              
28             $outputs = $self->run_test_files_on_one_commit($commit_range->[0]);
29              
30             $all_outputs = $self->run_test_files_on_all_commits();
31              
32             $rv = $self->get_digests_by_file_and_commit();
33              
34             $transitions = $self->examine_transitions();
35              
36             =head1 DESCRIPTION
37              
38             Given a Perl library or application kept in F for version control, it is
39             often useful to be able to compare the output collected from running one or
40             several test files over a range of F commits. If that range is sufficiently
41             large, a test may fail in B over that range.
42              
43             If that is the case, then simply asking, I<"When did this file start to
44             fail?"> is insufficient. We may want to capture the test output for each
45             commit, or, more usefully, may want to capture the test output only at those
46             commits where the output changed. F
47             provides methods for the first of those objectives. That is:
48              
49             =over 4
50              
51             =item *
52              
53             When you want to capture the test output for each commit in a specified range,
54             you can use this package, F.
55              
56             =item *
57              
58             When the number of commits in the specified range is large and you only need
59             the test output at those commits where the output materially changed, you can
60             use another package found in this library, F.
61              
62             =back
63              
64             =head1 METHODS
65              
66             This package inherits methods from F. Only methods unique to
67             F are documented here. See the documentation for
68             F for all other methods, including:
69              
70             new()
71             get_commits_range()
72             set_targets()
73             run_test_files_on_one_commit()
74              
75             =head2 C
76              
77             =over 4
78              
79             =item * Purpose
80              
81             Capture the output from a run of the selected test files at each specific git
82             checkout in the selected commit range.
83              
84             =item * Arguments
85              
86             $all_outputs = $self->run_test_files_on_all_commits();
87              
88             None; all data needed is already present in the object.
89              
90             =item * Return Value
91              
92             Array reference, each of whose elements is an array reference, each of whose
93             elements is a hash reference with the same four keys as in the return value
94             from C:
95              
96             commit
97             commit_short
98             file
99             file_stub
100             md5_hex
101              
102             Example:
103              
104             [
105             # Array where each element corresponds to a single git checkout
106              
107             [
108             # Array where each element corresponds to one of the selected test
109             # files (here, 2 test files were targetd)
110              
111             {
112             # Hash where each element correponds to the result of running a
113             # single test file at a single commit point
114              
115             commit => "2a2e54af709f17cc6186b42840549c46478b6467",
116             commit_short => "2a2e54a",
117             file => "/tmp/AHC_YkwUYg/2a2e54a.t_44_func_hashes_mult_unsorted_t.output.txt",
118             file_stub => "t_44_func_hashes_mult_unsorted_t",
119             md5_hex => "31b7c93474e15a16d702da31989ab565",
120             },
121             {
122             commit => "2a2e54af709f17cc6186b42840549c46478b6467",
123             commit_short => "2a2e54a",
124             file => "/tmp/AHC_YkwUYg/2a2e54a.t_45_func_hashes_alt_dual_sorted_t.output.txt",
125             file_stub => "t_45_func_hashes_alt_dual_sorted_t",
126             md5_hex => "6ee767b9d2838e4bbe83be0749b841c1",
127             },
128             ],
129             [
130             {
131             commit => "a624024294a56964eca53ec4617a58a138e91568",
132             commit_short => "a624024",
133             file => "/tmp/AHC_YkwUYg/a624024.t_44_func_hashes_mult_unsorted_t.output.txt",
134             file_stub => "t_44_func_hashes_mult_unsorted_t",
135             md5_hex => "31b7c93474e15a16d702da31989ab565",
136             },
137             {
138             commit => "a624024294a56964eca53ec4617a58a138e91568",
139             commit_short => "a624024",
140             file => "/tmp/AHC_YkwUYg/a624024.t_45_func_hashes_alt_dual_sorted_t.output.txt",
141             file_stub => "t_45_func_hashes_alt_dual_sorted_t",
142             md5_hex => "6ee767b9d2838e4bbe83be0749b841c1",
143             },
144             ],
145             # ...
146             [
147             {
148             commit => "d304a207329e6bd7e62354df4f561d9a7ce1c8c2",
149             commit_short => "d304a20",
150             file => "/tmp/AHC_YkwUYg/d304a20.t_44_func_hashes_mult_unsorted_t.output.txt",
151             file_stub => "t_44_func_hashes_mult_unsorted_t",
152             md5_hex => "31b7c93474e15a16d702da31989ab565",
153             },
154             {
155             commit => "d304a207329e6bd7e62354df4f561d9a7ce1c8c2",
156             commit_short => "d304a20",
157             file => "/tmp/AHC_YkwUYg/d304a20.t_45_func_hashes_alt_dual_sorted_t.output.txt",
158             file_stub => "t_45_func_hashes_alt_dual_sorted_t",
159             md5_hex => "6ee767b9d2838e4bbe83be0749b841c1",
160             },
161             ],
162             ]
163              
164             =item * Comment
165              
166             Note: If the number of commits in the commits range is large, this method
167             will take a long time to run. That time will be even longer if the
168             configuration and build times for each commit are large. For example, to run
169             one test over 160 commits from the Perl 5 core distribution might take 15
170             hours. YMMV. If either of these conditions holds, you are probably better
171             off using F.
172              
173             The implementation of this method is very much subject to change.
174              
175             =back
176              
177             =cut
178              
179             sub run_test_files_on_all_commits {
180 0     0 1   my $self = shift;
181 0           my $all_commits = $self->get_commits_range();
182 0           my @all_outputs;
183 0           my $start_time = time();
184 0           for my $commit (@{$all_commits}) {
  0            
185 0           my $outputs = $self->run_test_files_on_one_commit($commit);
186 0           push @all_outputs, $outputs;
187             }
188 0           my $end_time = time();
189             my %timings = (
190             elapsed => $end_time - $start_time,
191 0           runs => scalar(@{$all_commits}),
  0            
192             );
193 0 0         if ($timings{runs}) {
194 0           $timings{mean} = sprintf("%.02f" => $timings{elapsed} / $timings{runs}); }
195 0 0         if ($self->{verbose}) {
196 0           my $msg = "Ran $timings{runs} runs; elapsed: $timings{elapsed} sec";
197 0 0         if ($timings{runs}) {
198 0           $msg .= "; mean: $timings{mean} sec";
199             }
200 0           say $msg;
201             }
202 0           $self->{all_outputs} = [ @all_outputs ];
203 0           $self->{timings} = \%timings;
204 0           return \@all_outputs;
205             }
206              
207              
208             =head2 C
209              
210             =over 4
211              
212             =item * Purpose
213              
214             Present the same outcomes as C, but formatted
215             by target file, then commit.
216              
217             =item * Arguments
218              
219             $rv = $self->get_digests_by_file_and_commit();
220              
221             None; all data needed is already present in the object.
222              
223             =item * Return Value
224              
225             Reference to a hash keyed on the basename of the target file, modified to
226             substitute underscores for forward slashes and dots. The value of each
227             element in the hash is a reference to an array which, in turn, holds a list of
228             hash references, one per F commit. Each such hash has the following keys:
229              
230             commit
231             file
232             md5_hex
233              
234             Example:
235              
236             {
237             t_44_func_hashes_mult_unsorted_t => [
238             {
239             commit => "2a2e54af709f17cc6186b42840549c46478b6467",
240             file => "/tmp/Xhilc8ZSgS/2a2e54a.t_44_func_hashes_mult_unsorted_t.output.txt",
241             md5_hex => "31b7c93474e15a16d702da31989ab565",
242             },
243             {
244             commit => "a624024294a56964eca53ec4617a58a138e91568",
245             file => "/tmp/Xhilc8ZSgS/a624024.t_44_func_hashes_mult_unsorted_t.output.txt",
246             md5_hex => "31b7c93474e15a16d702da31989ab565",
247             },
248             # ...
249             {
250             commit => "d304a207329e6bd7e62354df4f561d9a7ce1c8c2",
251             file => "/tmp/Xhilc8ZSgS/d304a20.t_44_func_hashes_mult_unsorted_t.output.txt",
252             md5_hex => "31b7c93474e15a16d702da31989ab565",
253             },
254             ],
255             t_45_func_hashes_alt_dual_sorted_t => [
256             {
257             commit => "2a2e54af709f17cc6186b42840549c46478b6467",
258             file => "/tmp/Xhilc8ZSgS/2a2e54a.t_45_func_hashes_alt_dual_sorted_t.output.txt",
259             md5_hex => "6ee767b9d2838e4bbe83be0749b841c1",
260             },
261             {
262             commit => "a624024294a56964eca53ec4617a58a138e91568",
263             file => "/tmp/Xhilc8ZSgS/a624024.t_45_func_hashes_alt_dual_sorted_t.output.txt",
264             md5_hex => "6ee767b9d2838e4bbe83be0749b841c1",
265             },
266             # ...
267             {
268             commit => "d304a207329e6bd7e62354df4f561d9a7ce1c8c2",
269             file => "/tmp/Xhilc8ZSgS/d304a20.t_45_func_hashes_alt_dual_sorted_t.output.txt",
270             md5_hex => "6ee767b9d2838e4bbe83be0749b841c1",
271             },
272             ],
273             }
274              
275              
276             =item * Comment
277              
278             This method currently presumes that you have called
279             C. It will die otherwise.
280              
281             =back
282              
283             =cut
284              
285             sub get_digests_by_file_and_commit {
286 0     0 1   my $self = shift;
287 0 0         unless (exists $self->{all_outputs}) {
288 0           croak "You must call run_test_files_on_all_commits() before calling get_digests_by_file_and_commit()";
289             }
290 0           my $rv = {};
291 0           for my $commit (@{$self->{all_outputs}}) {
  0            
292 0           for my $target (@{$commit}) {
  0            
293 0           push @{$rv->{$target->{file_stub}}},
294             {
295             commit => $target->{commit},
296             file => $target->{file},
297             md5_hex => $target->{md5_hex},
298 0           };
299             }
300             }
301 0           return $rv;
302             }
303              
304             =head2 C
305              
306             =over 4
307              
308             =item * Purpose
309              
310             Determine whether a run of the same targeted test file run at two consecutive
311             commits produced the same or different output (as measured by string equality
312             or inequality of each commit's md5_hex value.
313              
314             =item * Arguments
315              
316             $hashref = $self->get_digests_by_file_and_commit();
317              
318             $transitions = $self->examine_transitions($hashref);
319              
320             Hash reference returned by C;
321              
322             =item * Return Value
323              
324             Reference to a hash keyed on the basename of the target file, modified to
325             substitute underscores for forward slashes and dots. The value of each
326             element in the hash is a reference to an array which, in turn, holds a list of
327             hash references, one per each pair of consecutive F commits. Each such hash
328             has the following keys:
329              
330             older
331             newer
332             compare
333              
334             The value for each of the C and C elements is a reference to a
335             hash with two elements:
336              
337             md5_hex
338             idx
339              
340             ... where C is the digest of the test output file and C is the
341             position (count starting at C<0>) of that element in the list of commits in
342             the commit range.
343              
344             Example:
345              
346             {
347             t_44_func_hashes_mult_unsorted_t => [
348             {
349             compare => "same",
350             newer => { md5_hex => "31b7c93474e15a16d702da31989ab565", idx => 1 },
351             older => { md5_hex => "31b7c93474e15a16d702da31989ab565", idx => 0 },
352             },
353             {
354             compare => "same",
355             newer => { md5_hex => "31b7c93474e15a16d702da31989ab565", idx => 2 },
356             older => { md5_hex => "31b7c93474e15a16d702da31989ab565", idx => 1 },
357             },
358             # ...
359             {
360             compare => "same",
361             newer => { md5_hex => "31b7c93474e15a16d702da31989ab565", idx => 9 },
362             older => { md5_hex => "31b7c93474e15a16d702da31989ab565", idx => 8 },
363             },
364             ],
365             t_45_func_hashes_alt_dual_sorted_t => [
366             {
367             compare => "same",
368             newer => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 1 },
369             older => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 0 },
370             },
371             {
372             compare => "same",
373             newer => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 2 },
374             older => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 1 },
375             },
376             {
377             compare => "same",
378             newer => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 3 },
379             older => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 2 },
380             },
381             # ...
382             {
383             compare => "same",
384             newer => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 9 },
385             older => { md5_hex => "6ee767b9d2838e4bbe83be0749b841c1", idx => 8 },
386             },
387             ],
388             }
389              
390             =item * Comment
391              
392             This method currently may be called only after calling
393             C and will die otherwise.
394              
395             Since in this method we are concerned with the B in the test
396             output between a pair of commits, the second-level arrays returned by this
397             method will have one fewer element than the second-level arrays returned by
398             C.
399              
400             =back
401              
402             =cut
403              
404             sub examine_transitions {
405 0     0 1   my ($self, $rv) = @_;
406 0           my %transitions;
407 0           for my $k (sort keys %{$rv}) {
  0            
408 0           my @arr = @{$rv->{$k}};
  0            
409 0           for (my $i = 1; $i <= $#arr; $i++) {
410 0           my $older = $arr[$i-1]->{md5_hex};
411 0           my $newer = $arr[$i]->{md5_hex};
412 0 0         if ($older eq $newer) {
413 0           push @{$transitions{$k}}, {
  0            
414             older => { idx => $i-1, md5_hex => $older },
415             newer => { idx => $i, md5_hex => $newer },
416             compare => 'same',
417             }
418             }
419             else {
420 0           push @{$transitions{$k}}, {
  0            
421             older => { idx => $i-1, md5_hex => $older },
422             newer => { idx => $i, md5_hex => $newer },
423             compare => 'different',
424             }
425             }
426             }
427             }
428 0           return \%transitions;
429             }
430              
431             1;
432