File Coverage

blib/lib/Test/POE/Stopping.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package Test::POE::Stopping;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Test::POE::Stopping - Test if a POE process has nothing left to do
8              
9             =head1 DESCRIPTION
10              
11             L is a curious beast, as most asynchronous environments are.
12              
13             But in regards to testing, one of the more interesting (and when it's not
14             working properly, annoying) situations is how to tell if the POE-controlled
15             process will, or has, stopped.
16              
17             The obvious solution is to just say something like
18              
19             POE::Kernel->run;
20             pass( "POE stopped" );
21              
22             But this isn't really useful to us, because this test never fails, it just
23             deadlocks forever if some event generator is left around.
24              
25             B takes an introspective method in determining this.
26              
27             In your test script, a top level controlling session should be set up.
28              
29             In this session, you should set a delayed alarm, that SHOULD fire after
30             everything is finished, and POE should have naturally stopped.
31              
32             The delayed alarm will keep POE from returning, but it should make the alarm
33             the very last event called.
34              
35             In this event you call the C function, which will examine the
36             running L to see if it displays the characteristics of one
37             with the last event in progress (no other sessions, empty queue, no event
38             generators, etc).
39              
40             If POE is B stopping, then the C function will emit a
41             fail result and then do a hard-stop of the POE kernel so that at least your
42             test script ends.
43              
44             =cut
45              
46 7     7   203860 use 5.006;
  7         25  
  7         269  
47 7     7   40 use strict;
  7         14  
  7         227  
48 7     7   32 use warnings;
  7         22  
  7         241  
49 7     7   7480 use YAML::Tiny 1.38 ();
  7         43134  
  7         259  
50 7     7   77 use Test::More 0.80 ();
  7         164  
  7         148  
51 7     7   72 use Test::Builder 0.80 ();
  7         106  
  7         165  
52 7     7   7673 use POE 1.310 qw( Session );
  7         463641  
  7         50  
53 7     7   919729 use POE::API::Peek 2.17 ();
  0            
  0            
