File Coverage

blib/lib/Test2/IPC/Driver/Files.pm
Criterion Covered Total %
statement 168 168 100.0
branch 75 92 81.5
condition 11 15 73.3
subroutine 24 24 100.0
pod 7 11 63.6
total 285 310 91.9


line stmt bran cond sub pod time code
1             package Test2::IPC::Driver::Files;
2 23     23   866 use strict;
  23         32  
  23         562  
3 23     23   72 use warnings;
  23         23  
  23         776  
4              
5             our $VERSION = '0.000044';
6              
7 23     23   77 use base 'Test2::IPC::Driver';
  23         31  
  23         9214  
8              
9 23     23   98 use Test2::Util::HashBase qw{tempdir event_id tid pid globals};
  23         28  
  23         71  
10              
11 23     23   90 use Scalar::Util qw/blessed/;
  23         24  
  23         917  
12 23     23   15045 use File::Temp();
  23         279338  
  23         497  
13 23     23   12286 use Storable();
  23         52455  
  23         519  
14 23     23   116 use File::Spec();
  23         26  
  23         389  
15              
16 23     23   73 use Test2::Util qw/try get_tid pkg_to_file/;
  23         23  
  23         1184  
17 23     23   88 use Test2::API qw/test2_ipc_set_pending/;
  23         29  
  23         34654  
