File Coverage

blib/lib/Test/Smoke/Syncer/Base.pm
Criterion Covered Total %
statement 98 140 70.0
branch 29 56 51.7
condition 11 28 39.2
subroutine 17 21 80.9
pod 10 10 100.0
total 165 255 64.7


line stmt bran cond sub pod time code
1             package Test::Smoke::Syncer::Base;
2 11     11   71 use warnings;
  11         29  
  11         319  
3 11     11   53 use strict;
  11         22  
  11         192  
4 11     11   45 use Carp;
  11         21  
  11         634  
5              
6             our $VERSION = '0.001';
7              
8 11     11   63 use Cwd qw/cwd abs_path/;
  11         26  
  11         539  
9 11     11   977 use Test::Smoke::Util qw/whereis/;
  11         36  
  11         638  
10 11     11   64 use Test::Smoke::LogMixin;
  11         19  
  11         17782  
11              
12             =head1 NAME
13              
14             Test;:Smoke::Syncer::Base - Base class for all the syncers.
15              
16             =head1 DESCRIPTION
17              
18             =head2 Test::Smoke::Syncer::Baase->new(%arguments)
19              
20             Return a new instance.
21              
22             =cut
23              
24             sub new {
25 23     23 1 73 my $class = shift;
26              
27 23         300 return bless {@_}, $class;
28             }
29              
30             =head2 $syncer->verbose
31              
32             Get/Set verbosity.
33              
34             =cut
35              
36             sub verbose {
37 21     21 1 268 my $self = shift;
38 21 50       68 $self->{v} = shift if @_;
39 21         212 return $self->{v};
40             }
41              
42             =head2 $syncer->sync()
43              
44             Abstract method.
45              
46             =cut
47              
48             sub sync {
49 0     0 1 0 my $self = shift;
50 0   0     0 my $class = ref $self || $self;
51 0         0 Carp::croak("Should have been implemented by '$class'");
52             }
53              
54             =head2 $syncer->_clear_souce_tree( [$tree_dir] )
55              
56             [ Method | private-ish ]
57              
58             C<_clear_source_tree()> removes B files in the source-tree
59             using B. (See L for caveats.)
60              
61             If C<$tree_dir> is not specified, C<< $self->{ddir} >> is used.
62              
63             =cut
64              
65             sub _clear_source_tree {
66 2     2   7 my( $self, $tree_dir ) = @_;
67              
68 2   33     18 $tree_dir ||= $self->{ddir};
69              
70 2         29 $self->log_info("Clear source-tree from '$tree_dir' ");
71 2         1181 my $cnt = File::Path::rmtree( $tree_dir, $self->{v} > 1 );
72              
73 2 50       328 File::Path::mkpath( $tree_dir, $self->{v} > 1 ) unless -d $tree_dir;
74 2         16 $self->log_info("clear-source-tree: $cnt items OK");
75              
76             }
77              
78             =head2 $syncer->_relocate_tree( $source_dir )
79              
80             [ Method | Private-ish ]
81              
82             C<_relocate_tree()> uses B to move the source-tree
83             from C<< $source_dir >> to its destination (C<< $self->{ddir} >>).
84              
85             =cut
86              
87             sub _relocate_tree {
88 2     2   13 my( $self, $source_dir ) = @_;
89              
90 2         747 require File::Copy;
91              
92 2 50       2365 $self->{v} and print "relocate source-tree ";
93              
94             # try to move it at once (sort of a rename)
95 2 50       22 my $ddir = $^O eq 'VMS' ? $self->{vms_ddir} : $self->{ddir};
96             my $ok = $source_dir eq $ddir
97 2 50       29 ? 1 : File::Copy::move( $source_dir, $self->{ddir} );
98              
99             # Failing that: Copy-By-File :-(
100 2 50 33     286 if ( ! $ok && -d $source_dir ) {
101 0         0 my $cwd = cwd();
102 0 0       0 chdir $source_dir or do {
103 0         0 print "Cannot chdir($source_dir): $!\n";
104 0         0 return 0;
105             };
106 0         0 require File::Find;
107             File::Find::finddepth( sub {
108              
109 0     0   0 my $dest = File::Spec->canonpath( $File::Find::name );
110 0         0 $dest =~ s/^\Q$source_dir//;
111 0         0 $dest = File::Spec->catfile( $self->{ddir}, $dest );
112              
113 0 0       0 $self->{v} > 1 and print "move $_ $dest\n";
114 0         0 File::Copy::move( $_, $dest );
115 0         0 }, "./" );
116 0 0       0 chdir $cwd or print "Cannot chdir($cwd) back: $!\n";
117 0         0 File::Path::rmtree( $source_dir, $self->{v} > 1 );
118 0         0 $ok = ! -d $source_dir;
119             }
120 2 50       12 die "Can't move '$source_dir' to $self->{ddir}' ($!)" unless $ok;
121 2 50       19 $self->{v} and print "OK\n";
122             }
123              
124             =head2 $syncer->check_dot_patch( )
125              
126             [ Method | Public ]
127              
128             C checks if there is a '.patch' file in the source-tree.
129             It will try to create one if it is not there (this is the case for snapshots).
130              
131             It returns the patchlevel found or C.
132              
133             =cut
134              
135             sub check_dot_patch {
136 16     16 1 115 my $self = shift;
137              
138 16         405 my $dot_patch = File::Spec->catfile( $self->{ddir}, '.patch' );
139              
140 16         80 local *DOTPATCH;
141 16         157 my $patch_level = '?????';
142 16 100       688 if ( open DOTPATCH, "< $dot_patch" ) {
143 7         168 chomp( $patch_level = );
144 7         73 close DOTPATCH;
145             # From rsync:
146             # blead 2019-11-06.00:32:06 +0100 cc8ba724ccabff255f384ab68d6f6806ac2eae7c v5.31.5-174-gcc8ba72
147             # from make_dot_patch.pl:
148             # blead 2019-11-05.23:32:06 cc8ba724ccabff255f384ab68d6f6806ac2eae7c v5.31.5-174-gcc8ba724cc
149 7 50       36 if ( $patch_level ) {
150 7         49 my @dot_patch = split ' ', $patch_level;
151              
152             # As we do not use time information, we can just pick the first and
153             # the last two elements
154 7         56 my ($branch, $sha, $describe) = @dot_patch[0, -2, -1];
155             # $sha -> sysinfo.git_id
156             # $describe -> sysinfo.git_describe
157              
158 7   33     66 $self->{patchlevel} = $sha || $branch;
159 7   33     40 $self->{patchdescr} = $describe || $branch;
160 7         81 return $self->{patchlevel};
161             }
162             }
163              
164             # There does not seem to be a '.patch', try 'patchlevel.h'
165 9         46 local *PATCHLEVEL_H;
166 9         132 my $patchlevel_h = File::Spec->catfile( $self->{ddir}, 'patchlevel.h' );
167 9 50       311 if ( open PATCHLEVEL_H, "< $patchlevel_h" ) {
168 9         54 my $declaration_seen = 0;
169 9         287 while ( ) {
170 1345   100     3351 $declaration_seen ||= /local_patches\[\]/;
171 1345 100 100     3198 $declaration_seen && /^\s+,"(?:DEVEL|MAINT)(\d+)|(RC\d+)"/ or next;
172 9   0     68 $patch_level = $1 || $2 || '?????';
173 9 50       56 if ( $patch_level =~ /^RC/ ) {
174 0         0 $patch_level = $self->version_from_patchlevel_h .
175             "-$patch_level";
176             } else {
177 9         35 $patch_level++;
178             }
179             }
180             # save 'patchlevel.h' mtime, so you can set it on '.patch'
181 9         228 my $mtime = ( stat PATCHLEVEL_H )[9];
182 9         97 close PATCHLEVEL_H;
183             # Now create '.patch' and return if $patch_level
184             # The patchlevel is off by one in snapshots
185 9 50 33     111 if ( $patch_level && $patch_level !~ /-RC\d+$/ ) {
186 9 50       553 if ( open DOTPATCH, "> $dot_patch" ) {
187 9         106 print DOTPATCH "$patch_level\n";
188 9         350 close DOTPATCH; # no use generating the error
189 9         178 utime $mtime, $mtime, $dot_patch;
190             }
191 9         83 $self->{patchlevel} = $patch_level;
192 9         76 return $self->{patchlevel};
193             } else {
194 0         0 $self->{patchlevel} = $patch_level;
195             return $self->{patchlevel}
196 0         0 }
197             }
198 0         0 return undef;
199             }
200              
201             =head2 version_from_patchlevel_h( $ddir )
202              
203             C returns a "dotted" version as derived
204             from the F file in the distribution.
205              
206             =cut
207              
208             sub version_from_patchlevel_h {
209 0     0 1 0 my $self = shift;
210              
211 0         0 require Test::Smoke::Util;
212 0         0 return Test::Smoke::Util::version_from_patchelevel( $self->{ddir} );
213             }
214              
215             =head2 is_git_dir()
216              
217             Checks, in a git way, if we are in a real git repository directory.
218              
219             =cut
220              
221             sub is_git_dir {
222 1     1 1 6 my $self = shift;
223              
224 1         21 my $gitbin = whereis('git');
225 1 50       14 if (!$gitbin) {
226 0         0 $self->log_debug("Could not find a git-binary to run for 'is_git_dir'");
227 0         0 return 0;
228             }
229 1         11 $self->log_debug("Found '$gitbin' for 'is_git_dir'");
230              
231 1         9 my $git = Test::Smoke::Util::Execute->new(
232             command => $gitbin,
233             verbose => $self->verbose,
234             );
235 1         10 my $out = $git->run(
236             'rev-parse' => '--is-inside-work-tree',
237             '2>&1'
238             );
239 1         55 $self->log_debug("git rev-parse --is-inside-work-tree: " . $out);
240 1 50       115 return $out eq 'true' ? 1 : 0;
241             }
242              
243             =head2 make_dot_patch
244              
245             If this is a git repo, run the C<< Porting/make_dot_patch.pl >> to generate the
246             .patch file
247              
248             =cut
249              
250             sub make_dot_patch {
251 0     0 1 0 my $self = shift;
252              
253 0         0 my $mk_dot_patch = Test::Smoke::Util::Execute->new(
254             command => "$^X",
255             verbose => $self->verbose,
256             );
257 0         0 my $perlout = $mk_dot_patch->run("Porting/make_dot_patch.pl", ">", ".patch");
258 0         0 $self->log_debug($perlout);
259             }
260              
261             =head2 $syncer->clean_from_directory( $source_dir[, @leave_these] )
262              
263             C uses File::Find to get the contents of
264             C<$source_dir> and compare these to {ddir} and remove all other files.
265              
266             The contents of @leave_these should be in "MANIFEST-format"
267             (See L).
268              
269             =cut
270              
271             sub clean_from_directory {
272 5     5 1 13 my $self = shift;
273 5         18 my ($clean_dir, @leave_these) = @_;
274 5         61 my $this_dir = abs_path(File::Spec->curdir);
275              
276 5 50       44 my $source_dir = File::Spec->file_name_is_absolute($clean_dir)
277             ? $clean_dir
278             : File::Spec->rel2abs($clean_dir, $this_dir);
279 5         74 $self->log_debug("[clean_from_directory($this_dir)] $clean_dir => $source_dir\n");
280              
281 5         746 require Test::Smoke::SourceTree;
282 5         56 my $tree = Test::Smoke::SourceTree->new($source_dir, $self->{v});
283              
284 5         13 my %orig_dir = map { ( $_ => 1) } @leave_these;
  0         0  
285             File::Find::find( sub {
286 40 100   40   937 return unless -f;
287 30         157 my $file = $tree->abs2mani( $File::Find::name );
288 30         376 $orig_dir{ $file } = 1;
289 5         266 }, $source_dir );
290              
291 5         49 $tree = Test::Smoke::SourceTree->new( $self->{ddir}, $self->{v} );
292             File::Find::find( sub {
293 41 100   41   1073 return unless -f;
294 31         125 my $file = $tree->abs2mani( $File::Find::name );
295 31 100       398 return if exists $orig_dir{ $file };
296 1         68 1 while unlink $_;
297 1 50       16 $self->log_debug("Unlink '$file': " . (-e $_ ? "$!" : "ok"));
298 5         247 }, $self->{ddir} );
299             }
300              
301             =head2 $syncer->pre_sync
302              
303             C should be called by the C methods to setup the
304             sync environment. Currently only useful on I.
305              
306             =cut
307              
308             sub pre_sync {
309 15 50   15 1 138 return 1 unless $^O eq 'VMS';
310 0         0 my $self = shift;
311 0         0 require Test::Smoke::Util;
312              
313 0         0 Test::Smoke::Util::set_vms_rooted_logical( TSP5SRC => $self->{ddir} );
314 0         0 $self->{vms_ddir} = $self->{ddir};
315 0         0 $self->{ddir} = 'TSP5SRC:[000000]';
316             }
317              
318             =head2 $syncer->post_sync
319              
320             C should be called by the C methods to unset the
321             sync environment. Currently only useful on I.
322              
323             =cut
324              
325             sub post_sync {
326 14 50   14 1 90 return 1 unless $^O eq 'VMS';
327 0           my $self = shift;
328              
329 0   0       ( my $logical = $self->{ddir} || '' ) =~ s/:\[000000\]$//;
330 0 0         return unless $logical;
331 0           my $result = system "DEASSIGN/JOB $logical";
332              
333 0           $self->{ddir} = delete $self->{vms_ddir};
334 0           return $result == 0;
335             }
336              
337             1;
338              
339             =head1 COPYRIGHT
340              
341             (c) 2002-2013, All rights reserved.
342              
343             * Abe Timmerman
344              
345             This library is free software; you can redistribute it and/or modify
346             it under the same terms as Perl itself.
347              
348             See:
349              
350             * ,
351             *
352              
353             This program is distributed in the hope that it will be useful,
354             but WITHOUT ANY WARRANTY; without even the implied warranty of
355             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
356              
357             =cut