54              
55             use vars qw{$VERSION @ISA @EXPORT};
56             BEGIN {
57             require Exporter;
58             $VERSION = '1.09';
59             @ISA = 'Exporter';
60             @EXPORT = 'poe_stopping';
61             }
62              
63             sub import {
64             my $class = shift;
65             my $pkg = caller;
66             my $test = Test::Builder->new;
67             $test->exported_to($pkg);
68             $test->plan(@_);
69             $class->export_to_level(1, $class, 'poe_stopping');
70             }
71              
72             sub fail {
73             my $test = Test::Builder->new;
74             local $Test::Builder::Level = $Test::Builder::Level + 1;
75             $test->ok( 0, 'POE appears to be stopping cleanly' );
76             $test->diag( YAML::Tiny->new(@_)->write_string );
77             $poe_kernel->stop;
78             }
79              
80              
81              
82              
83              
84             #####################################################################
85             # Main Methods
86              
87             =pod
88              
89             =head2 poe_stopping
90              
91             poe_stopping();
92              
93             The C test checks the kernel to see if, after the current
94             event, the POE kernel will have nothing else left to do and so will stop.
95              
96             =cut
97              
98             sub poe_stopping {
99             my $api = POE::API::Peek->new;
100             my $test = Test::Builder->new;
101              
102             # The kernel should be running
103             unless ( $api->is_kernel_running ) {
104             Test::More::diag("POE kernel is not running");
105             return fail();
106             }
107              
108             # Get the session information
109             my @sessions = map {
110             session_summary($_)
111             } $api->session_list;
112              
113             # Remove the master session
114             @sessions = grep {
115             $_->{id} ne POE::Kernel->ID
116             } @sessions;
117              
118             # Check we aren't trying to terminate POE in a nested event
119             my $i = 0;
120             my $invoke = 0;
121             while ( my @c = caller($i++) ) {
122             if ( $c[3] eq 'POE::Session::_invoke_state' ) {
123             $invoke++;
124             }
125             }
126             unless ( $invoke == 1 ) {
127             my $rv = fail(@sessions);
128             Test::More::diag("Tried to stop within nested events $invoke deep (probably due to using ->call)");
129             return $rv;
130             }
131              
132             # There should only be one session left
133             my $session = $sessions[0];
134             unless ( @sessions == 1 ) {
135             return fail(@sessions);
136             }
137              
138             # It should be the current session
139             unless ( $session->{current} ) {
140             return fail(@sessions);
141             }
142              
143             # There should be no registered aliases
144             if ( $session->{alias} ) {
145             return fail(@sessions);
146             }
147              
148             # There should be no extra references
149             if ( $session->{extra} ) {
150             return fail(@sessions);
151             }
152              
153             # There should be no handles on the session
154             if ( $session->{handles} ) {
155             return fail(@sessions);
156             }
157              
158             # There should be no events left for this session
159             if ( $session->{queue}->{distinct} ) {
160             return fail(@sessions);
161             }
162              
163             # There should be no registered signals
164             if ( $session->{signals} ) {
165             return fail(@sessions);
166             }
167              
168             # There should be no child sessions
169             if ( $session->{children} ) {
170             return fail(@sessions);
171             }
172              
173             # There should be no other kernel events left
174             # (other than maybe a stat tick)
175             my $kqueue = scalar grep {
176             $_->{destination}->isa('POE::Kernel')
177             and
178             $_->{event} ne '_stat_tick'
179             } $api->event_queue_dump;
180              
181             # All the evidence says that we are stopping
182             Test::Builder->new->ok( 1, 'POE appears to be stopping cleanly' );
183              
184             return @sessions;
185             }
186              
187             sub session_summary {
188             my $session = shift;
189             my $api = POE::API::Peek->new;
190             my $current = $api->current_session;
191             my @children = $api->get_session_children($session);
192             my %signals = eval {
193             $api->signals_watched_by_session($session);
194             };
195             if ( $@ and $@ =~ /^Can\'t use an undefined value as a HASH reference/ ) {
196             %signals = ();
197             }
198              
199             my @queue = $api->event_queue_dump;
200             my @to = grep {
201             $_->{destination}->isa('POE::Session')
202             and
203             $_->{destination}->ID == $current->ID
204             } @queue;
205             my @from = grep {
206             $_->{source}->isa('POE::Session')
207             and
208             $_->{source}->ID == $current->ID
209             } @queue;
210             my @distinct = do {
211             my %seen = ();
212             grep { ! $seen{$_}++ } ( @from, @to )
213             };
214              
215             my $summary = {
216             id => $session->ID,
217             alias => $api->session_alias_count($session),
218             refs => $api->get_session_refcount($session),
219             extra => $api->get_session_extref_count($session),
220             handles => $api->session_handle_count($session),
221             signals => scalar(keys %signals),
222             current => ($current->ID eq $session->ID) ? 1 : 0,
223             children => scalar(@children),
224             queue => {
225             distinct => scalar(@distinct),
226             from => scalar(@from),
227             to => scalar(@to),
228             },
229             };
230             return $summary;
231             }
232              
233             1;
234              
235             =pod
236              
237             =head1 SUPPORT
238              
239             All bugs should be filed via the bug tracker at
240              
241             L
242              
243             For other issues, or commercial enhancement and support, contact the author
244              
245             =head1 AUTHOR
246              
247             Adam Kennedy Eadamk@cpan.orgE
248              
249             =head1 SEE ALSO
250              
251             L, L
252              
253             =head1 COPYRIGHT
254              
255             Copyright 2006 - 2011 Adam Kennedy.
256              
257             This program is free software; you can redistribute
258             it and/or modify it under the same terms as Perl itself.
259              
260             The full text of the license can be found in the
261             LICENSE file included with this module.
262              
263             =cut