File Coverage

blib/lib/Test/CheckChanges.pm
Criterion Covered Total %
statement 132 132 100.0
branch 52 58 89.6
condition 22 26 84.6
subroutine 12 12 100.0
pod 1 1 100.0
total 219 229 95.6


line stmt bran cond sub pod time code
1             package Test::CheckChanges;
2 24     24   305323 use strict;
  24         55  
  24         1159  
3 24     24   150 use warnings;
  24         56  
  24         773  
4              
5 24     24   136 use Cwd;
  24         50  
  24         6373  
6 24     24   139 use Carp;
  24         43  
  24         1916  
7 24     24   141 use File::Spec;
  24         44  
  24         643  
8 24     24   145 use File::Basename;
  24         51  
  24         2509  
9 24     24   156 use File::Glob "bsd_glob";
  24         64  
  24         3873  
10 24     24   31595 use Test::Builder;
  24         311674  
  24         7537  
11              
12             our $test = Test::Builder->new();
13              
14             =head1 NAME
15              
16             Test::CheckChanges - Check that the Changes file matches the distribution.
17              
18             =head1 VERSION
19              
20             Version 0.14
21              
22             =cut
23              
24             our $VERSION = '0.14';
25              
26             =head1 SYNOPSIS
27              
28             use Test::CheckChanges;
29             ok_changes();
30              
31             You can make the test optional with
32              
33             use Test::More;
34             eval { require Test::CheckChanges };
35              
36             if ($@) {
37             plan skip_all => 'Test::CheckChanges required for testing the Changes file';
38             }
39             ok_changes();
40              
41             =head1 DESCRIPTION
42              
43             This module checks that you I file has an entry for the current version
44             of the B being tested.
45              
46             The version information for the distribution being tested is taken out
47             of the Build data, or if that is not found, out of the Makefile.
48              
49             It then attempts to open, in order, a file with the name I or I.
50              
51             The I file is then parsed for version numbers. If one and only one of the
52             version numbers matches the test passes. Otherwise the test fails.
53              
54             A message with the current version is printed if the test passes, otherwise
55             dialog messages are printed to help explain the failure.
56              
57             The I directory contains examples of the different formats of
58             I files that are recognized.
59              
60             =cut
61              
62             our $order = '';
63             our @change_files = qw (Changes CHANGES);
64             our $changes_regex = qr/(Changes|CHANGES)$/;
65             our $glob = "C[Hh][Aa][Nn][Gg][Ee][Ss]";
66              
67             sub import {
68 11     11   116 my ($self, %plan) = @_;
69 11         36 my $caller = caller;
70              
71 11 100       66 if (defined $plan{order}) {
72 2         8 $order = $plan{order};
73 2         6 delete $plan{order};
74             }
75              
76 11         30 for my $func ( qw( ok_changes ) ) {
77 24     24   195 no strict 'refs'; ## no critic
  24         61  
  24         39786  
78 11         33 *{$caller."::".$func} = \&$func;
  11         145  
79             }
80              
81 11         60 $test->exported_to($caller);
82 11         119 $test->plan(%plan);
83 11         29501 return;
84             }
85              
86             =head1 FUNCTIONS
87              
88             All functions listed below are exported to the calling namespace.
89              
90             =head2 ok_changes( )
91              
92             =over
93              
94             The ok_changes method takes no arguments and returns no value.
95              
96             =back
97              
98             =cut
99            
100             our @not_found;
101              
102             sub ok_changes
103             {
104 22     22 1 281135 my %p;
105 22 50       514 %p = @_ if @_ % 2 == 0;
106 22         52 my $version;
107 22         51 my $msg = 'Unknown Error';
108 22   100     186 my $_base = delete $p{base} || '';
109              
110 22 50 33     267 die "ok_changes takes no arguments" if keys %p || @_ % 2 == 1;
111              
112 22         3461 my $base = Cwd::realpath(File::Spec->catdir(dirname($0), '..', $_base));
113              
114 22         72 my $home = $base;
115 22         60 my @diag = ();
116              
117 22         168 my $makefile = File::Spec->catdir($base, 'Makefile');
118 22         164 my $build = File::Spec->catdir($home, '_build', 'build_params');
119              
120 22         56 my $extra_text;
121              
122 22 100 66     1629 if ($build && -r $build) {
    100 66        
123 7         10667 require Module::Build::Version;
124 7         20424 open(my $in, '<', $build);
125 7         441 my $data = join '', <$in>;
126 7         137 close($in);
127 7         3613 my $temp = eval $data; ## no critic
128 7         45 $version = $temp->[2]{dist_version};
129 7         166 $extra_text = "Build";
130             } elsif ($makefile && -r $makefile) {
131 13 50       812 open(my $in, '<', $makefile) or die "Could not open $makefile";
132 13         583 while (<$in>) {
133 387         377 chomp;
134 387 100       1078 if (/^VERSION\s*=\s*(.*)\s*/) {
135 13         81 $version = $1;
136 13         44 $extra_text = "Makefile";
137 13         46 last;
138             }
139             }
140 13 50       222 close($in) or die "Could not close $makefile";
141             }
142 22 100       155 if ($version) {
143 18         72 $msg = "CheckChages $version " . $extra_text;
144             } else {
145 4         15 push(@diag, "No way to determine version");
146 4         17 $msg = "No Build or Makefile found";
147             }
148              
149 22         240 my $ok = 0;
150              
151 22         173 my $mixed = 0;
152 22         40 my $found = 0;
153 22         157 my $parsed = '';
154 22         59 @not_found = ();
155              
156             # glob for the changes file and then filter if needed
157             # this is sorted here so the filesystem is not in control of
158             # the order of the files.
159            
160 22         253 my $glob_path = File::Spec->catdir($home, $glob);
161 22         16959 my @change_list = sort { $b cmp $a } grep({ m|$changes_regex|} bsd_glob($glob_path));
  1         5  
  24         354  
162              
163 22         83 my $change_file = $change_list[0];
164              
165 22 100       114 if (@change_list > 1) {
166 1         2 for (@change_list) {
167 2         25 s|^$home/||;
168             }
169 2         9 push(@diag, qq/Multiple Changes files found (/ .
170 1         2 join(', ', map({'"' . $_ . '"'} @change_list)) .
171             qq/) using "$change_list[0]"./);
172             }
173              
174 22 100 100     187 if ($change_file and $version) {
175 18 50       822 open(my $in, '<', $change_file) or die "Could not open ($change_file) File";
176 18         35 my $type = 0;
177 18         399 while (<$in>) {
178 220         264 chomp;
179 220 100       1730 if (/^(\d|v\d)/) {
    100          
    100          
    100          
180             # Common
181 22         87 my ($cvers, $date) = split(/\s+/, $_, 2);
182 22 100 100     126 $mixed++ if $type and $type != 1;
183 22         109 $type = 1;
184             # if ($date =~ /- version ([\d.]+)$/) {
185             # $cvers = $1;
186             # }
187 22 100       69 if ($version eq $cvers) {
188 11         23 $found = $_;
189 11         27 last;
190             } else {
191 11         88 push(@not_found, "$cvers");
192             }
193             } elsif (/^\s+version: ([\d.]+)$/) {
194             # YAML
195 11 100 100     69 $mixed++ if $type and $type != 2;
196 11         15 $type = 2;
197 11 100       33 if ($version eq $1) {
198 2         5 $found = $_;
199 2         4 last;
200             } else {
201 9         41 push(@not_found, "$1");
202             }
203             } elsif (/^\* (v?[\d._]+)$/) {
204             # Apocal
205 10 100 100     60 $mixed++ if $type and $type != 3;
206 10         15 $type = 3;
207 10 100       32 if ($version eq $1) {
208 2         14 $found = $_;
209 2         5 last;
210             } else {
211 8         35 push(@not_found, "$1");
212             }
213             } elsif (/^Version (v?[\d._]+)($|[:,[:space:]])/) {
214             # Plain "Version N"
215 6 100 100     909 $mixed++ if $type and $type != 4;
216 6         10 $type = 4;
217 6 100       19 if ($version eq $1) {
218 1         2 $found = $_;
219 1         2 last;
220             } else {
221 5         73 push(@not_found, "$1");
222             }
223             }
224             }
225 18 50       385 close($in) or die "Could not close ($change_file) file";
226 18 100       79 if ($found) {
227 16         67 $ok = 1;
228             } else {
229 2         3 $ok = 0;
230 2         5 $msg .= " Not Found.";
231 2 100       8 if (@not_found) {
232 1         6 push(@diag, qq(expecting version $version, found versions: ). join(', ', @not_found));
233             } else {
234 1         6 push(@diag, qq(expecting version $version, But no versions where found in the Changes file.));
235             }
236             }
237             }
238 22 100       88 if (!$change_file) {
239 1         2 push(@diag, q(No 'Changes' file found));
240             }
241              
242 22         162 $test->ok($ok, $msg);
243 22         33460 for my $diag (@diag) {
244 8         669 $test->diag($diag);
245             }
246 22         1741 return;
247             }
248              
249             END {
250 13 100   13   15464 if (!defined $test->has_plan()) {
251 8         105 $test->done_testing(1);
252             }
253             }
254              
255             1;
256              
257             =head1 CHANGES FILE FORMAT
258              
259             Currently this package parses 4 different types of C files.
260             The first is the common, free style, C file where the version
261             is first item on an unindented line:
262              
263             0.01 Fri May 2 15:56:25 EDT 2008
264             - more info
265              
266             The second type of file parsed is the L format changes file.
267              
268             The third type of file parsed has the version number proceeded by an * (asterisk).
269              
270             Revision history for Perl extension Foo::Bar
271              
272             * 1.00
273              
274             Is this a bug or a feature
275              
276             The fourth type of file parsed starts the line with the word Version
277             followed by the version number.
278              
279             Version 6.00 17.02.2008
280             + Oops. Fixed version number. '5.10' is less than '5.9'. I thought
281             CPAN would handle this but apparently not..
282              
283             There are examples of these Changes file in the I directory.
284              
285             Create an RT if you need a different format file supported. If it is not horrid, I will add it.
286              
287             The Debian style C file will likely be the first new format added.
288              
289             =head1 BUGS
290              
291             Please open an RT if you find a bug.
292              
293             L
294              
295             =head1 AUTHOR
296              
297             "G. Allen Morris III"
298              
299             =head1 COPYRIGHT & LICENSE
300              
301             Copyright (C) 2008-2010 G. Allen Morris III, all rights reserved.
302              
303             This program is free software; you can redistribute it and/or modify it
304             under the same terms as Perl itself.
305              
306             =cut