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   202161 use warnings;
  8         19  
  8         268  
4 8     8   40 use strict;
  8         13  
  8         253  
5 8     8   41 use vars qw($VERSION);
  8         19  
  8         453  
6              
7             $VERSION = '0.46';
8              
9             #----------------------------------------------------------------------------
10             # Library Modules
11              
12 8     8   6298 use CPAN::DistnameInfo;
  8         7784  
  8         230  
13 8     8   6771 use Email::Simple;
  8         54682  
  8         274  
14 8     8   7155 use MIME::Base64;
  8         6247  
  8         595  
15 8     8   6040 use MIME::QuotedPrint;
  8         1668  
  8         380  
16 8     8   9397 use Time::Local;
  8         18098  
  8         623  
17              
18 8     8   64 use base qw( Class::Accessor::Fast );
  8         15  
  8         8525  
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 11874 my($class, $article) = @_;
82 18         34 my $self = {};
83 18         40 bless $self, $class;
84              
85 18         58 $self->raw($article);
86 18 100       420 $article = decode_qp($article) if($article =~ /=3D/);
87 18         83 $self->cooked($article);
88              
89 18         94 my $mail;
90 18         27 eval { $mail = Email::Simple->new($article) };
  18         100  
91 18 50       6809 return unless $mail;
92              
93 18         54 $self->header($mail->header_obj());
94 18         174 $self->body($mail->body());
95              
96 18 100       234 return if $mail->header("In-Reply-To");
97              
98 16         739 my $from = $mail->header("From");
99 16         584 my $subject = $mail->header("Subject");
100 16 100       593 return unless $subject;
101 14 100       50 return if $subject =~ /::/; # it's supposed to be a distribution
102              
103 13         45 $self->osname_patterns( $OSNAMES );
104 13         78 $self->osname_fixes( \%OSNAMES );
105              
106 13         63 $self->{mail} = $mail;
107 13         36 $self->{from} = $from;
108 13         22 $self->{subject} = $subject;
109              
110 13         38 ($self->{postdate},$self->{date},$self->{epoch}) = $self->_parse_date($mail);
111              
112 13         39 return $self;
113             }
114              
115             sub parse_upload {
116 10     10 1 18269 my $self = shift;
117 10         18 my $mail = $self->{mail};
118 10         19 my $subject = $self->{subject};
119              
120 10 100       73 return 0 unless($subject =~ /CPAN Upload:\s+([-\w\/\.\+]+)/i);
121 7         23 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       24 $distvers .= '.tar.gz' unless $distvers =~ /\.(?:(?:tar\.|t)gz|zip)$/i;
129              
130             # CPAN::DistnameInfo doesn't support old form of uploads
131 5         20 my @parts = split("/",$distvers);
132 5 100       16 if(@parts == 2) {
133 1         4 my ($first,$second,$rest) = split(//,$distvers,3);
134 1         5 $distvers = "$first/$first$second/$first$second$rest";
135             }
136              
137 5         33 my $d = CPAN::DistnameInfo->new($distvers);
138 5         346 $self->distribution($d->dist);
139 5         61 $self->version($d->version);
140 5         46 $self->author($d->cpanid);
141 5         43 $self->filename($d->filename);
142              
143 5         81 return 1;
144             }
145              
146             sub parse_report {
147 12     12 1 7537 my $self = shift;
148 12         21 my $mail = $self->{mail};
149 12         16 my $from = $self->{from};
150 12         18 my $subject = $self->{subject};
151              
152 12         42 my ($status, $distversion, $platform, $osver) = split /\s+/, $subject;
153 12 100       70 return 0 unless $status =~ /^(PASS|FAIL|UNKNOWN|NA)$/i;
154              
155 10   100     33 $platform ||= "";
156 10         19 $platform =~ s/[\s&,<].*//;
157              
158 10   100     23 $distversion ||= "";
159 10         19 $distversion =~ s!/$!!;
160 10         13 $distversion =~ s/\.tar.*/.tar.gz/;
161 10 50       34 $distversion .= '.tar.gz' unless $distversion =~ /\.(tar|tgz|zip)/;
162              
163 10         59 my $d = CPAN::DistnameInfo->new($distversion);
164 10         545 my ($dist, $version) = ($d->dist, $d->version);
165 10 100       81 return 0 unless defined $dist;
166 9 100       26 return 0 unless defined $version;
167              
168 8         25 my $encoding = $mail->header('Content-Transfer-Encoding');
169 8         289 my $head = $mail->header("X-Test-Reporter-Perl");
170 8         254 my $body = $mail->body;
171 8 50 66     106 $body = decode_base64($body) if($encoding && $encoding eq 'base64');
172              
173 8         26 my $perl = $self->_extract_perl_version($body,$head);
174              
175 8         110 my ($osname) = $body =~ /(?:Summary of my perl5|Platform:).*?osname=([^\s\n,<\']+)/s;
176 8         87 my ($osvers) = $body =~ /(?:Summary of my perl5|Platform:).*?osvers=([^\s\n,<\']+)/s;
177 8         72 my ($archname) = $body =~ /(?:Summary of my perl5|Platform:).*?archname=([^\s\n&,<\']+)/s;
178 8 100       19 $archname =~ s/\n.*// if($archname);
179              
180 8         29 $self->status($status);
181 8         59 $self->distribution($dist);
182 8         53 $self->version($version);
183 8   100     64 $self->from($from || "");
184 8         44 $self->perl($perl);
185 8         59 $self->filename($d->filename);
186              
187 8 100 100     79 unless($archname || $platform) {
188 3 100 100     17 if($osname && $osvers) { $platform = "$osname-$osvers" }
  1 100       3  
189 1         2 elsif($osname) { $platform = $osname }
190             }
191              
192 8 100       14 unless($osname) {
193 2         5 my $patterns = $self->osname_patterns;
194 2         10 my $fixes = $self->osname_fixes;
195              
196 2         9 for my $text ($platform, $archname) {
197 3 100       7 next unless($text);
198 1 50       11 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       3 last if($osname);
209             }
210             }
211              
212 8   66     22 $osvers ||= $osver;
213              
214 8   100     32 $self->osname($osname || "");
215 8   100     71 $self->osvers($osvers || "");
216 8   100     58 $self->archname($archname || $platform);
217              
218 8         79 return 1;
219             }
220              
221             sub passed {
222 3     3 1 5812 my $self = shift;
223 3         11 return $self->status eq 'PASS';
224             }
225              
226             sub failed {
227 3     3 1 1089 my $self = shift;
228 3         42 return $self->status eq 'FAIL';
229             }
230              
231             #----------------------------------------------------------------------------
232             # The Private Methods
233              
234             sub _parse_date {
235 13     13   19 my ($self,$mail) = @_;
236 13         84 my ($date1,$date2,$date3) = $self->_extract_date($mail->header("Date"));
237 13         51 my @received = $mail->header("Received");
238              
239 13         595 for my $hdr (@received) {
240 68 50       315 next unless($hdr =~ /.*;\s+(.*)\s*$/);
241 68         173 my ($dt1,$dt2,$dt3) = $self->_extract_date($1);
242 68 50       207 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         63 return($date1,$date2,$date3);
251             }
252              
253             sub _extract_date {
254 93     93   7886 my ($self,$date) = @_;
255 93         108 my (%fields,@fields,$index);
256              
257             #print STDERR "# ... 0.[Date: ".($date||'')."]\n";
258              
259 93         319 for my $inx (sort {$a <=> $b} keys %regexes) {
  909         991  
260 187         1414 (@fields) = ($date =~ $regexes{$inx}->{re});
261 187 100       400 if(@fields) {
262 90         91 $index = $inx;
263 90         107 last;
264             }
265             }
266              
267 93 100       205 return('000000','000000000000',0) unless($index);
268              
269 90         91 @fields{@{$regexes{$index}->{f}}} = @fields;
  90         412  
270              
271 90         179 $fields{month} = substr($fields{month},0,3);
272 90         158 $fields{mon} = $month{$fields{month}};
273 90 100 100     401 return('000000','000000000000',0) unless($fields{mon} && $fields{year} > 1998);
274              
275 88   100     856 $fields{$_} ||= 0 for(qw(sec min hour day mon year));
276 88         160 my @date = map { $fields{$_} } qw(sec min hour day mon year);
  528         892  
277              
278             #print STDERR "# ... 1.[$_][$fields{$_}]\n" for(qw(year month day hour min));
279 88         250 my $short = sprintf "%04d%02d", $fields{year}, $fields{mon};
280 88         251 my $long = sprintf "%04d%02d%02d%02d%02d", $fields{year}, $fields{mon}, $fields{day}, $fields{hour}, $fields{min};
281 88         92 $date[4]--;
282 88         254 my $epoch = timegm(@date);
283              
284 88         2933 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   6813 my ($self, $body, $head) = @_;
294 22         29 my ($rev, $ver, $sub, $extra);
295              
296 22         40 for my $regex (@perl_extractions) {
297 41         303 ($rev, $ver, $sub, $extra) = $body =~ /$regex/si;
298 41 100       164 last if(defined $rev);
299             }
300              
301 22 100       62 return 0 unless(defined $rev);
302              
303             #$ver ||= 0; # current patterns require ver and sub values
304             #$sub ||= 0;
305              
306 15         54 my $perl = $rev + ($ver / 1000) + ($sub / 1000000);
307 15         17 $rev = int($perl);
308 15         26 $ver = int(($perl*1000)%1000);
309 15         20 $sub = int(($perl*1000000)%1000);
310              
311             # check for a release candidate (classed as a patch)
312 15 100 100     44 if($head && $head =~ /v5\.\d+\.\d+ (RC\d+)/) {
313 1 50       3 $extra .= ' ' if($extra);
314 1         4 $extra .= "$1";
315             }
316              
317 15         47 my $version = sprintf "%d.%d.%d", $rev, $ver, $sub;
318 15 100       31 $version .= " $extra" if $extra;
319 15         36 return $version;
320             }
321              
322             1;
323              
324             __END__