File Coverage

blib/lib/Test/CheckChanges.pm
Criterion Covered Total %
statement 126 132 95.4
branch 51 58 87.9
condition 21 26 80.7
subroutine 12 12 100.0
pod 1 1 100.0
total 211 229 92.1


line stmt bran cond sub pod time code
1             package Test::CheckChanges;
2 22     22   87329 use strict;
  22         32  
  22         589  
3 22     22   96 use warnings;
  22         25  
  22         572  
4              
5 22     22   80 use Cwd;
  22         25  
  22         1477  
6 22     22   83 use Carp;
  22         86  
  22         1156  
7 22     22   91 use File::Spec;
  22         28  
  22         494  
8 22     22   77 use File::Basename;
  22         27  
  22         1315  
9 22     22   82 use File::Glob "bsd_glob";
  22         27  
  22         1688  
10 22     22   7557 use Test::Builder;
  22         105100  
  22         3806  
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.12
21              
22             =cut
23              
24             our $VERSION = '0.12_0';
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 9     9   77 my ($self, %plan) = @_;
69 9         20 my $caller = caller;
70              
71 9 100       32 if (defined $plan{order}) {
72 2         3 $order = $plan{order};
73 2         3 delete $plan{order};
74             }
75              
76 9         13 for my $func ( qw( ok_changes ) ) {
77 22     22   113 no strict 'refs'; ## no critic
  22         29  
  22         22544  
78 9         18 *{$caller."::".$func} = \&$func;
  9         43  
79             }
80              
81 9         28 $test->exported_to($caller);
82 9         67 $test->plan(%plan);
83 9         6189 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 20     20 1 110944 my %p;
105 20 50       131 %p = @_ if @_ % 2 == 0;
106 20         28 my $version;
107 20         28 my $msg = 'Unknown Error';
108 20   100     118 my $_base = delete $p{base} || '';
109              
110 20 50 33     195 die "ok_changes takes no arguments" if keys %p || @_ % 2 == 1;
111              
112 20         1712 my $base = Cwd::realpath(File::Spec->catdir(dirname($0), '..', $_base));
113              
114 20         41 my $home = $base;
115 20         36 my @diag = ();
116              
117 20         98 my $makefile = File::Spec->catdir($base, 'Makefile');
118 20         92 my $build = File::Spec->catdir($home, '_build', 'build_params');
119              
120 20         25 my $extra_text;
121              
122 20 100 66     736 if ($build && -r $build) {
    100 66        
123 7         1882 require Module::Build::Version;
124 0         0 open(my $in, '<', $build);
125 0         0 my $data = join '', <$in>;
126 0         0 close($in);
127 0         0 my $temp = eval $data; ## no critic
128 0         0 $version = $temp->[2]{dist_version};
129 0         0 $extra_text = "Build";
130             } elsif ($makefile && -r $makefile) {
131 11 50       311 open(my $in, '<', $makefile) or die "Could not open $makefile";
132 11         225 while (<$in>) {
133 322         220 chomp;
134 322 100       602 if (/^VERSION\s*=\s*(.*)\s*/) {
135 11         29 $version = $1;
136 11         18 $extra_text = "Makefile";
137 11         15 last;
138             }
139             }
140 11 50       123 close($in) or die "Could not close $makefile";
141             }
142 13 100       29 if ($version) {
143 11         33 $msg = "CheckChages $version " . $extra_text;
144             } else {
145 2         4 push(@diag, "No way to determine version");
146 2         2 $msg = "No Build or Makefile found";
147             }
148              
149 13         125 my $ok = 0;
150              
151 13         17 my $mixed = 0;
152 13         11 my $found = 0;
153 13         15 my $parsed = '';
154 13         20 @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 13         91 my $glob_path = File::Spec->catdir($home, $glob);
161 13         1036 my @change_list = sort { $b cmp $a } grep({ m|$changes_regex|} bsd_glob($glob_path));
  1         4  
  15         158  
162              
163 13         35 my $change_file = $change_list[0];
164              
165 13 100       39 if (@change_list > 1) {
166 1         2 for (@change_list) {
167 2         20 s|^$home/||;
168             }
169             push(@diag, qq/Multiple Changes files found (/ .
170 1         2 join(', ', map({'"' . $_ . '"'} @change_list)) .
  2         7  
171             qq/) using "$change_list[0]"./);
172             }
173              
174 13 100 100     73 if ($change_file and $version) {
175 11 50       295 open(my $in, '<', $change_file) or die "Could not open ($change_file) File";
176 11         13 my $type = 0;
177 11         162 while (<$in>) {
178 184         132 chomp;
179 184 100       669 if (/^(\d|v\d)/) {
    100          
    100          
    100          
180             # Common
181 11         36 my ($cvers, $date) = split(/\s+/, $_, 2);
182 11 100 100     45 $mixed++ if $type and $type != 1;
183 11         11 $type = 1;
184             # if ($date =~ /- version ([\d.]+)$/) {
185             # $cvers = $1;
186             # }
187 11 100       18 if ($version eq $cvers) {
188 4         4 $found = $_;
189 4         8 last;
190             } else {
191 7         18 push(@not_found, "$cvers");
192             }
193             } elsif (/^\s+version: ([\d.]+)$/) {
194             # YAML
195 11 100 100     43 $mixed++ if $type and $type != 2;
196 11         8 $type = 2;
197 11 100       21 if ($version eq $1) {
198 2         4 $found = $_;
199 2         4 last;
200             } else {
201 9         24 push(@not_found, "$1");
202             }
203             } elsif (/^\* (v?[\d._]+)$/) {
204             # Apocal
205 10 100 100     42 $mixed++ if $type and $type != 3;
206 10         9 $type = 3;
207 10 100       17 if ($version eq $1) {
208 2         4 $found = $_;
209 2         2 last;
210             } else {
211 8         28 push(@not_found, "$1");
212             }
213             } elsif (/^Version (v?[\d._]+)($|[:,[:space:]])/) {
214             # Plain "Version N"
215 2 50 66     7 $mixed++ if $type and $type != 4;
216 2         3 $type = 4;
217 2 100       4 if ($version eq $1) {
218 1         2 $found = $_;
219 1         1 last;
220             } else {
221 1         4 push(@not_found, "$1");
222             }
223             }
224             }
225 11 50       85 close($in) or die "Could not close ($change_file) file";
226 11 100       28 if ($found) {
227 9         27 $ok = 1;
228             } else {
229 2         2 $ok = 0;
230 2         4 $msg .= " Not Found.";
231 2 100       4 if (@not_found) {
232 1         4 push(@diag, qq(expecting version $version, found versions: ). join(', ', @not_found));
233             } else {
234 1         4 push(@diag, qq(expecting version $version, But no versions where found in the Changes file.));
235             }
236             }
237             }
238 13 100       32 if (!$change_file) {
239 1         1 push(@diag, q(No 'Changes' file found));
240             }
241              
242 13         60 $test->ok($ok, $msg);
243 13         5109 for my $diag (@diag) {
244 6         53 $test->diag($diag);
245             }
246 13         697 return;
247             }
248              
249             END {
250 9 100   9   120 if (!defined $test->has_plan()) {
251 6         62 $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