File Coverage

blib/lib/CPAN/Reporter/History.pm
Criterion Covered Total %
statement 118 121 97.5
branch 34 42 80.9
condition 11 17 64.7
subroutine 24 24 100.0
pod 1 1 100.0
total 188 205 91.7


line stmt bran cond sub pod time code
1 36     36   6175 use strict;
  36         43  
  36         1531  
2             package CPAN::Reporter::History;
3              
4             our $VERSION = '1.2017';
5              
6 36     36   119 use vars qw/@ISA @EXPORT_OK/;
  36         37  
  36         1554  
7              
8 36     36   125 use Config;
  36         41  
  36         1047  
9 36     36   101 use Carp;
  36         36  
  36         1651  
10 36     36   121 use Fcntl qw/:flock/;
  36         44  
  36         3748  
11 36     36   157 use File::HomeDir ();
  36         44  
  36         554  
12 36     36   96 use File::Path (qw/mkpath/);
  36         37  
  36         1271  
13 36     36   122 use File::Spec ();
  36         35  
  36         391  
14 36     36   853 use IO::File ();
  36         1528  
  36         381  
15 36     36   98 use CPAN (); # for printing warnings
  36         39  
  36         374  
16 36     36   901 use CPAN::Reporter::Config ();
  36         37  
  36         3507  
