File Coverage

blib/lib/App/TestOnTap/WorkDirManager.pm
Criterion Covered Total %
statement 151 154 98.0
branch 21 36 58.3
condition n/a
subroutine 34 34 100.0
pod 0 17 0.0
total 206 241 85.4


line stmt bran cond sub pod time code
1             package App::TestOnTap::WorkDirManager;
2              
3 19     19   139 use strict;
  19         41  
  19         604  
4 19     19   100 use warnings;
  19         186  
  19         1058  
5              
6             our $VERSION = '1.001';
7             my $version = $VERSION;
8             $VERSION = eval $VERSION;
9              
10 19     19   115 use App::TestOnTap::Util qw(slashify stringifyTime $IS_WINDOWS);
  19         41  
  19         1847  
11              
12 19     19   12003 use Archive::Zip qw(:ERROR_CODES);
  19         1459783  
  19         2033  
13 19     19   181 use File::Path;
  19         46  
  19         863  
14 19     19   115 use File::Basename;
  19         46  
  19         987  
15 19     19   132 use File::Spec;
  19         38  
  19         566  
16 19     19   9650 use File::Copy::Recursive qw(dircopy);
  19         73209  
  19         1325  
17 19     19   167 use File::Temp qw(tempdir);
  19         48  
  19         1240  
18 19     19   9815 use File::Slurp qw(write_file);
  19         70130  
  19         1133  
19 19     19   11920 use JSON;
  19         156688  
  19         577  
20 19     19   11021 use Net::Domain qw(hostfqdn);
  19         155409  
  19         1351  
21 19     19   169 use POSIX qw(uname);
  19         48  
  19         121  
