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