File Coverage

blib/lib/Devel/Git/MultiBisect/Auxiliary.pm
Criterion Covered Total %
statement 71 71 100.0
branch 24 32 75.0
condition n/a
subroutine 10 10 100.0
pod 3 3 100.0
total 108 116 93.1


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