22              
23             # CTOR
24             #
25             sub new
26             {
27 27     27 0 92 my $class = shift;
28 27         60 my $args = shift;
29 27         61 my $workdir = shift;
30 27         107 my $suiteRoot = shift;
31            
32 27 50       114 if ($workdir)
33             {
34             # if user specifies a workdir this implies that it should be kept
35             # just make sure there is no such directory beforehand, and create it here
36             # (similar to below; tempdir() will also create one)
37             #
38 0         0 $workdir = slashify(File::Spec->rel2abs($workdir));
39 0 0       0 die("The workdir '$workdir' already exists\n") if -e $workdir;
40 0 0       0 mkpath($workdir) or die("Failed to create workdir '$workdir': $!\n");
41             }
42             else
43             {
44             # create a temp dir; use automatic cleanup
45             #
46 27         256 $workdir = slashify(tempdir("testontap-workdir-XXXX", TMPDIR => 1, CLEANUP => 1));
47             }
48              
49 27         187 my $self = bless
50             (
51             {
52             args => $args,
53             suiteroot => $suiteRoot,
54             root => $workdir,
55             tmp => slashify("$workdir/tmp"),
56             save => slashify("$workdir/save"),
57             save_suite => slashify("$workdir/save/suite"),
58             save_testontap => slashify("$workdir/save/testontap"),
59             tap => slashify("$workdir/save/testontap/tap"),
60             result => slashify("$workdir/save/testontap/result"),
61             json => JSON->new()->utf8()->pretty()->canonical(),
62             orderstrategy => undef,
63             dispensedorder => [],
64             foundtests => [],
65             commandlines => {},
66             fullgraph => undef,
67             prunedgraph => undef,
68             preprocess => undef,
69             },
70             $class
71             );
72              
73 27         213 foreach my $p (qw(tmp save save_suite save_testontap tap result))
74             {
75 162 50       19389 mkpath($self->{$p}) || die("Failed to mkdir '$self->{$p}': $!\n");
76             }
77              
78 27         224 return $self;
79             }
80              
81             sub beginTestRun
82             {
83 25     25 0 57 my $self = shift;
84            
85 25         118 $self->{begin} = time();
86            
87 25         914 $self->__save("$self->{save_testontap}/env", { %ENV });
88             }
89              
90             sub endTestRun
91             {
92 25     25 0 154 my $self = shift;
93 25         91 my $args = shift;
94 25         113 my $aggregator = shift;
95            
96 25         261 $self->{end} = time();
97 25         328 $self->{runid} = $args->getId();
98              
99 25 100       169 my $summary =
100             {
101             all_passed => $aggregator->all_passed() ? 1 : 0,
102             status => $aggregator->get_status(),
103             failed => [ $aggregator->failed() ],
104             parse_errors => [ $aggregator->parse_errors() ],
105             passed => [ $aggregator->passed() ],
106             planned => [ $aggregator->planned() ],
107             skipped => [ $aggregator->skipped() ],
108             todo => [ $aggregator->todo() ],
109             todo_passed => [ $aggregator->todo_passed() ],
110             };
111 25         2707 $self->__save("$self->{save_testontap}/summary", $summary);
112              
113             my $testinfo =
114             {
115             config => $self->{args}->getConfig()->getRawCfg(),
116             dispensedorder => $self->{dispensedorder},
117             found => $self->{foundtests},
118             commandlines => $self->{commandlines},
119             fullgraph => $self->{fullgraph},
120             prunedgraph => $self->{prunedgraph},
121 25         4730 };
122 25         566 $self->__save("$self->{save_testontap}/testinfo", $testinfo);
123              
124 25         4130 my $elapsed = $aggregator->elapsed();
125             my $meta =
126             {
127             format => { major => 1, minor => 0 }, # Change when format of result tree is changed in any way.
128             runid => $args->getId(),
129             suiteid => $args->getConfig()->getId(),
130             suitename => basename($args->getSuiteRoot()),
131             begin => stringifyTime($self->{begin}),
132             end => stringifyTime($self->{end}),
133             elapsed =>
134             {
135             str => $aggregator->elapsed_timestr(),
136             real => $elapsed->real(),
137             cpu => $elapsed->cpu_a(),
138             },
139             user => $IS_WINDOWS ? getlogin() : scalar(getpwuid($<)),
140             host => hostfqdn(),
141             jobs => $args->getJobs(),
142             dollar0 => slashify(File::Spec->rel2abs($0)),
143             argv => $args->getFullArgv(),
144             defines => $args->getDefines(),
145             platform => $^O,
146             uname => [ uname() ],
147 25 50       1310 order => $self->{orderstrategy} ? $self->{orderstrategy}->getStrategyName() : undef,
    100          
148             };
149 25         267 $self->__save("$self->{save_testontap}/meta", $meta);
150              
151 25 100       4541 $self->__saveText("$self->{save_testontap}/preprocess", $self->{preprocess}) if $self->{preprocess};
152             }
153              
154             # retain the tap handles we issue so we can 'manually' close them
155             # this can be necessary during a bailout on windows, where the
156             # spool handle closing is not called, and the automatic cleanup
157             # of temp stuff spouts errors to delete a file due to it having an
158             # open handle to it.
159             #
160             # note that putting the handle as a key stringifies it, so we
161             # must use the actual value when closing, not the string...
162             #
163             my %tapHandles;
164             END
165             {
166 19     19   309476 close($tapHandles{$_}) foreach (keys(%tapHandles));
167             }
168              
169             sub openTAPHandle
170             {
171 47     47 0 116 my $self = shift;
172 47         212 my $testPath = slashify(shift, '/');
173            
174 47         255 my $sr = slashify($self->{suiteroot}, '/');
175 47         1017 $testPath =~ s#^\Q$sr\E/(.*)#$1#;
176 47         317 my $tapPath = slashify("$self->{tap}/$testPath.tap");
177 47         3732 mkpath(dirname($tapPath));
178 47 50       3699 open(my $h, '>', $tapPath) or die("Failed to open '$tapPath': $!");
179              
180             # save the handle in the list, forcibly stringify it as key and
181             # save the actual value
182             #
183 47         430 $tapHandles{"$h"} = $h;
184            
185 47         377 return $h;
186             }
187              
188             sub closeTAPHandle
189             {
190 47     47 0 282 my $self = shift;
191 47         185 my $parser = shift;
192            
193 47         340 my $spool_handle = $parser->delete_spool;
194 47 50       538 if ($spool_handle)
195             {
196 47         2895 close($spool_handle);
197            
198             # don't forget to remove the key/value in the list
199             # using the stringified version of the handle!
200             #
201 47         871 delete($tapHandles{"$spool_handle"});
202             }
203            
204 47         925 return;
205             }
206              
207             sub getResultCollector
208             {
209 27     27 0 71 my $self = shift;
210            
211             return
212             sub
213             {
214 47     47   3703 my $pathAndNamePair = shift;
215 47         693 my $parser = shift;
216              
217 47 100       319 my %results =
    50          
    50          
218             (
219             # individual test results
220             #
221             passed => [ $parser->passed() ],
222             actual_passed => [ $parser->actual_passed() ],
223             failed => [ $parser->failed() ],
224             actual_failed => [ $parser->actual_failed() ],
225             todo => [ $parser->todo() ],
226             todo_passed => [ $parser->failed() ],
227             skipped => [ $parser->skipped() ],
228            
229             # total test results
230             #
231             has_problems => $parser->has_problems() ? 1 : 0,
232             plan => $parser->plan(),
233             is_good_plan => $parser->is_good_plan() ? 1 : 0,
234             tests_planned => $parser->tests_planned(),
235             tests_run => $parser->tests_run(),
236             skip_all => ($parser->skip_all() ? $parser->skip_all() : 0),
237             start_time => stringifyTime($parser->start_time()),
238             end_time => stringifyTime($parser->end_time()),
239             version => $parser->version(),
240             'exit' => $parser->exit(),
241             parse_errors => [ $parser->parse_errors() ],
242             );
243            
244 47         2238 $self->__save("$self->{result}/$pathAndNamePair->[1]", \%results);
245 27         416 };
246             }
247              
248             sub saveResult
249             {
250 2     2 0 5 my $self = shift;
251 2         5 my $resultDir = shift;
252 2         5 my $asArchive = shift;
253              
254 2         64 my $pfx = basename($self->{suiteroot});
255 2         12 my $runid = $self->{runid};
256 2         10 my $ts = stringifyTime($self->{begin});
257 2         12 my $name = "$pfx.$ts.$runid";
258 2         9 my $from = slashify($self->{save});
259              
260 2         5 my $to;
261 2 100       7 if ($asArchive)
262             {
263 1         7 $to = slashify("$resultDir/$name.zip");
264 1         23 my $zip = Archive::Zip->new();
265 1         100 $zip->addTree($from, $name);
266 1         10676 my $err = $zip->writeToFileNamed($to);
267 1 50       12823 die("Failed to write archive '$to': $!\n") if $err != AZ_OK;
268             }
269             else
270             {
271 1         5 $to = slashify("$resultDir/$name");
272             {
273 1         3 local $File::Copy::Recursive::KeepMode = 0;
  1         8  
274 1 50       13 die("Failed to copy result '$from' => '$to': $!\n") unless dircopy($from, $to);
275             }
276             }
277            
278 2         4125 return $to;
279             }
280              
281             sub getTmp
282             {
283 26     26 0 71 my $self = shift;
284            
285 26         171 return $self->{tmp};
286             }
287              
288             sub getSaveSuite
289             {
290 26     26 0 63 my $self = shift;
291            
292 26         143 return $self->{save_suite};
293             }
294              
295             sub recordOrderStrategy
296             {
297 55     55 0 144 my $self = shift;
298 55         126 my $orderstrategy = shift;
299            
300 55         174 $self->{orderstrategy} = $orderstrategy;
301             }
302              
303             sub recordDispensedOrder
304             {
305 55     55 0 132 my $self = shift;
306 55         182 my @dispensed = @_;
307            
308 55         97 push(@{$self->{dispensedorder}}, @dispensed);
  55         266  
309             }
310              
311             sub recordFoundTests
312             {
313 27     27 0 79 my $self = shift;
314 27         105 my @foundTests = @_;
315            
316 27         68 push(@{$self->{foundtests}}, @foundTests);
  27         146  
317             }
318              
319             sub recordFullGraph
320             {
321 27     27 0 72 my $self = shift;
322 27         113 my %fullgraph = @_;
323            
324 27         160 $self->{fullgraph} = \%fullgraph;
325             }
326              
327             sub recordPrunedGraph
328             {
329 2     2 0 5 my $self = shift;
330 2         8 my %prunedgraph = @_;
331            
332 2         27 $self->{prunedgraph} = \%prunedgraph;
333             }
334              
335             sub recordPreprocess
336             {
337 1     1 0 8 my $self = shift;
338 1         8 my $preproc = shift;
339            
340 1         8 $self->{preprocess} = $preproc;
341             }
342              
343             sub recordPostprocess
344             {
345 1     1 0 17 my $self = shift;
346 1         9 my $postproc = shift;
347            
348 1         21 $self->__saveText("$self->{save_testontap}/postprocess", $postproc);
349             }
350              
351             sub recordCommandLine
352             {
353 48     48 0 122 my $self = shift;
354 48         193 my $test = shift;
355 48         121 my $cmdline = shift;
356            
357 48         243 $self->{commandlines}->{$test} = $cmdline;
358             }
359              
360             sub __save
361             {
362 147     147   524 my $self = shift;
363 147         503 my $name = shift;
364 147         311 my $data = shift;
365            
366 147         1018 my $file = slashify("$name.json");
367 147         17012 mkpath(dirname($file));
368 147 50       5257 write_file($file, $self->{json}->encode($data)) || die("Failed to write '$file': $!\n");
369             }
370              
371             sub __saveText
372             {
373 2     2   8 my $self = shift;
374 2         10 my $name = shift;
375 2         5 my $data = shift;
376            
377 2         36 my $file = slashify("$name.txt");
378 2         430 mkpath(dirname($file));
379 2 50       394 write_file($file, @$data) || die("Failed to write '$file': $!\n");
380             }
381              
382             1;