18              
19 52     52 1 212 sub use_shm { 1 }
20             sub shm_size() { 64 }
21              
22 52     52 1 206 sub is_viable { 1 }
23              
24             sub init {
25 73     73 0 80 my $self = shift;
26              
27             my $tmpdir = File::Temp::tempdir(
28 73   33     566 $ENV{T2_TEMPDIR_TEMPLATE} || "test2-$$-XXXXXX",
29             CLEANUP => 0,
30             TMPDIR => 1,
31             );
32              
33 73 50       22655 $self->abort_trace("Could not get a temp dir") unless $tmpdir;
34              
35 73         385 $self->{+TEMPDIR} = File::Spec->canonpath($tmpdir);
36              
37             print STDERR "\nIPC Temp Dir: $tmpdir\n\n"
38 73 100       175 if $ENV{T2_KEEP_TEMPDIR};
39              
40 73         103 $self->{+EVENT_ID} = 1;
41              
42 73         103 $self->{+TID} = get_tid();
43 73         137 $self->{+PID} = $$;
44              
45 73         122 $self->{+GLOBALS} = {};
46              
47 73         174 return $self;
48             }
49              
50             sub hub_file {
51 180     180 0 206 my $self = shift;
52 180         178 my ($hid) = @_;
53 180         185 my $tdir = $self->{+TEMPDIR};
54 180         1166 return File::Spec->canonpath("$tdir/HUB-$hid");
55             }
56              
57             sub event_file {
58 36     36 0 970 my $self = shift;
59 36         55 my ($hid, $e) = @_;
60              
61 36         63 my $tempdir = $self->{+TEMPDIR};
62 36 100       194 my $type = blessed($e) or $self->abort("'$e' is not a blessed object!");
63              
64 35 100       267 $self->abort("'$e' is not an event object!")
65             unless $type->isa('Test2::Event');
66              
67 34         150 my @type = split '::', $type;
68 34         188 my $name = join('-', $hid, $$, get_tid(), $self->{+EVENT_ID}++, @type);
69              
70 34         185 return File::Spec->canonpath("$tempdir/$name");
71             }
72              
73             sub add_hub {
74 86     86 1 99 my $self = shift;
75 86         95 my ($hid) = @_;
76              
77 86         136 my $hfile = $self->hub_file($hid);
78              
79 86 100       1639 $self->abort_trace("File for hub '$hid' already exists")
80             if -e $hfile;
81              
82 85 50       3803 open(my $fh, '>', $hfile) or $self->abort_trace("Could not create hub file '$hid': $!");
83 85         748 print $fh "$$\n" . get_tid() . "\n";
84 85         2422 close($fh);
85             }
86              
87             sub drop_hub {
88 56     56 1 105 my $self = shift;
89 56         69 my ($hid) = @_;
90              
91 56         63 my $tdir = $self->{+TEMPDIR};
92 56         96 my $hfile = $self->hub_file($hid);
93              
94 56 100       778 $self->abort_trace("File for hub '$hid' does not exist")
95             unless -e $hfile;
96              
97 55 50       1255 open(my $fh, '<', $hfile) or $self->abort_trace("Could not open hub file '$hid': $!");
98 55         698 my ($pid, $tid) = <$fh>;
99 55         260 close($fh);
100              
101 55 50       173 $self->abort_trace("A hub file can only be closed by the process that started it\nExpected $pid, got $$")
102             unless $pid == $$;
103              
104 55 50       100 $self->abort_trace("A hub file can only be closed by the thread that started it\nExpected $tid, got " . get_tid())
105             unless get_tid() == $tid;
106              
107 55 100       125 if ($ENV{T2_KEEP_TEMPDIR}) {
108 1 50       36 rename($hfile, File::Spec->canonpath("$hfile.complete")) or $self->abort_trace("Could not rename file '$hfile' -> '$hfile.complete'");
109             }
110             else {
111 54 50       2553 unlink($hfile) or $self->abort_trace("Could not remove file for hub '$hid'");
112             }
113              
114 55 50       894 opendir(my $dh, $tdir) or $self->abort_trace("Could not open temp dir!");
115 55         656 for my $file (readdir($dh)) {
116 173 100       287 next if $file =~ m{\.complete$};
117 171 100       1000 next unless $file =~ m{^$hid};
118 1         5 $self->abort_trace("Not all files from hub '$hid' have been collected!");
119             }
120 54         3817 closedir($dh);
121             }
122              
123             sub send {
124 38     38 1 137 my $self = shift;
125 38         114 my ($hid, $e, $global) = @_;
126              
127 38         204 my $tempdir = $self->{+TEMPDIR};
128 38         159 my $hfile = $self->hub_file($hid);
129 38 100       136 my $dest = $global ? 'GLOBAL' : $hid;
130              
131 38 100 100     976 $self->abort(<<" EOT") unless $global || -f $hfile;
132             hub '$hid' is not available, failed to send event!
133              
134             There was an attempt to send an event to a hub in a parent process or thread,
135             but that hub appears to be gone. This can happen if you fork, or start a new
136             thread from inside subtest, and the parent finishes the subtest before the
137             child returns.
138              
139             This can also happen if the parent process is done testing before the child
140             finishes. Test2 normally waits automatically in the root process, but will not
141             do so if Test::Builder is loaded for legacy reasons.
142             EOT
143              
144 36         118 my $file = $self->event_file($dest, $e);
145 34         123 my $ready = File::Spec->canonpath("$file.ready");
146              
147 34 100       92 if ($global) {
148 19         22 my $name = $ready;
149 19         123 $name =~ s{^.*(GLOBAL)}{GLOBAL};
150 19         61 $self->{+GLOBALS}->{$hid}->{$name}++;
151             }
152              
153             my ($ok, $err) = try {
154 34     34   205 Storable::store($e, $file);
155 33 50       9815 rename($file, $ready) or $self->abort("Could not rename file '$file' -> '$ready'");
156 33         370 test2_ipc_set_pending(substr($file, -(shm_size)));
157 34         380 };
158 34 100       194 if (!$ok) {
159 1         1 my $src_file = __FILE__;
160 1         16 $err =~ s{ at \Q$src_file\E.*$}{};
161 1         2 chomp($err);
162 1         1 my $tid = get_tid();
163 1         6 my $trace = $e->trace->debug;
164 1         19 my $type = blessed($e);
165              
166 1         7 $self->abort(<<" EOT");
167              
168             *******************************************************************************
169             There was an error writing an event:
170             Destination: $dest
171             Origin PID: $$
172             Origin TID: $tid
173             Event Type: $type
174             Event Trace: $trace
175             File Name: $file
176             Ready Name: $ready
177             Error: $err
178             *******************************************************************************
179              
180             EOT
181             }
182              
183 33         86 return 1;
184             }
185              
186             sub cull {
187 70     70 1 93 my $self = shift;
188 70         78 my ($hid) = @_;
189              
190 70         85 my $tempdir = $self->{+TEMPDIR};
191              
192 70 50       1835 opendir(my $dh, $tempdir) or $self->abort("could not open IPC temp dir ($tempdir)!");
193              
194 70         88 my @out;
195 70         1273 for my $file (sort readdir($dh)) {
196 299 100       528 next if substr($file, 0, 1) eq '.';
197              
198 159 100       287 next unless substr($file, -6, 6) eq '.ready';
199              
200 38         55 my $global = substr($file, 0, 6) eq 'GLOBAL';
201 38         39 my $hid_len = length($hid);
202 38   66     147 my $have_hid = !$global && substr($file, 0, $hid_len) eq $hid && substr($file, $hid_len, 1) eq '-';
203              
204 38 50 66     174 next unless $have_hid || $global;
205              
206 38 100 100     158 next if $global && $self->{+GLOBALS}->{$hid}->{$file}++;
207              
208             # Untaint the path.
209 27         176 my $full = File::Spec->canonpath("$tempdir/$file");
210 27         181 ($full) = ($full =~ m/^(.*)$/gs);
211              
212 27         59 my $obj = $self->read_event_file($full);
213 27         36 push @out => $obj;
214              
215             # Do not remove global events
216 27 100       56 next if $global;
217              
218 13         56 my $complete = File::Spec->canonpath("$full.complete");
219 13 100       29 if ($ENV{T2_KEEP_TEMPDIR}) {
220 1 50       34 rename($full, $complete) or $self->abort("Could not rename IPC file '$full', '$complete'");
221             }
222             else {
223 12 50       631 unlink($full) or $self->abort("Could not unlink IPC file: $file");
224             }
225             }
226              
227 70         423 closedir($dh);
228 70         421 return @out;
229             }
230              
231             sub read_event_file {
232 31     31 0 91 my $self = shift;
233 31         31 my ($file) = @_;
234              
235 31         86 my $obj = Storable::retrieve($file);
236 31 100       1979 $self->abort("Got an unblessed object: '$obj'")
237             unless blessed($obj);
238              
239 30 100       128 unless ($obj->isa('Test2::Event')) {
240 2         5 my $pkg = blessed($obj);
241 2         5 my $mod_file = pkg_to_file($pkg);
242 2     2   9 my ($ok, $err) = try { require $mod_file };
  2         218  
243              
244 2 100       10 $self->abort("Event has unknown type ($pkg), tried to load '$mod_file' but failed: $err")
245             unless $ok;
246              
247 1 50       7 $self->abort("'$obj' is not a 'Test2::Event' object")
248             unless $obj->isa('Test2::Event');
249             }
250              
251 28         40 return $obj;
252             }
253              
254             sub waiting {
255 16     16 1 25 my $self = shift;
256 16         107 require Test2::Event::Waiting;
257 16         178 $self->send(
258             GLOBAL => Test2::Event::Waiting->new(
259             trace => Test2::Util::Trace->new(frame => [caller()]),
260             ),
261             'GLOBAL'
262             );
263 16         168 return;
264             }
265              
266             sub DESTROY {
267 55     55   116 my $self = shift;
268              
269 55 100       233 return unless defined $self->pid;
270 54 100       120 return unless defined $self->tid;
271              
272 53 100       101 return unless $$ == $self->pid;
273 51 100       84 return unless get_tid() == $self->tid;
274              
275 50         56 my $tempdir = $self->{+TEMPDIR};
276              
277 50 50       1076 opendir(my $dh, $tempdir) or $self->abort("Could not open temp dir! ($tempdir)");
278 50         544 while(my $file = readdir($dh)) {
279 106 100       528 next if $file =~ m/^\.+$/;
280 8 100       18 next if $file =~ m/\.complete$/;
281 6         30 my $full = File::Spec->canonpath("$tempdir/$file");
282              
283 6 100       22 if ($file =~ m/^(GLOBAL|HUB-)/) {
284 5         10 $full =~ m/^(.*)$/;
285 5         9 $full = $1; # Untaint it
286 5 100       23 next if $ENV{T2_KEEP_TEMPDIR};
287 4 50       165 unlink($full) or $self->abort("Could not unlink IPC file: $full");
288 4         17 next;
289             }
290              
291 1         4 $self->abort("Leftover files in the directory ($full)!\n");
292             }
293 49         312 closedir($dh);
294              
295 49 100       102 if ($ENV{T2_KEEP_TEMPDIR}) {
296 1         4 print STDERR "# Not removing temp dir: $tempdir\n";
297 1         8 return;
298             }
299              
300 48 50       3910 rmdir($tempdir) or warn "Could not remove IPC temp dir ($tempdir)";
301             }
302              
303             1;
304              
305             __END__