File Coverage

blib/lib/App/TestOnTap/Args.pm
Criterion Covered Total %
statement 166 211 78.6
branch 40 86 46.5
condition 19 29 65.5
subroutine 39 41 95.1
pod 0 19 0.0
total 264 386 68.3


line stmt bran cond sub pod time code
1             # Parses a commandline packaged as a list (e.g. normally just pass @ARGV)
2             # and processes it into real objects for later use by various functions
3             # in the testontap universe
4             #
5             package App::TestOnTap::Args;
6              
7 19     19   125 use strict;
  19         37  
  19         520  
8 19     19   96 use warnings;
  19         39  
  19         551  
9              
10 19     19   8061 use App::TestOnTap::Util qw(slashify expandAts $IS_WINDOWS);
  19         49  
  19         2218  
11 19     19   8204 use App::TestOnTap::Config;
  19         76  
  19         741  
12 19     19   9253 use App::TestOnTap::Preprocess;
  19         55  
  19         618  
13 19     19   8609 use App::TestOnTap::WorkDirManager;
  19         78  
  19         750  
14 19     19   155 use App::TestOnTap::OrderStrategy;
  19         47  
  19         484  
15 19     19   9856 use App::TestOnTap::PackInfo;
  19         70  
  19         782  
16              
17 19     19   134 use Archive::Zip qw(:ERROR_CODES);
  19         39  
  19         2413  
18 19     19   13646 use Getopt::Long qw(GetOptionsFromArray :config require_order no_ignore_case bundling);
  19         198020  
  19         120  
19 19     19   14657 use Pod::Usage;
  19         721177  
  19         2569  
20 19     19   12047 use Pod::Simple::Search;
  19         115891  
  19         652  
21 19     19   153 use Grep::Query;
  19         47  
  19         747  
22 19     19   116 use File::Spec;
  19         53  
  19         447  
23 19     19   103 use File::Path;
  19         44  
  19         1048  
24 19     19   153 use File::Temp qw(tempdir);
  19         39  
  19         846  
25 19     19   121 use UUID::Tiny qw(:std);
  19         42  
  19         3876  
26 19     19   12555 use LWP::UserAgent;
  19         837018  
  19         41716  
