File Coverage

blib/lib/CPAN/Testers/Common/Article.pm
Criterion Covered Total %
statement 163 172 94.7
branch 51 66 77.2
condition 31 33 93.9
subroutine 17 17 100.0
pod 5 5 100.0
total 267 293 91.1


line stmt bran cond sub pod time code
1             package CPAN::Testers::Common::Article;
2              
3 8     8   302953 use warnings;
  8         20  
  8         324  
4 8     8   47 use strict;
  8         18  
  8         296  
5 8     8   44 use vars qw($VERSION);
  8         20  
  8         530  
6              
7             $VERSION = '0.45';
8              
9             #----------------------------------------------------------------------------
10             # Library Modules
11              
12 8     8   23392 use CPAN::DistnameInfo;
  8         25012  
  8         323  
13 8     8   35493 use Email::Simple;
  8         86352  
  8         291  
14 8     8   9511 use MIME::Base64;
  8         7783  
  8         743  
15 8     8   12019 use MIME::QuotedPrint;
  8         9669  
  8         1144  
16 8     8   11574 use Time::Local;
  8         31986  
  8         758  
17              
18 8     8   75 use base qw( Class::Accessor::Fast );
  8         22  
  8         26005  
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 14     14 1 20041 my($class, $article) = @_;
82 14         36 my $self = {};
83 14         41 bless $self, $class;
84              
85 14         71 $self->raw($article);
86 14 100       437 $article = decode_qp($article) if($article =~ /=3D/);
87 14         64 $self->cooked($article);
88              
89 14         181 my $mail = Email::Simple->new($article);
90 14 50       11686 return unless $mail;
91              
92 14         67 $self->header($mail->header_obj());
93 14         191 $self->body($mail->body());
94              
95 14 100       296 return if $mail->header("In-Reply-To");
96              
97 13         706 my $from = $mail->header("From");
98 13         627 my $subject = $mail->header("Subject");
99 13 50       434 return unless $subject;
100 13 50       52 return if $subject =~ /::/; # it's supposed to be a distribution
101              
102 13         140 $self->osname_patterns( $OSNAMES );
103 13         103 $self->osname_fixes( \%OSNAMES );
104              
105 13         77 $self->{mail} = $mail;
106 13         44 $self->{from} = $from;
107 13         32 $self->{subject} = $subject;
108              
109 13         56 ($self->{postdate},$self->{date},$self->{epoch}) = $self->_parse_date($mail);
110              
111 13         120 return $self;
112             }
113              
114             sub parse_upload {
115 3     3 1 6772 my $self = shift;
116 3         8 my $mail = $self->{mail};
117 3         7 my $subject = $self->{subject};
118              
119 3 100       28 return 0 unless($subject =~ /CPAN Upload:\s+([-\w\/\.\+]+)/i);
120 1         4 my $distvers = $1;
121              
122             # only record supported archives
123 1 50       11 return 0 if($distvers !~ /\.(?:(?:tar\.|t)(?:gz|bz2)|zip)$/);
124              
125             # CPAN::DistnameInfo doesn't support .tar.bz2 files ... yet
126 1         5 $distvers =~ s/\.(?:tar\.|t)bz2$//i;
127 1 50       9 $distvers .= '.tar.gz' unless $distvers =~ /\.(?:(?:tar\.|t)gz|zip)$/i;
128              
129             # CPAN::DistnameInfo doesn't support old form of uploads
130 1         5 my @parts = split("/",$distvers);
131 1 50       5 if(@parts == 2) {
132 0         0 my ($first,$second,$rest) = split(//,$distvers,3);
133 0         0 $distvers = "$first/$first$second/$first$second$rest";
134             }
135              
136 1         11 my $d = CPAN::DistnameInfo->new($distvers);
137 1         95 $self->distribution($d->dist);
138 1         14 $self->version($d->version);
139 1         10 $self->author($d->cpanid);
140 1         11 $self->filename($d->filename);
141              
142 1         15 return 1;
143             }
144              
145             sub parse_report {
146 12     12 1 11277 my $self = shift;
147 12         30 my $mail = $self->{mail};
148 12         194 my $from = $self->{from};
149 12         59 my $subject = $self->{subject};
150              
151 12         83 my ($status, $distversion, $platform, $osver) = split /\s+/, $subject;
152 12 100       105 return 0 unless $status =~ /^(PASS|FAIL|UNKNOWN|NA)$/i;
153              
154 10   100     46 $platform ||= "";
155 10         27 $platform =~ s/[\s&,<].*//;
156              
157 10   100     37 $distversion ||= "";
158 10         24 $distversion =~ s!/$!!;
159 10         21 $distversion =~ s/\.tar.*/.tar.gz/;
160 10 50       47 $distversion .= '.tar.gz' unless $distversion =~ /\.(tar|tgz|zip)/;
161              
162 10         89 my $d = CPAN::DistnameInfo->new($distversion);
163 10         825 my ($dist, $version) = ($d->dist, $d->version);
164 10 100       112 return 0 unless defined $dist;
165 9 100       33 return 0 unless defined $version;
166              
167 8         31 my $encoding = $mail->header('Content-Transfer-Encoding');
168 8         363 my $head = $mail->header("X-Test-Reporter-Perl");
169 8         302 my $body = $mail->body;
170 8 50 66     181 $body = decode_base64($body) if($encoding && $encoding eq 'base64');
171              
172 8         32 my $perl = $self->_extract_perl_version($body,$head);
173              
174 8         127 my ($osname) = $body =~ /(?:Summary of my perl5|Platform:).*?osname=([^\s\n,<\']+)/s;
175 8         93 my ($osvers) = $body =~ /(?:Summary of my perl5|Platform:).*?osvers=([^\s\n,<\']+)/s;
176 8         76 my ($archname) = $body =~ /(?:Summary of my perl5|Platform:).*?archname=([^\s\n&,<\']+)/s;
177 8 100       21 $archname =~ s/\n.*// if($archname);
178              
179 8         41 $self->status($status);
180 8         70 $self->distribution($dist);
181 8         58 $self->version($version);
182 8   100     75 $self->from($from || "");
183 8         58 $self->perl($perl);
184 8         74 $self->filename($d->filename);
185              
186 8 100 100     99 unless($archname || $platform) {
187 3 100 100     22 if($osname && $osvers) { $platform = "$osname-$osvers" }
  1 100       4  
188 1         3 elsif($osname) { $platform = $osname }
189             }
190              
191 8 100       23 unless($osname) {
192 2         7 my $patterns = $self->osname_patterns;
193 2         13 my $fixes = $self->osname_fixes;
194              
195 2         10 for my $text ($platform, $archname) {
196 3 100       9 next unless($text);
197 1 50       11 if($text =~ $patterns) {
198 1         4 $osname = $1;
199             } else {
200 0         0 for my $rx (keys %$fixes) {
201 0 0       0 if($text =~ /$rx/i) {
202 0         0 $osname = $fixes->{$rx};
203 0         0 last;
204             }
205             }
206             }
207 1 50       5 last if($osname);
208             }
209             }
210              
211 8   66     25 $osvers ||= $osver;
212              
213 8   100     41 $self->osname($osname || "");
214 8   100     73 $self->osvers($osvers || "");
215 8   100     74 $self->archname($archname || $platform);
216              
217 8         95 return 1;
218             }
219              
220             sub passed {
221 3     3 1 9845 my $self = shift;
222 3         12 return $self->status eq 'PASS';
223             }
224              
225             sub failed {
226 3     3 1 1532 my $self = shift;
227 3         11 return $self->status eq 'FAIL';
228             }
229              
230             #----------------------------------------------------------------------------
231             # The Private Methods
232              
233             sub _parse_date {
234 13     13   29 my ($self,$mail) = @_;
235 13         43 my ($date1,$date2,$date3) = $self->_extract_date($mail->header("Date"));
236 13         68 my @received = $mail->header("Received");
237              
238 13         741 for my $hdr (@received) {
239 68 50       399 next unless($hdr =~ /.*;\s+(.*)\s*$/);
240 68         160 my ($dt1,$dt2,$dt3) = $self->_extract_date($1);
241 68 50       255 if($dt2 > $date2 + 1200) {
242 0         0 $date1 = $dt1;
243 0         0 $date2 = $dt2;
244 0         0 $date3 = $dt3;
245             }
246             }
247              
248             #print STDERR " ... X.[Date: ".($date||'')."]\n";
249 13         142 return($date1,$date2,$date3);
250             }
251              
252             sub _extract_date {
253 93     93   7457 my ($self,$date) = @_;
254 93         171 my (%fields,@fields,$index);
255              
256             #print STDERR "# ... 0.[Date: ".($date||'')."]\n";
257              
258 93         389 for my $inx (sort {$a <=> $b} keys %regexes) {
  894         1163  
259 187         2361 (@fields) = ($date =~ $regexes{$inx}->{re});
260 187 100       659 if(@fields) {
261 90         124 $index = $inx;
262 90         131 last;
263             }
264             }
265              
266 93 100       466 return('000000','000000000000',0) unless($index);
267              
268 90         130 @fields{@{$regexes{$index}->{f}}} = @fields;
  90         637  
269              
270 90         471 $fields{month} = substr($fields{month},0,3);
271 90         217 $fields{mon} = $month{$fields{month}};
272 90 100 100     497 return('000000','000000000000',0) unless($fields{mon} && $fields{year} > 1998);
273              
274 88   100     1067 $fields{$_} ||= 0 for(qw(sec min hour day mon year));
275 88         176 my @date = map { $fields{$_} } qw(sec min hour day mon year);
  528         1438  
276              
277             #print STDERR "# ... 1.[$_][$fields{$_}]\n" for(qw(year month day hour min));
278 88         340 my $short = sprintf "%04d%02d", $fields{year}, $fields{mon};
279 88         329 my $long = sprintf "%04d%02d%02d%02d%02d", $fields{year}, $fields{mon}, $fields{day}, $fields{hour}, $fields{min};
280 88         117 $date[4]--;
281 88         277 my $epoch = timegm(@date);
282              
283 88         2698 return($short,$long,$epoch);
284             }
285              
286             # there are a number of test reports that either omitted the perl version
287             # completely, or have had it truncated by the NNTP mail server. In more recent
288             # reports the perl version number is also listed towards the beginning of the
289             # report. The cocde below now attempts to find something in all known places.
290              
291             sub _extract_perl_version {
292 22     22   5094 my ($self, $body, $head) = @_;
293 22         28 my ($rev, $ver, $sub, $extra);
294              
295 22         50 for my $regex (@perl_extractions) {
296 41         321 ($rev, $ver, $sub, $extra) = $body =~ /$regex/si;
297 41 100       116 last if(defined $rev);
298             }
299              
300 22 100       63 return 0 unless(defined $rev);
301              
302             #$ver ||= 0; # current patterns require ver and sub values
303             #$sub ||= 0;
304              
305 15         64 my $perl = $rev + ($ver / 1000) + ($sub / 1000000);
306 15         19 $rev = int($perl);
307 15         32 $ver = int(($perl*1000)%1000);
308 15         20 $sub = int(($perl*1000000)%1000);
309              
310             # check for a release candidate (classed as a patch)
311 15 100 100     52 if($head && $head =~ /v5\.\d+\.\d+ (RC\d+)/) {
312 1 50       3 $extra .= ' ' if($extra);
313 1         3 $extra .= "$1";
314             }
315              
316 15         56 my $version = sprintf "%d.%d.%d", $rev, $ver, $sub;
317 15 100       34 $version .= " $extra" if $extra;
318 15         44 return $version;
319             }
320              
321             1;
322              
323             __END__