bean_counter 0.0.1
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 +17 -0
- data/.rdoc_options +9 -0
- data/.rspec +1 -0
- data/.travis.yml +21 -0
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/README.md +306 -0
- data/Rakefile +38 -0
- data/bean_counter.gemspec +26 -0
- data/lib/bean_counter.rb +8 -0
- data/lib/bean_counter/core.rb +115 -0
- data/lib/bean_counter/enqueued_expectation.rb +130 -0
- data/lib/bean_counter/mini_test.rb +3 -0
- data/lib/bean_counter/spec.rb +5 -0
- data/lib/bean_counter/spec_matchers.rb +25 -0
- data/lib/bean_counter/strategies/stalk_climber_strategy.rb +150 -0
- data/lib/bean_counter/strategy.rb +250 -0
- data/lib/bean_counter/test_assertions.rb +227 -0
- data/lib/bean_counter/test_unit.rb +3 -0
- data/lib/bean_counter/tube_expectation.rb +66 -0
- data/lib/bean_counter/version.rb +4 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/spec_spec.rb +224 -0
- data/test/test_helper.rb +52 -0
- data/test/unit/core_test.rb +156 -0
- data/test/unit/enqueued_expectation_test.rb +223 -0
- data/test/unit/strategies/stalk_climber_strategy_test.rb +501 -0
- data/test/unit/strategy_test.rb +98 -0
- data/test/unit/test_assertions_test.rb +270 -0
- data/test/unit/tube_expectation_test.rb +93 -0
- metadata +149 -0
data/lib/bean_counter.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'beaneater'
|
3
|
+
require 'bean_counter/version'
|
4
|
+
require 'bean_counter/core'
|
5
|
+
require 'bean_counter/strategy'
|
6
|
+
require 'bean_counter/strategies/stalk_climber_strategy'
|
7
|
+
require 'bean_counter/enqueued_expectation'
|
8
|
+
require 'bean_counter/tube_expectation'
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module BeanCounter
|
2
|
+
|
3
|
+
# Use StalkClimberStrategy by default because it works with standard Beanstalkd
|
4
|
+
DEFAULT_STRATEGY = :'BeanCounter::Strategy::StalkClimberStrategy'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# Beanstalkd server urls to be used by BeanCounter
|
8
|
+
attr_writer :beanstalkd_url
|
9
|
+
end
|
10
|
+
|
11
|
+
# :call-seq:
|
12
|
+
# beanstalkd_url() => Array[String]
|
13
|
+
#
|
14
|
+
# Returns an array of parsed beanstalkd urls.
|
15
|
+
# Server urls provided via the environment variable +BEANSTALKD_URL+ are given
|
16
|
+
# precedence. When setting beanstalkd_url from the environment, urls are
|
17
|
+
# expected in a comma separated list. If ENV['BEANSTALKD_URL'] is not set
|
18
|
+
# BeanCounter::beanstalkd_url is checked and parsed next. Finally, if
|
19
|
+
# BeanCounter::beanstalkd_url has not been set, the configuration for Beaneater is
|
20
|
+
# checked and parsed. If no beanstalkd_url can be determined a RuntimeError is raised.
|
21
|
+
# URLs can be provided in any of three supported formats:
|
22
|
+
# * localhost (host only)
|
23
|
+
# * 192.168.1.100:11300 (host and port)
|
24
|
+
# * beanstalk://127.0.0.1:11300 (host and port prefixed by beanstalk scheme)
|
25
|
+
#
|
26
|
+
# In short, a host is the only required component. If no port is provided, the default
|
27
|
+
# beanstalkd port of 11300 is assumed. If a scheme other than beanstalk is provided
|
28
|
+
# a StalkClimber::ConnectionPool::InvalidURIScheme error is raised.
|
29
|
+
#
|
30
|
+
# $ BEANSTALKD_URL='127.0.0.1,beanstalk://localhost:11300,localhost:11301' rake test
|
31
|
+
# BeanCounter.beanstalkd_url
|
32
|
+
# #=> ['127.0.0.1', 'beanstalk://localhost:11300', 'localhost:11301']
|
33
|
+
#
|
34
|
+
# BeanCounter.beanstalkd_url = 'beanstalk://localhost'
|
35
|
+
# BeanCounter.beanstalkd_url
|
36
|
+
# #=> 'beanstalk://localhost'
|
37
|
+
#
|
38
|
+
# Beaneater.configure do |config|
|
39
|
+
# config.beanstalkd_url = ['localhost', 'localhost:11301']
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# BeanCounter.beanstalkd_url
|
43
|
+
# #=> ['localhost', 'localhost:11301']
|
44
|
+
def self.beanstalkd_url
|
45
|
+
@hosts_from_env = ENV['BEANSTALKD_URL']
|
46
|
+
@hosts_from_env = @hosts_from_env.split(',').map!(&:strip) unless @hosts_from_env.nil?
|
47
|
+
beanstalkd_url = @hosts_from_env || @beanstalkd_url || Beaneater.configuration.beanstalkd_url
|
48
|
+
raise 'Could not determine beanstalkd url' if beanstalkd_url.to_s == ''
|
49
|
+
return beanstalkd_url
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# :call-seq:
|
54
|
+
# default_strategy() => subclass of BeanCounter::Strategy
|
55
|
+
#
|
56
|
+
# Return a previously materialized default strategy or materialize a new default
|
57
|
+
# strategy for use. See BeanCounter::Strategy::materialize_strategy for more
|
58
|
+
# information on the materialization process.
|
59
|
+
def self.default_strategy
|
60
|
+
return @default_strategy ||= BeanCounter::Strategy.materialize_strategy(DEFAULT_STRATEGY)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# :call-seq:
|
65
|
+
# reset!(tube_name = nil) => Boolean
|
66
|
+
# Uses the strategy to delete all jobs from the +tube_name+ tube. If +tube_name+
|
67
|
+
# is not provided, all jobs on the beanstalkd server are deleted.
|
68
|
+
#
|
69
|
+
# It should be noted that jobs that are reserved can only be deleted by the
|
70
|
+
# reserving connection and thus cannot be deleted via this method. As such,
|
71
|
+
# care may need to be taken to ensure that jobs are not left in a reserved
|
72
|
+
# state.
|
73
|
+
#
|
74
|
+
# Returns true if all encountered jobs were deleted successfully. Returns
|
75
|
+
# false if any of the jobs enumerated could not be deleted.
|
76
|
+
def self.reset!(tube_name = nil)
|
77
|
+
partial_failure = false
|
78
|
+
strategy.jobs.each do |job|
|
79
|
+
success = strategy.delete_job(job) if tube_name.nil? || strategy.job_matches?(job, :tube => tube_name)
|
80
|
+
partial_failure ||= success
|
81
|
+
end
|
82
|
+
return !partial_failure
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Returns a list of known subclasses of BeanCounter::Strategy.
|
87
|
+
# Typically this list represents the strategies available for interacting
|
88
|
+
# with beanstalkd.
|
89
|
+
def self.strategies
|
90
|
+
return BeanCounter::Strategy.strategies
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
# Sets the strategy that BeanCounter should use when interacting with
|
95
|
+
# beanstalkd. The value provided for +strategy_identifier+ will be used to
|
96
|
+
# materialize an instance of the matching strategy class. If the provided
|
97
|
+
# +strategy_identifier+ is nil, any existing strategy will be cleared and
|
98
|
+
# the next call to BeanCounter::strategy will use the default strategy.
|
99
|
+
def self.strategy=(strategy_identifier)
|
100
|
+
if strategy_identifier.nil?
|
101
|
+
@strategy = nil
|
102
|
+
else
|
103
|
+
@strategy = BeanCounter::Strategy.materialize_strategy(strategy_identifier).new
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# Returns a previously materialized strategy if one exists. If no previous
|
109
|
+
# strategy exists, a new instance of the default strategy is instantiated
|
110
|
+
# and returned.
|
111
|
+
def self.strategy
|
112
|
+
return @strategy ||= default_strategy.new
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class BeanCounter::EnqueuedExpectation
|
2
|
+
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators BeanCounter, :strategy
|
6
|
+
|
7
|
+
# The value that the expectation expects
|
8
|
+
attr_reader :expected
|
9
|
+
|
10
|
+
# The number of matching jobs the expectation expects
|
11
|
+
attr_reader :expected_count
|
12
|
+
|
13
|
+
# The jobs found by the expecation during matching
|
14
|
+
attr_reader :found
|
15
|
+
|
16
|
+
|
17
|
+
# Iterates over the provided collection searching for jobs matching the
|
18
|
+
# Hash of expected options provided at instantiation.
|
19
|
+
def collection_matcher(collection)
|
20
|
+
@found = collection.send(expected_count? ? :select : :detect) do |job|
|
21
|
+
strategy.job_matches?(job, expected)
|
22
|
+
end
|
23
|
+
|
24
|
+
@found = [@found] unless expected_count? || @found.nil?
|
25
|
+
|
26
|
+
expected_count? ? expected_count === found.to_a.length : !found.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# Returns a Boolean indicating whether a specific number of jobs are expected.
|
31
|
+
def expected_count?
|
32
|
+
return !!expected_count
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Builds the failure message used in the event of a positive expectation
|
37
|
+
# failure
|
38
|
+
def failure_message
|
39
|
+
if found.nil?
|
40
|
+
found_count = 'none'
|
41
|
+
found_string = nil
|
42
|
+
else
|
43
|
+
found_count = "#{found.length}:"
|
44
|
+
found_string = found.map {|job| strategy.pretty_print_job(job) }.join("\n")
|
45
|
+
end
|
46
|
+
[
|
47
|
+
"expected #{expected_count || 'any number of'} jobs matching #{expected.to_s},",
|
48
|
+
"found #{found_count}",
|
49
|
+
found_string,
|
50
|
+
].compact.join(' ')
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# Create a new enqueued expectation. Uses the given +expected+ Hash to determine
|
55
|
+
# if any jobs are enqueued that match the expected options.
|
56
|
+
#
|
57
|
+
# Each _key_ in +expected+ is a String or a Symbol that identifies an attribute
|
58
|
+
# of a job that the corresponding _value_ should be compared against. All attribute
|
59
|
+
# comparisons are performed using the triple equal (===) operator/method of
|
60
|
+
# the given _value_.
|
61
|
+
#
|
62
|
+
# +expected+ may additionally include a _count_ key of 'count' or :count that
|
63
|
+
# can be used to specify that a particular number of matching jobs are found.
|
64
|
+
#
|
65
|
+
# See BeanCounter::TestAssertions and/or BeanCounter::SpecMatchers for more information.
|
66
|
+
def initialize(expected)
|
67
|
+
@expected = expected
|
68
|
+
@expected_count = [expected.delete(:count), expected.delete('count')].compact.first
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Checks the beanstalkd pool for jobs matching the Hash of expected options
|
73
|
+
# provided at instantiation.
|
74
|
+
#
|
75
|
+
# If no _count_ option is provided, the expectation succeeds if any job is found
|
76
|
+
# that matches all of the expected options. If no jobs are found that match the
|
77
|
+
# expected options, the expecation fails.
|
78
|
+
#
|
79
|
+
# If a _count_ option is provided the expectation only succeeds if the triple equal
|
80
|
+
# (===) operator/method of the value of _count_ evaluates to true when given the
|
81
|
+
# total number of matching jobs. Otherwise the expecation fails. The use of ===
|
82
|
+
# allows for more advanced comparisons using Procs, Ranges, Regexps, etc.
|
83
|
+
#
|
84
|
+
# See Strategy#job_matches? and/or the #job_matches? method of the strategy
|
85
|
+
# in use for more detailed information on how it is determined whether or not
|
86
|
+
# a job matches the options expected.
|
87
|
+
#
|
88
|
+
# See also BeanCounter::TestAssertions and/or BeanCounter::SpecMatchers for additional
|
89
|
+
# information.
|
90
|
+
def matches?(given = nil)
|
91
|
+
if given.kind_of?(Proc)
|
92
|
+
return proc_matcher(given)
|
93
|
+
else
|
94
|
+
return collection_matcher(strategy.jobs)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Builds the failure message used in the event of a negative expectation
|
100
|
+
# failure
|
101
|
+
def negative_failure_message
|
102
|
+
return '' if found.nil? || found == []
|
103
|
+
|
104
|
+
found_count = found.length
|
105
|
+
found_string = found.map {|job| strategy.pretty_print_job(job) }.join("\n")
|
106
|
+
if expected_count?
|
107
|
+
job_count = expected_count
|
108
|
+
job_word = expected_count == 1 ? 'job' : 'jobs'
|
109
|
+
else
|
110
|
+
job_count = 'any'
|
111
|
+
job_word = 'jobs'
|
112
|
+
end
|
113
|
+
return [
|
114
|
+
"did not expect #{job_count} #{job_word} matching #{expected.to_s},",
|
115
|
+
"found #{found_count}:",
|
116
|
+
found_string,
|
117
|
+
].join(' ')
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Monitors the beanstalkd pool for new jobs enqueued during the provided
|
122
|
+
# block than passes any collected jobs to the collection matcher to determine
|
123
|
+
# if any jobs were enqueued that match the expected options provided at
|
124
|
+
# instantiation
|
125
|
+
def proc_matcher(block)
|
126
|
+
new_jobs = strategy.collect_new_jobs(&block)
|
127
|
+
collection_matcher(new_jobs)
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BeanCounter::SpecMatchers
|
2
|
+
|
3
|
+
# Creates a new BeanCounter::EnqueuedExpectation with +expected+ stored for
|
4
|
+
# later use when matching. Most of the time the value provided for +expected+
|
5
|
+
# will be ignored, the only exception is when +expected+ is given a block.
|
6
|
+
# When a block is provided for +expected+, only jobs enqueued during the
|
7
|
+
# execution of the block will be considered when matching.
|
8
|
+
#
|
9
|
+
# See BeanCounter::EnqueuedExpectation for additional information and usage
|
10
|
+
# patterns.
|
11
|
+
def have_enqueued(expected)
|
12
|
+
BeanCounter::EnqueuedExpectation.new(expected)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Creates a new BeanCounter::TubeExpectation with +expected+ stored for
|
16
|
+
# later use when matching. However, +expected+ is never used when matching.
|
17
|
+
# Instead, all tubes are matched against until a match is found.
|
18
|
+
#
|
19
|
+
# See BeanCounter::TubeExpectation for additional information and usage
|
20
|
+
# patterns.
|
21
|
+
def have_tube(expected)
|
22
|
+
BeanCounter::TubeExpectation.new(expected)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'stalk_climber'
|
2
|
+
|
3
|
+
class BeanCounter::Strategy::StalkClimberStrategy < BeanCounter::Strategy
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
# Index of what method should be called to retrieve each stat
|
8
|
+
STATS_METHOD_NAMES = begin
|
9
|
+
attrs = (
|
10
|
+
BeanCounter::Strategy::MATCHABLE_JOB_ATTRIBUTES +
|
11
|
+
BeanCounter::Strategy::MATCHABLE_TUBE_ATTRIBUTES
|
12
|
+
).map!(&:to_sym).uniq.sort
|
13
|
+
method_names = attrs.map {|method| method.to_s.gsub(/-/, '_').to_sym }
|
14
|
+
attr_methods = Hash[attrs.zip(method_names)]
|
15
|
+
attr_methods[:pause] = :pause_time
|
16
|
+
attr_methods
|
17
|
+
end
|
18
|
+
|
19
|
+
# Default tube used by StalkClimber when probing the beanstalkd pool
|
20
|
+
TEST_TUBE = 'bean_counter_stalk_climber_test'
|
21
|
+
|
22
|
+
# The tube that will be used by StalkClimber when probing the beanstalkd pool.
|
23
|
+
# Uses TEST_TUBE if no value provided.
|
24
|
+
attr_writer :test_tube
|
25
|
+
|
26
|
+
def_delegators :climber, :jobs, :tubes
|
27
|
+
|
28
|
+
|
29
|
+
# :call-seq:
|
30
|
+
# collect_new_jobs { block } => Array[StalkClimber::Job]
|
31
|
+
#
|
32
|
+
# Collects all jobs enqueued during the execution of the provided +block+.
|
33
|
+
# Returns an Array of StalkClimber::Job.
|
34
|
+
#
|
35
|
+
# Fulfills Strategy#collect_new_jobs contract. See Strategy#collect_new_jobs
|
36
|
+
# for more information.
|
37
|
+
def collect_new_jobs
|
38
|
+
raise ArgumentError, 'Block required' unless block_given?
|
39
|
+
|
40
|
+
min_ids = climber.max_job_ids
|
41
|
+
yield
|
42
|
+
max_ids = climber.max_job_ids
|
43
|
+
new_jobs = []
|
44
|
+
min_ids.each do |connection, min_id|
|
45
|
+
testable_ids = (min_id..max_ids[connection]).to_a
|
46
|
+
new_jobs.concat(connection.fetch_jobs(testable_ids).compact)
|
47
|
+
end
|
48
|
+
return new_jobs
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# :call-seq:
|
53
|
+
# delete_job(job) => Boolean
|
54
|
+
#
|
55
|
+
# Attempts to delete the given StalkClimber::Job +job+. Returns true if
|
56
|
+
# deletion succeeds or if +job+ does not exist. Returns false if +job+ could
|
57
|
+
# not be deleted (typically due to it being reserved by another connection).
|
58
|
+
#
|
59
|
+
# Fulfills Strategy#delete_job contract. See Strategy#delete_job for more
|
60
|
+
# information.
|
61
|
+
def delete_job(job)
|
62
|
+
job.delete
|
63
|
+
return true
|
64
|
+
rescue Beaneater::NotFoundError
|
65
|
+
return job.exists? ? false : true
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# :call-seq:
|
70
|
+
# job_matches?(job, options => {Symbol,String => Numeric,Proc,Range,Regexp,String}) => Boolean
|
71
|
+
#
|
72
|
+
# Returns a boolean indicating whether or not the provided StalkClimber::Job
|
73
|
+
# +job+ matches the given Hash of +options.
|
74
|
+
#
|
75
|
+
# Fulfills Strategy#job_matches? contract. See Strategy#job_matches? for more
|
76
|
+
# information.
|
77
|
+
def job_matches?(job, opts = {})
|
78
|
+
return matcher(MATCHABLE_JOB_ATTRIBUTES, job, opts)
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# :call-seq:
|
83
|
+
# pretty_print_job(job) => String
|
84
|
+
#
|
85
|
+
# Returns a String representation of the StalkClimber::Job +job+ in a pretty,
|
86
|
+
# human readable format.
|
87
|
+
#
|
88
|
+
# Fulfills Strategy#pretty_print_job contract. See Strategy#pretty_print_job
|
89
|
+
# for more information.
|
90
|
+
def pretty_print_job(job)
|
91
|
+
return job.to_h.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# :call-seq:
|
96
|
+
# pretty_print_tube(tube) => String
|
97
|
+
#
|
98
|
+
# Returns a String representation of +tube+ in a pretty, human readable format.
|
99
|
+
#
|
100
|
+
# Fulfills Strategy#pretty_print_tube contract. See Strategy#pretty_print_tube
|
101
|
+
# for more information.
|
102
|
+
def pretty_print_tube(tube)
|
103
|
+
return tube.to_h.to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# :call-seq:
|
108
|
+
# tube_matches?(tube, options => {Symbol,String => Numeric,Proc,Range,Regexp,String}) => Boolean
|
109
|
+
#
|
110
|
+
# Returns a boolean indicating whether or not the provided StalkClimber +tube+
|
111
|
+
# matches the given Hash of +options.
|
112
|
+
#
|
113
|
+
# Fulfills Strategy#tube_matches? contract. See Strategy#tube_matches? for
|
114
|
+
# more information.
|
115
|
+
def tube_matches?(tube, opts = {})
|
116
|
+
return matcher(MATCHABLE_TUBE_ATTRIBUTES, tube, opts)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# StalkClimber instance used to climb/crawl beanstalkd pool
|
122
|
+
def climber
|
123
|
+
return @climber ||= StalkClimber::Climber.new(BeanCounter.beanstalkd_url, test_tube)
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Given the set of valid attributes, +valid_attributes+, determines if every
|
128
|
+
# _value_ of +opts+ evaluates to true when compared to the attribute of
|
129
|
+
# +matchable+ identified by the corresponding +opts+ _key_.
|
130
|
+
def matcher(valid_attributes, matchable, opts = {})
|
131
|
+
# Refresh state/stats before checking match
|
132
|
+
return false unless matchable.exists?
|
133
|
+
return (opts.keys & valid_attributes).all? do |key|
|
134
|
+
opts[key] === matchable.send(stats_method_name(key))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# Simplify lookup of what method to call to retrieve requested stat
|
140
|
+
def stats_method_name(stats_attr)
|
141
|
+
return STATS_METHOD_NAMES[stats_attr.to_sym]
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
# Accessor for test_tube that defaults to TEST_TUBE
|
146
|
+
def test_tube
|
147
|
+
return @test_tube ||= TEST_TUBE
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
class BeanCounter::Strategy
|
2
|
+
|
3
|
+
# Available job attributes. These attributes can be used by strategies
|
4
|
+
# to validate what attributes are used for matching jobs.
|
5
|
+
MATCHABLE_JOB_ATTRIBUTES = begin
|
6
|
+
attrs = [
|
7
|
+
:age, :body, :buries, :connection, :delay, :id, :kicks, :pri, :releases,
|
8
|
+
:reserves, :state, :'time-left', :timeouts, :ttr, :tube,
|
9
|
+
]
|
10
|
+
attrs.concat(attrs.map(&:to_s))
|
11
|
+
end
|
12
|
+
|
13
|
+
# Available tube attributes. These attributes can be used by strategies
|
14
|
+
# to validate what attributes are used for matching tubes.
|
15
|
+
MATCHABLE_TUBE_ATTRIBUTES = begin
|
16
|
+
attrs = [
|
17
|
+
:'cmd-delete', :'cmd-pause-tube', :'current-jobs-buried',
|
18
|
+
:'current-jobs-delayed', :'current-jobs-ready', :'current-jobs-reserved',
|
19
|
+
:'current-jobs-urgent', :'current-using', :'current-waiting',
|
20
|
+
:'current-watching', :name, :pause, :'pause-time-left', :'total-jobs',
|
21
|
+
]
|
22
|
+
attrs.concat(attrs.map(&:to_s))
|
23
|
+
end
|
24
|
+
|
25
|
+
@@strategies = {}
|
26
|
+
|
27
|
+
|
28
|
+
# Maintain an index of classes known to subclass Strategy.
|
29
|
+
def self.inherited(subclass)
|
30
|
+
identifier = subclass.name || subclass.to_s[8, subclass.to_s.length - 9]
|
31
|
+
@@strategies[identifier.to_sym] = subclass
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# :call-seq:
|
36
|
+
# known_strategy?(strategy_identifier) => Boolean
|
37
|
+
#
|
38
|
+
# Determines if the provided +strategy_identifer+ corresponds to a known
|
39
|
+
# subclass of strategy. The provided +strategy_identifer+ can be a class
|
40
|
+
# or any object that responds to :to_sym. Classes are compared directly.
|
41
|
+
# For non-class objects that respond to :to_sym, the symbolized form
|
42
|
+
# of +strategy_identifer+ is used as a key to attempt to retrieve a strategy
|
43
|
+
# from the strategies Hash.
|
44
|
+
#
|
45
|
+
# Returns true if +strategy_identifier+ isa known strategy or maps to a known
|
46
|
+
# strategy. Otherwise, false is returned.
|
47
|
+
#
|
48
|
+
# BeanCounter::Strategy.known_strategy?(Object)
|
49
|
+
# #=> false
|
50
|
+
#
|
51
|
+
# BeanCounter::Strategy.known_strategy?(BeanCounter::Strategy)
|
52
|
+
# #=> true
|
53
|
+
def self.known_strategy?(strategy_identifier)
|
54
|
+
return true if strategy_identifier.is_a?(Class) &&
|
55
|
+
strategy_identifier <= BeanCounter::Strategy
|
56
|
+
return true if strategy_identifier.respond_to?(:to_sym) &&
|
57
|
+
strategies.key?(strategy_identifier.to_sym)
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# :call-seq:
|
63
|
+
# materialize_strategy(strategy_identifier) => subclass of Strategy
|
64
|
+
#
|
65
|
+
# Materialize the provided +strategy_identifier+ into a subclass of Strategy.
|
66
|
+
# If +strategy_identifer+ is already a subclass of Strategy,
|
67
|
+
# +strategy_identifier+ is returned. Otherwise, +strategy_identifer+ is
|
68
|
+
# converted into a Symbol and is used as a key to retrieve a strategy from
|
69
|
+
# the known subclasses of Strategy. If +strategy_identifier does not map to
|
70
|
+
# a known subclass of Strategy, an ArgumentError is raised.
|
71
|
+
#
|
72
|
+
# BeanCounter::Strategy.materialize_strategy(:'BeanCounter::Strategy::StalkClimberStrategy')
|
73
|
+
# #=> BeanCounter::Strategy::StalkClimberStrategy
|
74
|
+
def self.materialize_strategy(strategy_identifier)
|
75
|
+
unless BeanCounter::Strategy.known_strategy?(strategy_identifier)
|
76
|
+
raise(
|
77
|
+
ArgumentError,
|
78
|
+
"Could not find #{strategy_identifier} among known strategies: #{strategies.keys.to_s}"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
return strategy_identifier.is_a?(Class) ? strategy_identifier : strategies[strategy_identifier.to_sym]
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# :call-seq:
|
86
|
+
# strategies() => Hash
|
87
|
+
#
|
88
|
+
# Returns a list of known classes that inherit from BeanCounter::Strategy.
|
89
|
+
# Typically this list represents the strategies available for interacting
|
90
|
+
# with beanstalkd.
|
91
|
+
def self.strategies
|
92
|
+
return @@strategies.dup
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# :call-seq:
|
97
|
+
# collect_new_jobs { block } => Array[Strategy Job]
|
98
|
+
#
|
99
|
+
# Provide a means of collecting jobs enqueued during the execution of the
|
100
|
+
# provided +block+. Returns an Array of Jobs as implemented by the Strategy.
|
101
|
+
#
|
102
|
+
# Used internally to reduce the set of jobs that must be examined to evaluate
|
103
|
+
# the truth of an assertion to only those enqueued during the evaluation of
|
104
|
+
# the given +block+.
|
105
|
+
#
|
106
|
+
# new_jobs = strategy.collect_new_jobs do
|
107
|
+
# ...
|
108
|
+
# end
|
109
|
+
# #=> [job_enqueued_during_block, job_enqueued_during_block]
|
110
|
+
def collect_new_jobs
|
111
|
+
raise NotImplementedError
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# :call-seq:
|
116
|
+
# delete_job(job) => Boolean
|
117
|
+
#
|
118
|
+
# Provide a means for deleting a job specific to the job interface used by
|
119
|
+
# the strategy. Should return true if the job was deleted successfully or
|
120
|
+
# no longer exists and false if the job could not be deleted.
|
121
|
+
#
|
122
|
+
# Used internally to delete a job, allowing the beanstalkd pool to be reset.
|
123
|
+
#
|
124
|
+
# strategy.delete_job(job)
|
125
|
+
# #=> true
|
126
|
+
def delete_job
|
127
|
+
raise NotImplementedError
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# :call-seq:
|
132
|
+
# job_matches?(job, options => {Symbol,String => Numeric,Proc,Range,Regexp,String}) => Boolean
|
133
|
+
#
|
134
|
+
# Returns a boolean indicating whether or not the provided +job+ matches the
|
135
|
+
# given Hash of +options. Each _key_ in +options+ is a String or a Symbol that
|
136
|
+
# identifies an attribute of +job+ that the corresponding _value_ should be
|
137
|
+
# compared against. True is returned if every _value_ in +options+ evaluates to
|
138
|
+
# true when compared to the attribute of +job+ identified by the corresponding
|
139
|
+
# _key_. False is returned if any of the comparisons evaluates to false.
|
140
|
+
#
|
141
|
+
# If no options are given, returns true for any job that exists at the time of
|
142
|
+
# evaluation.
|
143
|
+
#
|
144
|
+
# Each attribute comparison is performed using the triple equal (===)
|
145
|
+
# operator/method of _value_ with the attribute of +job+ identified by _key_
|
146
|
+
# passed into the method. Use of === allows for more complex comparisons
|
147
|
+
# using Procs, Ranges, Regexps, etc.
|
148
|
+
#
|
149
|
+
# Consult MATCHABLE_JOB_ATTRIBUTES for a list of which attributes of +job+
|
150
|
+
# can be matched against.
|
151
|
+
#
|
152
|
+
# Used internally to evaluate if a job matches an assertion.
|
153
|
+
#
|
154
|
+
# strategy.job_matches?(reserved_job, :state => 'reserved')
|
155
|
+
# #=> true
|
156
|
+
#
|
157
|
+
# strategy.job_matches(small_job, :body => lambda {|body| body.length > 50 })
|
158
|
+
# #=> false
|
159
|
+
#
|
160
|
+
# strategy.job_matches(unreliable_job, :buries => 6..100)
|
161
|
+
# #=> true
|
162
|
+
def job_matches?
|
163
|
+
raise NotImplementedError
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# :call-seq:
|
168
|
+
# jobs() => Enumerator
|
169
|
+
#
|
170
|
+
# Returns an Enumerator providing a means to enumerate all jobs in the beanstalkd
|
171
|
+
# pool.
|
172
|
+
#
|
173
|
+
# Used internally to enumerate all jobs to find jobs matching an assertion.
|
174
|
+
def jobs
|
175
|
+
raise NotImplementedError
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# :call-seq:
|
180
|
+
# pretty_print_job(job) => String
|
181
|
+
#
|
182
|
+
# Returns a String representation of +job+ in a pretty, human readable
|
183
|
+
# format.
|
184
|
+
#
|
185
|
+
# Used internally to print a job when an assertion fails.
|
186
|
+
def pretty_print_job
|
187
|
+
raise NotImplementedError
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
# :call-seq:
|
192
|
+
# pretty_print_tube(tube) => String
|
193
|
+
#
|
194
|
+
# Returns a String representation of the tube in a pretty, human readable
|
195
|
+
# format. Used internally to print a tube when an assertion fails.
|
196
|
+
#
|
197
|
+
# Used internally to print a tube when an assertion fails.
|
198
|
+
def pretty_print_tube
|
199
|
+
raise NotImplementedError
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
# :call-seq:
|
204
|
+
# tube_matches?(tube, options => {Symbol,String => Numeric,Proc,Range,Regexp,String}) => Boolean
|
205
|
+
#
|
206
|
+
# Returns a boolean indicating whether or not the provided +tube+ matches the
|
207
|
+
# given Hash of +options. Each _key_ in +options+ is a String or a Symbol that
|
208
|
+
# identifies an attribute of +tube+ that the corresponding _value_ should be
|
209
|
+
# compared against. True is returned if every _value_ in +options+ evaluates to
|
210
|
+
# true when compared to the attribute of +tube+ identified by the corresponding
|
211
|
+
# _key_. False is returned if any of the comparisons evaluates to false.
|
212
|
+
#
|
213
|
+
# If no options are given, returns true for any tube that exists at the time of
|
214
|
+
# evaluation.
|
215
|
+
#
|
216
|
+
# Each attribute comparison is performed using the triple equal (===)
|
217
|
+
# operator/method of _value_ with the attribute of +job+ identified by _key_
|
218
|
+
# passed into the method. Use of === allows for more complex comparisons using
|
219
|
+
# Procs, Ranges, Regexps, etc.
|
220
|
+
#
|
221
|
+
# Consult MATCHABLE_TUBE_ATTRIBUTES for a list of which attributes of +tube+
|
222
|
+
# can be matched against.
|
223
|
+
#
|
224
|
+
# Used internally to evaluate if a tube matches an assertion.
|
225
|
+
#
|
226
|
+
# strategy.tube_matches?(paused_tube, :state => 'paused')
|
227
|
+
# #=> true
|
228
|
+
#
|
229
|
+
# strategy.tube_matches(test_tube, :name => /test/)
|
230
|
+
# #=> true
|
231
|
+
#
|
232
|
+
# strategy.tube_matches(backed_up_tube, 'current-jobs-ready' => 50..100)
|
233
|
+
# #=> true
|
234
|
+
def tube_matches?
|
235
|
+
raise NotImplementedError
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
# :call-seq:
|
240
|
+
# tubes() => Enumerator
|
241
|
+
#
|
242
|
+
# Returns an Enumerator providing a means to enumerate all tubes in the beanstalkd
|
243
|
+
# pool.
|
244
|
+
#
|
245
|
+
# Used internally to enumerate all tubes to find tubes matching an assertion.
|
246
|
+
def tubes
|
247
|
+
raise NotImplementedError
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|