File Coverage

blib/lib/Test/Gearman.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Test::Gearman;
2              
3 1     1   24977 use Moose;
  0            
  0            
4             use Test::TCP qw();
5             use File::Which qw();
6             use Carp qw();
7             use Proc::Guard qw();
8              
9             use Gearman::XS qw(:constants);
10             use Gearman::XS::Worker;
11             use Gearman::XS::Client;
12              
13             use version; our $VERSION = version->declare('v0.2.0');
14              
15             # ABSTRACT: A class for testing and mocking Gearman workers.
16              
17              
18             has functions => (
19             is => 'ro',
20             isa => 'HashRef[CodeRef]',
21             traits => ['Hash'],
22             required => 1,
23             reader => '_functions', ## prevent direct tampering
24             writer => undef,
25             handles => {
26             function_names => 'keys',
27             get_function => 'get',
28             },
29             );
30              
31              
32             has gearmand_bin => (
33             is => 'ro',
34             isa => 'Str',
35             lazy => 1,
36             builder => '_build_gearmand_bin',
37             );
38              
39             sub _build_gearmand_bin {
40             ## find gearmand binary in $PATH
41             return $ENV{GEARMAND} || File::Which::which('gearmand') || q{};
42             }
43              
44              
45             has host => (
46             is => 'ro',
47             isa => 'Str',
48             default => '127.0.0.1',
49             );
50              
51              
52             has port => (
53             is => 'ro',
54             isa => 'Int',
55             lazy => 1,
56             builder => '_build_port',
57             );
58              
59             sub _build_port {
60             return Test::TCP::empty_port();
61             }
62              
63              
64             has worker_timeout => (
65             is => 'ro',
66             isa => 'Int',
67             default => 5,
68             );
69              
70              
71             has client_timeout => (
72             is => 'ro',
73             isa => 'Int',
74             default => 5,
75             );
76              
77              
78             has client => (
79             is => 'ro',
80             isa => 'Gearman::XS::Client',
81             lazy => 1,
82             builder => '_build_client',
83             );
84              
85             sub _build_client {
86             my $self = shift;
87              
88             my $client = Gearman::XS::Client->new;
89              
90             my $ret = $client->add_server($self->host, $self->port);
91             if ($ret != GEARMAN_SUCCESS) {
92             Carp::croak($client->error());
93             }
94              
95             $client->set_timeout(1000 * $self->client_timeout);
96              
97             return $client;
98             }
99              
100              
101             has log_file => (
102             is => 'ro',
103             isa => 'Str',
104             default => 'stderr',
105             );
106              
107              
108             has server => (
109             is => 'ro',
110             isa => 'Proc::Guard',
111             lazy => 1,
112             builder => '_build_server',
113             predicate => 'has_server',
114             );
115              
116             sub _build_server {
117             my $self = shift;
118              
119             ## get port on which we'll run
120             my $port = $self->port;
121              
122             ## build gearmand args
123             my %args = (
124             'listen' => $self->host,
125             'port' => $port,
126             'log-file' => $self->log_file,
127             );
128             my @args = map { sprintf('--%s=%s', $_, $args{$_}) } keys %args;
129              
130             ## launch gearmand
131             my $proc = Proc::Guard->new(command => [ $self->gearmand_bin, @args ]);
132              
133             ## wait for port to initialize
134             Test::TCP::wait_port($port);
135              
136             ## only now we can return with confidence
137             return $proc;
138             }
139              
140              
141             has worker => (
142             is => 'ro',
143             isa => 'Proc::Guard',
144             lazy => 1,
145             builder => '_build_worker',
146             predicate => 'has_worker',
147             );
148              
149             sub _build_worker {
150             my $self = shift;
151              
152             my $server = $self->server;
153             my $worker = Gearman::XS::Worker->new;
154              
155             ## add our server instance
156             my $ret = $worker->add_server($self->host, $self->port);
157             if ($ret != GEARMAN_SUCCESS) {
158             Carp::croak($worker->error());
159             }
160              
161             ## assign functions
162             foreach my $function_name ($self->function_names) {
163             my $ret = $worker->add_function($function_name, 1000 * $self->worker_timeout, $self->get_function($function_name), {});
164             if ($ret != GEARMAN_SUCCESS) {
165             Carp::croak($worker->error());
166             }
167             }
168              
169             ## now fork and loop
170             return Proc::Guard->new(code => sub {
171             while (1) {
172             if (GEARMAN_SUCCESS != $worker->work()) {
173             Carp::croak($worker->error());
174             }
175             }
176             });
177             }
178              
179             sub BUILD {
180             my $self = shift;
181              
182             my $bin = $self->gearmand_bin;
183              
184             ## did we not find it?
185             unless ($bin) {
186             Carp::croak('Cannot find gearmand in your $PATH. You can set it via gearmand_bin attribute or $ENV{GEARMAND} environment variable');
187             }
188              
189             ## make sure the path actually exists
190             unless (-e $bin) {
191             Carp::croak("The $bin does not exist.");
192             }
193              
194             ## make sure it is executable
195             unless (-x $bin) {
196             Carp::croak("The $bin is not an executable.");
197             }
198              
199             ## make sure we have a C binary, and not a Perl version one
200             open (my $fh, '<', $bin) or Carp::croak("Cannot open $bin: $!");
201             my $shebang = <$fh>;
202             close $fh;
203              
204             if (substr($shebang, 0, 2) eq '#!') {
205             Carp::croak("The gearmand ($bin) appears to be a Perl version. This module only support C version.");
206             }
207              
208             ## launch server
209             $self->server;
210              
211             ## launch workers
212             $self->worker;
213             }
214              
215             sub DEMOLISH {
216             my $self = shift;
217              
218             ## clean up and stop server and workers
219             ## otherwise in the event of error they will
220             ## hang and never exit properly
221             ##
222             ## we also need to check whether the attribute
223             ## was initialized, as in the case when the BUILD fails
224             ## early
225             $self->worker->stop if $self->has_worker;
226             $self->server->stop if $self->has_server;
227             }
228              
229             no Moose;
230             __PACKAGE__->meta->make_immutable;
231             1; ## eof
232              
233             __END__
234              
235             =pod
236              
237             =head1 NAME
238              
239             Test::Gearman - A class for testing and mocking Gearman workers.
240              
241             =head1 VERSION
242              
243             version v0.2.0
244              
245             =head1 SYNOPSIS
246              
247             use Test::Gearman;
248              
249             my $tg = Test::Gearman->new(
250             functions => {
251             reverse => sub {
252             my $job = shift;
253             my $workload = $job->workload();
254             my $result = reverse($workload);
255              
256             return $result;
257             },
258             },
259             );
260              
261             ## now you can either get a client object
262             ## from Test::Gearman object
263             my ($ret, $result) = $tg->client->do('reverse', 'this is a test');
264              
265             ## or build your own
266             use Gearman::XS::Client;
267             my $client = Gearman::XS::Client->new;
268             $client->add_server($tg->host, $tg->port);
269             my ($ret, $job_handle) = $client->do_background('reverse', 'hello world');
270              
271             =head1 DESCRIPTION
272              
273             Test::Gearman is a class for testing Gearman workers.
274              
275             This class only works with C version of gearmand, and L<Gearman::XS>
276             bindings.
277              
278             An actual Gearman daemon is launched, and workers are forked
279             when you instantiate the class. The Gearman and workers are automatically
280             shut down and destroyed when the instance of the class goes out of scope.
281              
282             By default Gearman daemon will listen on a random available L</port>.
283              
284             =head1 PUBLIC ATTRIBUTES
285              
286             =head2 functions
287              
288             A HashRef of CodeRefs that stores worker function names as keys and
289             a CodeRef as work to be done.
290              
291             my $tg = Test::Gearman->new(
292             functions => {
293             function_name => sub {
294             ## worker code
295             },
296             },
297             );
298              
299             =head3 function_names()
300              
301             Returns a list of all registered worker function names.
302              
303             =head3 get_function($function_name)
304              
305             Returns a CodeRef for the given function name.
306              
307             =head2 gearmand_bin
308              
309             Path to Gearman daemon binary. If one is not provided it tries
310             to find it in the C<$PATH>.
311              
312             B<Note>: this must be a C version of gearmand, and not the Perl version
313             as they have different interfaces.
314              
315             You can also set the path to the binary via C<$ENV{GEARMAND}>.
316              
317             =head2 host
318              
319             Host to which Gearman daemon will bind.
320              
321             Default is 127.0.0.1.
322              
323             =head2 port
324              
325             Port on which gearmand runs. It is picked randomly at the start, but
326             you can manually specify a port if you wish.
327              
328             =head2 worker_timeout
329              
330             Worker timeout in seconds.
331              
332             Default is 5.
333              
334             =head2 client_timeout
335              
336             Client timeout in seconds.
337              
338             Default is 5.
339              
340             =head2 client
341              
342             An instance of L<Gearman::XS::Client> that you can use to inject jobs.
343              
344             =head2 log_file
345              
346             Gearman daemon log file. This is synonymous with C<--log-file> option.
347              
348             Default: stderr
349              
350             =head1 PRIVATE ATTRIBUTES
351              
352             =head2 server
353              
354             An instance of L<Proc::Guard> that runs gearmand server.
355              
356             =head2 worker
357              
358             An instance of L<Proc::Guard> that runs workers.
359              
360             =head1 AUTHOR
361              
362             Roman F. <romanf@cpan.org>
363              
364             =head1 COPYRIGHT AND LICENSE
365              
366             This software is copyright (c) 2013 by L<Need Backup|http://www.needbackup.com/>.
367              
368             This is free software; you can redistribute it and/or modify it under
369             the same terms as the Perl 5 programming language system itself.
370              
371             =cut