File Coverage

blib/lib/Devel/Git/MultiBisect/AllCommits.pm
Criterion Covered Total %
statement 29 76 38.1
branch 0 10 0.0
condition n/a
subroutine 10 13 76.9
pod 3 3 100.0
total 42 102 41.1


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