File Coverage

blib/lib/Devel/MAT/Tool/Future.pm
Criterion Covered Total %
statement 131 151 86.7
branch 59 94 62.7
condition 11 15 73.3
subroutine 24 27 88.8
pod 4 10 40.0
total 229 297 77.1


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2014-2022 -- leonerd@leonerd.org.uk
5              
6             package Devel::MAT::Tool::Future 0.02;
7              
8 3     3   20727232 use v5.14;
  3         11  
9 3     3   12 use warnings;
  3         4  
  3         97  
10 3     3   12 use base qw( Devel::MAT::Tool );
  3         4  
  3         684  
11             Devel::MAT::Tool->VERSION( '0.49' );
12              
13 3     3   4257 use Carp;
  3         14  
  3         156  
14              
15 3     3   14 use Syntax::Keyword::Match;
  3         6  
  3         18  
16 3     3   1507 use String::Tagged;
  3         16986  
  3         89  
17              
18 3     3   17 use constant FOR_UI => 1;
  3         6  
  3         172  
19              
20 3     3   1498 use File::ShareDir qw( module_file );
  3         61756  
  3         3804  
21              
22             =head1 NAME
23              
24             C - analyse C logic
25              
26             =head1 DESCRIPTION
27              
28             This C tool performs analysis of objects and code logic built
29             using the L module.
30              
31             This version supports analysing code based on C version 0.24.
32              
33             =cut
34              
35             sub AUTOLOAD_TOOL
36             {
37 2     2 1 89563 shift;
38 2         7 my ( $pmat ) = @_;
39 2 50       35 return 1 if eval { $pmat->find_symbol( '%Future::' ) };
  2         10  
40             }
41              
42             sub init_tool
43             {
44 2     2 1 318 my $self = shift;
45              
46 2         12 my $df = $self->df;
47              
48 2         19 my $heap_total = scalar $df->heap;
49 2         14 my $count;
50              
51             # Find all the classes that derive from Future
52 2         8 $self->{classes} = \my %classes;
53 2         5 $classes{Future}++;
54              
55 2         5 $count = 0;
56 2         6 foreach my $sv ( $df->heap ) {
57 151806         582150 $count++;
58 151806 100       180977 $self->report_progress( sprintf "Finding Future subclasses in %d of %d (%.2f%%)",
59             $count, $heap_total, 100*$count / $heap_total ) if ($count % 1000) == 0;
60              
61 151806 100       256501 next unless $sv->type eq "STASH";
62              
63             # populate the %classes hash
64 640         1311 $self->class_is_future( $sv );
65             }
66              
67 2         11363 $count = 0;
68 2         30 foreach my $sv ( $df->heap ) {
69 151806         985365 $count++;
70 151806 100       174389 $self->report_progress( sprintf "Finding Future instances in %d of %d (%.2f%%)",
71             $count, $heap_total, 100*$count / $heap_total ) if ($count % 1000) == 0;
72              
73 151806 100       220932 next unless my $pkg = $sv->blessed;
74              
75 621 100       7159 $classes{ $pkg->stashname } and $sv->{tool_future}++;
76             }
77              
78 2         9741 $self->init_cmd;
79             }
80              
81             sub init_cmd
82             {
83 2     2 0 9 my $self = shift;
84              
85             Devel::MAT::Tool::Show->register_extra(
86             sub {
87 4     4   3844 my ( $sv ) = @_;
88              
89 4 50       11 $sv->is_future or return undef;
90              
91 4         9 my $state = $sv->future_state;
92              
93 4         10 Devel::MAT::Cmd->printf( " %s state %s\n",
94             Devel::MAT::Cmd->format_symbol( "Future" ),
95             $state,
96             );
97              
98             match( $state : eq ) {
99             case( "done" ) {
100 1         6 my @result = $sv->future_result;
101 1         59 my @str;
102 1 50       3 push @str, "(empty)" if !@result;
103 1 50       9 push @str, Devel::MAT::Cmd->format_sv_with_value( $result[0] ) if @result;
104 1 50       80 push @str, "..." if @result > 1;
105              
106 1         3 Devel::MAT::Cmd->printf( " %s result: %s\n",
107             Devel::MAT::Cmd->format_symbol( "Future" ),
108             String::Tagged->join( ", ", @str ),
109             );
110             }
111 4 100       39 case( "failed" ) {
    100          
112 1         6 my @failure = $sv->future_failure;
113 1         38 my @str;
114 1 50       3 push @str, "(empty)" if !@failure;
115 1 50       3 if( @failure ) {
116 1 50       8 push @str, defined $failure[0]->pv
117             ? Devel::MAT::Cmd->format_value( $failure[0]->pv, pv => 1 )
118             : Devel::MAT::Cmd->format_sv( $failure[0] );
119 1 50       22 push @str, "..." if @failure > 1;
120             }
121              
122 1         3 Devel::MAT::Cmd->printf( " %s failure: %s\n",
123             Devel::MAT::Cmd->format_symbol( "Future" ),
124             String::Tagged->join( ", ", @str ),
125             );
126             }
127             }
128             }
129 2         89 );
130             }
131              
132             sub init_ui
133             {
134 0     0 1 0 my $self = shift;
135 0         0 my ( $ui ) = @_;
136              
137 0         0 foreach (qw( pending done failed cancelled )) {
138 0         0 $ui->register_icon( name => "future-$_", svg => module_file( __PACKAGE__, "icons/future-$_.svg" ) );
139             }
140              
141             $ui->provides_sv_detail(
142             type => "widget",
143             title => "Future",
144 0     0   0 render => sub { $self->render_sv_detail( @_ ) },
145 0         0 );
146             }
147              
148             =head1 METHODS
149              
150             =cut
151              
152             =head2 class_is_future
153              
154             $ok = $tool->class_is_future( $pkg )
155              
156             Returns true if the given package is a C class. C<$pkg> may be either
157             a C instance referring to a stash, or a plain string.
158              
159             =cut
160              
161             # TODO: This kind of logic might belong in Devel::MAT::SV itself
162              
163             sub class_is_future
164             {
165 913     913 1 1538 my $self = shift;
166 913         1358 my ( $pkg ) = @_;
167 913 100       2743 ref $pkg or $pkg = $self->{pmat}->find_symbol( "%${pkg}::" ); # stash
168              
169 913   100     35508 return $self->{classes}{$pkg->stashname} //= $self->_class_is_future( $pkg );
170             }
171              
172             sub _class_is_future
173             {
174 638     638   4786 my $self = shift;
175 638         798 my ( $pkg ) = @_;
176              
177 638 50       968 return 1 if $pkg->stashname eq "Future";
178              
179 638 100       2838 my $isagv = $pkg->value( "ISA" ) or return 0;
180 261 50       4881 my $isaav = $isagv->array or return 0;
181              
182 261         3662 foreach my $superclass ( $isaav->elems ) {
183 269 100       5468 return 1 if $self->class_is_future( $superclass->pv );
184             }
185              
186 260         1966 return 0;
187             }
188              
189             =head1 SV METHODS
190              
191             This tool adds the following SV methods.
192              
193             =cut
194              
195             =head2 is_future (SV)
196              
197             $ok = $sv->is_future
198              
199             Returns true if the C instance represents a C
200             instance.
201              
202             =cut
203              
204             sub Devel::MAT::SV::is_future
205             {
206 303822     303822 0 298226 my $sv = shift;
207              
208 303822         635441 return defined $sv->{tool_future};
209             }
210              
211             sub Devel::MAT::SV::_future_xs_struct
212             {
213 28     28   37 my $sv = shift;
214              
215 28 50       165 $sv->basetype eq "SV" or return undef;
216              
217 0 0       0 my $ref = $sv->maybe_outref_named( "the FutureXS structure" ) or return undef;
218 0         0 return $ref->sv;
219             }
220              
221             =head2 future_state (SV)
222              
223             $state = $sv->future_state
224              
225             Returns a string describing the state of the given C instance; one of
226             C, C, C or C.
227              
228             =cut
229              
230             sub Devel::MAT::SV::future_state
231             {
232 24     24 0 1614 my $sv = shift;
233              
234 24 50       66 $sv->is_future or croak "$sv is not a Future";
235              
236 24 50       65 if( my $struct = $sv->_future_xs_struct ) {
237             # Using Future::XS
238 0 0       0 if( $struct->field_named( "cancelled" ) ) {
    0          
    0          
239 0         0 return "cancelled";
240             }
241             elsif( $struct->maybe_field_named( "the failure AV" ) ) {
242 0         0 return "failed";
243             }
244             elsif( $struct->field_named( "ready" ) ) {
245 0         0 return "done";
246             }
247             else {
248 0         0 return "pending";
249             }
250             }
251             else {
252             # Using Future::PP
253 24         36 my $tmp;
254 24 100 66     105 if( $tmp = $sv->value( "cancelled" ) and $tmp->uv ) {
    100 66        
    100          
255 6         210 return "cancelled";
256             }
257             elsif( $tmp = $sv->value( "failure" ) ) {
258 6         235 return "failed";
259             }
260             elsif( $tmp = $sv->value( "ready" ) and $tmp->uv ) {
261 6         289 return "done";
262             }
263             else {
264 6         296 return "pending";
265             }
266             }
267             }
268              
269             =head2 future_result
270              
271             @result = $sv->future_result
272              
273             Returns a list of SVs containing the result of a successful C.
274              
275             =cut
276              
277             sub Devel::MAT::SV::future_result
278             {
279 2     2 0 4 my $sv = shift;
280              
281 2 50       6 $sv->is_future or croak "$sv is not a Future";
282              
283 2 50       5 if( my $struct = $sv->_future_xs_struct ) {
284             # Using Future::XS
285 0         0 return $struct->field_named( "the result AV" )->elems;
286             }
287             else {
288             # Using Future::PP
289 2         5 return $sv->value( "result" )->rv->elems;
290             }
291             }
292              
293             =head2 future_failure
294              
295             @failure = $sv->future_failure
296              
297             Returns a list of SVs containing the failure of a failed C.
298              
299             =cut
300              
301             sub Devel::MAT::SV::future_failure
302             {
303 2     2 0 4 my $sv = shift;
304              
305 2 50       6 $sv->is_future or croak "$sv is not a Future";
306              
307 2 50       6 if( my $struct = $sv->_future_xs_struct ) {
308             # Using Future::XS
309 0         0 return $struct->field_named( "the failure AV" )->elems;
310             }
311             else {
312             # Using Future::XS
313 2         6 return $sv->value( "failure" )->rv->elems;
314             }
315             }
316              
317             sub render_sv_detail
318             {
319 0     0 0 0 my $self = shift;
320 0         0 my ( $sv ) = @_;
321              
322 0 0       0 $self->is_future( $sv ) or return undef;
323              
324 0         0 my $state = $self->future_state( $sv );
325              
326 0         0 return Devel::MAT::UI->make_table(
327             State => Devel::MAT::UI->make_widget_text_icon( ucfirst $state, "future-$state" ),
328             );
329             }
330              
331             =head1 EXTENSIONS TO FIND
332              
333             =cut
334              
335             package # hide
336             Devel::MAT::Tool::Find::filter::future;
337 3     3   21 use base qw( Devel::MAT::Tool::Find::filter );
  3         12  
  3         1002  
