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 8     8   663 use v5.14.0;
  8         29  
3 8     8   37 use warnings;
  8         13  
  8         370  
4             our $VERSION = '0.20';
5             $VERSION = eval $VERSION;
6 8     8   38 use base qw( Exporter );
  8         12  
  8         1183  
7             our @EXPORT_OK = qw(
8             clean_outputfile
9             hexdigest_one_file
10             validate_list_sequence
11             write_transitions_report
12             );
13 8     8   49 use Carp;
  8         16  
  8         445  
14 8     8   4548 use Data::Dumper;
  8         46919  
  8         454  
15 8     8   50 use Digest::MD5;
  8         16  
  8         283  
16 8     8   3548 use File::Copy;
  8         18081  
  8         413  
17 8     8   46 use File::Spec;
  8         16  
  8         182  
18 8     8   110 use List::Util qw(first);
  8         16  
  8         6987  
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 3547 my $outputfile = shift;
77 2         7 my $replacement = "$outputfile.tmp";
78 2 50       57 open my $IN, '<', $outputfile
79             or croak "Could not open $outputfile for reading";
80 2 50       100 open my $OUT, '>', $replacement
81             or croak "Could not open $replacement for writing";
82 2         31 while (my $l = <$IN>) {
83 98         133 chomp $l;
84 98 100       268 say $OUT $l unless $l =~ m/^Files=\d+,\sTests=\d+/;
85             }
86 2 50       53 close $OUT or croak "Could not close after writing";
87 2 50       17 close $IN or croak "Could not close after reading";
88 2 50       14 move $replacement => $outputfile or croak "Could not replace";
89 2         303 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 7985 my $filename = shift;
123 10         84 my $state = Digest::MD5->new();
124 10 50       197 open my $FH, '<', $filename or croak "Unable to open $filename for reading";
125 10         540 $state->addfile($FH);
126 10 50       89 close $FH or croak "Unable to close $filename after reading";
127 10         108 my $hexdigest = $state->hexdigest;
128 10         85 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 21038 my $list = shift;
247 10 100       344 croak "Must provide array ref to validate_list_sequence()"
248             unless ref($list) eq 'ARRAY';;
249 9         14 my $rv = [];
250 9         14 my $status = 1;
251 9 100       21 if (! defined $list->[0]) {
252 1         8 $rv = [0, 0, 'first element undefined'];
253 1         4 return $rv;
254             }
255 8 100       11 if (! defined $list->[$#{$list}]) {
  8         18  
256 1         1 $rv = [0, $#{$list}, 'last element undefined'];
  1         8  
257 1         6 return $rv;
258             }
259             # lpd => 'last previously defined'
260 7         13 my $lpd = $list->[0];
261 7         13 my %previous = ();
262 7         13 for (my $j = 1; $j <= $#{$list}; $j++) {
  425         606  
263 422 100       539 if (! defined $list->[$j]) {
264 262         297 next;
265             }
266             else {
267 160 100       201 if ($list->[$j] eq $lpd) {
268 141         159 next;
269             }
270             else {
271             # Value differs from last previously observed.
272             # Was it ever previously observed? If so, bad.
273 19 100       30 if (exists $previous{$list->[$j]}) {
274 3         4 $status = 0;
275 3         10 $rv = [$status, $j, "$list->[$j] previously observed"];
276 3         11 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       36 if (! defined $list->[$j-1]) {
283 1         3 $status = 0;
284 1         4 $rv = [
285             $status,
286             $j,
287             "Immediately preceding element (index " . ($j-1) . ") not defined",
288             ];
289 1         3 return $rv;
290             }
291             else {
292 15         26 $previous{$lpd}++;
293 15 50       26 if (defined $list->[$j]) { $lpd = $list->[$j]; }
  15         23  
294 15         18 next;
295             }
296             }
297             }
298             }
299             }
300 3         11 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 4059 my ($outputdir, $report_basename, $transitions_data) = @_;
346 5 100       206 croak "Must supply 3 arguments to write_transitions_report()"
347             unless @_ == 3;
348 4 100       127 croak "3rd argument to write_transitions_report() must be hashref"
349             unless ref($transitions_data) eq 'HASH';
350 3 100       94 croak "Must be 3 elements in 3rd argument to write_transitions_report()"
351             unless (scalar keys %$transitions_data == 3);
352 2         5 my %expected_keys = map { $_ => 1 } (qw| newest oldest transitions |);
  6         26  
353 2         8 for my $k (keys %expected_keys) {
354             croak "'$k' element missing from 3rd argument to write_transitions_report()"
355 4 100       96 unless $transitions_data->{$k};
356             }
357              
358 1         11 my $transitions_report = File::Spec->catfile($outputdir, $report_basename);
359 1 50       101 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         709  
362 1         4743 my $old_fh = select($TR);
363 1         5 Data::Dump::dd($transitions_data);
364 1         2191 select($old_fh);
365             }
366             else {
367 0         0 print Data::Dumper->Dump($transitions_data);
368             }
369 1 50       67 close $TR or croak "Unable to close $transitions_report after writing";
370 1         10 return $transitions_report;
371             }
372              
373             1;
374              
375