27              
28             # CTOR
29             #
30             sub new
31             {
32 29     29 0 144 my $class = shift;
33 29         140 my $version = shift;
34              
35 29         319 my $self = bless( { id => create_uuid_as_string() }, $class);
36 29         10459 $self->__parseArgv($version, @_);
37              
38 27         321 return $self;
39             }
40              
41             sub __parseArgv
42             {
43 29     29   216 my $self = shift;
44 29         104 my $version = shift;
45 29         126 my @argv = @_;
46            
47 29         1157 my %rawOpts =
48             (
49             usage => 0,
50             help => 0,
51             manual => 0,
52             version => 0,
53             configuration => undef, # no alternate config
54             define => {}, # arbitrary key=value defines
55             skip => undef, # no skip filter
56             include => undef, # no include filter
57             jobs => 1, # run only one job at a time (no parallelism)
58             order => undef, # have no particular strategy for test order
59             timer => 0, # don't show timing output
60             workdirectory => undef, # explicit directory to use
61             savedirectory => undef, # don't save results (unless -archive is used)
62             archive => 0, # don't save results as archive
63             v => 0, # don't let through output from tests
64             harness => 1, # use the normal test harness
65             merge => undef, # ask the harness to merge stdout/stderr of tests
66             dryrun => 0, # don't actually run tests
67            
68             # hidden
69             #
70             _help => 0,
71             _pp => 0,
72             _pp_script => undef,
73             _pp_info => 0,
74             _ignore_dependencies => 0,
75             );
76            
77 29         572 my @specs =
78             (
79             'usage|?',
80             'help|h',
81             'manual',
82             'version',
83             'configuration|cfg=s',
84             'define|D=s%',
85             'skip=s',
86             'include=s',
87             'jobs=i',
88             'order=s',
89             'timer!',
90             'workdirectory=s',
91             'savedirectory=s',
92             'archive',
93             'v|verbose+',
94             'harness!',
95             'merge!',
96             'dryrun!',
97            
98             # hidden
99             #
100             '_help',
101             '_pp',
102             '_pp_script=s',
103             '_pp_info',
104             '_ignore_dependencies',
105             );
106              
107 29         118 my $_argsPodName = 'App/TestOnTap/_Args._pod';
108 29         644 my $_argsPodInput = Pod::Simple::Search->find($_argsPodName);
109 29         23450 my $argsPodName = 'App/TestOnTap/Args.pod';
110 29         271 my $argsPodInput = Pod::Simple::Search->find($argsPodName);
111 29         16862 my $manualPodName = 'App/TestOnTap.pod';
112 29         223 my $manualPodInput = Pod::Simple::Search->find($manualPodName);
113            
114             # for consistent error handling below, trap getopts problems
115             #
116             eval
117 29         16284 {
118 29         401 @argv = expandAts('.', @argv);
119 29         300 $self->{fullargv} = [ @argv ];
120 29     0   479 local $SIG{__WARN__} = sub { die(@_) };
  0         0  
121 29         472 GetOptionsFromArray(\@argv, \%rawOpts, @specs)
122             };
123 29 50       51070 if ($@)
124             {
125 0         0 pod2usage(-input => $argsPodInput, -message => "Failure parsing options:\n $@", -exitval => 255, -verbose => 0);
126             }
127              
128             # simple copies
129             #
130 29         262 $self->{$_} = $rawOpts{$_} foreach (qw(v archive timer harness dryrun));
131 29         131 $self->{defines} = $rawOpts{define};
132              
133             # help with the hidden flags...
134             #
135              
136 29 50       120 pod2usage(-input => $_argsPodInput, -exitval => 0, -verbose => 2, -noperldoc => 1) if $rawOpts{_help};
137              
138             # for the special selection of using --_pp* turn over to packinfo
139             #
140 29         78 my %packHelperOpts;
141 29         213 foreach my $opt (keys(%rawOpts))
142             {
143 667 50 66     1829 $packHelperOpts{$opt} = $rawOpts{$opt} if ($opt =~ /^_pp(_.+)?/ && $rawOpts{$opt});
144             }
145 29 50       157 if (keys(%packHelperOpts))
146             {
147 0         0 $packHelperOpts{verbose} = $rawOpts{v};
148 0         0 App::TestOnTap::PackInfo::handle
149             (
150             \%packHelperOpts,
151             $version,
152             $_argsPodName, $_argsPodInput,
153             $argsPodName, $argsPodInput,
154             $manualPodName, $manualPodInput
155             );
156 0         0 die("INTERNAL ERROR");
157             }
158              
159             # if any of the doc switches made, display the pod
160             #
161 29 50       178 pod2usage(-input => $manualPodInput, -exitval => 0, -verbose => 2, -noperldoc => 1) if $rawOpts{manual};
162 29 50       134 pod2usage(-input => $argsPodInput, -exitval => 0, -verbose => 2, -noperldoc => 1) if $rawOpts{help};
163 29 50       108 pod2usage(-input => $argsPodInput, -exitval => 0, -verbose => 0) if $rawOpts{usage};
164 29 50       117 pod2usage(-message => (slashify($0) . " version $version"), -exitval => 0, -verbose => 99, -sections => '_') if $rawOpts{version};
165              
166             # use the user skip or include filter for pruning the list of tests later
167             #
168             eval
169 29         74 {
170 29 100 100     251 if (defined($rawOpts{skip}) || defined($rawOpts{include}))
171             {
172 3 100 100     87 die("The options --skip and --include are mutually exclusive\n") if (defined($rawOpts{skip}) && defined($rawOpts{include}));
173 2 100       16 if ($rawOpts{skip})
174             {
175             # try to compile the query first, to trigger any syntax problem now
176             #
177 1         45 Grep::Query->new($rawOpts{skip});
178            
179             # since we later want to select *included* files,
180             # we nefariously reverse the expression given
181             #
182 1         6015 $self->{include} = Grep::Query->new("NOT ( $rawOpts{skip} )");
183             }
184             else
185             {
186 1         34 $self->{include} = Grep::Query->new($rawOpts{include});
187             }
188             }
189             };
190 29 100       12873 if ($@)
191             {
192 1         21 $! = 255;
193 1         48 die("Failure creating filter:\n $@");
194             }
195              
196             # make sure we have a valid jobs value
197             #
198             my $maxJobs = $ENV{_TESTONTAP_MAX_JOBS}
199             ? 0 + $ENV{_TESTONTAP_MAX_JOBS}
200 28 50       194 : $IS_WINDOWS
    50          
201             ? 60 # read about max being 64, leave room
202             : ~0; # max int, e.g. almost 'infinite'
203            
204 28 50       130 if ($rawOpts{jobs} > $maxJobs)
205             {
206 0         0 $rawOpts{jobs} = $maxJobs;
207 0         0 warn("WARNING: Maximum jobs restricted, resetting to '--jobs $maxJobs' \n");
208             }
209 28 50       131 pod2usage(-message => "Invalid -jobs value: '$rawOpts{jobs}'", -exitval => 255, -verbose => 0) if $rawOpts{jobs} < 1;
210 28         73 $self->{jobs} = $rawOpts{jobs};
211            
212             # verify known order strategies
213             #
214 28 100       156 $self->{orderstrategy} = App::TestOnTap::OrderStrategy->new($rawOpts{order}) if $rawOpts{order};
215            
216             # set up savedir, if given - or, if archive is given fall back to current dir
217             #
218 28 100 66     200 if (defined($rawOpts{savedirectory}) || $rawOpts{archive})
219             {
220             eval
221 2         4 {
222 2   50     35 $self->{savedirectory} = slashify(File::Spec->rel2abs($rawOpts{savedirectory} || '.'));
223 2 50 33     66 die("The -savedirectory '$self->{savedirectory}' exists but is not a directory\n") if (-e $self->{savedirectory} && !-d $self->{savedirectory});
224 2 50       47 if (!-e $self->{savedirectory})
225             {
226 0 0       0 mkpath($self->{savedirectory}) or die("Failed to create -savedirectory '$self->{savedirectory}': $!\n");
227             }
228             };
229 2 50       13 if ($@)
230             {
231 0         0 $! = 255;
232 0         0 die("Failure setting up the save directory:\n $@");
233             }
234             }
235              
236             # make sure we have the suite root and that it exists as directory
237             #
238             eval
239 28         71 {
240 28 50       105 die("No suite root provided!\n") unless @argv;
241 28         145 $self->{suiteroot} = $self->__findSuiteRoot(shift(@argv));
242             };
243 28 50       127 if ($@)
244             {
245 0         0 $! = 255;
246 0         0 die("Failure getting suite root directory:\n $@");
247             }
248              
249             # we want a config in the suite root
250             #
251             eval
252 28         58 {
253 28         432 $self->{config} = App::TestOnTap::Config->new($self->{suiteroot}, $rawOpts{configuration}, $rawOpts{_ignore_dependencies});
254             };
255 28 100       128 if ($@)
256             {
257 1         4 $! = 255;
258 1         21 die("Failure handling config in '$self->{suiteroot}':\n $@");
259             }
260              
261             # set up the workdir manager
262             #
263             eval
264 27         65 {
265 27         515 $self->{workdirmgr} = App::TestOnTap::WorkDirManager->new($self, $rawOpts{workdirectory}, $self->{suiteroot});
266             };
267 27 50       151 if ($@)
268             {
269 0         0 $! = 255;
270 0         0 die("Failure setting up the working directory:\n $@");
271             };
272              
273             # final sanity checks
274             #
275 27 100 100     192 if ($self->{jobs} > 1 && !$self->{config}->hasParallelizableRule())
276             {
277 1         26 warn("WARNING: No 'parallelizable' rule found ('--jobs $self->{jobs}' has no effect); all tests will run serially!\n");
278             }
279              
280             # unless merge is explicitly set:
281             # * default to merge if the results are saved in any way (to force stderr to the tap files)
282             # * otherwise default to no merge
283             #
284             $self->{merge} =
285             defined($rawOpts{merge})
286             ? $rawOpts{merge}
287 27 100 66     511 : ($rawOpts{workdirectory} || $rawOpts{savedirectory} || $rawOpts{archive}) ? 1 : 0;
    50          
288              
289             # run preprocessing
290             #
291 27         218 $self->{preprocess} = App::TestOnTap::Preprocess->new($self->{config}->getPreprocessCmd(), $self, { %ENV }, \@argv);
292             }
293              
294             sub getFullArgv
295             {
296 25     25 0 163 my $self = shift;
297            
298 25         251 return $self->{fullargv};
299             }
300              
301             sub getArgv
302             {
303 48     48 0 114 my $self = shift;
304              
305 48         403 return $self->{preprocess}->getArgv();
306             }
307              
308             sub getId
309             {
310 50     50 0 202 my $self = shift;
311            
312 50         368 return $self->{id};
313             }
314              
315             sub getJobs
316             {
317 79     79 0 18143 my $self = shift;
318            
319 79         2721 return $self->{jobs};
320             }
321              
322             sub getOrderStrategy
323             {
324 55     55 0 152 my $self = shift;
325            
326 55         440 return $self->{orderstrategy};
327             }
328              
329             sub getPreprocess
330             {
331 27     27 0 69 my $self = shift;
332            
333 27         170 return $self->{preprocess};
334             }
335              
336             sub getTimer
337             {
338 27     27 0 59 my $self = shift;
339            
340 27         139 return $self->{timer};
341             }
342              
343             sub getArchive
344             {
345 2     2 0 9 my $self = shift;
346            
347 2         15 return $self->{archive};
348             }
349              
350             sub getDefines
351             {
352 25     25 0 90 my $self = shift;
353            
354 25         1063 return $self->{defines};
355             }
356              
357             sub getVerbose
358             {
359 27     27 0 60 my $self = shift;
360            
361 27         184 return $self->{v};
362             }
363              
364             sub getMerge
365             {
366 27     27 0 68 my $self = shift;
367            
368 27         145 return $self->{merge};
369             }
370              
371             sub getSuiteRoot
372             {
373 127     127 0 328 my $self = shift;
374            
375 127         2112 return $self->{suiteroot};
376             }
377              
378             sub getSaveDir
379             {
380 26     26 0 126 my $self = shift;
381            
382 26         162 return $self->{savedirectory};
383             }
384              
385             sub getWorkDirManager
386             {
387 365     365 0 1066 my $self = shift;
388            
389 365         3418 return $self->{workdirmgr};
390             }
391              
392             sub getConfig
393             {
394 364     364 0 882 my $self = shift;
395            
396 364         2974 return $self->{config};
397             }
398              
399             sub useHarness
400             {
401 26     26 0 67 my $self = shift;
402            
403 26   66     167 return $self->{harness} && !$self->doDryRun();
404             }
405              
406             sub doDryRun
407             {
408 31     31 0 76 my $self = shift;
409            
410 31         188 return $self->{dryrun};
411             }
412              
413             sub include
414             {
415 26     26 0 60 my $self = shift;
416 26         54 my $tests = shift;
417            
418             return
419             $self->{include}
420 26 100       179 ? [ $self->{include}->qgrep(@$tests) ]
421             : undef;
422             }
423              
424             # PRIVATE
425             #
426              
427             sub __findSuiteRoot
428             {
429 28     28   99 my $self = shift;
430 28         64 my $suiteroot = shift;
431              
432 28 50       675 if (-d $suiteroot)
433             {
434 28         542 $suiteroot = slashify(File::Spec->rel2abs($suiteroot));
435             }
436             else
437             {
438 0 0       0 die("Not a directory or zip archive: '$suiteroot'\n") unless $suiteroot =~ /\.zip$/i;
439 0         0 my $zipfile = $suiteroot;
440 0         0 my $tmpdir = slashify(tempdir("testontap-XXXX", TMPDIR => 1, CLEANUP => 1));
441              
442 0 0       0 if (!-f $suiteroot)
443             {
444             # maybe it's a url?
445             # need to dl it before unpacking
446             #
447 0         0 my $localzip = slashify("$tmpdir/local.zip");
448 0 0       0 print "Downloading '$suiteroot' => $localzip...\n" if $self->{v};
449 0         0 my $ua = LWP::UserAgent->new();
450 0         0 $ua->ssl_opts(verify_hostname => 0);
451 0         0 my $response = $ua->get($suiteroot, ':content_file' => $localzip);
452 0 0 0     0 if ($response->is_error() || !-f $localzip)
453             {
454 0         0 my $rc = $response->code();
455 0         0 die("Treated '$suiteroot' as URL - failed to download : $rc\n");
456             }
457 0         0 $zipfile = $localzip;
458             }
459            
460 0 0       0 print "Unpacking '$zipfile'...\n" if $self->{v};
461 0         0 my $zipErr;
462 0     0   0 Archive::Zip::setErrorHandler(sub { $zipErr = $_[0]; chomp($zipErr) });
  0         0  
  0         0  
463 0         0 my $zip = Archive::Zip->new($zipfile);
464 0 0       0 die("Error when unpacking '$zipfile': $zipErr\n") if $zipErr;
465 0         0 my @memberNames = $zip->memberNames();
466 0 0       0 die("The zip archive '$suiteroot' is empty\n") unless @memberNames;
467 0         0 my @rootEntries = grep(m#^[^/]+/?$#, @memberNames);
468 0 0       0 die("The zip archive '$suiteroot' has more than one root entry\n") if scalar(@rootEntries) > 1;
469 0         0 my $testSuiteDir = $rootEntries[0];
470 0 0       0 die("The zip archive '$suiteroot' must have a test suite directory as root entry\n") unless $testSuiteDir =~ m#/$#;
471 0         0 my $cfgFile = $testSuiteDir . App::TestOnTap::Config::getName();
472 0 0       0 die("The zip archive '$suiteroot' must have a '$cfgFile' entry\n") unless grep(/^\Q$cfgFile\E$/, @memberNames);
473 0 0       0 die("Failed to extract '$suiteroot': $!\n") unless $zip->extractTree('', $tmpdir) == AZ_OK;
474 0         0 $suiteroot = slashify(File::Spec->rel2abs("$tmpdir/$testSuiteDir"));
475 0 0       0 print "Unpacked '$suiteroot'\n" if $self->{v};
476             }
477            
478 28         194 return $suiteroot;
479             }
480              
481             1;