338              
339             =head2 find future
340              
341             pmat> find future -f
342             HASH(2)=Future at 0x55d43c854660: Future(failed) - SCALAR(PV) at 0x55d43c8546f0 = "It failed"
343              
344             Lists SVs that are Future instances, optionally matching only futures in a
345             given state.
346              
347             Takes the following named options
348              
349             =over 4
350              
351             =item --pending, -p
352              
353             Lists only Futures in the pending state
354              
355             =item --done, -d
356              
357             Lists only Futures in the done state
358              
359             =item --failed, -f
360              
361             Lists only Futures in the failed state
362              
363             =item --cancelled, -c
364              
365             Lists only Futures in the cancelled state
366              
367             =back
368              
369             =cut
370              
371 3     3   17 use constant FILTER_DESC => "Future instances";
  3         5  
  3         284  
372              
373 3         1028 use constant FILTER_OPTS => (
374             pending => { help => "only pending futures",
375             alias => "p" },
376             done => { help => "only done futures",
377             alias => "d" },
378             failed => { help => "only failed futures",
379             alias => "f" },
380             cancelled => { help => "only cancelled futures",
381             alias => "c" },
382 3     3   23 );
  3         7  
383              
384             sub build
385             {
386 4     4   4401 my $self = shift;
387 4         6 my $inv = shift;
388 4         8 my %opts = %{ +shift };
  4         11  
389              
390 4         6 my %only;
391 4   66     25 $opts{$_} and $only{$_}++ for qw( pending done failed cancelled );
392              
393             return sub {
394 303788     303788   1514672 my ( $sv ) = @_;
395              
396 303788 100       363806 return unless $sv->is_future;
397              
398 16         62 my $state = $sv->future_state;
399              
400 16 100 66     97 return if %only and !$only{$state};
401              
402 4         39 my $ret = String::Tagged->from_sprintf( "%s(%s)",
403             Devel::MAT::Cmd->format_symbol( "Future" ), # TODO: full class name of this instance?
404             Devel::MAT::Cmd->format_note( $state, 1 ),
405             );
406              
407             match( $state : eq ) {
408             case( "done" ) {
409 1         5 my @result = $sv->future_result;
410 1 50       71 $ret .= " - (empty)" if !@result;
411 1 50       6 $ret .= " - " . Devel::MAT::Cmd->format_sv_with_value( $result[0] ) if @result;
412 1 50       111 $ret .= ", ..." if @result > 1;
413             }
414 4 100       926 case( "failed" ) {
    100          
415 1         7 my @failure = $sv->future_failure;
416 1 50       68 $ret .= " - (empty)" if !@failure;
417 1 50       5 if( @failure ) {
418 1 50       15 $ret .= " - " . ( defined $failure[0]->pv
419             ? Devel::MAT::Cmd->format_value( $failure[0]->pv, pv => 1 )
420             : Devel::MAT::Cmd->format_sv( $failure[0] ) );
421 1 50       56 $ret .= ", ..." if @failure > 1;
422             }
423             }
424             }
425              
426 4         22 return $ret;
427             }
428 4         27 }
429              
430             =head1 AUTHOR
431              
432             Paul Evans
433              
434             =cut
435              
436             0x55AA;