bean_counter 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/.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
|