File Coverage

blib/lib/POE/Test/Helpers.pm
Criterion Covered Total %
statement 139 140 99.2
branch 57 62 91.9
condition 10 10 100.0
subroutine 20 20 100.0
pod 8 8 100.0
total 234 240 97.5


line stmt bran cond sub pod time code
1 9     9   3715515 use strictures 1;
  9         7645  
  9         304  
2             package POE::Test::Helpers;
3             BEGIN {
4 9     9   834 $POE::Test::Helpers::VERSION = '1.11';
5             }
6             # ABSTRACT: Testing framework for POE
7              
8 9     9   63 use Carp;
  9         22  
  9         1009  
9 9     9   8106 use parent 'Test::Builder::Module';
  9         2776  
  9         51  
10 9     9   11840 use POE::Session;
  9         71588  
  9         69  
11 9     9   9518 use Data::Validate 'is_integer';
  9         6759192  
  9         942  
12 9     9   10233 use List::AllUtils qw( first none );
  9         29303  
  9         849  
13 9     9   8049 use Test::Deep::NoTest qw( bag eq_deeply );
  9         2479437  
  9         89  
14 9     9   12190 use namespace::autoclean;
  9         961436  
  9         66  
15              
16             my $CLASS = __PACKAGE__;
17              
18             sub new {
19 23     23 1 16034 my ( $class, %opts ) = @_;
20              
21             # must have tests
22 23         50 my $tests = $opts{'tests'};
23 23 100       110 defined $tests or croak 'Missing tests data in new';
24 21 100       83 ref $tests eq 'HASH' or croak 'Tests data should be a hashref in new';
25              
26             # must have run method
27 20 100       88 exists $opts{'run'} or croak 'Missing run method in new';
28 19 100       79 ref $opts{'run'} eq 'CODE' or croak 'Run method should be a coderef in new';
29              
30 18         29 foreach my $name ( keys %{$tests} ) {
  18         64  
31 32         57 my $test_data = $tests->{$name};
32              
33 32         124 my ( $count, $order, $params, $deps ) =
34 32         46 @{$test_data}{ qw/ count order params deps / };
35              
36             # currently we still allow to register tests without requiring
37             # at least a count or params
38              
39             # check the count
40 32 100       72 if ( defined $count ) {
41             # count is only tested in the last run so we just check the param
42 13 100       303 defined is_integer($count) or croak 'Bad event count in new';
43             }
44              
45             # check the order
46 30 100       15326 if ( defined $order ) {
47 6 100       145 defined is_integer($order) or croak 'Bad event order in new';
48             }
49              
50             # check deps
51 28 100       5497 if ( defined $deps ) {
52 9 100       50 ref $deps eq 'ARRAY' or croak 'Bad event deps in new';
53             }
54              
55             # check the params
56 26 100       81 if ( defined $params ) {
57 8 100       50 ref $params eq 'ARRAY' or croak 'Bad event params in new';
58             }
59             }
60              
61 10   100     118 my $self = bless {
62             tests => $tests,
63             run => $opts{'run'},
64             params_type => $opts{'params_type'} || 'ordered',
65             }, $class;
66              
67 10         51 return $self;
68             }
69              
70             sub spawn {
71 6     6 1 202 my ( $class, %opts ) = @_;
72              
73 6         44 my $self = $class->new(%opts);
74              
75 6         75 $self->{'session_id'} = POE::Session->create(
76             object_states => [
77             $self => [ '_start', '_child' ],
78             ],
79             )->ID;
80              
81 6         740 return $self;
82             }
83              
84             sub reached_event {
85 51     51 1 10996 my ( $self, %opts ) = @_;
86             # we don't have to get params,
87             # but we do have to get the name and order
88              
89 51         88 my $name = $opts{'name'};
90             # must have name
91 51 100 100     327 defined $name && $name ne ''
92             or croak 'Missing event name in reached_event';
93              
94 49         109 my ( $event_order, $event_params, $event_deps ) =
95             @opts{ qw/ order params deps / };
96              
97 49 100       113 defined $event_order
98             or croak 'Missing event order in reached_event';
99 48 100       1392 defined is_integer($event_order)
100             or croak 'Event order must be integer in reached_event';
101              
102 46 100       32690 if ( defined $event_params ) {
103 32 100       111 ref $event_params eq 'ARRAY'
104             or croak 'Event params must be arrayref in reached_event';
105             }
106              
107 44 100       101 if ( defined $event_deps ) {
108 2 50       24 ref $event_deps eq 'ARRAY'
109             or croak 'Event deps must be arrayref in reached_event';
110             }
111              
112 42         89 my $test_data = $self->{'tests'}{$name};
113              
114 42         106 my ( $test_count, $test_order, $test_params, $test_deps ) =
115 42         65 @{$test_data}{ qw/ count order params deps / };
116              
117             # currently we still allow to register events without requiring
118             # at least a count or params
119              
120             # add the event to the list of events
121 42         49 push @{ $self->{'events_order'} }, $name;
  42         92  
122              
123             # check the order
124 42 100       91 if ( defined $test_order ) {
125 4         9 $self->check_order( $name, $event_order );
126             }
127              
128             # check deps
129 42 100       84 if ( defined $test_deps ) {
130 13         31 $self->check_deps( $name, $event_deps );
131             }
132              
133             # check the params
134 42 100       5632 if ( defined $test_params ) {
135 8         28 $self->check_params( $name, $event_params );
136             }
137              
138 42         23724 return 1;
139             }
140              
141             sub check_count {
142 9     9 1 15 my ( $self, $event, $count ) = @_;
143 9         39 my $tb = $CLASS->builder;
144              
145 9         67 my $count_from_event = grep /^$event$/, @{ $self->{'events_order'} };
  9         205  
146 9         46 $tb->is_num( $count_from_event, $count, "$event ran $count times" );
147              
148 9         3951 return 1;
149             }
150              
151             sub check_order {
152 4     4 1 6 my ( $self, $event, $event_order ) = @_;
153 4         18 my $tb = $CLASS->builder;
154              
155 4         36 my $event_from_order = $self->{'events_order'}[$event_order];
156              
157 4         17 $tb->is_eq( $event, $event_from_order, "($event_order) $event" );
158              
159 4         1703 return 1;
160             }
161              
162             sub check_deps {
163 13     13 1 19 my ( $self, $event, $deps ) = @_;
164 13         60 my $tb = $CLASS->builder;
165              
166             # get the event's tested dependencies and all events run so far
167 13         104 my @deps_from_event = @{ $self->{'tests'}{$event}{'deps'} };
  13         42  
168 13         14 my @all_events = @{ $self->{'events_order'} };
  13         31  
169              
170             # check for problematic dependencies
171 13         15 my @problems = ();
172 13         21 foreach my $dep_event (@deps_from_event) {
173 25 50       403 if ( ! grep /^$dep_event$/, @all_events ) {
174 0         0 push @problems, $dep_event;
175             }
176             }
177              
178             # serialize possible errors
179 13         27 my $missing = join ', ', @problems;
180 13 50       30 my $extra = @problems ? "[$missing missing]" : q{};
181              
182 13         63 $tb->ok( ( @problems == 0 ), "Correct sub deps for ${event}${extra}" );
183             }
184              
185             sub check_params {
186 8     8 1 13 my ( $self, $event, $current_params ) = @_;
187 8         58 my $tb = $CLASS->builder;
188              
189 8         103 my $test_params = $self->{'tests'}{$event}{'params'};
190              
191 8 100       26 if ( $self->{'params_type'} eq 'ordered' ) {
192             # remove the fetched
193 4   100     6 my $expected_params = shift @{$test_params} || [];
194              
195 4         18 $tb->ok(
196             eq_deeply(
197             $current_params,
198             $expected_params,
199             ),
200             "($event) Correct params",
201             );
202             } else {
203             # don't remove, just match
204 4         6 my $okay = 0;
205              
206 4         6 foreach my $expected_params ( @{$test_params} ) {
  4         9  
207 6 100       6642410 if ( eq_deeply(
  6         27  
208             $current_params,
209             bag(@{$expected_params}) ) ) {
210 4         1586 $okay++;
211             }
212             }
213              
214 4         764 $tb->ok( $okay, "($event) Correct [unordered] params" );
215             }
216             }
217              
218             sub _child {
219             # this says that _start on our spawned session started
220             # we should mark _start on our superhash
221 12     12   3315 my $self = $_[OBJECT];
222 12         23 my $change = $_[ARG0];
223 12         21 my $session = $_[ARG1];
224              
225 12         42 my $internals = $session->[KERNEL];
226              
227 12 100       180 if ( $change eq 'create' ) {
    50          
228 6         37 $self->reached_event(
229             name => '_start',
230             order => 0,
231             );
232             } elsif ( $change eq 'lose' ) {
233             # get the last events_order
234 6         15 my $order = $self->{'events_order'} ?
235 6 50       29 scalar @{ $self->{'events_order'} } :
236             0;
237              
238 6         25 $self->reached_event(
239             name => '_stop',
240             order => $order,
241             );
242              
243             # checking the count
244 6         24 $self->check_all_counts;
245             }
246             }
247              
248             sub check_all_counts {
249 6     6 1 13 my $self = shift;
250 6         15 foreach my $test ( keys %{ $self->{'tests'} } ) {
  6         28  
251 21         46 my $test_data = $self->{'tests'}{$test};
252              
253 21 100       85 if ( exists $test_data->{'count'} ) {
254 9         24 $self->check_count( $test, $test_data->{'count'} );
255             }
256             }
257             }
258              
259             sub _start {
260 6     6   714721 my ( $self, $kernel ) = @_[ OBJECT, KERNEL ];
261              
262             # collect the keys of everyone
263             # if exists key in test, add a test for it for them
264 6         38 $self->{'session_id'} = $_[SESSION]->ID();
265              
266 6         40 my @subs_to_override = keys %{ $self->{'tests'} };
  6         61  
267              
268 6         22 my $callback = $self->{'run'};
269 6         30 my $session_to_test = $callback->();
270 6         547 my $internal_data = $session_to_test->[KERNEL];
271              
272             # 0 is done by _start in _child event, so we start from 1
273 6         13 my $count = 1;
274              
275 6         16 foreach my $sub_to_override (@subs_to_override) {
276             # use _child event to handle these
277 21 100 100     118 $sub_to_override eq '_start' || $sub_to_override eq '_stop' and next;
278              
279             # override the subroutine
280 14         24 my $old_sub = $internal_data->{$sub_to_override};
281             my $new_sub = sub {
282 30     30   6498 $self->reached_event(
283             name => $sub_to_override,
284             order => $count++,
285             params => [ @_[ ARG0 .. $#_ ] ],
286             );
287              
288 30         121 goto &$old_sub;
289 14         66 };
290              
291 14         56 $internal_data->{$sub_to_override} = $new_sub;
292             }
293             }
294              
295             1;
296              
297              
298              
299             =pod
300              
301             =head1 NAME
302              
303             POE::Test::Helpers - Testing framework for POE
304              
305             =head1 VERSION
306              
307             version 1.11
308              
309             =head1 SYNOPSIS
310              
311             This module provides you with a framework to easily write tests for your POE
312             code.
313              
314             The main purpose of this module is to be non-instrusive (nor abstrusive) and
315             allow you to write your code without getting in your way.
316              
317             use Test::More tests => 1;
318             use POE;
319             use POE::Test::Helpers;
320              
321             # defining a callback to create a session
322             my $run = sub {
323             return POE::Session->create(
324             inline_states => {
325             '_start' => sub {
326             print "Start says hi!\n";
327             $_[KERNEL]->yield('next');
328             },
329             'next' => sub { print "Next says hi!\n" },
330             }
331             );
332             };
333              
334             # here we define the tests
335             # and tell POE::Test::Helpers to run your session
336             POE::Test::Helpers->spawn(
337             run => $run,
338             tests => {
339             # _start is actually 0
340             # next will run right after _start
341             next => { order => 1 },
342             },
343             );
344              
345             POE::Kernel->run;
346              
347             Testing event-based programs is not trivial at all. There's a lot of hidden race
348             conditions and unknown behavior afoot. Usually we separate the testing to
349             components, subroutines and events. However, as good as it is (and it's good!),
350             it doesn't give us the exact behavior we'll get from the application once
351             running.
352              
353             There are also a lot of types of tests that we would want to run, such as:
354              
355             =over 4
356              
357             =item * Ordered Events:
358              
359             Did every event run in the specific order I wanted it to?
360              
361             I<(maybe some event was called first instead of third...)>
362              
363             =item * Sequence Ordered Events:
364              
365             Did every event run only after other events?
366              
367             Imagine you want to check whether C<run_updates> ran, but you know it can should
368             only run after C<get_main_status> ran. In event-based programming, you would
369             give up the idea of testing this possible race condition, but with
370             Test::POE::Helpers you can test it.
371              
372             I<< C<run_updates> can only run after C<get_main_status> >>
373              
374             =item * Event Counting:
375              
376             How many times can each event run?
377              
378             I<(this event can be run only 4 times, no more, no less)>
379              
380             =item * Ordered Event Parameters:
381              
382             Checking specific parameters an event received, supporting multiple options.
383              
384             I<(did this event get the right parameters for each call?)>
385              
386             =item * Unordered Event Parameters:
387              
388             Same thing, just without having a specific order of sets of events.
389              
390             =back
391              
392             This module allows to do all those things using a simple API.
393              
394             =head1 METHODS
395              
396             =head2 spawn
397              
398             Creates a new L<POE::Session> that manages in the background the tests. If you
399             wish not to create a session, but manage things yourself, check C<new> below and
400             the additionally available methods.
401              
402             Accepts the following options:
403              
404             =head3 run
405              
406             A callback to create your session. This is required so POE::Test::Helpers could
407             hook up to your code internally without you having to set up hooks for it.
408              
409             The callback is expected to return the session object. This means that you can
410             either provide a code reference to your C<< POE::Session->create() >> call or
411             you could set up an arbitrary code reference that just returns a session object
412             you want to monitor.
413              
414             use POE::Test::Helpers;
415              
416             # we want to test Our::Module
417             POE::Test::Helpers->spawn(
418             run => sub { Our::Module->spawn( ... ) },
419             ...
420             );
421              
422             # or, if we want to set up the session ourselves in more intricate ways
423             my $object = Our::Module->new( ... );
424             my $code = sub { $object->create_session };
425              
426             POE::Test::Helpers->spawn(
427             run => $code,
428             ...
429             );
430              
431             POE::Kernel->run;
432              
433             In case you want to simply run a test in an asynchronous way (and that is why
434             you're using POE), you could do it this way:
435              
436             use POE::Test::Helpers;
437              
438             sub start {
439             # POE code
440             $_[KERNEL]->yield('next');
441             }
442              
443             sub next {
444             # POE code
445             }
446              
447             # now provide POE::Test::Helpers with a coderef that creates a POE::Session
448             POE::Test::Helpers->spawn(
449             run => sub {
450             POE::Session->create(
451             inline_states => [ qw/ _start next / ],
452             );
453             },
454             );
455              
456             POE::Kernel->run;
457              
458             =head3 tests
459              
460             Describes what tests should be done. You need to provide each event that will be
461             tested and what is tested with it and how. There are a lot of different tests
462             that are available for you.
463              
464             You can provide multiple tests per event, as much as you want.
465              
466             POE::Test::Helpers->spawn(
467             run => $run_method,
468             tests => {
469             # testing that "next" was run once
470             next => { count => 1 },
471              
472             # testing that "more" wasn't run at all
473             more => { count => 0 },
474              
475             # testing that "again" was run 3 times
476             # and that "next" was run beforehand
477             again => {
478             count => 3,
479             deps => ['next'],
480             },
481              
482             # testing that "last" was run 4th
483             # and what were the subroutine parameters each time
484             last => {
485             order => 3, # 0 is first, 1 is second...
486             params => [ [ 'first', 'params' ], ['second'] ],
487             },
488             },
489             );
490              
491             POE::Kernel->run;
492              
493             =head3 params_type
494              
495             Ordinarily, the params are checked in an I<ordered> fashion. This means that it
496             checks the first ones against the first arrayref, the second one against the
497             second and so on.
498              
499             However, sometimes you just want to provide a few sets of I<possible> parameters
500             which means it I<might> be one of these, but not necessarily in this order.
501              
502             This helps in case of race conditions when you don't know what comes first and
503             frankly don't even care.
504              
505             You can change this simply by setting this attribute to C<unordered>.
506              
507             use POE::Test::Helpers;
508              
509             POE::Test::Helpers->spawn(
510             run => $run_method,
511             event_params => 'unordered',
512             tests => {
513             checks => {
514             # either called with "now" or "then" parameters
515             # doesn't matter the order
516             params => [ ['now'], ['then'] ],
517             },
518             },
519             );
520              
521             POE::Kernel->run;
522              
523             =head2 new
524              
525             Creates the underlying object. Please review L<POE::Test::Helpers::API> for
526             this.
527              
528             =head2 reached_event
529              
530             Underlying object method. Please review L<POE::Test::Helpers::API> for this.
531              
532             =head2 check_deps
533              
534             Underlying object method. Please review L<POE::Test::Helpers::API> for this.
535              
536             =head2 check_order
537              
538             Underlying object method. Please review L<POE::Test::Helpers::API> for this.
539              
540             =head2 check_params
541              
542             Underlying object method. Please review L<POE::Test::Helpers::API> for this.
543              
544             =head2 check_all_counts
545              
546             Underlying object method. Please review L<POE::Test::Helpers::API> for this.
547              
548             =head2 check_count
549              
550             Underlying object method. Please review L<POE::Test::Helpers::API> for this.
551              
552             =head1 AUTHOR
553              
554             Sawyer X, C<< <xsawyerx at cpan.org> >>
555              
556             =head1 BUGS
557              
558             Please use the Github Issues tracker.
559              
560             =head1 SUPPORT
561              
562             You can find documentation for this module with the perldoc command.
563              
564             perldoc POE::Test::Helpers
565              
566             You can also look for information at:
567              
568             =over 4
569              
570             =item * RT: CPAN's request tracker
571              
572             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=POE-Test-Helpers>
573              
574             =item * AnnoCPAN: Annotated CPAN documentation
575              
576             L<http://annocpan.org/dist/POE-Test-Helpers>
577              
578             =item * CPAN Ratings
579              
580             L<http://cpanratings.perl.org/d/POE-Test-Helpers>
581              
582             =item * Search CPAN
583              
584             L<http://search.cpan.org/dist/POE-Test-Helpers/>
585              
586             =back
587              
588             =head1 ACKNOWLEDGEMENTS
589              
590             I owe a lot of thanks to the following people:
591              
592             =over 4
593              
594             =item * Chris (perigrin) Prather
595              
596             Thanks for all the comments and ideas. Thanks for L<MooseX::POE>!
597              
598             =item * Rocco (dngor) Caputo
599              
600             Thanks for the input and ideas. Thanks for L<POE>!
601              
602             =item * #moose and #poe
603              
604             Really great people and constantly helping me with stuff, including one of the
605             core principles in this module.
606              
607             =back
608              
609             =head1 AUTHOR
610              
611             Sawyer X <xsawyerx@cpan.org>
612              
613             =head1 COPYRIGHT AND LICENSE
614              
615             This software is copyright (c) 2010 by Sawyer X.
616              
617             This is free software; you can redistribute it and/or modify it under
618             the same terms as the Perl 5 programming language system itself.
619              
620             =cut
621              
622              
623             __END__
624