stalk_climber 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +19 -0
- data/Gemfile +10 -0
- data/LICENSE +20 -0
- data/README.md +15 -0
- data/Rakefile +9 -0
- data/lib/stalk_climber/climber.rb +38 -0
- data/lib/stalk_climber/connection.rb +143 -0
- data/lib/stalk_climber/connection_pool.rb +30 -0
- data/lib/stalk_climber/job.rb +179 -0
- data/lib/stalk_climber/lazy_enumerable.rb +15 -0
- data/lib/stalk_climber/version.rb +3 -0
- data/lib/stalk_climber.rb +9 -0
- data/stalk_climber.gemspec +25 -0
- data/test/test_helper.rb +24 -0
- data/test/unit/climber_test.rb +59 -0
- data/test/unit/connection_pool_test.rb +49 -0
- data/test/unit/connection_test.rb +192 -0
- data/test/unit/job_test.rb +166 -0
- metadata +119 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 1.9.2
|
5
|
+
- 1.9.3
|
6
|
+
- 2.0.0
|
7
|
+
|
8
|
+
before_install:
|
9
|
+
- sudo apt-get update -qq
|
10
|
+
- sudo apt-get install -qq beanstalkd
|
11
|
+
- sudo sed -i 's/#START=yes/START=yes/' /etc/default/beanstalkd
|
12
|
+
- sudo service beanstalkd start
|
13
|
+
- cat /etc/init.d/beanstalkd | sed 's/NAME=beanstalkd/NAME=beanstalkd2/' | sudo tee /etc/init.d/beanstalkd2 2>&1>/dev/null
|
14
|
+
- cat /etc/default/beanstalkd | sed 's/11300/11301/' | sudo tee /etc/default/beanstalkd2 2>&1>/dev/null
|
15
|
+
- sudo chmod +x /etc/init.d/beanstalkd2
|
16
|
+
- sudo service beanstalkd2 start
|
17
|
+
|
18
|
+
env:
|
19
|
+
- BEANSTALK_ADDRESSES='beanstalk://localhost:11300,beanstalk://localhost:11301'
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Freewrite.org
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
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, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# StalkClimber
|
2
|
+
[![Build Status](https://secure.travis-ci.org/freewrite/stalk_climber.png)](http://travis-ci.org/freewrite/stalk_climber)
|
3
|
+
[![Dependency Status](https://gemnasium.com/freewrite/stalk_climber.png)](https://gemnasium.com/freewrite/stalk_climber)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/freewrite/stalk_climber/badge.png?branch=master)](https://coveralls.io/r/freewrite/stalk_climber)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/freewrite/stalk_climber.png)](https://codeclimate.com/github/freewrite/stalk_climber)
|
6
|
+
|
7
|
+
StalkClimber is a Ruby library allowing improved sequential access to Beanstalk via a job cache.
|
8
|
+
|
9
|
+
## Contributing
|
10
|
+
|
11
|
+
1. Fork it
|
12
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
13
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
14
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
15
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module StalkClimber
|
2
|
+
class Climber
|
3
|
+
include LazyEnumerable
|
4
|
+
|
5
|
+
attr_accessor :beanstalk_addresses, :test_tube
|
6
|
+
attr_reader :cache
|
7
|
+
|
8
|
+
# Returns or creates a ConnectionPool from beanstalk_addresses
|
9
|
+
def connection_pool
|
10
|
+
return @connection_pool unless @connection_pool.nil?
|
11
|
+
if self.beanstalk_addresses.nil?
|
12
|
+
raise RuntimeError, 'beanstalk_addresses must be set in order to establish a connection'
|
13
|
+
end
|
14
|
+
@connection_pool = ConnectionPool.new(self.beanstalk_addresses)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
# Perform a threaded climb across all connections in the connection pool.
|
19
|
+
# An instance of Job is yielded to +block+
|
20
|
+
def climb(&block)
|
21
|
+
threads = []
|
22
|
+
self.connection_pool.connections.each do |connection|
|
23
|
+
threads << Thread.new { connection.each(&block) }
|
24
|
+
end
|
25
|
+
threads.each(&:join)
|
26
|
+
return
|
27
|
+
end
|
28
|
+
alias_method :each, :climb
|
29
|
+
|
30
|
+
|
31
|
+
# Creates a new Climber instance, optionally yielding the instance
|
32
|
+
# if a block is given
|
33
|
+
def initialize
|
34
|
+
yield(self) if block_given?
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module StalkClimber
|
2
|
+
class Connection < Beaneater::Connection
|
3
|
+
include LazyEnumerable
|
4
|
+
|
5
|
+
DEFAULT_TUBE = 'stalk_climber'
|
6
|
+
PROBE_TRANSMISSION = "put 4294967295 0 300 2\r\n{}"
|
7
|
+
|
8
|
+
attr_accessor :test_tube
|
9
|
+
attr_reader :max_climbed_job_id, :min_climbed_job_id
|
10
|
+
|
11
|
+
|
12
|
+
# Returns or creates a Hash used for caching jobs by ID
|
13
|
+
def cache
|
14
|
+
return @cache ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
# Resets the job cache and reinitializes the min and max climbed job ids
|
19
|
+
def clear_cache
|
20
|
+
@cache = nil
|
21
|
+
@min_climbed_job_id = Float::INFINITY
|
22
|
+
@max_climbed_job_id = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Handles job enumeration in descending ID order, passing an instance of Job to +block+ for
|
27
|
+
# each existing job on the beanstalk server. Jobs are enumerated in three phases. Jobs between
|
28
|
+
# max_job_id and the max_climbed_job_id are pulled from beanstalk, cached, and given to
|
29
|
+
# +block+. Jobs that have already been cached are yielded to +block+ if they still exist,
|
30
|
+
# otherwise they are deleted from the cache. Finally, jobs between min_climbed_job_id and 1
|
31
|
+
# are pulled from beanstalk, cached, and given to +block+.
|
32
|
+
# Connection#each fulfills Enumberable contract, allowing connection to behave as an Enumerable.
|
33
|
+
def each(&block)
|
34
|
+
climb(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Initializes a new Connection to the beanstalk +address+ provided and
|
39
|
+
# configures the Connection to only use the configured test_tube for
|
40
|
+
# all transmissions.
|
41
|
+
# Optionally yields the instance if a block is given. The instance is yielded
|
42
|
+
# prior to test_tube configuration to allow the test_tube to be configured.
|
43
|
+
def initialize(address)
|
44
|
+
super
|
45
|
+
self.test_tube = DEFAULT_TUBE
|
46
|
+
clear_cache
|
47
|
+
yield(self) if block_given?
|
48
|
+
[
|
49
|
+
"use #{self.test_tube}",
|
50
|
+
"watch #{self.test_tube}",
|
51
|
+
'ignore default',
|
52
|
+
].each do |transmission|
|
53
|
+
transmit(transmission)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Determintes the max job ID of the connection by inserting a job into the test tube
|
59
|
+
# and immediately deleting it. Before returning the max ID, the max ID is used to
|
60
|
+
# update the max_climbed_job_id (if sequentual) and possibly invalidate the cache.
|
61
|
+
# The cache will be invalidated if the max ID is less than any known IDs since
|
62
|
+
# new job IDs should always increment unless there's been a change in server state.
|
63
|
+
def max_job_id
|
64
|
+
job = Job.new(transmit(PROBE_TRANSMISSION))
|
65
|
+
job.delete
|
66
|
+
update_climbed_job_ids_from_max_id(job.id)
|
67
|
+
return job.id
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Safe form of with_job!, yields a Job instance to +block+ for the specified +job_id+.
|
72
|
+
# If the job does not exist, the error is caught and nil is passed to +block+ instead.
|
73
|
+
def with_job(job_id, &block)
|
74
|
+
begin
|
75
|
+
with_job!(job_id, &block)
|
76
|
+
rescue Beaneater::NotFoundError
|
77
|
+
block.call(nil)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Yields a Job instance to +block+ for the specified +job_id+.
|
83
|
+
# If the job does not exist, a Beaneater::NotFoundError will bubble up from Beaneater.
|
84
|
+
def with_job!(job_id, &block)
|
85
|
+
job = Job.new(transmit("peek #{job_id}"))
|
86
|
+
block.call(job)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
# Helper method, similar to with_job, that retrieves the job identified by
|
93
|
+
# +job_id+, caches it, and updates counters before yielding the job.
|
94
|
+
# If the job does not exist, +block+ is not called and nothing is cached,
|
95
|
+
# however counters will be updated.
|
96
|
+
def cache_job_and_yield(job_id, &block)
|
97
|
+
with_job(job_id) do |job|
|
98
|
+
self.cache[job_id] = job unless job.nil?
|
99
|
+
@min_climbed_job_id = job_id if job_id < @min_climbed_job_id
|
100
|
+
@max_climbed_job_id = job_id if job_id > @max_climbed_job_id
|
101
|
+
yield(job) unless job.nil?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# Handles job enumeration. See Connection#each for more information.
|
107
|
+
def climb(&block)
|
108
|
+
max_id = max_job_id
|
109
|
+
|
110
|
+
initial_cached_jobs = cache.values_at(*cache.keys.sort.reverse)
|
111
|
+
|
112
|
+
max_id.downto(self.max_climbed_job_id + 1) do |job_id|
|
113
|
+
cache_job_and_yield(job_id, &block)
|
114
|
+
end
|
115
|
+
|
116
|
+
initial_cached_jobs.each do |job|
|
117
|
+
if job.exists?
|
118
|
+
yield job
|
119
|
+
else
|
120
|
+
self.cache.delete(job.id)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
([self.min_climbed_job_id - 1, max_id].min).downto(1) do |job_id|
|
125
|
+
cache_job_and_yield(job_id, &block)
|
126
|
+
end
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# Uses +new_max_id+ to update the max_climbed_job_id (if sequentual) and possibly invalidate
|
132
|
+
# the cache. The cache will be invalidated if +new_max_id+ is less than any known IDs since
|
133
|
+
# new job IDs should always increment unless there's been a change in server state.
|
134
|
+
def update_climbed_job_ids_from_max_id(new_max_id)
|
135
|
+
if @max_climbed_job_id > 0 && @max_climbed_job_id == new_max_id - 1
|
136
|
+
@max_climbed_job_id = new_max_id
|
137
|
+
elsif new_max_id < @max_climbed_job_id
|
138
|
+
clear_cache
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module StalkClimber
|
2
|
+
class ConnectionPool < Beaneater::Pool
|
3
|
+
|
4
|
+
class InvalidURIScheme < RuntimeError; end
|
5
|
+
|
6
|
+
attr_reader :addresses
|
7
|
+
|
8
|
+
# Constructs a Beaneater::Pool from a less strict URL
|
9
|
+
# +url+ can be a string i.e 'localhost:11300' or an array of addresses.
|
10
|
+
def initialize(addresses = nil)
|
11
|
+
@addresses = Array(parse_addresses(addresses) || host_from_env || Beaneater.configuration.beanstalkd_url)
|
12
|
+
@connections = @addresses.map { |address| Connection.new(address) }
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# Parses the given url into a collection of beanstalk addresses
|
19
|
+
def parse_addresses(addresses)
|
20
|
+
return if addresses.empty?
|
21
|
+
uris = addresses.is_a?(Array) ? addresses.dup : addresses.split(/[\s,]+/)
|
22
|
+
uris.map! do |uri_string|
|
23
|
+
uri = URI.parse(uri_string)
|
24
|
+
raise(InvalidURIScheme, "Invalid beanstalk URI: #{uri_string}") unless uri.scheme == 'beanstalk'
|
25
|
+
"#{uri.host}:#{uri.port || 11300}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module StalkClimber
|
2
|
+
class Job
|
3
|
+
|
4
|
+
STATS_ATTRIBUTES = %w[age buries delay kicks pri releases reserves state time-left timeouts ttr tube]
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
STATS_ATTRIBUTES.each do |method_name|
|
8
|
+
define_method method_name do |force_refresh = false|
|
9
|
+
return stats(force_refresh)[method_name]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
# Returns or fetches the body of the job obtained via the peek command
|
15
|
+
def body
|
16
|
+
return @body ||= connection.transmit("peek #{id}")[:body]
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Returns the connection provided by the job data given to the initialize method
|
21
|
+
def connection
|
22
|
+
return @connection
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Deletes the job from beanstalk. If the job is not found it is assumed that it
|
27
|
+
# has already been otherwise deleted.
|
28
|
+
def delete
|
29
|
+
begin
|
30
|
+
@connection.transmit("delete #{id}")
|
31
|
+
rescue Beaneater::NotFoundError
|
32
|
+
end
|
33
|
+
@status = 'DELETED'
|
34
|
+
@stats = nil
|
35
|
+
@body = nil
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Determines if a job exists by retrieving stats for the job. If Beaneater can't find
|
41
|
+
# the jobm then it does not exist and false is returned. The stats command is used
|
42
|
+
# because it will return a response of a near constant size, whereas, depending on
|
43
|
+
# the job, the peek command could return a much larger response. Rather than waste
|
44
|
+
# the trip to the server, stats are updated each time the method is called.
|
45
|
+
def exists?
|
46
|
+
begin
|
47
|
+
stats(:force_refresh)
|
48
|
+
return true
|
49
|
+
rescue Beaneater::NotFoundError
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Initializes a Job instance using +job_data+ which should be the Beaneater response to either
|
56
|
+
# a put, peek, or stats-job command. Other Beaneater responses are not supported.
|
57
|
+
#
|
58
|
+
# No single beanstalk command provides all the data an instance might need, so as more
|
59
|
+
# information is required, additional calls are made to beanstalk. For example, accessing both
|
60
|
+
# a job's tube and its body requires both a peek and stats-job call.
|
61
|
+
#
|
62
|
+
# Put provides only the ID of the job and as such yields the least informed instance. Both a
|
63
|
+
# peek and stats-job call may be required to retrieve anything but the ID of the instance
|
64
|
+
#
|
65
|
+
# Peek provides the ID and body of the job. A stats-job call may be required to access anything
|
66
|
+
# but the ID or body of the job.
|
67
|
+
#
|
68
|
+
# Stats-job provides the most information about the job, but lacks the crtical component of the
|
69
|
+
# job body. As such, a peek call would be required to access the body of the job.
|
70
|
+
def initialize(job_data)
|
71
|
+
case job_data[:status]
|
72
|
+
when 'INSERTED' # put
|
73
|
+
@id = job_data[:id].to_i
|
74
|
+
@body = @stats = nil
|
75
|
+
when 'FOUND' # peek
|
76
|
+
@id = job_data[:id].to_i
|
77
|
+
@body = job_data[:body]
|
78
|
+
@stats = nil
|
79
|
+
when 'OK' # stats-job
|
80
|
+
@body = nil
|
81
|
+
@stats = job_data.delete(:body)
|
82
|
+
@id = @stats.delete('id').to_i
|
83
|
+
else
|
84
|
+
raise RuntimeError, "Unexpected job status: #{job_data[:status]}"
|
85
|
+
end
|
86
|
+
@status = job_data[:status]
|
87
|
+
@connection = job_data[:connection]
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# Returns or retrieves stats for the job. Optionally, a retrieve may be forced
|
92
|
+
# by passing a non-false value for +force_refresh+
|
93
|
+
def stats(force_refresh = false)
|
94
|
+
return @stats unless @stats.nil? || force_refresh
|
95
|
+
@stats = connection.transmit("stats-job #{id}")[:body]
|
96
|
+
@stats.delete('id')
|
97
|
+
return @stats
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# :method: age
|
102
|
+
# Retrieves the age of the job from the job's stats. Passing a non-false value for
|
103
|
+
# +force_refresh+ will force retrieval of updated stats for the job
|
104
|
+
# :call-seq:
|
105
|
+
# age(force_refresh = false)
|
106
|
+
|
107
|
+
# :method: buries
|
108
|
+
# Retrieves the number of times the job has been buried from the job's stats. Passing
|
109
|
+
# a non-false value for +force_refresh+ will force retrieval of updated stats for the job
|
110
|
+
# :call-seq:
|
111
|
+
# buries(force_refresh = false)
|
112
|
+
|
113
|
+
# :method: delay
|
114
|
+
# Retrieves the the remaining delay before the job is ready from the job's stats. Passing
|
115
|
+
# a non-false value for +force_refresh+ will force retrieval of updated stats for the job
|
116
|
+
# :call-seq:
|
117
|
+
# delay(force_refresh = false)
|
118
|
+
|
119
|
+
# :method: kicks
|
120
|
+
# Retrieves the number of times the job has been kicked from the job's stats. Passing
|
121
|
+
# a non-false value for +force_refresh+ will force retrieval of updated stats for the job
|
122
|
+
# :call-seq:
|
123
|
+
# kicks(force_refresh = false)
|
124
|
+
|
125
|
+
# :method: pri
|
126
|
+
# Retrieves the priority of the job from the job's stats. Passing a non-false value for
|
127
|
+
# +force_refresh+ will force retrieval of updated stats for the job
|
128
|
+
# :call-seq:
|
129
|
+
# pri(force_refresh = false)
|
130
|
+
|
131
|
+
# :method: releases
|
132
|
+
# Retrieves the number of times the job has been released from the job's stats. Passing
|
133
|
+
# a non-false value for +force_refresh+ will force retrieval of updated stats for the job
|
134
|
+
# :call-seq:
|
135
|
+
# releases(force_refresh = false)
|
136
|
+
|
137
|
+
# :method: reserves
|
138
|
+
# Retrieves the number of times the job has been reserved from the job's stats. Passing
|
139
|
+
# a non-false value for +force_refresh+ will force retrieval of updated stats for the job
|
140
|
+
# :call-seq:
|
141
|
+
# reserves(force_refresh = false)
|
142
|
+
|
143
|
+
# :method: state
|
144
|
+
# Retrieves the state of the job from the job's stats. Value will be one of "ready",
|
145
|
+
# "delayed", "reserved", or "buried". Passing a non-false value for +force_refresh+
|
146
|
+
# will force retrieval of updated stats for the job
|
147
|
+
# :call-seq:
|
148
|
+
# timeouts(force_refresh = false)
|
149
|
+
|
150
|
+
# :method: time-left
|
151
|
+
# Retrieves the number of seconds left until the server puts this job into the ready
|
152
|
+
# queue. This number is only meaningful if the job is reserved or delayed. If the job
|
153
|
+
# is reserved and this amount of time elapses before its state changes, it is considered
|
154
|
+
# to have timed out. Passing a non-false value for +force_refresh+ will force retrieval
|
155
|
+
# of updated stats for the job
|
156
|
+
# :call-seq:
|
157
|
+
# timeouts(force_refresh = false)
|
158
|
+
|
159
|
+
# :method: timeouts
|
160
|
+
# Retrieves the number of times the job has timed out from the job's stats. Passing
|
161
|
+
# a non-false value for +force_refresh+ will force retrieval of updated stats for the job
|
162
|
+
# :call-seq:
|
163
|
+
# timeouts(force_refresh = false)
|
164
|
+
|
165
|
+
# :method: ttr
|
166
|
+
# Retrieves the time to run for the job, the number of seconds a worker is allowed to work
|
167
|
+
# to run the job. Passing a non-false value for +force_refresh+ will force retrieval of
|
168
|
+
# updated stats for the job
|
169
|
+
# :call-seq:
|
170
|
+
# timeouts(force_refresh = false)
|
171
|
+
|
172
|
+
# :method: tube
|
173
|
+
# Retrieves the name of the tube that contains job. Passing a non-false value for
|
174
|
+
# +force_refresh+ will force retrieval of updated stats for the job
|
175
|
+
# :call-seq:
|
176
|
+
# timeouts(force_refresh = false)
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module StalkClimber
|
2
|
+
module LazyEnumerable
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def self.make_lazy(*methods)
|
6
|
+
methods.each do |method|
|
7
|
+
define_method method do |*args, &block|
|
8
|
+
lazy.public_send(method, *args, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
make_lazy(*(Enumerable.public_instance_methods - [:lazy]))
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module StalkClimber; end
|
2
|
+
|
3
|
+
require 'beaneater'
|
4
|
+
require 'stalk_climber/version'
|
5
|
+
require 'stalk_climber/lazy_enumerable'
|
6
|
+
require 'stalk_climber/connection'
|
7
|
+
require 'stalk_climber/connection_pool'
|
8
|
+
require 'stalk_climber/climber'
|
9
|
+
require 'stalk_climber/job'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'stalk_climber/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'stalk_climber'
|
8
|
+
spec.version = StalkClimber::VERSION
|
9
|
+
spec.authors = ['Freewrite.org']
|
10
|
+
spec.email = ['dev@freewrite.org']
|
11
|
+
spec.description = %q{Improved sequential access to Beanstalk}
|
12
|
+
spec.summary = %q{StalkClimber is a Ruby library allowing improved sequential access to Beanstalk via a job cache.}
|
13
|
+
spec.homepage = 'https://github.com/freewrite/stalk_climber'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
|
24
|
+
spec.add_dependency 'beaneater'
|
25
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'debugger'
|
2
|
+
require 'coveralls'
|
3
|
+
Coveralls.wear!
|
4
|
+
|
5
|
+
lib = File.expand_path('../../lib', __FILE__)
|
6
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'mocha/setup'
|
10
|
+
|
11
|
+
require 'stalk_climber'
|
12
|
+
|
13
|
+
BEANSTALK_ADDRESS = ENV['BEANSTALK_ADDRESS'] || 'beanstalk://localhost'
|
14
|
+
BEANSTALK_ADDRESSES = ENV['BEANSTALK_ADDRESSES'] || BEANSTALK_ADDRESS
|
15
|
+
|
16
|
+
class Test::Unit::TestCase
|
17
|
+
|
18
|
+
def seed_jobs(count = 5)
|
19
|
+
count.times.map do
|
20
|
+
StalkClimber::Job.new(@connection.transmit(StalkClimber::Connection::PROBE_TRANSMISSION))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ClimberTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_climb_caches_jobs_for_later_use
|
6
|
+
climber = StalkClimber::Climber.new do |c|
|
7
|
+
c.beanstalk_addresses = BEANSTALK_ADDRESSES
|
8
|
+
end
|
9
|
+
|
10
|
+
test_jobs = {}
|
11
|
+
climber.connection_pool.connections.each do |connection|
|
12
|
+
test_jobs[connection.address] = []
|
13
|
+
5.times.to_a.map! do
|
14
|
+
test_jobs[connection.address] << StalkClimber::Job.new(connection.transmit(StalkClimber::Connection::PROBE_TRANSMISSION))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
jobs = {}
|
19
|
+
climber.each do |job|
|
20
|
+
jobs[job.connection.address] ||= {}
|
21
|
+
jobs[job.connection.address][job.id] = job
|
22
|
+
end
|
23
|
+
|
24
|
+
climber.expects(:with_job).never
|
25
|
+
climber.each do |job|
|
26
|
+
assert_equal jobs[job.connection.address][job.id], job
|
27
|
+
end
|
28
|
+
|
29
|
+
climber.connection_pool.connections.each do |connection|
|
30
|
+
test_jobs[connection.address].map(&:delete)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def test_connection_pool_creates_a_connection_pool
|
36
|
+
climber = StalkClimber::Climber.new do |c|
|
37
|
+
c.beanstalk_addresses = 'beanstalk://localhost'
|
38
|
+
end
|
39
|
+
assert_kind_of StalkClimber::ConnectionPool, climber.connection_pool
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def test_connection_pool_raises_an_error_without_beanstalk_addresses
|
44
|
+
climber = StalkClimber::Climber.new
|
45
|
+
assert_raise RuntimeError do
|
46
|
+
climber.connection_pool
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def test_each_is_an_alias_for_climb
|
52
|
+
assert_equal(
|
53
|
+
StalkClimber::Climber.instance_method(:climb),
|
54
|
+
StalkClimber::Climber.instance_method(:each),
|
55
|
+
'Expected StalkClimber::Climber#each to be an alias for StalkClimber::Climber#climb'
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Tests adapted from Backburner::Connection tests
|
2
|
+
# https://github.com/nesquena/backburner/blob/master/test/connection_test.rb
|
3
|
+
|
4
|
+
require 'test_helper'
|
5
|
+
|
6
|
+
class ConnectionPoolTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_for_bad_url_it_should_raise_a_bad_url_error
|
9
|
+
assert_raises(StalkClimber::ConnectionPool::InvalidURIScheme) do
|
10
|
+
StalkClimber::ConnectionPool.new('fake://foo')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def test_for_delegated_methods_it_should_delegate_methods_to_beanstalk_connection
|
16
|
+
connection = StalkClimber::ConnectionPool.new('beanstalk://localhost')
|
17
|
+
assert_equal 'localhost', connection.connections.first.host
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def test_with_multiple_urls_it_should_support_array_of_connections
|
22
|
+
connection = StalkClimber::ConnectionPool.new(['beanstalk://localhost:11300','beanstalk://localhost'])
|
23
|
+
connections = connection.connections
|
24
|
+
assert_equal 2, connection.connections.size
|
25
|
+
assert_equal ['localhost:11300','localhost:11300'], connections.map(&:address)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def test_with_multiple_urls_it_should_support_single_string_with_commas
|
30
|
+
connection = StalkClimber::ConnectionPool.new('beanstalk://localhost:11300,beanstalk://localhost')
|
31
|
+
connections = connection.connections
|
32
|
+
assert_equal 2, connections.size
|
33
|
+
assert_equal ['localhost:11300','localhost:11300'], connections.map(&:address)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def test_with_single_url_it_should_set_up_connection_pool
|
38
|
+
connection = StalkClimber::ConnectionPool.new('beanstalk://localhost')
|
39
|
+
assert_kind_of StalkClimber::ConnectionPool, connection
|
40
|
+
assert_kind_of Beaneater::Pool, connection
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def test_with_a_single_url_it_should_convert_url_to_address_array
|
45
|
+
connection = StalkClimber::ConnectionPool.new('beanstalk://localhost')
|
46
|
+
assert_equal ['localhost:11300'], connection.addresses
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConnectionTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@connection = StalkClimber::Connection.new('localhost:11300')
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def test_cache_creates_and_returns_hash_instance_variable
|
11
|
+
refute @connection.instance_variable_get(:@cache)
|
12
|
+
assert_equal({}, @connection.cache)
|
13
|
+
assert_equal({}, @connection.instance_variable_get(:@cache))
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def test_cache_is_reset_if_max_job_id_lower_than_max_climbed_job_id
|
18
|
+
seeds = seed_jobs
|
19
|
+
@connection.each {}
|
20
|
+
assert_not_equal({}, @connection.cache)
|
21
|
+
assert_not_equal(Float::INFINITY, @connection.min_climbed_job_id)
|
22
|
+
assert_not_equal(0, @connection.max_climbed_job_id)
|
23
|
+
|
24
|
+
@connection.send(:update_climbed_job_ids_from_max_id, 1)
|
25
|
+
assert_equal({}, @connection.cache)
|
26
|
+
assert_equal(Float::INFINITY, @connection.min_climbed_job_id)
|
27
|
+
assert_equal(0, @connection.max_climbed_job_id)
|
28
|
+
seeds.map(&:delete)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def test_connection_is_some_kind_of_enumerable
|
33
|
+
assert @connection.kind_of?(Enumerable)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def test_each_caches_jobs_for_later_use
|
38
|
+
seeds = seed_jobs
|
39
|
+
|
40
|
+
jobs = {}
|
41
|
+
@connection.each do |job|
|
42
|
+
jobs[job.id] = job
|
43
|
+
end
|
44
|
+
|
45
|
+
@connection.expects(:with_job).never
|
46
|
+
@connection.each do |job|
|
47
|
+
assert_equal jobs[job.id], job
|
48
|
+
end
|
49
|
+
|
50
|
+
seeds.map(&:delete)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def test_each_deletes_cached_jobs_that_no_longer_exist
|
55
|
+
seeds = seed_jobs
|
56
|
+
|
57
|
+
jobs = {}
|
58
|
+
@connection.each do |job|
|
59
|
+
jobs[job.id] = job
|
60
|
+
end
|
61
|
+
|
62
|
+
deleted_job = jobs[jobs.keys[2]]
|
63
|
+
deleted_job.delete
|
64
|
+
|
65
|
+
@connection.expects(:with_job).never
|
66
|
+
@connection.each do |job|
|
67
|
+
assert_equal jobs.delete(job.id), job
|
68
|
+
end
|
69
|
+
|
70
|
+
assert_equal 1, jobs.length
|
71
|
+
assert_equal deleted_job.id, jobs.values.first.id
|
72
|
+
|
73
|
+
seeds.map(&:delete)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def test_each_hits_jobs_below_climbed_range_that_have_not_been_hit
|
78
|
+
seeds = seed_jobs(10)
|
79
|
+
|
80
|
+
count = 0
|
81
|
+
@connection.each do |job|
|
82
|
+
count += 1
|
83
|
+
break if count == 5
|
84
|
+
end
|
85
|
+
|
86
|
+
initial_min_climbed_job_id = @connection.min_climbed_job_id
|
87
|
+
|
88
|
+
all_jobs = {}
|
89
|
+
@connection.each do |job|
|
90
|
+
all_jobs[job.id] = job
|
91
|
+
end
|
92
|
+
|
93
|
+
seeds.each do |job|
|
94
|
+
assert_equal all_jobs[job.id].body, job.body
|
95
|
+
assert_equal all_jobs[job.id].id, job.id
|
96
|
+
job.delete
|
97
|
+
end
|
98
|
+
|
99
|
+
assert @connection.min_climbed_job_id < initial_min_climbed_job_id
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def test_each_only_hits_each_job_once
|
104
|
+
seeds = seed_jobs
|
105
|
+
|
106
|
+
jobs = {}
|
107
|
+
@connection.each do |job|
|
108
|
+
refute jobs[job.id]
|
109
|
+
jobs[job.id] = job
|
110
|
+
end
|
111
|
+
|
112
|
+
seeds.map(&:delete)
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def test_each_calls_climb
|
117
|
+
@connection.expects(:climb)
|
118
|
+
@connection.each {}
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def test_max_job_id_returns_expected_max_job_id
|
123
|
+
initial_max = @connection.max_job_id
|
124
|
+
seed_jobs(3).map(&:delete)
|
125
|
+
# 3 new jobs, +1 for the probe job
|
126
|
+
assert_equal initial_max + 4, @connection.max_job_id
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def test_max_job_id_should_increment_max_climbed_id_if_successor
|
131
|
+
@connection.each {}
|
132
|
+
max = @connection.max_climbed_job_id
|
133
|
+
@connection.max_job_id
|
134
|
+
assert_equal max + 1, @connection.max_climbed_job_id
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def test_max_job_id_should_not_increment_max_climbed_id_unless_successor
|
139
|
+
@connection.each {}
|
140
|
+
max = @connection.max_climbed_job_id
|
141
|
+
seed_jobs(1).map(&:delete)
|
142
|
+
@connection.max_job_id
|
143
|
+
assert_equal max, @connection.max_climbed_job_id
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def test_each_sets_min_and_max_climbed_job_ids_appropriately
|
148
|
+
assert_equal Float::INFINITY, @connection.min_climbed_job_id
|
149
|
+
assert_equal 0, @connection.max_climbed_job_id
|
150
|
+
|
151
|
+
max_id = @connection.max_job_id
|
152
|
+
@connection.expects(:max_job_id).once.returns(max_id)
|
153
|
+
@connection.each {}
|
154
|
+
|
155
|
+
assert_equal 1, @connection.min_climbed_job_id
|
156
|
+
assert_equal max_id, @connection.max_climbed_job_id
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
def test_test_tube_is_initialized_but_configurable
|
161
|
+
assert_equal StalkClimber::Connection::DEFAULT_TUBE, @connection.test_tube
|
162
|
+
tube_name = 'test_tube'
|
163
|
+
@connection.test_tube = tube_name
|
164
|
+
assert_equal tube_name, @connection.test_tube
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def test_with_job_yields_nil_or_the_requested_job_if_it_exists
|
169
|
+
assert_nothing_raised do
|
170
|
+
@connection.with_job(@connection.max_job_id) do |job|
|
171
|
+
assert_equal nil, job
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
probe = seed_jobs(1).first
|
176
|
+
@connection.with_job(probe.id) do |job|
|
177
|
+
assert_equal probe.id, job.id
|
178
|
+
end
|
179
|
+
probe.delete
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def test_with_job_bang_does_not_execute_block_and_raises_error_if_job_does_not_exist
|
184
|
+
block = lambda {}
|
185
|
+
block.expects(:call).never
|
186
|
+
assert_raise Beaneater::NotFoundError do
|
187
|
+
Object.any_instance.expects(:yield).never
|
188
|
+
@connection.with_job!(@connection.max_job_id, &block)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Job < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@connection = StalkClimber::Connection.new('localhost:11300')
|
7
|
+
@job = seed_jobs(1).first
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def test_body_retrives_performs_peek
|
12
|
+
body = {:test => true}
|
13
|
+
|
14
|
+
@job.connection.expects(:transmit).returns({
|
15
|
+
:body => body,
|
16
|
+
})
|
17
|
+
assert_equal body, @job.body
|
18
|
+
|
19
|
+
@job.connection.expects(:transmit).never
|
20
|
+
assert_equal body, @job.body
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def test_connection_stored_with_instance
|
25
|
+
assert @job.instance_variable_get(:@connection)
|
26
|
+
assert @job.connection
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def test_delete
|
31
|
+
assert @job.delete
|
32
|
+
assert_raise Beaneater::NotFoundError do
|
33
|
+
@connection.transmit("peek #{@job.id}")
|
34
|
+
end
|
35
|
+
refute @job.instance_variable_get(:@body)
|
36
|
+
refute @job.instance_variable_get(:@stats)
|
37
|
+
assert_equal 'DELETED', @job.instance_variable_get(:@status)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def test_exists?
|
42
|
+
assert @job.exists?
|
43
|
+
|
44
|
+
@job.delete
|
45
|
+
refute @job.exists?
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def test_initialize_with_peek_response
|
50
|
+
job = StalkClimber::Job.new(@connection.transmit("peek #{@job.id}"))
|
51
|
+
@connection.expects(:transmit).never
|
52
|
+
assert_equal @connection, job.connection
|
53
|
+
assert_equal @job.id, job.id
|
54
|
+
assert_equal 'FOUND', job.instance_variable_get(:@status)
|
55
|
+
assert_equal '{}', job.body
|
56
|
+
refute job.instance_variable_get(:@stats)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def test_initialize_with_put_response
|
61
|
+
@connection.expects(:transmit).never
|
62
|
+
assert @job.id
|
63
|
+
assert_equal @connection, @job.connection
|
64
|
+
assert_equal 'INSERTED', @job.instance_variable_get(:@status)
|
65
|
+
refute @job.instance_variable_get(:@body)
|
66
|
+
refute @job.instance_variable_get(:@stats)
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def test_initialize_with_stats_response
|
71
|
+
job = StalkClimber::Job.new(@connection.transmit("stats-job #{@job.id}"))
|
72
|
+
@connection.expects(:transmit).never
|
73
|
+
assert_equal @job.id, job.id
|
74
|
+
assert_equal @connection, job.connection
|
75
|
+
assert_equal 'OK', job.instance_variable_get(:@status)
|
76
|
+
assert job.stats
|
77
|
+
assert job.instance_variable_get(:@stats)
|
78
|
+
refute job.instance_variable_get(:@body)
|
79
|
+
StalkClimber::Job::STATS_ATTRIBUTES.each do |method_name|
|
80
|
+
assert job.send(method_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def test_initialize_with_stats_attribute_methods_return_correct_values
|
86
|
+
body = {
|
87
|
+
'age'=>3,
|
88
|
+
'buries'=>0,
|
89
|
+
'delay'=>0,
|
90
|
+
'id' => 4412,
|
91
|
+
'kicks'=>0,
|
92
|
+
'pri'=>4294967295,
|
93
|
+
'releases'=>0,
|
94
|
+
'reserves'=>0,
|
95
|
+
'state'=>'ready',
|
96
|
+
'time-left'=>0,
|
97
|
+
'timeouts'=>0,
|
98
|
+
'ttr'=>300,
|
99
|
+
'tube'=>'default',
|
100
|
+
}
|
101
|
+
stats = {
|
102
|
+
:body => body,
|
103
|
+
:connection => @connection,
|
104
|
+
:id => 149,
|
105
|
+
:status => 'OK',
|
106
|
+
}
|
107
|
+
@connection.expects(:transmit).returns(stats)
|
108
|
+
job = StalkClimber::Job.new(@connection.transmit("stats-job #{@job.id}"))
|
109
|
+
@connection.expects(:transmit).never
|
110
|
+
StalkClimber::Job::STATS_ATTRIBUTES.each do |method_name|
|
111
|
+
assert_equal body[method_name], job.send(method_name), "Expected #{body[method_name.to_sym]} for #{method_name}, got #{job.send(method_name)}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def test_initialize_with_unknown_status_raises_error
|
117
|
+
assert_raise RuntimeError do
|
118
|
+
StalkClimber::Job.new({:status => 'DELETED'})
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def test_stats_attribute_method_can_force_refresh
|
124
|
+
initial_value = @job.age
|
125
|
+
@connection.expects(:transmit).returns({:body => {'age' => initial_value + 100}})
|
126
|
+
assert_equal initial_value + 100, @job.age(true)
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def test_stats_can_force_a_refresh
|
131
|
+
body = {
|
132
|
+
'age'=>3,
|
133
|
+
'buries'=>0,
|
134
|
+
'delay'=>0,
|
135
|
+
'kicks'=>0,
|
136
|
+
'id' => 4412,
|
137
|
+
'pri'=>4294967295,
|
138
|
+
'releases'=>0,
|
139
|
+
'reserves'=>0,
|
140
|
+
'state'=>'ready',
|
141
|
+
'time-left'=>0,
|
142
|
+
'timeouts'=>0,
|
143
|
+
'ttr'=>300,
|
144
|
+
'tube'=>'default',
|
145
|
+
}
|
146
|
+
stats_1 = {
|
147
|
+
:body => {},
|
148
|
+
:connection => @connection,
|
149
|
+
:id => 149,
|
150
|
+
:status => 'OK',
|
151
|
+
}
|
152
|
+
stats_2 = {
|
153
|
+
:body => body,
|
154
|
+
:connection => @connection,
|
155
|
+
:id => 149,
|
156
|
+
:status => 'OK',
|
157
|
+
}
|
158
|
+
@connection.expects(:transmit).twice.returns(stats_1, stats_2)
|
159
|
+
job = StalkClimber::Job.new(@connection.transmit("stats-job #{@job.id}"))
|
160
|
+
job.stats(:force_refresh)
|
161
|
+
StalkClimber::Job::STATS_ATTRIBUTES.each do |method_name|
|
162
|
+
assert_equal body[method_name], job.send(method_name), "Expected #{body[method_name.to_sym]} for #{method_name}, got #{job.send(method_name)}"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stalk_climber
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Freewrite.org
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: beaneater
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Improved sequential access to Beanstalk
|
63
|
+
email:
|
64
|
+
- dev@freewrite.org
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .travis.yml
|
71
|
+
- Gemfile
|
72
|
+
- LICENSE
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- lib/stalk_climber.rb
|
76
|
+
- lib/stalk_climber/climber.rb
|
77
|
+
- lib/stalk_climber/connection.rb
|
78
|
+
- lib/stalk_climber/connection_pool.rb
|
79
|
+
- lib/stalk_climber/job.rb
|
80
|
+
- lib/stalk_climber/lazy_enumerable.rb
|
81
|
+
- lib/stalk_climber/version.rb
|
82
|
+
- stalk_climber.gemspec
|
83
|
+
- test/test_helper.rb
|
84
|
+
- test/unit/climber_test.rb
|
85
|
+
- test/unit/connection_pool_test.rb
|
86
|
+
- test/unit/connection_test.rb
|
87
|
+
- test/unit/job_test.rb
|
88
|
+
homepage: https://github.com/freewrite/stalk_climber
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.8.25
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: StalkClimber is a Ruby library allowing improved sequential access to Beanstalk
|
113
|
+
via a job cache.
|
114
|
+
test_files:
|
115
|
+
- test/test_helper.rb
|
116
|
+
- test/unit/climber_test.rb
|
117
|
+
- test/unit/connection_pool_test.rb
|
118
|
+
- test/unit/connection_test.rb
|
119
|
+
- test/unit/job_test.rb
|