17              
18             require Exporter;
19             @ISA = qw/Exporter/;
20             @EXPORT_OK = qw/have_tested/;
21              
22             #--------------------------------------------------------------------------#
23             # Some platforms don't implement flock, so fake it if necessary
24             #--------------------------------------------------------------------------#
25              
26             BEGIN {
27 36     36   48     eval {
28 36         1266         my $temp_file = File::Spec->catfile(
29                         File::Spec->tmpdir(), $$ . time()
30                     );
31 36         259         my $fh = IO::File->new( $temp_file, "w" );
32 36         7281         flock $fh, LOCK_EX;
33 36         294         $fh->close;
34 36         2182         unlink $temp_file;
35                 };
36 36 50       39430     if ( $@ ) {
37 0         0         *CORE::GLOBAL::flock = sub (*$) { 1 };
  0         0  
38                 }
39             }
40              
41             #--------------------------------------------------------------------------#
42             # Back-compatibility checks -- just once per load
43             #--------------------------------------------------------------------------#
44              
45              
46             # 0.99_08 changed the history file format and name
47             # If an old file exists, convert it to the new name and format. Note --
48             # someone running multiple installations of CPAN::Reporter might have old
49             # and new versions running so only convert in the case where the old file
50             # exists and the new file does not
51              
52             {
53                 my $old_history_file = _get_old_history_file();
54                 my $new_history_file = _get_history_file();
55                 last if -f $new_history_file || ! -f $old_history_file;
56              
57                 $CPAN::Frontend->mywarn("CPAN::Reporter: Your history file is in an old format. Upgrading automatically.\n");
58              
59             # open old and new files
60                 my ($old_fh, $new_fh);
61                 if (! ( $old_fh = IO::File->new( $old_history_file ) ) ) {
62                     $CPAN::Frontend->mywarn("CPAN::Reporter: error opening old history file: $!\nContinuing without conversion.\n");
63                     last;
64                 }
65                 if (! ($new_fh = IO::File->new( $new_history_file, "w" ) ) ) {
66                     $CPAN::Frontend->mywarn("CPAN::Reporter: error opening new history file: $!\nContinuing without conversion.\n");
67                     last;
68                 }
69              
70                 print {$new_fh} _generated_by();
71                 while ( my $line = <$old_fh> ) {
72                     chomp $line;
73             # strip off perl version and convert
74             # try not to match 5.1 from "MSWin32-x86-multi-thread 5.1"
75             # from really old CPAN::Reporter history formats
76                     my ($old_version, $perl_patch);
77                     if ( $line =~ m{ (5\.0\d{2,5}) ?(patch \d+)?\z} ) {
78                         ($old_version, $perl_patch) = ($1, $2);
79                         $line =~ s{ (5\.0\d{2,5}) ?(patch \d+)?\z}{};
80                     }
81                     my $pv = $old_version ? "perl-" . _perl_version($old_version)
82                                           : "unknown";
83                     $pv .= " $perl_patch" if $perl_patch;
84                     my ($grade_dist, $arch_os) = ($line =~ /(\S+ \S+) (.+)/);
85                     print {$new_fh} "test $grade_dist ($pv) $arch_os\n";
86                 }
87                 close $old_fh;
88                 close $new_fh;
89             }
90              
91              
92             #--------------------------------------------------------------------------#
93             # Public methods
94             #--------------------------------------------------------------------------#
95              
96             #--------------------------------------------------------------------------#
97             # have_tested -- search for dist in history file
98             #--------------------------------------------------------------------------#
99              
100             sub have_tested { ## no critic RequireArgUnpacking
101             # validate arguments
102 40 100   40 1 2086612     croak "arguments to have_tested() must be key value pairs"
103                   if @_ % 2;
104              
105 39         119     my $args = { @_ };
106              
107                 my @bad_params = grep {
108 39         153         $_ !~ m{^(?:dist|phase|grade|perl|archname|osvers)$} } keys %$args;
  60         251  
109 39 100       186     croak "bad parameters for have_tested(): " . join(q{, },@bad_params)
110                     if @bad_params;
111              
112              
113             # DWIM: grades to upper case
114 38 100       156     $args->{grade} = uc $args->{grade} if defined $args->{grade};
115              
116             # default to current platform
117 38 100       128     $args->{perl} = _format_perl_version() unless defined $args->{perl};
118 38 100       228     $args->{archname} = $Config{archname} unless defined $args->{archname};
119 38 100       270     $args->{osvers} = $Config{osvers} unless defined $args->{osvers};
120              
121 38         52     my @found;
122 38 50       61     my $history = _open_history_file('<') or return;
123 38         208     flock $history, LOCK_SH;
124 38         375     <$history>; # throw away format line
125 38         131     while ( defined (my $line = <$history>) ) {
126 429 50       602         my $fields = _split_history( $line ) or next;
127 429 100       484         push @found, $fields if _match($fields, $args);
128                 }
129 38         105     $history->close;
130 38         641     return @found;
131             }
132              
133             #--------------------------------------------------------------------------#
134             # Private methods
135             #--------------------------------------------------------------------------#
136              
137             #--------------------------------------------------------------------------#
138             # _format_history --
139             #
140             # phase grade dist-version (perl-version patchlevel) archname osvers
141             #--------------------------------------------------------------------------#
142              
143             sub _format_history {
144 216     216   267     my ($result) = @_;
145 216         377     my $phase = $result->{phase};
146 216         561     my $grade = uc $result->{grade};
147 216         315     my $dist_name = $result->{dist_name};
148 216         361     my $perlver = "perl-" . _format_perl_version();
149 216         2073     my $platform = "$Config{archname} $Config{osvers}";
150 216         996     return "$phase $grade $dist_name ($perlver) $platform\n";
151             }
152              
153             #--------------------------------------------------------------------------#
154             # _format_perl_version
155             #--------------------------------------------------------------------------#
156              
157             sub _format_perl_version {
158 417     417   1506     my $pv = _perl_version();
159                 $pv .= " patch $Config{perl_patchlevel}"
160 417 50       5242         if $Config{perl_patchlevel};
161 417         1635     return $pv;
162             }
163              
164             sub _generated_by {
165 21     21   180   return "# Generated by CPAN::Reporter "
166                 . "$CPAN::Reporter::History::VERSION\n";
167             }
168              
169             #--------------------------------------------------------------------------#
170             # _get_history_file
171             #--------------------------------------------------------------------------#
172              
173             sub _get_history_file {
174 289     289   852     return File::Spec->catdir(
175                     CPAN::Reporter::Config::_get_config_dir(), "reports-sent.db"
176                 );
177             }
178              
179             #--------------------------------------------------------------------------#
180             # _get_old_history_file -- prior to 0.99_08
181             #--------------------------------------------------------------------------#
182              
183             sub _get_old_history_file {
184 36     36   108     return File::Spec->catdir(
185                     CPAN::Reporter::Config::_get_config_dir(), "history.db"
186                 );
187             }
188              
189             #--------------------------------------------------------------------------#
190             # _is_duplicate
191             #--------------------------------------------------------------------------#
192              
193             sub _is_duplicate {
194 155     155   240     my ($result) = @_;
195 155         744     my $log_line = _format_history( $result );
196 155 100       488     my $history = _open_history_file('<') or return;
197 133         288     my $found = 0;
198 133         885     flock $history, LOCK_SH;
199 133         1839     while ( defined (my $line = <$history>) ) {
200 373 100       1060         if ( $line eq $log_line ) {
201 98         233             $found++;
202 98         1371             last;
203                     }
204                 }
205 132         747     $history->close;
206 132         1971     return $found;
207             }
208              
209             #--------------------------------------------------------------------------#
210             # _match
211             #--------------------------------------------------------------------------#
212              
213             sub _match {
214 428     429   318     my ($fields, $search) = @_;
215 428         611     for my $k ( keys %$search ) {
216 1037 100       1323         next if $search->{$k} eq q{}; # empty string matches anything
217 866 100       2168         return unless $fields->{$k} eq $search->{$k};
218                 }
219 87         425     return 1; # all keys matched
220             }
221              
222             #--------------------------------------------------------------------------#
223             # _open_history_file
224             #--------------------------------------------------------------------------#
225              
226             sub _open_history_file {
227 253   50 254   685     my $mode = shift || '<';
228 253         483     my $history_filename = _get_history_file();
229 253         3836     my $file_exists = -f $history_filename;
230              
231             # shortcut if reading and doesn't exist
232 253 100 100     1560     return if ( $mode eq '<' && ! $file_exists );
233              
234             # open it in the desired mode
235 231 50       1959     my $history = IO::File->new( $history_filename, $mode )
236                     or $CPAN::Frontend->mywarn("CPAN::Reporter: couldn't open history file "
237                     . "'$history_filename': $!\n");
238              
239             # if writing and it didn't exist before, initialize with header
240 231 100 100     21701     if ( substr($mode,0,1) eq '>' && ! $file_exists ) {
241 20         30         print {$history} _generated_by();
  20         85  
242                 }
243              
244 231         778     return $history;
245             }
246              
247             #--------------------------------------------------------------------------#
248             # _perl_version
249             #--------------------------------------------------------------------------#
250              
251             sub _perl_version {
252 423   33 424   32264     my $ver = shift || "$]";
253 423         5921     $ver =~ qr/(\d)\.(\d{3})(\d{0,3})/;
254 423   50     4566     my ($maj,$min,$pat) = (0 + ($1||0), 0 + ($2||0), 0 + ($3||0));
      50        
      50        
255 423         423     my $pv;
256 423 50       927     if ( $min < 6 ) {
257 0         0         $pv = $ver;
258                 }
259                 else {
260 423         1028         $pv = "$maj\.$min\.$pat";
261                 }
262 423         777     return $pv;
263             }
264              
265             #--------------------------------------------------------------------------#
266             # _record_history
267             #--------------------------------------------------------------------------#
268              
269             sub _record_history {
270 61     62   415     my ($result) = @_;
271 61         119     my $log_line = _format_history( $result );
272 61 50       139     my $history = _open_history_file('>>') or return;
273              
274 61         813     flock( $history, LOCK_EX );
275 61         136     seek( $history, 0, 2 ); # seek to end of file
276 61         281     $history->print( $log_line );
277 61         1140     flock( $history, LOCK_UN );
278              
279 61         201     $history->close;
280 61         689     return;
281             }
282              
283             #--------------------------------------------------------------------------#
284             # _split_history
285             #
286             # splits lines created with _format_history. Returns hash ref with
287             # phase, grade, dist, perl, platform
288             #--------------------------------------------------------------------------#
289              
290             sub _split_history {
291 428     429   409     my ($line) = @_;
292 428         340     chomp $line;
293 428         253     my %fields;
294 428         2129     @fields{qw/phase grade dist perl archname osvers/} =
295                     $line =~ m{
296             ^(\S+) \s+ # phase
297             (\S+) \s+ # grade
298             (\S+) \s+ # dist
299             \(perl- ([^)]+) \) \s+ # (perl-version-patchlevel)
300             (\S+) \s+ # archname
301             (.+)$ # osvers
302             }xms;
303              
304             # return nothing if parse fails
305 428 50       770     return if scalar keys %fields == 0;# grep { ! defined($_) } values %fields;
306             # otherwise return hashref
307 428         678     return \%fields;
308             }
309              
310             1;
311              
312             # ABSTRACT: Read or write a CPAN::Reporter history log
313              
314             __END__
315            
316             =pod
317            
318             =encoding UTF-8
319            
320             =head1 NAME
321            
322             CPAN::Reporter::History - Read or write a CPAN::Reporter history log
323            
324             =head1 VERSION
325            
326             version 1.2017
327            
328             =head1 SYNOPSIS
329            
330             use CPAN::Reporter::History 'have_tested';
331            
332             @results = have_tested( dist => 'Dist-Name-1.23' );
333            
334             =head1 DESCRIPTION
335            
336             Interface for interacting with the CPAN::Reporter history file. Most methods
337             are private for use only within CPAN::Reporter itself. However, a public
338             function is provided to query the history file for results.
339            
340             =head1 USAGE
341            
342             The following function is available. It is not exported by default.
343            
344             =head2 C<<< have_tested() >>>
345            
346             # all reports for Foo-Bar-1.23
347             @results = have_tested( dist => 'Foo-Bar-1.23' );
348            
349             # all NA reports
350             @results = have_tested( grade => 'NA' );
351            
352             # all reports on the current Perl/platform
353             @results = have_tested();
354            
355             Searches the CPAN::Reporter history file for records exactly matching search
356             criteria, given as pairs of field-names and desired values.
357            
358             Ordinary search criteria include:
359            
360             =over
361            
362             =item *
363            
364             C<<< dist >>> -- the distribution tarball name without any filename suffix; from
365             a C<<< CPAN::Distribution >>> object, this is provided by the C<<< base_id >>> method.
366            
367             =item *
368            
369             C<<< phase >>> -- phase the report was generated during: either 'PL',
370             'make' or 'test'
371            
372             =item *
373            
374             C<<< grade >>> -- CPAN Testers grade: 'PASS', 'FAIL', 'NA' or 'UNKNOWN'; Also may
375             be 'DISCARD' for any failing reports not sent due to missing prerequisites
376            
377             =back
378            
379             Without additional criteria, a search will be limited to the current
380             version of Perl and the current architecture and OS version.
381             Additional criteria may be specified explicitly or, by specifying the empty
382             string, C<<< q{} >>>, will match that field for I<any> record.
383            
384             # all reports for Foo-Bar-1.23 on any version of perl
385             # on the current architecture and OS version
386             @results = have_tested( dist => 'Foo-Bar-1.23', perl => q{} );
387            
388             These additional criteria include:
389            
390             =over
391            
392             =item *
393            
394             C<<< perl >>> -- perl version and possible patchlevel; this will be
395             dotted decimal (5.6.2) starting with version 5.6, or will be numeric style as
396             given by C<<< $] >>> for older versions; if a patchlevel exists, it must be specified
397             similar to "5.11.0 patch 12345"
398            
399             =item *
400            
401             C<<< archname >>> -- platform architecture name as given by $Config{archname}
402            
403             =item *
404            
405             C<<< osvers >>> -- operating system version as given by $Config{osvers}
406            
407             =back
408            
409             The function returns an array of hashes representing each test result, with
410             all of the fields listed above.
411            
412             =head1 SEE ALSO
413            
414             =over
415            
416             =item *
417            
418             L<CPAN::Reporter>
419            
420             =item *
421            
422             L<CPAN::Reporter::FAQ>
423            
424             =back
425            
426             =head1 AUTHOR
427            
428             David Golden <dagolden@cpan.org>
429            
430             =head1 COPYRIGHT AND LICENSE
431            
432             This software is Copyright (c) 2006 by David Golden.
433            
434             This is free software, licensed under:
435            
436             The Apache License, Version 2.0, January 2004
437            
438             =cut
439