File Coverage

blib/lib/CPAN/Testers/Common/Article.pm
Criterion Covered Total %
statement 167 174 95.9
branch 56 66 84.8
condition 31 33 93.9
subroutine 17 17 100.0
pod 5 5 100.0
total 276 295 93.5


line stmt bran cond sub pod time code
1             package CPAN::Testers::Common::Article;
2              
3 8     8   433382 use warnings;
  8         63  
  8         292  
4 8     8   46 use strict;
  8         16  
  8         184  
5 8     8   41 use vars qw($VERSION);
  8         16  
  8         622  
6              
7             $VERSION = '0.47';
8              
9             #----------------------------------------------------------------------------
10             # Library Modules
11              
12 8     8   3738 use CPAN::DistnameInfo;
  8         7880  
  8         257  
13 8     8   3602 use Email::Simple;
  8         35782  
  8         274  
14 8     8   3627 use MIME::Base64;
  8         5400  
  8         529  
15 8     8   3373 use MIME::QuotedPrint;
  8         1944  
  8         410  
16 8     8   3782 use Time::Local;
  8         18001  
  8         508  
17              
18 8     8   63 use base qw( Class::Accessor::Fast );
  8         17  
  8         3844  
19              
20             #----------------------------------------------------------------------------
21             # Variables
22              
23             my %month = (
24             Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5, Jun => 6,
25             Jul => 7, Aug => 8, Sep => 9, Oct => 10, Nov => 11, Dec => 12,
26             );
27              
28             my @perl_extractions = (
29             # Summary of my perl5 (revision 5.0 version 6 subversion 1) configuration:
30             # Summary of my perl5 (revision 5 version 10 subversion 0) configuration:
31             qr/Summary of my (?:perl(?:\d+)?)? \((?:revision )?(\d+(?:\.\d+)?) (?:version|patchlevel) (\d+) subversion\s+(\d+) ?(.*?)\) configuration/,
32              
33             # the following is experimental and may provide incorrect data
34             qr!/(?:(?:site_perl|perl|perl5|\.?cpanplus)/|perl-)(5)\.?([6-9]|1[0-2])\.?(\d+)/!,
35              
36             # this dissects the report introduction and is used in the event that
37             # the report gets truncated and no perl -V information is available.
38             qr/on Perl (\d+)\.(\d+)(?:\.(\d+))?/i,
39             );
40              
41             my %regexes = (
42             # with time
43             1 => { re => qr/(?:\w+,)?\s+(\d+)\s+(\w+)\s+(\d+)\s+(\d+):(\d+)/, f => [qw(day month year hour min)] }, # Wed, 13 September 2004 06:29
44             2 => { re => qr/(\d+)\s+(\w+)\s+(\d+)\s+(\d+):(\d+)/, f => [qw(day month year hour min)] }, # 13 September 2004 06:29
45             3 => { re => qr/(\w+)?\s+(\d+),?\s+(\d+)\s+(\d+):(\d+)/, f => [qw(month day year hour min)] }, # September 22, 1999 06:29
46              
47             # just the date
48             4 => { re => qr/(?:\w+,)?\s+(\d+)\s+(\w+)\s+(\d+)/, f => [qw(day month year)] }, # Wed, 13 September 2004
49             5 => { re => qr/(\d+)\s+(\w+)\s+(\d+)/, f => [qw(day month year)] }, # 13 September 2004
50             6 => { re => qr/(\w+)?\s+(\d+),?\s+(\d+)/, f => [qw(month day year)] }, # September 22, 1999
51             );
52              
53             my $OSNAMES = qr/(cygwin|freebsd|netbsd|openbsd|darwin|linux|cygwin|darwin|MSWin32|dragonfly|solaris|MacOS|irix|mirbsd|gnu|bsdos|aix|sco|os2|haiku|beos|midnight)/i;
54             my %OSNAMES = (
55             'MacPPC' => 'macos',
56             'osf' => 'dec_osf',
57             'pa-risc' => 'hpux',
58             's390' => 'os390',
59             'VMS_' => 'vms',
60             'ARCHREV_0' => 'hpux',
61             'linuxThis' => 'linux',
62             'linThis' => 'linux',
63             'linuThis' => 'linux',
64             'lThis' => 'linux',
65             'openThis' => 'openbsd',
66             );
67              
68             #----------------------------------------------------------------------------
69             # The Public API
70              
71             __PACKAGE__->mk_accessors(
72             qw(
73             raw cooked header body
74             postdate date epoch status from distribution version
75             perl osname osvers archname subject author filename
76             osname_patterns osname_fixes
77             )
78             );
79              
80             sub new {
81 18     18 1 13711 my($class, $article) = @_;
82 18         64 my $self = {};
83 18         47 bless $self, $class;
84              
85 18         487 $self->raw($article);
86 18 100       381 $article = decode_qp($article) if($article =~ /=3D/);
87 18         348 $self->cooked($article);
88              
89 18         128 my $mail;
90 18         33 eval { $mail = Email::Simple->new($article) };
  18         92  
91 18 50       6783 return unless $mail;
92              
93 18         51 $self->header($mail->header_obj());
94 18         586 $self->body($mail->body());
95              
96 18 100       646 return if $mail->header("In-Reply-To");
97              
98 16         991 my $from = $mail->header("From");
99 16         778 my $subject = $mail->header("Subject");
100 16 100       667 return unless $subject;
101 14 100       57 return if $subject =~ /::/; # it's supposed to be a distribution
102              
103 13         301 $self->osname_patterns( $OSNAMES );
104 13         299 $self->osname_fixes( \%OSNAMES );
105              
106 13         84 $self->{mail} = $mail;
107 13         35 $self->{from} = $from;
108 13         27 $self->{subject} = $subject;
109              
110 13         33 ($self->{postdate},$self->{date},$self->{epoch}) = $self->_parse_date($mail);
111              
112 13         179 return $self;
113             }
114              
115             sub parse_upload {
116 10     10 1 15057 my $self = shift;
117 10         18 my $mail = $self->{mail};
118 10         19 my $subject = $self->{subject};
119              
120 10 100       82 return 0 unless($subject =~ /CPAN Upload:\s+([-\w\/\.\+]+)/i);
121 7         20 my $distvers = $1;
122              
123             # only record supported archives
124 7 100       48 return 0 if($distvers !~ /\.(?:(?:tar\.|t)(?:gz|bz2)|zip)$/);
125              
126             # CPAN::DistnameInfo doesn't support .tar.bz2 files ... yet
127 5         23 $distvers =~ s/\.(?:tar\.|t)bz2$//i;
128 5 100       25 $distvers .= '.tar.gz' unless $distvers =~ /\.(?:(?:tar\.|t)gz|zip)$/i;
129              
130             # CPAN::DistnameInfo doesn't support old form of uploads
131 5         19 my @parts = split("/",$distvers);
132 5 100       17 if(@parts == 2) {
133 1         4 my ($first,$second,$rest) = split(//,$distvers,3);
134 1         7 $distvers = "$first/$first$second/$first$second$rest";
135             }
136              
137 5         28 my $d = CPAN::DistnameInfo->new($distvers);
138 5         358 $self->distribution($d->dist);
139 5         174 $self->version($d->version);
140 5         126 $self->author($d->cpanid);
141 5         133 $self->filename($d->filename);
142              
143 5         151 return 1;
144             }
145              
146             sub parse_report {
147 12     12 1 6862 my $self = shift;
148 12         22 my $mail = $self->{mail};
149 12         19 my $from = $self->{from};
150 12         21 my $subject = $self->{subject};
151              
152 12         78 my ($status, $distversion, $platform, $osver) = split /\s+/, $subject;
153 12 100       81 return 0 unless $status =~ /^(PASS|FAIL|UNKNOWN|NA)$/i;
154              
155 10   100     49 $platform ||= "";
156 10         21 $platform =~ s/[\s&,<].*//;
157              
158 10   100     24 $distversion ||= "";
159 10         21 $distversion =~ s!/$!!;
160 10         14 $distversion =~ s/\.tar.*/.tar.gz/;
161 10 50       40 $distversion .= '.tar.gz' unless $distversion =~ /\.(tar|tgz|zip)/;
162              
163 10         60 my $d = CPAN::DistnameInfo->new($distversion);
164 10         688 my ($dist, $version) = ($d->dist, $d->version);
165 10 100       109 return 0 unless defined $dist;
166 9 100       23 return 0 unless defined $version;
167              
168 8         59 my $encoding = $mail->header('Content-Transfer-Encoding');
169 8         460 my $head = $mail->header("X-Test-Reporter-Perl");
170 8         379 my $body = $mail->body;
171 8 50 66     124 $body = decode_base64($body) if($encoding && $encoding eq 'base64');
172              
173 8         26 my $perl = $self->_extract_perl_version($body,$head);
174              
175 8         104 my ($osname) = $body =~ /(?:Summary of my perl5|Platform:).*?osname=([^\s\n,<\']+)/s;
176 8         71 my ($osvers) = $body =~ /(?:Summary of my perl5|Platform:).*?osvers=([^\s\n,<\']+)/s;
177 8         61 my ($archname) = $body =~ /(?:Summary of my perl5|Platform:).*?archname=([^\s\n&,<\']+)/s;
178 8 100       24 $archname =~ s/\n.*// if($archname);
179              
180 8         206 $self->status($status);
181 8         192 $self->distribution($dist);
182 8         181 $self->version($version);
183 8   100     192 $self->from($from || "");
184 8         177 $self->perl($perl);
185 8         81 $self->filename($d->filename);
186              
187 8 100 100     226 unless($archname || $platform) {
188 3 100 100     16 if($osname && $osvers) { $platform = "$osname-$osvers" }
  1 100       5  
189 1         3 elsif($osname) { $platform = $osname }
190             }
191              
192 8 100       18 unless($osname) {
193 2         41 my $patterns = $self->osname_patterns;
194 2         40 my $fixes = $self->osname_fixes;
195              
196 2         11 for my $text ($platform, $archname) {
197 3 100       8 next unless($text);
198 1 50       10 if($text =~ $patterns) {
199 1         3 $osname = $1;
200             } else {
201 0         0 for my $rx (keys %$fixes) {
202 0 0       0 if($text =~ /$rx/i) {
203 0         0 $osname = $fixes->{$rx};
204 0         0 last;
205             }
206             }
207             }
208 1 50       6 last if($osname);
209             }
210             }
211              
212 8   66     22 $osvers ||= $osver;
213              
214 8   100     162 $self->osname($osname || "");
215 8   100     187 $self->osvers($osvers || "");
216 8   100     194 $self->archname($archname || $platform);
217              
218 8         98 return 1;
219             }
220              
221             sub passed {
222 3     3 1 6462 my $self = shift;
223 3         73 return $self->status eq 'PASS';
224             }
225              
226             sub failed {
227 3     3 1 839 my $self = shift;
228 3         69 return $self->status eq 'FAIL';
229             }
230              
231             #----------------------------------------------------------------------------
232             # The Private Methods
233              
234             sub _parse_date {
235 13     13   26 my ($self,$mail) = @_;
236 13         34 my ($date1,$date2,$date3) = $self->_extract_date($mail->header("Date"));
237 13         42 my @received = $mail->header("Received");
238              
239 13         803 for my $hdr (@received) {
240 68 50       334 next unless($hdr =~ /.*;\s+(.*)\s*$/);
241 68         137 my ($dt1,$dt2,$dt3) = $self->_extract_date($1);
242 68 50       205 if($dt2 > $date2 + 1200) {
243 0         0 $date1 = $dt1;
244 0         0 $date2 = $dt2;
245 0         0 $date3 = $dt3;
246             }
247             }
248              
249             #print STDERR " ... X.[Date: ".($date||'')."]\n";
250 13         55 return($date1,$date2,$date3);
251             }
252              
253             sub _extract_date {
254 93     93   7310 my ($self,$date) = @_;
255 93         150 my (%fields,@fields,$index);
256              
257             #print STDERR "# ... 0.[Date: ".($date||'')."]\n";
258              
259 93         327 for my $inx (sort {$a <=> $b} keys %regexes) {
  951         1275  
260 187         1282 (@fields) = ($date =~ $regexes{$inx}->{re});
261 187 100       414 if(@fields) {
262 90         116 $index = $inx;
263 90         128 last;
264             }
265             }
266              
267 93 100       206 return('000000','000000000000',0) unless($index);
268              
269 90         124 @fields{@{$regexes{$index}->{f}}} = @fields;
  90         352  
270              
271 90         178 $fields{month} = substr($fields{month},0,3);
272 90         156 $fields{mon} = $month{$fields{month}};
273 90 100 100     386 return('000000','000000000000',0) unless($fields{mon} && $fields{year} > 1998);
274              
275 88   100     584 $fields{$_} ||= 0 for(qw(sec min hour day mon year));
276 88         152 my @date = map { $fields{$_} } qw(sec min hour day mon year);
  528         863  
277              
278             #print STDERR "# ... 1.[$_][$fields{$_}]\n" for(qw(year month day hour min));
279 88         283 my $short = sprintf "%04d%02d", $fields{year}, $fields{mon};
280 88         282 my $long = sprintf "%04d%02d%02d%02d%02d", $fields{year}, $fields{mon}, $fields{day}, $fields{hour}, $fields{min};
281 88         114 $date[4]--;
282 88         213 my $epoch = timegm(@date);
283              
284 88         2636 return($short,$long,$epoch);
285             }
286              
287             # there are a number of test reports that either omitted the perl version
288             # completely, or have had it truncated by the NNTP mail server. In more recent
289             # reports the perl version number is also listed towards the beginning of the
290             # report. The cocde below now attempts to find something in all known places.
291              
292             sub _extract_perl_version {
293 22     22   7583 my ($self, $body, $head) = @_;
294 22         43 my ($rev, $ver, $sub, $extra);
295              
296 22         46 for my $regex (@perl_extractions) {
297 41         314 ($rev, $ver, $sub, $extra) = $body =~ /$regex/si;
298 41 100       112 last if(defined $rev);
299             }
300              
301 22 100       51 return 0 unless(defined $rev);
302              
303             #$ver ||= 0; # current patterns require ver and sub values
304             #$sub ||= 0;
305              
306 15         64 my $perl = $rev + ($ver / 1000) + ($sub / 1000000);
307 15         32 $rev = int($perl);
308 15         32 $ver = int(($perl*1000)%1000);
309 15         24 $sub = int(($perl*1000000)%1000);
310              
311             # check for a release candidate (classed as a patch)
312 15 100 100     50 if($head && $head =~ /v5\.\d+\.\d+ (RC\d+)/) {
313 1 50       3 $extra .= ' ' if($extra);
314 1         3 $extra .= "$1";
315             }
316              
317 15         69 my $version = sprintf "%d.%d.%d", $rev, $ver, $sub;
318 15 100       36 $version .= " $extra" if $extra;
319 15         41 return $version;
320             }
321              
322             1;
323              
324             __END__