File Coverage

blib/lib/CPAN/Testers/Common/Client/History.pm
Criterion Covered Total %
statement 110 113 97.3
branch 28 42 66.6
condition 11 17 64.7
subroutine 21 21 100.0
pod 3 3 100.0
total 173 196 88.2


line stmt bran cond sub pod time code
1             package CPAN::Testers::Common::Client::History;
2 4     4   1356 use strict;
  4         5  
  4         100  
3 4     4   13 use warnings;
  4         4  
  4         102  
4              
5 4     4   1850 use CPAN::Testers::Common::Client::Config;
  4         9  
  4         138  
6              
7 4     4   44 use Config;
  4         5  
  4         148  
8 4     4   16 use Carp ();
  4         5  
  4         56  
9 4     4   22 use Fcntl qw(:flock);
  4         5  
  4         536  
10 4     4   17 use File::Spec ();
  4         8  
  4         66  
11 4     4   39 use IO::File ();
  4         5  
  4         357  
12              
13              
14             # Some platforms don't implement flock(), so fake it if necessary
15             BEGIN {
16 4     4   8 eval {
17 4         396 my $temp_file = File::Spec->catfile(
18             File::Spec->tmpdir(), $$ . time()
19             );
20 4         42 my $fh = IO::File->new( $temp_file, "w" );
21 4         822 flock $fh, LOCK_EX;
22 4         35 $fh->close;
23 4         271 unlink $temp_file;
24             };
25 4 50       4239 if ( $@ ) {
26 0         0 *CORE::GLOBAL::flock = sub (*$) { 1 };
  0         0  
27             }
28             }
29              
30             # Back-compatibility checks -- just once per load
31             #
32             # 0.99_08 changed the history file format and name.
33             # If an old file exists, convert it to the new name and format. Note --
34             # someone running multiple installations of reporter modules might have old
35             # and new versions running so only convert in the case where the old file
36             # exists and the new file does not.
37             {
38             my $old_history_file = _get_old_history_file();
39             my $new_history_file = _get_history_file();
40             last if -f $new_history_file || ! -f $old_history_file;
41              
42             # FIXME: all CORE::warn calls here should be 'mywarn' like in CTCC::Config
43             CORE::warn("CPAN Testers: Your history file is in an old format. Upgrading automatically.\n");
44              
45             # open old and new files
46             my ($old_fh, $new_fh);
47             if (! ( $old_fh = IO::File->new( $old_history_file ) ) ) {
48             CORE::warn("CPAN Testers: error opening old history file: $!\nContinuing without conversion.\n");
49             last;
50             }
51             if (! ($new_fh = IO::File->new( $new_history_file, 'w' ) ) ) {
52             CORE::warn("CPAN Testers: error opening new history file: $!\nContinuing without conversion.\n");
53             last;
54             }
55              
56             print {$new_fh} _generated_by();
57             while ( my $line = <$old_fh> ) {
58             chomp $line;
59             # strip off perl version and convert
60             # try not to match 5.1 from "MSWin32-x86-multi-thread 5.1"
61             # from really old CPAN Testers' history formats
62             my ($old_version, $perl_patch);
63             if ( $line =~ m{ (5\.0\d{2,5}) ?(patch \d+)?\z} ) {
64             ($old_version, $perl_patch) = ($1, $2);
65             $line =~ s{ (5\.0\d{2,5}) ?(patch \d+)?\z}{};
66             }
67             my $pv = $old_version ? 'perl-' . _perl_version($old_version)
68             : 'unknown';
69             $pv .= " $perl_patch" if $perl_patch;
70             my ($grade_dist, $arch_os) = ($line =~ /(\S+ \S+) (.+)/);
71             print {$new_fh} "test $grade_dist ($pv) $arch_os\n";
72             }
73             close $old_fh;
74             close $new_fh;
75             }
76              
77             sub _get_history_file {
78 14     14   1255 return File::Spec->catdir(
79             CPAN::Testers::Common::Client::Config::get_config_dir(),
80             'reports-sent.db'
81             );
82             }
83              
84             # prior to 0.99_08
85             sub _get_old_history_file {
86 4     4   32 return File::Spec->catdir(
87             CPAN::Testers::Common::Client::Config::get_config_dir(),
88             'history.db'
89             );
90             }
91              
92             sub _generated_by {
93 1     1   555 require CPAN::Testers::Common::Client;
94 1         9 return '# Generated by CPAN::Testers::Common::Client '
95             . "$CPAN::Testers::Common::Client::VERSION\n";
96             }
97              
98             sub _perl_version {
99 9   33 9   39 my $ver = shift || "$]";
100 9         69 $ver =~ qr/(\d)\.(\d{3})(\d{0,3})/;
101 9   50     62 my ($maj,$min,$pat) = (0 + ($1||0), 0 + ($2||0), 0 + ($3||0));
      50        
      50        
102 9         7 my $pv;
103 9 50       15 if ( $min < 6 ) {
104 0         0 $pv = $ver;
105             }
106             else {
107 9         16 $pv = "$maj\.$min\.$pat";
108             }
109 9         14 return $pv;
110             }
111              
112             # search for dist in history file
113             sub have_tested {
114 2 50   2 1 344 Carp::croak "arguments to have_tested() must be key value pairs"
115             if @_ % 2;
116              
117 2         5 my $args = { @_ };
118              
119             my @bad_params = grep {
120 2         5 $_ !~ m{^(?:dist|phase|grade|perl|archname|osvers)$}
  2         12  
121             } keys %$args;
122              
123 2 50       5 Carp::croak "bad parameters for have_tested(): " . join(q{, },@bad_params)
124             if @bad_params;
125              
126             # DWIM: grades to upper case
127 2 100       5 $args->{grade} = uc $args->{grade} if defined $args->{grade};
128              
129             # default to current platform
130 2 50       5 $args->{perl} = _format_perl_version() unless defined $args->{perl};
131 2 50       11 $args->{archname} = $Config{archname} unless defined $args->{archname};
132 2 50       30 $args->{osvers} = $Config{osvers} unless defined $args->{osvers};
133              
134 2         3 my @found;
135 2 50       4 my $history = _open_history_file('<') or return;
136 2         7 flock $history, LOCK_SH;
137 2         16 <$history>; # throw away format line
138 2         7 while ( defined (my $line = <$history>) ) {
139 8 50       12 my $fields = _split_history( $line ) or next;
140 8 100       10 push @found, $fields if _match($fields, $args);
141             }
142 2         6 $history->close;
143 2         28 return @found;
144             }
145              
146             sub _match {
147 8     8   8 my ($fields, $search) = @_;
148 8         11 for my $k ( keys %$search ) {
149 26 50       31 next if $search->{$k} eq q{}; # empty string matches anything
150 26 100       58 return unless $fields->{$k} eq $search->{$k};
151             }
152 4         14 return 1; # all keys matched
153             }
154              
155             sub _format_perl_version {
156 9     9   11 my $pv = _perl_version();
157             $pv .= " patch $Config{perl_patchlevel}"
158 9 50       99 if $Config{perl_patchlevel};
159 9         18 return $pv;
160             }
161              
162             sub _open_history_file {
163 9   50 9   15 my $mode = shift || '<';
164 9         13 my $history_filename = _get_history_file();
165 9         139 my $file_exists = -f $history_filename;
166              
167             # shortcut if reading and doesn't exist
168 9 100 100     34 return if ( $mode eq '<' && ! $file_exists );
169              
170             # open it in the desired mode
171 8 50       34 my $history = IO::File->new( $history_filename, $mode )
172             or CORE::warn("CPAN Testers: couldn't open history file "
173             . "'$history_filename': $!\n");
174              
175             # if writing and it didn't exist before, initialize with header
176 8 100 100     509 if ( substr($mode,0,1) eq '>' && ! $file_exists ) {
177 1         2 print {$history} _generated_by();
  1         2  
178             }
179              
180 8         20 return $history;
181             }
182              
183             # phase grade dist-version (perl-version patchlevel) archname osvers
184             sub _format_history {
185 7     7   8 my ($result) = @_;
186              
187 7         7 my $phase = $result->{phase};
188 7         10 my $grade = uc $result->{grade};
189 7         7 my $dist_name = $result->{dist_name};
190 7         11 my $perlver = "perl-" . _format_perl_version();
191 7         32 my $platform = "$Config{archname} $Config{osvers}";
192              
193 7         25 return "$phase $grade $dist_name ($perlver) $platform\n";
194             }
195              
196             sub is_duplicate {
197 3     3 1 294 my ($result) = @_;
198 3         5 my $log_line = _format_history( $result );
199 3 100       5 my $history = _open_history_file('<') or return;
200 2         3 my $found = 0;
201 2         6 flock $history, LOCK_SH;
202 2         19 while ( defined (my $line = <$history>) ) {
203 7 100       18 if ( $line eq $log_line ) {
204 1         1 $found++;
205 1         1 last;
206             }
207             }
208 2         5 $history->close;
209 2         26 return $found;
210             }
211              
212             sub record_history {
213 4     4 1 1113 my ($result) = @_;
214 4         5 my $log_line = _format_history( $result );
215 4 50       5 my $history = _open_history_file('>>') or return;
216              
217 4         68 flock( $history, LOCK_EX );
218 4         9 seek( $history, 0, 2 ); # seek to end of file
219 4         20 $history->print( $log_line );
220 4         78 flock( $history, LOCK_UN );
221              
222 4         11 $history->close;
223 4         42 return;
224             }
225              
226             # splits lines created with _format_history. Returns hashref with
227             # phase, grade, dist, perl, platform
228             sub _split_history {
229 8     8   4 my ($line) = @_;
230 8         8 chomp $line;
231 8         8 my %fields;
232 8         43 @fields{qw/phase grade dist perl archname osvers/} =
233             $line =~ m{
234             ^(\S+) \s+ # phase
235             (\S+) \s+ # grade
236             (\S+) \s+ # dist
237             \(perl- ([^)]+) \) \s+ # (perl-version-patchlevel)
238             (\S+) \s+ # archname
239             (.+)$ # osvers
240             }xms;
241              
242             # return nothing if parse fails
243 8 50       18 return if scalar keys %fields == 0;
244              
245             # otherwise return hashref
246 8         11 return \%fields;
247             }
248              
249             1;
250             __END__