beanstalk_farmer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rvmrc +26 -0
- data/.yardopts +5 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +29 -0
- data/Rakefile +19 -0
- data/beanstalk_farmer.gemspec +29 -0
- data/example/echo.rb +18 -0
- data/lib/beanstalk_farmer/config.rb +53 -0
- data/lib/beanstalk_farmer/dsl.rb +14 -0
- data/lib/beanstalk_farmer/job.rb +50 -0
- data/lib/beanstalk_farmer/runner.rb +51 -0
- data/lib/beanstalk_farmer/service.rb +83 -0
- data/lib/beanstalk_farmer/version.rb +3 -0
- data/lib/farmer.rb +33 -0
- data/spec/beanstalk_farmer/config_spec.rb +25 -0
- data/spec/beanstalk_farmer/dsl_spec.rb +17 -0
- data/spec/beanstalk_farmer/job_spec.rb +57 -0
- data/spec/beanstalk_farmer/runner_spec.rb +60 -0
- data/spec/beanstalk_farmer/service_spec.rb +66 -0
- data/spec/beastalk_farmer_spec.rb +26 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/have_tube_named.rb +7 -0
- metadata +127 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rvmrc
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
#
|
4
|
+
# The following is cribbed directly from the rvm-site project. It's a more
|
5
|
+
# convenient way of ensuring we're using the correct gemset, or initializing it
|
6
|
+
# if need be.
|
7
|
+
#
|
8
|
+
|
9
|
+
ruby_name="ruby-1.9.2"
|
10
|
+
gemset_name="farmer"
|
11
|
+
environment_id="$ruby_name@$gemset_name"
|
12
|
+
|
13
|
+
#
|
14
|
+
# First we attempt to load the desired environment directly from the environment
|
15
|
+
# file, this is very fast and efficient compared to running through the entire
|
16
|
+
# CLI and selector. If you want feedback on which environment was used then
|
17
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
18
|
+
#
|
19
|
+
|
20
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
21
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] ; then
|
22
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
23
|
+
else
|
24
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
25
|
+
rvm --create use "$environment_id"
|
26
|
+
fi
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 James F. Herdman
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Beanstalk Farmer
|
2
|
+
|
3
|
+
Farmer is a simple library that helps you manage your Beanstalk job queue. Its
|
4
|
+
API is heavily inspired by [Stalker](https://github.com/han/stalker), to help
|
5
|
+
you get done as quickly as possible, but whilst allowing you to use plain old
|
6
|
+
Ruby classes to ensure testability.
|
7
|
+
|
8
|
+
## Setting up Jobs
|
9
|
+
|
10
|
+
Jobs are handled by any object whose instances respond to a `#call` method, à
|
11
|
+
la Rack (e.g. `Proc`s, `procs`s). The payload of the job will be passed into
|
12
|
+
the arguments of this method. Bear in mind that the payload for a Beanstalk job
|
13
|
+
is de-serialized JSON.
|
14
|
+
|
15
|
+
### Example
|
16
|
+
|
17
|
+
BeanstalkFarmer::Runner.register_handlers do
|
18
|
+
# You can use objects
|
19
|
+
tube 'email.welcome_message', WelcomeMessageHandler
|
20
|
+
|
21
|
+
# You can use Procs
|
22
|
+
tube 'push.message', Proc.new { |args| PushService.send_message(args) }
|
23
|
+
end
|
24
|
+
|
25
|
+
BeanstalkFarmer::Runner.run!
|
26
|
+
|
27
|
+
## Configuration
|
28
|
+
|
29
|
+
Easy as pie! See `BeanstalkFarmer::Config` for all available options.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'bundler/gem_helper'
|
4
|
+
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
t.rspec_opts = %w[--color]
|
11
|
+
t.verbose = false
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'yard'
|
15
|
+
require 'yard/rake/yardoc_task'
|
16
|
+
|
17
|
+
YARD::Rake::YardocTask.new do |t|
|
18
|
+
# Options managed by .yardopts
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$:.unshift(lib) unless $:.include?(lib)
|
4
|
+
|
5
|
+
require 'beanstalk_farmer/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'beanstalk_farmer'
|
9
|
+
s.version = BeanstalkFarmer::VERSION
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = ['James Herdamn']
|
12
|
+
s.email = ['james.herdman@me.com']
|
13
|
+
s.homepage = 'https://github.com/jherdman/beanstalk_farmer'
|
14
|
+
s.summary = 'A nice little kit to manage a Beanstalk job queue'
|
15
|
+
s.description = 'Farmer is a nice little kit to manage a Beanstalk job queue'
|
16
|
+
|
17
|
+
s.required_rubygems_version = '>= 1.3.6'
|
18
|
+
s.rubyforge_project = 'farmer'
|
19
|
+
|
20
|
+
s.add_dependency 'beanstalk-client', ['~> 1.1.0']
|
21
|
+
s.add_dependency 'multi_json', ['~> 1.0.2']
|
22
|
+
|
23
|
+
s.add_development_dependency 'rake', ['~> 0.8.7']
|
24
|
+
s.add_development_dependency 'rspec', ['~> 2.6']
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
28
|
+
s.require_path = ['lib']
|
29
|
+
end
|
data/example/echo.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
##
|
2
|
+
# This is a simple echo worker example. To use this, you must have the Farmer
|
3
|
+
# gem installed, or built.
|
4
|
+
|
5
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
6
|
+
require 'farmer'
|
7
|
+
|
8
|
+
BeanstalkFarmer::Runner.register_handlers do
|
9
|
+
tube 'echo.small_proc', proc { |args| BeanstalkFarmer.logger.info "Small proc says: #{args.inspect}" }
|
10
|
+
tube 'echo.big_proc', Proc.new { |args| BeanstalkFarmer.logger.info "Big Proc says: #{args.inspect}" }
|
11
|
+
end
|
12
|
+
|
13
|
+
trap 'INT' do
|
14
|
+
puts "\rExiting"
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
|
18
|
+
BeanstalkFarmer::Runner.run!
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module BeanstalkFarmer
|
2
|
+
# Manages configuration settings and defaults.
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# BeanstalkFarmer::Config.logger = Rails.logger
|
6
|
+
# BeanstalkFarmer::Config.json_engine = :yajl
|
7
|
+
module Config
|
8
|
+
extend self
|
9
|
+
|
10
|
+
DEFAULT_HOST = '0.0.0.0'
|
11
|
+
DEFAULT_PORT = 11300
|
12
|
+
|
13
|
+
attr_accessor :settings
|
14
|
+
@settings = {}
|
15
|
+
|
16
|
+
# Define a configuration option with a default.
|
17
|
+
#
|
18
|
+
# @example Define the option.
|
19
|
+
# Config.option(:persist_in_safe_mode, :default => false)
|
20
|
+
#
|
21
|
+
# @param [Symbol] name The name of the configuration option.
|
22
|
+
# @param [Hash] options Extras for the option.
|
23
|
+
#
|
24
|
+
# @option options [Object] :default The default value.
|
25
|
+
#
|
26
|
+
# @note Copied from Mongoid. Thank you!
|
27
|
+
#
|
28
|
+
# @private
|
29
|
+
def option(name, options = {})
|
30
|
+
define_method(name) do
|
31
|
+
settings.has_key?(name) ? settings[name] : options[:default]
|
32
|
+
end
|
33
|
+
define_method("#{name}=") { |value| settings[name] = value }
|
34
|
+
define_method("#{name}?") { send(name) }
|
35
|
+
end
|
36
|
+
|
37
|
+
option :host, default: DEFAULT_HOST
|
38
|
+
|
39
|
+
option :port, default: DEFAULT_PORT
|
40
|
+
|
41
|
+
option :logger, default: ::Logger.new($stdout)
|
42
|
+
|
43
|
+
# @see MultiJson.default_engine
|
44
|
+
def json_engine
|
45
|
+
MultiJson.engine
|
46
|
+
end
|
47
|
+
|
48
|
+
# @see MultiJson.engine=
|
49
|
+
def json_engine=(json_engine)
|
50
|
+
MultiJson.engine = json_engine
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module BeanstalkFarmer
|
2
|
+
# @private Provides a clean slate object that we can safely instance eval on.
|
3
|
+
class DSL
|
4
|
+
def tube(tube_name, job_handler)
|
5
|
+
Job.handler_pool[tube_name] = job_handler
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.execute!(&block)
|
9
|
+
new.tap do |dsl|
|
10
|
+
dsl.instance_eval(&block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module BeanstalkFarmer
|
5
|
+
class Job
|
6
|
+
# Raised when a job cannot complete in time
|
7
|
+
class OutOfTimeError < Timeout::Error; end
|
8
|
+
|
9
|
+
attr_accessor :name, :args, :job
|
10
|
+
|
11
|
+
# @param [Beanstalk::Job] job A Beanstalk job that has been reserved to be
|
12
|
+
# worked upon
|
13
|
+
def initialize(job)
|
14
|
+
self.job = job
|
15
|
+
set_name_and_arguments
|
16
|
+
end
|
17
|
+
|
18
|
+
# Performs work for this job
|
19
|
+
def work
|
20
|
+
# Stalker, the inspiration for this project, subtracted 1 from the job's
|
21
|
+
# TTR value. I'm not sure why at this point in time. Maybe to compensate
|
22
|
+
# for Job setup time.
|
23
|
+
Timeout.timeout(job.ttr) do
|
24
|
+
handler = self.class.handler_pool[name]
|
25
|
+
handler.call(args)
|
26
|
+
end
|
27
|
+
rescue Timeout::Error
|
28
|
+
raise OutOfTimeError, "#{name} could not finish in #{job.ttr} seconds"
|
29
|
+
ensure
|
30
|
+
job.delete
|
31
|
+
end
|
32
|
+
|
33
|
+
# A pool of job handlers that can work on jobs in our queue
|
34
|
+
#
|
35
|
+
# @param [Boolean] reset (false) When true, our collection of job handlers
|
36
|
+
# will be purged from memory
|
37
|
+
#
|
38
|
+
# @return [Hash] the collection of job handlers
|
39
|
+
def self.handler_pool(reset=false)
|
40
|
+
@pool = nil if reset
|
41
|
+
@pool ||= Hash.new
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def set_name_and_arguments
|
47
|
+
self.name, self.args = MultiJson.decode(job.body)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module BeanstalkFarmer
|
4
|
+
# Maps tube names to job handlers, and manages the run loop
|
5
|
+
class Runner
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
# @see Farmer::Job.handler_pool
|
9
|
+
def handler_pool
|
10
|
+
Job.handler_pool
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Farmer::BeanstalkService] a connection to the Beanstalk queue
|
14
|
+
def service
|
15
|
+
@service ||= Service.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Prepares tubes for watching
|
19
|
+
def prep_tubes
|
20
|
+
service.prep(handler_pool.keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Reserves a job, and works it
|
24
|
+
def reserve_and_work_job
|
25
|
+
job = service.reserve
|
26
|
+
job.work
|
27
|
+
end
|
28
|
+
|
29
|
+
# Runs a loop looking for jobs to reserve and run
|
30
|
+
def work_jobs
|
31
|
+
loop { reserve_and_work_job }
|
32
|
+
end
|
33
|
+
|
34
|
+
# @yield block A DSL to define jobs for your queue
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# Farmer::Runner.register_handlers do
|
38
|
+
# tube 'sms', proc { |args| puts args }
|
39
|
+
# tube 'mine', Proc.new { |args| puts args }
|
40
|
+
# end
|
41
|
+
def self.register_handlers(&block)
|
42
|
+
DSL.execute!(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Looks for jobs to reserve, and applies handlers to them
|
46
|
+
def self.run!
|
47
|
+
instance.prep_tubes
|
48
|
+
instance.work_jobs
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'beanstalk-client'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module BeanstalkFarmer
|
5
|
+
##
|
6
|
+
# Provides an abstraction against our Beanstalk client to buffer us against
|
7
|
+
# changes in their API.
|
8
|
+
#
|
9
|
+
# @todo Abstract all 3rd party errors
|
10
|
+
# @todo Catch all 3rd party errors, and re-raise our own
|
11
|
+
#
|
12
|
+
# @private
|
13
|
+
class Service
|
14
|
+
DEFAULT_DELAY = 0
|
15
|
+
DEFAULT_PRIORITY = 65536
|
16
|
+
DEFAULT_TTR = 120
|
17
|
+
|
18
|
+
# Raised when we cannot connect to the Beanstalk queue
|
19
|
+
class NotConnectedError < Beanstalk::NotConnected; end
|
20
|
+
|
21
|
+
attr_accessor :uri
|
22
|
+
|
23
|
+
# Sets the URI of your Beanstalk queue.
|
24
|
+
#
|
25
|
+
# @param [String] host (DEFAULT_HOST) the host name to of your Beanstalk queue
|
26
|
+
#
|
27
|
+
# @param [String] port (DEFAULT_PORT) the port that your Beanstalk queue is on
|
28
|
+
def initialize(host=Config.host, port=Config.port)
|
29
|
+
self.uri = build_uri(host, port)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Beanstalk::Pool] a connection to Beanstalk
|
33
|
+
def connection
|
34
|
+
@connection ||= Beanstalk::Pool.new([uri])
|
35
|
+
end
|
36
|
+
|
37
|
+
# Closes the Beanstalk connection
|
38
|
+
#
|
39
|
+
# @return [nil] Nothing. Absolutely nothing.
|
40
|
+
def close
|
41
|
+
@connection.close
|
42
|
+
@connection = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [Array<String>] tube_names The tube names to be watched
|
46
|
+
def prep(tube_names)
|
47
|
+
watch_tubes(tube_names)
|
48
|
+
ignore_unwatched_tubes(tube_names)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [Array<String>] tube_names The tube names to be watched
|
52
|
+
def watch_tubes(tube_names)
|
53
|
+
tube_names.each { |tube_name| connection.watch(tube_name) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Ignores any tubes that aren't of interest, excluding the default tube.
|
57
|
+
def ignore_unwatched_tubes(watched_tube_names)
|
58
|
+
connection.list_tubes_watched.values do |tube_name|
|
59
|
+
connection.ignore(tube_name) unless watched_tube_names.include?(tube_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Helper method. Used when testing to see if our queue works
|
64
|
+
def enqueue(tube_name, args={})
|
65
|
+
connection.use(tube_name)
|
66
|
+
connection.put MultiJson.encode([tube_name, args]), DEFAULT_PRIORITY, DEFAULT_DELAY, DEFAULT_TTR
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Integer, nil] timeout When nil, a job is reserved indefinitely,
|
70
|
+
# otherwise for the number of seconds provided
|
71
|
+
#
|
72
|
+
# @return [Farmer::Job] a Farmer Job to work
|
73
|
+
def reserve(timeout=nil)
|
74
|
+
Job.new(connection.reserve(timeout))
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def build_uri(host, port)
|
80
|
+
[host, port].join(':')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/farmer.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Automatically loads classes as needed, provides logger, etc.
|
5
|
+
module BeanstalkFarmer
|
6
|
+
autoload :Config, 'beanstalk_farmer/config'
|
7
|
+
autoload :Service, 'beanstalk_farmer/service'
|
8
|
+
autoload :DSL, 'beanstalk_farmer/dsl'
|
9
|
+
autoload :Job, 'beanstalk_farmer/job'
|
10
|
+
autoload :Runner, 'beanstalk_farmer/runner'
|
11
|
+
|
12
|
+
# @param [String] host The host name where your beanstalkd connection is
|
13
|
+
# located. Defaults to `DEFAULT_PORT`.
|
14
|
+
#
|
15
|
+
# @param [#to_s] port The port number where your beanstalkd connection may
|
16
|
+
# be accessed. Defaults to `DEFAULT_PORT`.
|
17
|
+
def self.connection(host=Config.host, port=Config.port)
|
18
|
+
@service ||= Service.new(host, port)
|
19
|
+
@service.connection
|
20
|
+
end
|
21
|
+
|
22
|
+
# Close Beanstalk connection
|
23
|
+
def self.close_connection
|
24
|
+
@service.close
|
25
|
+
@service = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Logger] the logger, defaulting to a STDOUT logger
|
29
|
+
def self.logger
|
30
|
+
@logger ||= Config.logger
|
31
|
+
@logger
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BeanstalkFarmer::Config do
|
4
|
+
describe ".option" do
|
5
|
+
before(:all) do
|
6
|
+
described_class.option(:test_setting, default: true)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "creates a getter for an option" do
|
10
|
+
described_class.should respond_to(:test_setting)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "creates a setter for the option" do
|
14
|
+
described_class.should respond_to(:test_setting=)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "creates a conditional for the option" do
|
18
|
+
described_class.should respond_to(:test_setting?)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "allows the setting of a default value" do
|
22
|
+
described_class.test_setting.should == true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BeanstalkFarmer::DSL do
|
4
|
+
describe '#tube' do
|
5
|
+
let(:tube_name) { 'my_tube' }
|
6
|
+
let(:handler) { Proc.new { |args| args } }
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
BeanstalkFarmer::Job.handler_pool(true)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'correctly adds job handlers for some tube' do
|
13
|
+
subject.tube(tube_name, handler)
|
14
|
+
BeanstalkFarmer::Job.handler_pool[tube_name].should == handler
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BeanstalkFarmer::Job do
|
4
|
+
let(:job) {
|
5
|
+
double('job', body: "[\"bacon\",{\"msg\":\"Hello\"}]", name: 'bacon', args: { 'msg' => 'Hello' }, ttr: 56, delete: true)
|
6
|
+
}
|
7
|
+
|
8
|
+
describe '.new' do
|
9
|
+
it 'sets the job' do
|
10
|
+
described_class.new(job).job.should == job
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'decodes the name of the job' do
|
14
|
+
described_class.new(job).name.should == job.name
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'decodes the arguments for the job' do
|
18
|
+
described_class.new(job).args.should == job.args
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.handler_pool' do
|
23
|
+
it 'is a Hash of job handlers' do
|
24
|
+
described_class.handler_pool.should be_a_kind_of(Hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'can reset the Hash of job hanlders' do
|
28
|
+
described_class.handler_pool['bacon'] = lambda { |args| args }
|
29
|
+
described_class.handler_pool(true)
|
30
|
+
described_class.handler_pool.should be_empty
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#work' do
|
35
|
+
let(:handler) { proc { |args| args } }
|
36
|
+
|
37
|
+
subject { described_class.new(job) }
|
38
|
+
|
39
|
+
before(:each) do
|
40
|
+
described_class.handler_pool[job.name] = handler
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'performs some work for the handler' do
|
44
|
+
subject.work.should == job.args
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'times out if the job has run out of time' do
|
48
|
+
Timeout.should_receive(:timeout).with(job.ttr) { raise Timeout::Error }
|
49
|
+
expect { subject.work }.to raise_error(BeanstalkFarmer::Job::OutOfTimeError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'deletes the job' do
|
53
|
+
subject.job.should_receive(:delete)
|
54
|
+
subject.work
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BeanstalkFarmer::Runner do
|
4
|
+
subject { described_class.instance }
|
5
|
+
|
6
|
+
it 'is a singleton' do
|
7
|
+
described_class.included_modules.should include(Singleton)
|
8
|
+
end
|
9
|
+
|
10
|
+
its(:service) { should be_a_kind_of(BeanstalkFarmer::Service) }
|
11
|
+
|
12
|
+
describe '#handler_pool' do
|
13
|
+
it 'is the job handler pool' do
|
14
|
+
BeanstalkFarmer::Job.should_receive(:handler_pool)
|
15
|
+
subject.handler_pool
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#prep_tubes' do
|
20
|
+
it 'prepares tubes for watching' do
|
21
|
+
subject.service.should_receive(:prep).with(subject.handler_pool.keys)
|
22
|
+
subject.prep_tubes
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#reserve_and_work_job' do
|
27
|
+
let(:job) { double('Farmer::Job', work: true) }
|
28
|
+
|
29
|
+
before(:each) do
|
30
|
+
subject.stub_chain(:service, :reserve) { job }
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'reserves a job' do
|
34
|
+
subject.service.should_receive(:reserve) { job }
|
35
|
+
subject.reserve_and_work_job
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'works the job' do
|
39
|
+
job.should_receive(:work)
|
40
|
+
subject.reserve_and_work_job
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '.run!' do
|
45
|
+
before(:each) do
|
46
|
+
subject.stub(:prep_tubes)
|
47
|
+
subject.stub(:work_jobs)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'prepares tubes for watching' do
|
51
|
+
subject.should_receive(:prep_tubes)
|
52
|
+
described_class.run!
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'works jobs' do
|
56
|
+
subject.should_receive(:work_jobs)
|
57
|
+
described_class.run!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BeanstalkFarmer::Service do
|
4
|
+
describe '.new' do
|
5
|
+
it 'sets the `uri` attribute' do
|
6
|
+
described_class.new.uri.should =~ /\A#{BeanstalkFarmer::Config.host}:#{BeanstalkFarmer::Config.port}\z/
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#connection' do
|
11
|
+
after(:each) do
|
12
|
+
subject.close
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'establishes a connection to Beanstalk' do
|
16
|
+
expect {
|
17
|
+
subject.connection.put 'Hello'
|
18
|
+
}.to_not raise_error(BeanstalkFarmer::Service::NotConnectedError)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#close' do
|
23
|
+
it 'closes the Beanstalk connection' do
|
24
|
+
connection = subject.connection # ensure we're connected
|
25
|
+
subject.close
|
26
|
+
connection.open_connections.should be_empty
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#watch_tubes' do
|
31
|
+
it 'adds a tube to the list of watched tubes' do
|
32
|
+
subject.watch_tubes(%w[bacon])
|
33
|
+
subject.connection.list_tubes_watched.should have_tube_named('bacon')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#ignore_unwatched_tubes' do
|
38
|
+
it 'ignores tubes that are not being watched' do
|
39
|
+
pending 'a creative way to test this problem'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#reserve' do
|
44
|
+
let(:job) {
|
45
|
+
double('job', body: "[\"bacon\",{\"msg\":\"Hello\"}]", name: 'bacon', args: { 'msg' => 'Hello' }, ttr: 56)
|
46
|
+
}
|
47
|
+
|
48
|
+
before(:each) do
|
49
|
+
subject.connection.stub(:reserve) { job }
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'builds a Farmer::Job' do
|
53
|
+
subject.reserve.should be_a_kind_of(BeanstalkFarmer::Job)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'reserves a job indefinitely' do
|
57
|
+
subject.connection.should_receive(:reserve).with(nil) { job }
|
58
|
+
subject.reserve
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'reserves a job for the specified number of seconds' do
|
62
|
+
subject.connection.should_receive(:reserve).with(30) { job }
|
63
|
+
subject.reserve(30)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BeanstalkFarmer do
|
4
|
+
describe '.connection' do
|
5
|
+
it 'initializes a new Beanstalk connection' do
|
6
|
+
described_class::Service.any_instance.should_receive(:connection)
|
7
|
+
described_class.connection
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.close_connection' do
|
12
|
+
before(:each) do
|
13
|
+
described_class::Service.any_instance.stub(:connection)
|
14
|
+
described_class::Service.any_instance.stub(:close)
|
15
|
+
|
16
|
+
described_class.connection # sets up a connection
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'closes all connections to Beanstalk' do
|
20
|
+
described_class::Service.any_instance.should_receive(:close)
|
21
|
+
described_class.close_connection
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
its(:logger) { should be_a_kind_of(Logger) }
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: beanstalk_farmer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James Herdamn
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-16 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: beanstalk-client
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.1.0
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_json
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.0.2
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rake
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.8.7
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "2.6"
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
description: Farmer is a nice little kit to manage a Beanstalk job queue
|
61
|
+
email:
|
62
|
+
- james.herdman@me.com
|
63
|
+
executables: []
|
64
|
+
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
extra_rdoc_files: []
|
68
|
+
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- .rspec
|
72
|
+
- .rvmrc
|
73
|
+
- .yardopts
|
74
|
+
- Gemfile
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- beanstalk_farmer.gemspec
|
79
|
+
- example/echo.rb
|
80
|
+
- lib/beanstalk_farmer/config.rb
|
81
|
+
- lib/beanstalk_farmer/dsl.rb
|
82
|
+
- lib/beanstalk_farmer/job.rb
|
83
|
+
- lib/beanstalk_farmer/runner.rb
|
84
|
+
- lib/beanstalk_farmer/service.rb
|
85
|
+
- lib/beanstalk_farmer/version.rb
|
86
|
+
- lib/farmer.rb
|
87
|
+
- spec/beanstalk_farmer/config_spec.rb
|
88
|
+
- spec/beanstalk_farmer/dsl_spec.rb
|
89
|
+
- spec/beanstalk_farmer/job_spec.rb
|
90
|
+
- spec/beanstalk_farmer/runner_spec.rb
|
91
|
+
- spec/beanstalk_farmer/service_spec.rb
|
92
|
+
- spec/beastalk_farmer_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
- spec/support/have_tube_named.rb
|
95
|
+
has_rdoc: true
|
96
|
+
homepage: https://github.com/jherdman/beanstalk_farmer
|
97
|
+
licenses: []
|
98
|
+
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
|
102
|
+
require_paths:
|
103
|
+
- - lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: -1506406479877648838
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 1.3.6
|
119
|
+
requirements: []
|
120
|
+
|
121
|
+
rubyforge_project: farmer
|
122
|
+
rubygems_version: 1.6.2
|
123
|
+
signing_key:
|
124
|
+
specification_version: 3
|
125
|
+
summary: A nice little kit to manage a Beanstalk job queue
|
126
|
+
test_files: []
|
127
|
+
|