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