File Coverage

blib/lib/Devel/Git/MultiBisect/Auxiliary.pm
Criterion Covered Total %
statement 94 95 98.9
branch 35 46 76.0
condition n/a
subroutine 13 13 100.0
pod 4 4 100.0
total 146 158 92.4


line stmt bran cond sub pod time code
1             package Devel::Git::MultiBisect::Auxiliary;
2 9     9   6639 use v5.14.0;
  9         39  
3 9     9   62 use warnings;
  9         30  
  9         885  
4             our $VERSION = '0.21';
5             $VERSION = eval $VERSION;
6 9     9   59 use base qw( Exporter );
  9         20  
  9         1692  
7             our @EXPORT_OK = qw(
8             clean_outputfile
9             hexdigest_one_file
10             validate_list_sequence
11             write_transitions_report
12             );
13 9     9   56 use Carp;
  9         17  
  9         595  
14 9     9   5489 use Data::Dumper;
  9         85086  
  9         716  
15 9     9   85 use Digest::MD5;
  9         15  
  9         362  
16 9     9   4805 use File::Copy;
  9         52540  
  9         583  
17 9     9   58 use File::Spec;
  9         15  
  9         311  
18 9     9   46 use List::Util qw(first);
  9         16  
  9         10002  
19              
20             =head1 NAME
21              
22             Devel::Git::MultiBisect::Auxiliary - Helper functions for Devel::Git::MultiBisect
23              
24             =head1 SYNOPSIS
25              
26             use Devel::Git::MultiBisect::Auxiliary qw(
27             clean_outputfile
28             hexdigest_one_file
29             validate_list_sequence
30             );
31              
32             =head1 DESCRIPTION
33              
34             This package exports, on demand only, subroutines used within publicly available
35             methods in Devel::Git::MultiBisect.
36              
37             =head1 SUBROUTINES
38              
39             =head2 C
40              
41             =over 4
42              
43             =item * Purpose
44              
45             When we redirect the output of a test harness program such as F to a
46             file, we typically get at the end a line matching this pattern:
47              
48             m/^Files=\d+,\sTests=\d+/
49              
50             This line also contains measurements of the time it took for a particular file
51             to be run. These timings vary from one run to the next, which makes the
52             content of otherwise identical files different, which in turn makes their
53             md5_hex digests different. So we simply rewrite the test output file to
54             remove this line.
55              
56             =item * Arguments
57              
58             $outputfile = clean_outputfile($outputfile);
59              
60             A string holding the path to a file holding TAP output.
61              
62             =item * Return Value
63              
64             A string holding the path to a file holding TAP output.
65              
66             =item * Comment
67              
68             The return value is provided for the purpose of chaining function calls; the
69             file itself is changed in place.
70              
71             =back
72              
73             =cut
74              
75             sub clean_outputfile {
76 2     2 1 7102 my $outputfile = shift;
77 2         9 my $replacement = "$outputfile.tmp";
78 2 50       85 open my $IN, '<', $outputfile
79             or croak "Could not open $outputfile for reading";
80 2 50       287 open my $OUT, '>', $replacement
81             or croak "Could not open $replacement for writing";
82 2         99 while (my $l = <$IN>) {
83 98         185 chomp $l;
84 98 100       617 say $OUT $l unless $l =~ m/^Files=\d+,\sTests=\d+/;
85             }
86 2 50       116 close $OUT or croak "Could not close after writing";
87 2 50       30 close $IN or croak "Could not close after reading";
88 2 50       11 move $replacement => $outputfile or croak "Could not replace";
89 2         900 return $outputfile;
90             }
91              
92             =head2 C
93              
94             =over 4
95              
96             =item * Purpose
97              
98             To compare multiple files for same or different content, we need a convenient,
99             short datum. We will use the C value provided by the F
100             module which is part of the Perl 5 core distribution.
101              
102             =item * Arguments
103              
104             $md5_hex = hexdigest_one_file($outputfile);
105              
106             A string holding the path to a file holding TAP output.
107              
108             =item * Return Value
109              
110             A string holding the C digest for that file.
111              
112             =item * Comment
113              
114             The file provided as argument should be run through C
115             before being passed to this function.
116              
117             =back
118              
119             =cut
120              
121             sub hexdigest_one_file {
122 10     10 1 426335 my $filename = shift;
123 10         107 my $state = Digest::MD5->new();
124 10 50       332 open my $FH, '<', $filename or croak "Unable to open $filename for reading";
125 10         1076 $state->addfile($FH);
126 10 50       118 close $FH or croak "Unable to close $filename after reading";
127 10         88 my $hexdigest = $state->hexdigest;
128 10         121 return $hexdigest;
129             }
130              
131             =head2 C
132              
133             =over 4
134              
135             =item * Purpose
136              
137             Determine whether a given list consists of one or more sub-lists, each of
138             which conforms to the following properties:
139              
140             =over 4
141              
142             =item 1
143              
144             The sub-list consists of one or more elements, the first and last of which are
145             defined and identical. Elements between the first and last (if any) are
146             either identical to the first and last or are undefined.
147              
148             =item 2
149              
150             The sole defined value in any sub-list is not found in any other sub-list.
151              
152             =back
153              
154             Examples:
155              
156             =over 4
157              
158             =item * C<['alpha', 'alpha', undef, 'alpha', undef, 'beta']>
159              
160             Does not qualify, as the sub-list terminating with C starts with an C.
161              
162             =item * C<['alpha', 'alpha', undef, 'alpha', 'beta', undef]>
163              
164             Does not qualify, as the sub-list starting with C ends with an C.
165              
166             =item * C<['alpha', 'alpha', undef, 'alpha', 'beta', undef, 'beta', 'alpha', 'alpha']>
167              
168             Does not qualify, as C occurs in both the first and third sub-lists.
169              
170             =item * C<['alpha', 'alpha', undef, 'alpha', 'beta', undef, 'beta']>
171              
172             Qualifies.
173              
174             =back
175              
176             =item * Arguments
177              
178             my $vls = validate_list_sequence( [
179             'alpha', 'alpha', undef, 'alpha', 'beta', undef, 'beta'
180             ] );
181              
182             Reference to an array holding scalars.
183              
184             =item * Return Value
185              
186             Array reference consisting of either 1 or 3 elements. If the list qualifies,
187             the array holds just one element which is a Perl-true value. If the list does
188             B qualify, the array hold 3 elements as follows:
189              
190             =over 4
191              
192             =item * Element 0
193              
194             Perl-false value, indicating that the list does not qualify.
195              
196             =item * Element 1
197              
198             Index of the array element at which the first non-conforming value was observed.
199              
200             =item * Element 2
201              
202             String holding explanation for failure to qualify.
203              
204             =back
205              
206             Examples:
207              
208             =over 4
209              
210             =item 1
211              
212             Qualifying list:
213              
214             use Data::Dumper; $Data::Dumper::Indent = 0;
215             my $vls;
216              
217             my $good =
218             ['alpha', 'alpha', undef, 'alpha', 'beta', undef, 'beta', 'gamma'];
219             $vls = validate_list_sequence($good);
220             print Dumper($vls);
221              
222             #####
223              
224             $VAR1 = [1];
225              
226             =item 2
227              
228             Non-qualifying list:
229              
230             my $bad =
231             ['alpha', 'alpha', undef, 'alpha', 'beta', undef, 'beta', 'alpha', 'alpha'];
232             $vls = validate_list_sequence($bad);
233             print Dumper($vls);
234              
235             #####
236              
237             $VAR1 = [0,7,'alpha previously observed']
238              
239             =back
240              
241             =back
242              
243             =cut
244              
245             sub validate_list_sequence {
246 10     10 1 48259 my $list = shift;
247 10 100       342 croak "Must provide array ref to validate_list_sequence()"
248             unless ref($list) eq 'ARRAY';;
249 9         21 my $rv = [];
250 9         16 my $status = 1;
251 9 100       35 if (! defined $list->[0]) {
252 1         4 $rv = [0, 0, 'first element undefined'];
253 1         4 return $rv;
254             }
255 8 100       16 if (! defined $list->[$#{$list}]) {
  8         29  
256 1         3 $rv = [0, $#{$list}, 'last element undefined'];
  1         4  
257 1         9 return $rv;
258             }
259             # lpd => 'last previously defined'
260 7         18 my $lpd = $list->[0];
261 7         17 my %previous = ();
262 7         19 for (my $j = 1; $j <= $#{$list}; $j++) {
  425         955  
263 422 100       836 if (! defined $list->[$j]) {
264 262         504 next;
265             }
266             else {
267 160 100       309 if ($list->[$j] eq $lpd) {
268 141         251 next;
269             }
270             else {
271             # Value differs from last previously observed.
272             # Was it ever previously observed? If so, bad.
273 19 100       49 if (exists $previous{$list->[$j]}) {
274 3         6 $status = 0;
275 3         14 $rv = [$status, $j, "$list->[$j] previously observed"];
276 3         43 return $rv;
277             }
278             else {
279             # Value not previously observed, but since previous
280             # sequence ends with an undef, that sequence was not
281             # properly terminated. Bad.
282 16 100       38 if (! defined $list->[$j-1]) {
283 1         3 $status = 0;
284 1         7 $rv = [
285             $status,
286             $j,
287             "Immediately preceding element (index " . ($j-1) . ") not defined",
288             ];
289 1         7 return $rv;
290             }
291             else {
292 15         42 $previous{$lpd}++;
293 15 50       42 if (defined $list->[$j]) { $lpd = $list->[$j]; }
  15         58  
294 15         29 next;
295             }
296             }
297             }
298             }
299             }
300 3         26 return [$status];
301             }
302              
303              
304             =head2 C
305              
306             =over 4
307              
308             =item * Purpose
309              
310             Write data about transitions to file on disk.
311              
312             =item * Arguments
313              
314             $transitions_report = write_transitions_report($outputdir, $report_basename, $transitions_data);
315              
316             List of 3 arguments:
317              
318             =over 4
319              
320             =item *
321              
322             String holding path to output directory (typically,
323             C<$self-E{outputdir}>).
324              
325             =item *
326              
327             String holding desired basename for transitions report file (typically,
328             C<$self-E{transitions_report}>).
329              
330             =item *
331              
332             Hash reference which is return value of C<$self-Einspect_transitions()>.
333              
334             =back
335              
336             =item * Return Value
337              
338             String holding full path to transitions report file.
339              
340             =back
341              
342             =cut
343              
344             sub write_transitions_report {
345 5     5 1 8868 my ($outputdir, $report_basename, $transitions_data) = @_;
346 5 100       261 croak "Must supply 3 arguments to write_transitions_report()"
347             unless @_ == 3;
348 4 100       193 croak "3rd argument to write_transitions_report() must be hashref"
349             unless ref($transitions_data) eq 'HASH';
350 3 100       158 croak "Must be 3 elements in 3rd argument to write_transitions_report()"
351             unless (scalar keys %$transitions_data == 3);
352 2         6 my %expected_keys = map { $_ => 1 } (qw| newest oldest transitions |);
  6         33  
353 2         9 for my $k (keys %expected_keys) {
354             croak "'$k' element missing from 3rd argument to write_transitions_report()"
355 6 100       172 unless $transitions_data->{$k};
356             }
357              
358 1         14 my $transitions_report = File::Spec->catfile($outputdir, $report_basename);
359 1 50       207 open my $TR, '>', $transitions_report
360             or croak "Unable to open $transitions_report for writing";
361 1 50       5 if ( eval { require Data::Dump; } ) {
  1         868  
362 1         8493 my $old_fh = select($TR);
363 1         6 Data::Dump::dd($transitions_data);
364 1         3725 select($old_fh);
365             }
366             else {
367 0         0 print Data::Dumper->Dump($transitions_data);
368             }
369 1 50       81 close $TR or croak "Unable to close $transitions_report after writing";
370 1         13 return $transitions_report;
371             }
372              
373             1;
374              
375