File Coverage

blib/lib/Test/Smoke/Patcher.pm
Criterion Covered Total %
statement 148 174 85.0
branch 59 90 65.5
condition 16 32 50.0
subroutine 14 14 100.0
pod 7 7 100.0
total 244 317 76.9


line stmt bran cond sub pod time code
1             package Test::Smoke::Patcher;
2 3     3   190661 use strict;
  3         8  
  3         88  
3              
4 3     3   14 use vars qw( $VERSION @EXPORT );
  3         5  
  3         155  
5             $VERSION = '0.012';
6              
7 3     3   15 use base 'Exporter';
  3         4  
  3         315  
8 3     3   17 use File::Spec;
  3         6  
  3         63  
9 3     3   20 use Cwd;
  3         6  
  3         166  
10              
11 3     3   24 use Test::Smoke::Util qw( get_regen_headers );
  3         6  
  3         5691  
12              
13             @EXPORT = qw( &TRY_REGEN_HEADERS );
14              
15             sub MAX_FLAG_COUNT () { 16 }
16             sub ALL_FLAGS () { (2**MAX_FLAG_COUNT) - 1 }
17             sub TRY_REGEN_HEADERS () { 1 }
18              
19             my %CONFIG = (
20             df_ddir => File::Spec->rel2abs( cwd ),
21             df_fdir => undef,
22             df_pfile => undef,
23             df_patchbin => 'patch',
24             df_popts => '', # '-p1' is added in call_patch()
25             df_flags => 0,
26             df_regen => 1, # regen => set/unset TRY_REGEN_HEADERS in flags
27             df_oldpatch => 0,
28             df_v => 0,
29              
30             valid_type => { single => 1, multi => 1 },
31             single => [qw( pfile patchbin popts flags regen oldpatch )],
32             multi => [qw( pfile patchbin popts flags regen oldpatch )],
33             );
34              
35             =head1 NAME
36              
37             Test::Smoke::Patcher - OO interface to help patching the source-tree
38              
39             =head1 SYNOPSIS
40              
41             use Test::Smoke::Patcher;
42              
43             my $patcher = Test::Smoke::Patcher->new( single => {
44             ddir => $build_dir,
45             pfile => $patch,
46             popts => '-p1',
47             v => 1, # 0..2
48             });
49             $patcher->patch;
50              
51             or
52              
53             my $patcher = Test::Smoke::Patcher->new( multi => {
54             ddir => $buildir,
55             pfile => $patch_info,
56             v => 1, #0..2
57             });
58             $patcher->patch;
59              
60             =head1 DESCRIPTION
61              
62             Okay, you will need a working B program, which I believe is available
63             for most platforms perl runs on.
64              
65             There are two ways to initialise the B object.
66              
67             =over 4
68              
69             =item B mode
70              
71             The B attribute is a pointer to a I patch.
72             There are four (4) ways to specify that patch.
73              
74             =over 4
75              
76             =item I
77              
78             The scalar holds the complete patch as literal text.
79              
80             =item I
81              
82             The array holds a list of lines (with newlines) that make up the
83             patch as literal text (C<< $patch = join "", @$array_ref >>).
84              
85             =item I
86              
87             You passed an opened filehandle to a file containing the patch.
88              
89             =item I
90              
91             If none of the above apply, it is assumed you passed a filename.
92             Relative paths are rooted at the builddir (B attribute).
93              
94             =back
95              
96             =item B mode
97              
98             The B attribute is a pointer to a recource that contains filenames
99             of patches.
100             The format of this recource is one filename per line optionally followed
101             by a semi-colon (;) and switches for the patch program.
102              
103             The patch-resource can also be specified in four (4) ways.
104              
105             =over 4
106              
107             =item I
108              
109             =item I
110              
111             =item I
112              
113             =item I
114              
115             =back
116              
117             =back
118              
119             =head2 TRY_REGEN_HEADERS
120              
121             Constant: 1
122              
123             =head2 MAX_FLAG_COUNT
124              
125             Constant: 16
126              
127             =head2 ALL_FLAGS
128              
129             Constant: 2**MAX_FLAG_COUNT) - 1
130              
131             =head1 METHODS
132              
133             =over 4
134              
135             =cut
136              
137             =item Test::Smoke::Patcher->new( $type => \%args );
138              
139             C crates the object. Valid types are B and B.
140             Valid keys for C<%args>:
141              
142             * ddir: the build directory
143             * fdir: the intermediate forest dir (preferred)
144             * pfile: path to either the patch (single) or a textfile (multi)
145             * popts: options to pass to 'patch' (-p1)
146             * patchbin: full path to the patch binary (patch)
147             * regen: flag to set/unset the TRY_REGEN_HEADERS flag
148             * v: verbosity 0..2
149              
150             =cut
151              
152             sub new {
153 14     14 1 9504 my $proto = shift;
154 14   33     183 my $class = ref $proto || $proto;
155              
156 14         60 my $type = lc shift;
157 14 50 33     153 unless ( $type && exists $CONFIG{valid_type}->{ $type } ) {
158 0 0       0 defined $type or $type = 'undef';
159 0         0 require Carp;
160 0         0 Carp::croak( "Invalid Patcher-type: '$type'" );
161             }
162              
163 14 50       98 my %args_raw = @_ ? UNIVERSAL::isa( $_[0], 'HASH' ) ? %{ $_[0] } : @_ : ();
  14 50       206  
164              
165             my %args = map {
166 14         91 ( my $key = $_ ) =~ s/^-?(.+)$/lc $1/e;
  52         367  
  52         189  
167 52         203 ( $key => $args_raw{ $_ } );
168             } keys %args_raw;
169              
170             my %fields = map {
171 126 100       332 my $value = exists $args{$_} ? $args{ $_ } : $CONFIG{ "df_$_" };
172 126         418 ( $_ => $value )
173 14         47 } ( v => ddir => fdir => @{ $CONFIG{ $type } } );
  14         80  
174             $fields{pdir} = File::Spec->rel2abs(
175             defined $fields{fdir} ? $fields{fdir} : $fields{ddir}
176 14 100       601 );
177 14         46 $fields{ptype} = $type;
178 14 50       62 if ($fields{regen}) {
179 14         45 $fields{flags} = ($fields{flags} | TRY_REGEN_HEADERS) & ALL_FLAGS;
180             }
181             else {
182 0         0 $fields{flags} = ($fields{flags} & ~TRY_REGEN_HEADERS) & ALL_FLAGS;
183             }
184              
185 14         277 bless { %fields }, $class;
186             }
187              
188             =item Test::Smoke::Patcher->config( $key[, $value] )
189              
190             C is an interface to the package lexical C<%CONFIG>,
191             which holds all the default values for the C arguments.
192              
193             With the special key B this returns a reference
194             to a hash holding all the default values.
195              
196             =cut
197              
198             sub config {
199 2     2 1 2902 my $dummy = shift;
200              
201 2         18 my $key = lc shift;
202              
203 2 100       9 if ( $key eq 'all_defaults' ) {
204             my %default = map {
205 1         12 my( $pass_key ) = $_ =~ /^df_(.+)/;
  9         24  
206 9         24 ( $pass_key => $CONFIG{ $_ } );
207             } grep /^df_/ => keys %CONFIG;
208 1         5 return \%default;
209             }
210              
211 1 50       16 return undef unless exists $CONFIG{ "df_$key" };
212              
213 1 50       11 $CONFIG{ "df_$key" } = shift if @_;
214              
215 1         8 return $CONFIG{ "df_$key" };
216             }
217              
218             =item $patcher->patch
219              
220             C is a simple dispatcher.
221              
222             =cut
223              
224             sub patch {
225 10     10 1 383 my $self = shift;
226              
227 10         39 my $method = "patch_$self->{ptype}";
228 10         85 my $ret = $self->$method( @_ );
229 10   33     249 $ret &&= $self->perl_regen_headers;
230              
231 10 100       58 if ( $self->{fdir} ) { # This is a forest setup, re-sync
232 1         6 require Test::Smoke::Syncer;
233             my %options = (
234             hdir => $self->{fdir},
235             ddir => $self->{ddir},
236             v => $self->{v},
237 1         20 );
238 1         28 my $resync = Test::Smoke::Syncer->new( hardlink => %options );
239 1         17 $resync->sync;
240             }
241 10         77 return $ret;
242             }
243              
244             =item perl_regen_headers( )
245              
246             Try to run F if the flag is set.
247              
248             =cut
249              
250             sub perl_regen_headers {
251 10     10 1 34 my $self = shift;
252 10 50       53 return 1 unless $self->{flags} & TRY_REGEN_HEADERS;
253              
254 10         198 my $regen_headers = get_regen_headers( $self->{pdir} );
255             my $regen_perly = $self->{perly}
256 10 100       80 ? qq|$^X "| . File::Spec->catfile( $self->{pdir}, 'regen_perly.pl' ).
257             qq|"|
258             : "";
259 10         94 my @regens = grep $_ => ( $regen_headers, $regen_perly );
260 10         66 for my $regen ( @regens ) {
261 2         4909 my $cwd = cwd;
262 2 50       57 chdir $self->{pdir} or return;
263 2         22 local *RUN_REGEN;
264 2 50       2963 if ( open RUN_REGEN, "$regen |" ) {
265 2 50       21 $self->{v} and print "Started [$regen]\n";
266 2         137358 while ( ) {
267 0 0       0 $self->{v} and print;
268             }
269 2 50       121 close RUN_REGEN or do {
270 0         0 require Carp;
271 0         0 Carp::carp( "Error while running [$regen]" );
272 0         0 return;
273             };
274             } else {
275 0         0 require Carp;
276 0         0 Carp::carp( "Could not fork [$regen]" );
277 0         0 return;
278             }
279 2         70 chdir $cwd;
280             }
281 10         94 return 1;
282             }
283              
284             =item $patcher->patch_single( )
285              
286             C checks if the B attribute is a plain scalar
287             or a ref to a scalar, array, glob. In the first case this is taken to
288             be a filename. A GLOB-ref is a filehandle, the other two are taken to
289             be literal content.
290              
291             =cut
292              
293             sub patch_single {
294 22     22 1 18271 my $self = shift;
295              
296 22   66     187 my $pfile = shift || $self->{pfile};
297              
298 22         91 local *PATCH;
299 22         57 my $content;
300 22 100       116 if ( ref $pfile eq 'SCALAR' ) {
    100          
    100          
301 9         30 $content = $$pfile;
302 9   50     77 $self->{pfinfo} ||= 'internal content';
303             } elsif ( ref $pfile eq 'ARRAY' ) {
304 1         12 $content = join "", @$pfile;
305 1   50     14 $self->{pfinfo} ||= 'internal content';
306             } elsif ( ref $pfile eq 'GLOB' ) {
307 1         16 *PATCH = *$pfile;
308 1         6 $content = do { local $/; };
  1         13  
  1         30  
309 1   50     14 $self->{pfinfo} ||= 'file content';
310             } else {
311             my $full_name = File::Spec->file_name_is_absolute( $pfile )
312 11 50       309 ? $pfile : File::Spec->rel2abs( $pfile, $self->{pdir} );
313              
314 11         35 $self->{pfinfo} = $full_name;
315 11 50       391 open PATCH, "< $full_name" or do {
316 0         0 require Carp;
317 0         0 Carp::croak( "Cannot open '$full_name': $!" );
318             };
319 11         33 $content = do { local $/; };
  11         84  
  11         233  
320 11         103 close PATCH;
321             }
322              
323 22 50       78 $self->{v} and print "Get patch from $self->{pfinfo}\n";
324 22         124 $self->call_patch( \$content, @_ );
325             }
326              
327             =item $patcher->patch_multi( )
328              
329             C checks the B attribute is a plain scalar
330             or a ref to a scalar, array, glob. In the first case this is taken to
331             be a filename. A GLOB-ref is a filehandle, the other two are taken to
332             be literal content.
333              
334             =cut
335              
336             sub patch_multi {
337 6     6 1 6276 my $self = shift;
338              
339 6   66     44 my $pfile = shift || $self->{pfile};
340              
341 6         22 local *PATCHES;
342 6         10 my @patches;
343 6 100       55 if ( ref $pfile eq 'SCALAR' ) {
    100          
    100          
344 1         4 @patches = split /\n/, $$pfile;
345 1   50     16 $self->{pfinfo} ||= 'internal content';
346             } elsif ( ref $pfile eq 'ARRAY' ) {
347 1         4 chomp( @patches = @$pfile );
348 1   50     4 $self->{pfinfo} ||= 'internal content';
349             } elsif ( ref $pfile eq 'GLOB' ) {
350 1         13 *PATCHES = *$pfile;
351 1         23 chomp( @patches = );
352 1   50     12 $self->{pfinfo} ||= 'file content';
353             } else {
354             my $full_name = File::Spec->file_name_is_absolute( $pfile )
355 3 50       52 ? $pfile : File::Spec->rel2abs( $pfile, $self->{pdir} );
356 3         15 $self->{pfinfo} = $full_name;
357 3 50       99 open PATCHES, "< $full_name" or do {
358 0         0 require Carp;
359 0         0 Carp::croak( "Cannot open '$self->{pfile}': $!" );
360             };
361 3         72 chomp( @patches = );
362 3         34 close PATCHES;
363             }
364              
365 6 50       30 $self->{v} and print "Get patchinfo from $self->{pfinfo}\n";
366              
367 6         14 my $ok = 1;
368 6         25 foreach my $patch ( @patches ) {
369 17 100       105 next if $patch =~ /^\s*[#]/;
370 10 50       43 next if $patch =~ /^\s*$/;
371 10 100       57 if ( $patch =~ /^\s*!\s*perly$/ ) {
372 1         7 $self->{perly} = 1;
373 1         7 next;
374             }
375 9         70 my( $filename, $switches, $descr ) = split /\s*;\s*/, $patch, 3;
376 9 50       26 $descr = $descr ? $descr . " ($filename)" : $filename;
377 9         15 eval { $self->patch_single( $filename, $switches, $descr ) };
  9         29  
378 9 50       104 if ( $@ ) {
379 0         0 require Carp;
380 0         0 Carp::carp( "[$filename] $@" );
381 0         0 $ok = 0;
382             }
383             }
384 6         57 return $ok;
385             }
386              
387             =item $self->_make_opts( $switches )
388              
389             C<_make_opts()> just creates a string of options to pass to the
390             B program. Some implementations of patch do not grog '-u',
391             so be careful!
392              
393             =cut
394              
395             sub _make_opts {
396 23     23   3754 my $self = shift;
397 23         101 @_ = grep defined $_ => @_;
398 23 100       158 my $switches = @_ ? join " ", @_ : "";
399              
400 23   100     231 my $opts = $switches || $self->{popts} || "";
401 23 100       90 $opts .= " -p1" unless $opts =~ /-[a-zA-Z]*p\d/;
402             # $opts .= " -b" unless $opts =~ /-[a-zA-Z]*b/i;
403 23 50 33     78 $opts .= " --verbose" if $self->{v} > 1 && !$self->{oldpatch};
404              
405 23         68 return $opts;
406             }
407              
408             =item $patcher->call_patch( $ref_to_content )
409              
410             C opens a pipe to the B program and prints
411             C<< $$ref_to_content >> to it. It will Carp::croak() on any error!
412              
413             =cut
414              
415             sub call_patch {
416 22     22 1 68 my( $self, $ref_to_content, $switches, $descr ) = @_;
417              
418 22         49 local *PATCHBIN;
419              
420 22         108 my $opts = $self->_make_opts( $switches );
421              
422 22 50       3770 my $redir = $self->{v} ? "" : ">" . File::Spec->devnull . " 2>&1";
423              
424 22         56588 my $cwd = cwd();
425 22 50       573 chdir $self->{pdir} or do {
426 0         0 require Carp;
427 0         0 Carp::croak( "Cannot chdir($self->{pdir}): $!" );
428             };
429              
430             # patch is verbose enough if $self->{v} == 1
431 22 50       101 $self->{v} > 1 and
432             print "[$self->{pfinfo}] | $self->{patchbin} $opts $redir\n";
433              
434 22 50       43841 if ( open PATCHBIN, "| $self->{patchbin} $opts $redir" ) {
435 22         227 binmode PATCHBIN;
436 22         514 print PATCHBIN $$ref_to_content;
437 22 50       198790 close PATCHBIN or do {
438 0         0 require Carp;
439 0         0 Carp::croak( "Error while patching from '$self->{pfinfo}': $!" );
440             };
441             } else {
442 0         0 require Carp;
443 0         0 Carp::croak( "Cannot fork ($self->{patchbin}): $!" );
444             }
445              
446             # Add a line to patchlevel.h if $descr
447 22 100       395 if ( defined $descr ) {
448 10         276 require Test::Smoke::Util;
449 10         235 Test::Smoke::Util::set_local_patch( $self->{pdir}, $descr );
450             }
451              
452 22 50       929 chdir $cwd or do {
453 0           require Carp;
454 0           Carp::croak( "Cannot chdir($cwd) back: $!" );
455             };
456             }
457              
458             =back
459              
460             =head1 SEE ALSO
461              
462             L, L
463              
464             =head1 COPYRIGHT
465              
466             (c) 2002-2003, All rights reserved.
467              
468             * Abe Timmerman
469              
470             This library is free software; you can redistribute it and/or modify
471             it under the same terms as Perl itself.
472              
473             See:
474              
475             =over 4
476              
477             =item * http://www.perl.com/perl/misc/Artistic.html
478              
479             =item * http://www.gnu.org/copyleft/gpl.html
480              
481             =back
482              
483             This program is distributed in the hope that it will be useful,
484             but WITHOUT ANY WARRANTY; without even the implied warranty of
485             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
486              
487             =cut