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/test/test_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear_merged!
|
3
|
+
|
4
|
+
lib = File.expand_path('../../lib', __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
require 'mocha/setup'
|
9
|
+
require 'minitest/autorun'
|
10
|
+
require 'minitest/should'
|
11
|
+
require 'bean_counter'
|
12
|
+
require 'bean_counter/mini_test'
|
13
|
+
require 'securerandom'
|
14
|
+
|
15
|
+
# Open TCP connection with beanstalkd server prevents
|
16
|
+
# JRuby timeout from killing thread. Has some weird
|
17
|
+
# side effects, but allows Timeout#timeout to work
|
18
|
+
if RUBY_PLATFORM == 'java'
|
19
|
+
module Timeout
|
20
|
+
def timeout(sec, klass=nil)
|
21
|
+
return yield(sec) if sec == nil or sec.zero?
|
22
|
+
thread = Thread.new { yield(sec) }
|
23
|
+
|
24
|
+
if thread.join(sec).nil?
|
25
|
+
java_thread = JRuby.reference(thread)
|
26
|
+
thread.kill
|
27
|
+
java_thread.native_thread.interrupt
|
28
|
+
thread.join(0.15)
|
29
|
+
raise (klass || Error), 'execution expired'
|
30
|
+
else
|
31
|
+
thread.value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
BeanCounter.beanstalkd_url = 'beanstalk://localhost'
|
38
|
+
|
39
|
+
class BeanCounter::TestCase < MiniTest::Should::TestCase
|
40
|
+
|
41
|
+
include Timeout
|
42
|
+
|
43
|
+
|
44
|
+
def client
|
45
|
+
return @client ||= Beaneater::Connection.new('localhost:11300')
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class BeanCounter::KnownStrategy < BeanCounter::Strategy
|
52
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CoreTest < BeanCounter::TestCase
|
4
|
+
|
5
|
+
context '::beanstalkd_url' do
|
6
|
+
|
7
|
+
should 'check BeanCounter, then ENV, then Beaneater for beanstalkd_url and raise an error if no url found' do
|
8
|
+
original_url_bean_counter = BeanCounter.beanstalkd_url
|
9
|
+
original_url_env = ENV['BEANSTALKD_URL']
|
10
|
+
original_url_beaneater = Beaneater.configuration.beanstalkd_url
|
11
|
+
|
12
|
+
BeanCounter.beanstalkd_url = nil
|
13
|
+
Beaneater.configuration.beanstalkd_url = nil
|
14
|
+
ENV['BEANSTALKD_URL'] = nil
|
15
|
+
|
16
|
+
assert_raises(RuntimeError) do
|
17
|
+
BeanCounter.beanstalkd_url
|
18
|
+
end
|
19
|
+
|
20
|
+
new_url = 'beanstalk://beaneater'
|
21
|
+
Beaneater.configuration.beanstalkd_url = new_url
|
22
|
+
assert_equal new_url, BeanCounter.beanstalkd_url
|
23
|
+
Beaneater.configuration.beanstalkd_url = original_url_beaneater
|
24
|
+
|
25
|
+
|
26
|
+
new_url = 'beanstalk://bean_counter'
|
27
|
+
BeanCounter.beanstalkd_url = new_url
|
28
|
+
assert_equal new_url, BeanCounter.beanstalkd_url
|
29
|
+
BeanCounter.beanstalkd_url = original_url_bean_counter
|
30
|
+
|
31
|
+
# Env beanstalkd_url is always turned into an array
|
32
|
+
new_url = 'beanstalk://env'
|
33
|
+
ENV['BEANSTALKD_URL'] = new_url
|
34
|
+
assert_equal [new_url], BeanCounter.beanstalkd_url
|
35
|
+
ENV['BEANSTALKD_URL'] = original_url_env
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
context '::default_strategy' do
|
42
|
+
|
43
|
+
should 'return materialized version of DEFAULT_STRATEGY' do
|
44
|
+
assert_equal(
|
45
|
+
BeanCounter::Strategy.materialize_strategy(BeanCounter::DEFAULT_STRATEGY),
|
46
|
+
BeanCounter.default_strategy
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
context '::reset!' do
|
54
|
+
|
55
|
+
setup do
|
56
|
+
@tube_name = SecureRandom.uuid
|
57
|
+
client.transmit("use #{@tube_name}")
|
58
|
+
@message = SecureRandom.uuid
|
59
|
+
client.transmit("watch #{@tube_name}")
|
60
|
+
client.transmit('ignore default')
|
61
|
+
end
|
62
|
+
|
63
|
+
should 'remove all jobs from all tubes when not given a tube name' do
|
64
|
+
jobs = []
|
65
|
+
jobs << client.transmit("put 0 0 120 #{@message.bytesize}\r\n#{@message}")[:id]
|
66
|
+
timeout(1) do
|
67
|
+
job_id = client.transmit('reserve')[:id]
|
68
|
+
client.transmit("bury #{job_id} 0")
|
69
|
+
end
|
70
|
+
5.times do
|
71
|
+
client.transmit("use #{SecureRandom.uuid}")
|
72
|
+
jobs << client.transmit("put 0 0 120 #{@message.bytesize}\r\n#{@message}")[:id]
|
73
|
+
end
|
74
|
+
client.transmit("use #{SecureRandom.uuid}")
|
75
|
+
jobs << client.transmit("put 0 1024 120 #{@message.bytesize}\r\n#{@message}")[:id]
|
76
|
+
|
77
|
+
BeanCounter.reset!
|
78
|
+
jobs.each do |job_id|
|
79
|
+
assert_raises(Beaneater::NotFoundError) do
|
80
|
+
client.transmit("stats-job #{job_id}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
should 'only remove jobs from the specified tube when given a tube name' do
|
87
|
+
jobs = []
|
88
|
+
jobs << client.transmit("put 0 0 120 #{@message.bytesize}\r\n#{@message}")[:id]
|
89
|
+
timeout(1) do
|
90
|
+
job_id = client.transmit('reserve')[:id]
|
91
|
+
client.transmit("bury #{job_id} 0")
|
92
|
+
end
|
93
|
+
jobs << client.transmit("put 0 1024 120 #{@message.bytesize}\r\n#{@message}")[:id]
|
94
|
+
|
95
|
+
client.transmit("use #{SecureRandom.uuid}")
|
96
|
+
other_job_id = client.transmit("put 0 0 120 #{@message.bytesize}\r\n#{@message}")[:id].to_i
|
97
|
+
|
98
|
+
BeanCounter.reset!(@tube_name)
|
99
|
+
jobs.each do |job_id|
|
100
|
+
assert_raises(Beaneater::NotFoundError) do
|
101
|
+
client.transmit("stats-job #{job_id}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
assert_equal other_job_id, client.transmit("stats-job #{other_job_id}")[:body]['id']
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
context '::strategies' do
|
111
|
+
|
112
|
+
should 'return strategies from BeanCounter::Strategy' do
|
113
|
+
BeanCounter::Strategy.expects(:strategies).once.returns(:return_value)
|
114
|
+
assert_equal :return_value, BeanCounter.strategies
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
context '::strategy=' do
|
121
|
+
|
122
|
+
should 'set instance variable when given valid strategy' do
|
123
|
+
original_strategy = BeanCounter.strategy.class
|
124
|
+
[
|
125
|
+
'BeanCounter::KnownStrategy',
|
126
|
+
:'BeanCounter::KnownStrategy',
|
127
|
+
BeanCounter::KnownStrategy
|
128
|
+
].each do |good_strategy|
|
129
|
+
BeanCounter.strategy = BeanCounter::KnownStrategy
|
130
|
+
assert_kind_of BeanCounter::KnownStrategy, BeanCounter.strategy
|
131
|
+
BeanCounter.strategy = BeanCounter::Strategy
|
132
|
+
end
|
133
|
+
BeanCounter.strategy = original_strategy
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
should 'accept nil for strategy' do
|
138
|
+
original_strategy = BeanCounter.strategy.class
|
139
|
+
BeanCounter.strategy = nil
|
140
|
+
assert_nil BeanCounter.instance_variable_get(:@strategy)
|
141
|
+
BeanCounter.strategy = original_strategy
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
context '::strategy' do
|
148
|
+
|
149
|
+
should 'default to BeanCounter.default_strategy' do
|
150
|
+
BeanCounter.strategy = nil
|
151
|
+
assert_kind_of BeanCounter.default_strategy, BeanCounter.strategy
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class EnqueuedExpectationTest < BeanCounter::TestCase
|
4
|
+
|
5
|
+
EnqueuedExpectation = BeanCounter::EnqueuedExpectation
|
6
|
+
|
7
|
+
context '#collection_matcher' do
|
8
|
+
|
9
|
+
should 'traverse collection with detect and return true if no count given' do
|
10
|
+
expectation = EnqueuedExpectation.new({})
|
11
|
+
collection = [:job]
|
12
|
+
collection.expects(:detect).once.returns(:job)
|
13
|
+
expectation.strategy.expects(:jobs).returns(collection)
|
14
|
+
assert expectation.matches?
|
15
|
+
assert_equal collection, expectation.found
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
should 'traverse collection with select and return true if count matches' do
|
20
|
+
expectation = EnqueuedExpectation.new({:count => 1})
|
21
|
+
collection = [:job]
|
22
|
+
collection.expects(:select).once.returns(collection)
|
23
|
+
expectation.strategy.expects(:jobs).returns(collection)
|
24
|
+
assert expectation.matches?
|
25
|
+
assert_equal collection, expectation.found
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
should 'return false and set found if strategy does not include match' do
|
30
|
+
expectation = EnqueuedExpectation.new({})
|
31
|
+
expectation.strategy.expects(:jobs).returns([:wrong_job])
|
32
|
+
expectation.strategy.expects(:job_matches?).returns(false)
|
33
|
+
refute expectation.matches?
|
34
|
+
assert_nil expectation.found
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
should 'return false and set found if strategy does not include expected number of matches' do
|
39
|
+
collection = [:job, :job, :job]
|
40
|
+
[{:count => 0}, {:count => 1}, {:count => 2}, {:count => 1..2}].each do |expected|
|
41
|
+
expectation = EnqueuedExpectation.new(expected)
|
42
|
+
expectation.strategy.expects(:jobs).returns(collection)
|
43
|
+
expectation.strategy.expects(:job_matches?).times(3).returns(true)
|
44
|
+
refute expectation.matches?
|
45
|
+
assert_equal collection, expectation.found
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
context '#expected_count?' do
|
53
|
+
|
54
|
+
should 'return true if a certain number of matches expected' do
|
55
|
+
expectation = EnqueuedExpectation.new(:count => 5)
|
56
|
+
assert expectation.expected_count?
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
should 'return false if any number of matches expected' do
|
61
|
+
expectation = EnqueuedExpectation.new({})
|
62
|
+
refute expectation.expected_count?
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
context '#failure message' do
|
69
|
+
|
70
|
+
should 'return expected message if nothing found and no count' do
|
71
|
+
expected = {}
|
72
|
+
expectation = EnqueuedExpectation.new(expected)
|
73
|
+
expected.expects(:to_s).returns('expected')
|
74
|
+
expected = 'expected any number of jobs matching expected, found none'
|
75
|
+
assert_equal expected, expectation.failure_message
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
should 'return expected message if nothing found and count given' do
|
80
|
+
expected = {:count => 3}
|
81
|
+
expectation = EnqueuedExpectation.new(expected)
|
82
|
+
expected.expects(:to_s).returns('expected')
|
83
|
+
expected = 'expected 3 jobs matching expected, found none'
|
84
|
+
assert_equal expected, expectation.failure_message
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
should 'return expected message if number found does not match count given' do
|
89
|
+
expected = {:count => 3}
|
90
|
+
expectation = EnqueuedExpectation.new(expected)
|
91
|
+
expectation.strategy.jobs.expects(:select).returns([:job, :job])
|
92
|
+
expectation.matches?
|
93
|
+
expectation.strategy.expects(:pretty_print_job).twice.returns('job')
|
94
|
+
expected.expects(:to_s).returns('expected')
|
95
|
+
expected = "expected 3 jobs matching expected, found 2: job\njob"
|
96
|
+
assert_equal expected, expectation.failure_message
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
context '#new' do
|
103
|
+
|
104
|
+
should 'set expected correctly' do
|
105
|
+
expected = {}
|
106
|
+
expectation = EnqueuedExpectation.new(expected)
|
107
|
+
assert_equal expectation.expected, expected
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
should 'pull out count and set expected_count correctly' do
|
112
|
+
expected = {:count => 1}
|
113
|
+
expectation = EnqueuedExpectation.new(expected)
|
114
|
+
assert_equal({}, expectation.expected)
|
115
|
+
assert_equal 1, expectation.expected_count
|
116
|
+
|
117
|
+
expected = {'count' => 2}
|
118
|
+
expectation = EnqueuedExpectation.new(expected)
|
119
|
+
assert_equal({}, expectation.expected)
|
120
|
+
assert_equal 2, expectation.expected_count
|
121
|
+
|
122
|
+
expected = {:count => 3, 'count' => 4}
|
123
|
+
expectation = EnqueuedExpectation.new(expected)
|
124
|
+
assert_equal({}, expectation.expected)
|
125
|
+
assert_equal 3, expectation.expected_count
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
context '#matches?' do
|
132
|
+
|
133
|
+
setup do
|
134
|
+
@expectation = EnqueuedExpectation.new({})
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
should 'call #proc_matcher when given Proc' do
|
139
|
+
lamb = lambda {}
|
140
|
+
prok = proc {}
|
141
|
+
proc_new = Proc.new {}
|
142
|
+
@expectation.expects(:proc_matcher).times(3)
|
143
|
+
@expectation.matches?(lamb)
|
144
|
+
@expectation.matches?(prok)
|
145
|
+
@expectation.matches?(proc_new)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
should 'call #collection_macther with strategy.jobs when not given proc' do
|
150
|
+
@expectation.expects(:collection_matcher)
|
151
|
+
BeanCounter.strategy.expects(:jobs).returns([])
|
152
|
+
@expectation.matches?
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
context '#negative_failure_message' do
|
159
|
+
|
160
|
+
should 'return empty string if nothing found' do
|
161
|
+
expectation = EnqueuedExpectation.new({})
|
162
|
+
expectation.strategy.expects(:jobs).returns([])
|
163
|
+
refute expectation.matches?
|
164
|
+
assert_equal '', expectation.negative_failure_message
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
should 'return expected message if matching job found and no count given' do
|
169
|
+
expectation = EnqueuedExpectation.new({})
|
170
|
+
expectation.strategy.jobs.expects(:detect).returns(:job)
|
171
|
+
expectation.strategy.expects(:pretty_print_job).returns('job')
|
172
|
+
expectation.expected.expects(:to_s).returns('expected')
|
173
|
+
assert expectation.matches?
|
174
|
+
expected = 'did not expect any jobs matching expected, found 1: job'
|
175
|
+
assert_equal expected, expectation.negative_failure_message
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
should 'return expected message if matching job found and count of 1 given' do
|
180
|
+
expectation = EnqueuedExpectation.new({:count => 1})
|
181
|
+
expectation.strategy.jobs.expects(:select).returns([:job])
|
182
|
+
expectation.strategy.expects(:pretty_print_job).returns('job')
|
183
|
+
expectation.expected.expects(:to_s).returns('expected')
|
184
|
+
assert expectation.matches?
|
185
|
+
expected = 'did not expect 1 job matching expected, found 1: job'
|
186
|
+
assert_equal expected, expectation.negative_failure_message
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
should 'return expected message if matching jobs found and count not equal 1' do
|
191
|
+
expectation = EnqueuedExpectation.new({:count => 2})
|
192
|
+
expectation.strategy.jobs.expects(:select).returns([:job, :job])
|
193
|
+
expectation.strategy.expects(:pretty_print_job).twice.returns('job')
|
194
|
+
expectation.expected.expects(:to_s).returns('expected')
|
195
|
+
assert expectation.matches?
|
196
|
+
expected = "did not expect 2 jobs matching expected, found 2: job\njob"
|
197
|
+
assert_equal expected, expectation.negative_failure_message
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
context '#proc_matcher' do
|
203
|
+
|
204
|
+
setup do
|
205
|
+
@prok = proc {}
|
206
|
+
@expectation = EnqueuedExpectation.new({})
|
207
|
+
end
|
208
|
+
|
209
|
+
should 'pass provided block to strategy#collect_new_jobs' do
|
210
|
+
@expectation.strategy.expects(:collect_new_jobs).returns([])
|
211
|
+
refute @expectation.matches?(@prok)
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
should 'pass collected jobs to #collection_matcher' do
|
216
|
+
@expectation.strategy.expects(:collect_new_jobs).returns([:job])
|
217
|
+
@expectation.strategy.expects(:job_matches?).returns(true)
|
218
|
+
assert @expectation.matches?(@prok)
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,501 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StalkClimberStrategyTest < BeanCounter::TestCase
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@strategy = BeanCounter::Strategy::StalkClimberStrategy.new
|
7
|
+
end
|
8
|
+
|
9
|
+
context '#jobs' do
|
10
|
+
|
11
|
+
should 'implement #jobs' do
|
12
|
+
assert @strategy.respond_to?(:jobs)
|
13
|
+
assert_kind_of Enumerable, @strategy.jobs
|
14
|
+
begin
|
15
|
+
@strategy.jobs.each do
|
16
|
+
break
|
17
|
+
end
|
18
|
+
rescue NotImplementedError
|
19
|
+
raise 'Expected subclass of Strategy, BeanCounter::Strategy::StalkClimberStrategy, to provide #jobs enumerator'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
context '#collect_new_jobs' do
|
27
|
+
|
28
|
+
should 'raise ArgumentError unless block given' do
|
29
|
+
assert_raises(ArgumentError) do
|
30
|
+
@strategy.collect_new_jobs
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
should 'return empty array if no new jobs enqueued' do
|
36
|
+
new_jobs = @strategy.collect_new_jobs {}
|
37
|
+
assert_equal [], new_jobs
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
should 'return only jobs enqueued during block execution' do
|
42
|
+
@tube_name = SecureRandom.uuid
|
43
|
+
client.transmit("use #{@tube_name}")
|
44
|
+
client.transmit("watch #{@tube_name}")
|
45
|
+
client.transmit('ignore default')
|
46
|
+
message = SecureRandom.uuid
|
47
|
+
all_jobs = []
|
48
|
+
all_jobs << client.transmit("put 0 0 120 #{message.bytesize}\r\n#{message}")[:id].to_i
|
49
|
+
|
50
|
+
jobs = []
|
51
|
+
new_jobs = @strategy.collect_new_jobs do
|
52
|
+
5.times do
|
53
|
+
message = SecureRandom.uuid
|
54
|
+
job_id = client.transmit("put 0 0 120 #{message.bytesize}\r\n#{message}")[:id].to_i
|
55
|
+
jobs << job_id
|
56
|
+
all_jobs << job_id
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
message = SecureRandom.uuid
|
61
|
+
all_jobs << client.transmit("put 0 0 120 #{message.bytesize}\r\n#{message}")[:id].to_i
|
62
|
+
assert_equal new_jobs.map(&:id), jobs
|
63
|
+
all_jobs.each { |job_id| client.transmit("delete #{job_id}") }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
context '#delete_job' do
|
70
|
+
|
71
|
+
should 'delete the provided job and return true' do
|
72
|
+
message = SecureRandom.uuid
|
73
|
+
job = client.transmit("put 0 0 120 #{message.bytesize}\r\n#{message}")
|
74
|
+
strategy_job = StalkClimber::Job.new(job)
|
75
|
+
assert @strategy.delete_job(strategy_job), 'Failed to delete job'
|
76
|
+
refute strategy_job.exists?
|
77
|
+
assert_raises(Beaneater::NotFoundError) do
|
78
|
+
client.transmit("stats-job #{job[:id]}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
should 'not complain and return true if job already deleted' do
|
84
|
+
message = SecureRandom.uuid
|
85
|
+
job = client.transmit("put 0 0 120 #{message.bytesize}\r\n#{message}")
|
86
|
+
strategy_job = StalkClimber::Job.new(job)
|
87
|
+
client.transmit("delete #{job[:id]}")
|
88
|
+
assert @strategy.delete_job(strategy_job), 'Expected true return value after attempt to delete deleted job'
|
89
|
+
refute strategy_job.exists?
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
should 'return false if the job could not be deleted' do
|
94
|
+
tube_name = SecureRandom.uuid
|
95
|
+
client.transmit("use #{tube_name}")
|
96
|
+
client.transmit("watch #{tube_name}")
|
97
|
+
client.transmit('ignore default')
|
98
|
+
message = SecureRandom.uuid
|
99
|
+
job = client.transmit("put 0 0 120 #{message.bytesize}\r\n#{message}")
|
100
|
+
strategy_job = @strategy.jobs.detect{|strat_job| strat_job.body == message}
|
101
|
+
reserved_job = client.transmit('reserve')
|
102
|
+
assert_equal strategy_job.id, reserved_job[:id].to_i
|
103
|
+
refute @strategy.delete_job(strategy_job), 'Expected false return value after failed attempt to delete reserved job'
|
104
|
+
assert strategy_job.exists?
|
105
|
+
client.transmit("delete #{job[:id]}")
|
106
|
+
refute strategy_job.exists?
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
context '#job_matches?' do
|
112
|
+
|
113
|
+
setup do
|
114
|
+
@tube_name = SecureRandom.uuid
|
115
|
+
@message = SecureRandom.uuid
|
116
|
+
client.transmit("use #{@tube_name}")
|
117
|
+
client.transmit("watch #{@tube_name}")
|
118
|
+
client.transmit('ignore default')
|
119
|
+
@job = client.transmit("put 0 0 120 #{@message.bytesize}\r\n#{@message}")
|
120
|
+
@strategy_job = StalkClimber::Job.new(@job)
|
121
|
+
# Update job state so everything is cached
|
122
|
+
@strategy_job.age
|
123
|
+
@strategy_job.body
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
teardown do
|
128
|
+
begin
|
129
|
+
client.transmit("delete #{@job[:id]}")
|
130
|
+
rescue Beaneater::NotFoundError
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
should 'match string fields using string or regex' do
|
136
|
+
job_stats = client.transmit("stats-job #{@job[:id]}")[:body]
|
137
|
+
{
|
138
|
+
:body => @message,
|
139
|
+
:state => job_stats['state'],
|
140
|
+
:tube => @tube_name,
|
141
|
+
}.each_pair do |key, value|
|
142
|
+
assert @strategy.job_matches?(@strategy_job, key => value)
|
143
|
+
assert @strategy.job_matches?(@strategy_job, key => %r[#{value[2, 3]}])
|
144
|
+
refute @strategy.job_matches?(@strategy_job, key => 'foobar')
|
145
|
+
refute @strategy.job_matches?(@strategy_job, key => /foobar/)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
should 'match integer fields using integer or range' do
|
151
|
+
job_stats = client.transmit("stats-job #{@job[:id]}")[:body]
|
152
|
+
verify_attrs(@strategy_job, {
|
153
|
+
:age => job_stats['age'],
|
154
|
+
:buries => job_stats['buries'],
|
155
|
+
:delay => job_stats['delay'],
|
156
|
+
:id => @job[:id].to_i,
|
157
|
+
:kicks => job_stats['kicks'],
|
158
|
+
:pri => job_stats['pri'],
|
159
|
+
:releases => job_stats['releases'],
|
160
|
+
:reserves => job_stats['reserves'],
|
161
|
+
:'time-left' => job_stats['time-left'],
|
162
|
+
:timeouts => job_stats['timeouts'],
|
163
|
+
:ttr => job_stats['ttr'],
|
164
|
+
})
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
should 'match integer fields using integer or range (with more stubs)' do
|
169
|
+
job_attrs = {
|
170
|
+
:age => 100,
|
171
|
+
:buries => 101,
|
172
|
+
:delay => 102,
|
173
|
+
:id => 12345,
|
174
|
+
:kicks => 103,
|
175
|
+
:pri => 104,
|
176
|
+
:releases => 105,
|
177
|
+
:reserves => 106,
|
178
|
+
:'time-left' => 107,
|
179
|
+
:timeouts => 108,
|
180
|
+
:ttr => 109,
|
181
|
+
}
|
182
|
+
job_attrs.each_pair do |key, value|
|
183
|
+
sanitized_key = @strategy.send(:stats_method_name, key)
|
184
|
+
@strategy_job.expects(sanitized_key).times(8).returns(value)
|
185
|
+
end
|
186
|
+
@strategy_job.expects(:exists?).times(job_attrs.length * 4).returns(true)
|
187
|
+
verify_attrs(@strategy_job, job_attrs)
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
should 'be able to match with a proc' do
|
192
|
+
matching_connection_proc = proc do |connection|
|
193
|
+
connection == client
|
194
|
+
end
|
195
|
+
assert @strategy.job_matches?(@strategy_job, :connection => matching_connection_proc)
|
196
|
+
|
197
|
+
failing_connection_proc = proc do |connection|
|
198
|
+
connection != client
|
199
|
+
end
|
200
|
+
refute @strategy.job_matches?(@strategy_job, :connection => failing_connection_proc)
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
should 'not try to match on non-matachable attributes' do
|
205
|
+
# Called once to refresh job state before checking match
|
206
|
+
@strategy_job.expects(:exists?).once.returns(true)
|
207
|
+
|
208
|
+
# Would be called but skipped because of expectation on exists?
|
209
|
+
@strategy_job.expects(:stats).never
|
210
|
+
|
211
|
+
# Should never be called
|
212
|
+
@strategy_job.expects(:delete).never
|
213
|
+
|
214
|
+
assert @strategy.job_matches?(@strategy_job, {
|
215
|
+
:body => @message,
|
216
|
+
:delete => 'bar',
|
217
|
+
:exists? => 'baz',
|
218
|
+
:stats => 'boom',
|
219
|
+
})
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
should 'not match job deleted after it was cached' do
|
224
|
+
client.transmit("delete #{@job[:id]}")
|
225
|
+
refute @strategy.job_matches?(@strategy_job, :body => @message)
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
should 'match against updated job stats' do
|
230
|
+
pri = 1024
|
231
|
+
delay = 2048
|
232
|
+
job = nil
|
233
|
+
timeout(1) do
|
234
|
+
job = client.transmit('reserve')
|
235
|
+
end
|
236
|
+
client.transmit("release #{job[:id]} #{pri} #{delay}")
|
237
|
+
assert @strategy.job_matches?(@strategy_job, {
|
238
|
+
:body => @message,
|
239
|
+
:delay => (delay - 2)..delay,
|
240
|
+
:pri => pri,
|
241
|
+
:state => 'delayed',
|
242
|
+
})
|
243
|
+
refute @strategy.job_matches?(@strategy_job, {
|
244
|
+
:body => @message,
|
245
|
+
:delay => 0,
|
246
|
+
:pri => 0,
|
247
|
+
})
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
context '#pretty_print_job' do
|
254
|
+
|
255
|
+
should 'return expected text representation of job' do
|
256
|
+
job_body = SecureRandom.uuid
|
257
|
+
stats_body = {
|
258
|
+
'age' => age = 3,
|
259
|
+
'body' => job_body, # Will be ignored during job init
|
260
|
+
'buries' => buries = 0,
|
261
|
+
'delay' => delay = 0,
|
262
|
+
'id' => id = 4412,
|
263
|
+
'kicks' => kicks = 0,
|
264
|
+
'pri' => pri = 4294967295,
|
265
|
+
'releases' => releases = 0,
|
266
|
+
'reserves' => reserves = 0,
|
267
|
+
'state' => state = 'ready',
|
268
|
+
'time-left' => time_left = 0,
|
269
|
+
'timeouts' => timeouts = 0,
|
270
|
+
'ttr' => ttr = 300,
|
271
|
+
'tube' => tube = 'default',
|
272
|
+
}
|
273
|
+
stats_response = {
|
274
|
+
:body => stats_body,
|
275
|
+
:connection => client,
|
276
|
+
:id => 149,
|
277
|
+
:status => 'OK',
|
278
|
+
}
|
279
|
+
job = StalkClimber::Job.new(stats_response)
|
280
|
+
job.instance_variable_set(:@body, job_body)
|
281
|
+
job.connection.expects(:transmit).once.returns(stats_response)
|
282
|
+
expected = %W[
|
283
|
+
"age"=>#{age} "body"=>"#{job_body}" "buries"=>#{buries} "connection"=>#{client.to_s}
|
284
|
+
"delay"=>#{delay} "id"=>#{id} "kicks"=>#{kicks} "pri"=>#{pri} "releases"=>#{releases}
|
285
|
+
"reserves"=>#{reserves} "state"=>"#{state}" "time-left"=>#{time_left}
|
286
|
+
"timeouts"=>#{timeouts} "ttr"=>#{ttr} "tube"=>"#{tube}"
|
287
|
+
].join(', ')
|
288
|
+
assert_equal "{#{expected}}", @strategy.pretty_print_job(job)
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
context '#pretty_print_tube' do
|
295
|
+
|
296
|
+
should 'return expected text representation of tube' do
|
297
|
+
tube_name = SecureRandom.uuid
|
298
|
+
stats_struct = Beaneater::StatStruct.from_hash({
|
299
|
+
'cmd-delete' => cmd_delete = 100,
|
300
|
+
'cmd-pause-tube' => cmd_pause_tube = 101,
|
301
|
+
'current-jobs-buried' => current_jobs_buried = 102,
|
302
|
+
'current-jobs-delayed' => current_jobs_delayed = 103,
|
303
|
+
'current-jobs-ready' => current_jobs_ready = 104,
|
304
|
+
'current-jobs-reserved' => current_jobs_reserved = 105,
|
305
|
+
'current-jobs-urgent' => current_jobs_urgent = 106,
|
306
|
+
'current-using' => current_using = 107,
|
307
|
+
'current-waiting' => current_waiting = 108,
|
308
|
+
'current-watching' => current_watching = 109,
|
309
|
+
'name' => name = tube_name,
|
310
|
+
'pause-time-left' => pause_time_left = 110,
|
311
|
+
'pause' => pause = 111,
|
312
|
+
'total-jobs' => total_jobs = 112,
|
313
|
+
})
|
314
|
+
connection_pool = @strategy.send(:climber).connection_pool
|
315
|
+
tube = StalkClimber::Tube.new(connection_pool, tube_name)
|
316
|
+
tube.stubs(:stats).returns(stats_struct)
|
317
|
+
expected = %W[
|
318
|
+
"cmd-delete"=>#{cmd_delete} "cmd-pause-tube"=>#{cmd_pause_tube}
|
319
|
+
"current-jobs-buried"=>#{current_jobs_buried}
|
320
|
+
"current-jobs-delayed"=>#{current_jobs_delayed}
|
321
|
+
"current-jobs-ready"=>#{current_jobs_ready}
|
322
|
+
"current-jobs-reserved"=>#{current_jobs_reserved}
|
323
|
+
"current-jobs-urgent"=>#{current_jobs_urgent}
|
324
|
+
"current-using"=>#{current_using} "current-waiting"=>#{current_waiting}
|
325
|
+
"current-watching"=>#{current_watching} "name"=>"#{name}"
|
326
|
+
"pause"=>#{pause} "pause-time-left"=>#{pause_time_left}
|
327
|
+
"total-jobs"=>#{total_jobs}
|
328
|
+
].join(', ')
|
329
|
+
assert_equal "{#{expected}}", @strategy.pretty_print_tube(tube)
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
context 'tube_matches?' do
|
336
|
+
|
337
|
+
setup do
|
338
|
+
@tube_name = SecureRandom.uuid
|
339
|
+
@message = SecureRandom.uuid
|
340
|
+
client.transmit("watch #{@tube_name}")
|
341
|
+
@tube = StalkClimber::Tube.new(@strategy.send(:climber).connection_pool, @tube_name)
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
should 'match name using string or regex' do
|
346
|
+
assert @strategy.tube_matches?(@tube, :name => @tube_name)
|
347
|
+
assert @strategy.tube_matches?(@tube, :name => %r[#{@tube_name[2, 3]}])
|
348
|
+
refute @strategy.tube_matches?(@tube, :name => 'foobar')
|
349
|
+
refute @strategy.tube_matches?(@tube, :name => /foobar/)
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
should 'match integer fields using integer or range' do
|
354
|
+
tube_stats = client.transmit("stats-tube #{@tube_name}")[:body]
|
355
|
+
verify_attrs(@tube, {
|
356
|
+
'cmd-delete' => tube_stats['cmd-delete'],
|
357
|
+
'cmd-pause-tube' => tube_stats['cmd-pause-tube'],
|
358
|
+
'current-jobs-buried' => tube_stats['current-jobs-buried'],
|
359
|
+
'current-jobs-delayed' => tube_stats['current-jobs-delayed'],
|
360
|
+
'current-jobs-ready' => tube_stats['current-jobs-ready'],
|
361
|
+
'current-jobs-reserved' => tube_stats['current-jobs-reserved'],
|
362
|
+
'current-jobs-urgent' => tube_stats['current-jobs-urgent'],
|
363
|
+
'current-using' => tube_stats['current-using'],
|
364
|
+
'current-waiting' => tube_stats['current-waiting'],
|
365
|
+
'current-watching' => tube_stats['current-watching'],
|
366
|
+
'pause' => tube_stats['pause'],
|
367
|
+
'pause-time-left' => tube_stats['pause-time-left'],
|
368
|
+
'total-jobs' => tube_stats['total-jobs'],
|
369
|
+
})
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
should 'match integer fields using integer or range (with more stubs)' do
|
374
|
+
tube_attrs = {
|
375
|
+
'cmd-delete' => 100,
|
376
|
+
'cmd-pause-tube' => 101,
|
377
|
+
'current-jobs-buried' => 102,
|
378
|
+
'current-jobs-delayed' => 103,
|
379
|
+
'current-jobs-ready' => 104,
|
380
|
+
'current-jobs-reserved' => 105,
|
381
|
+
'current-jobs-urgent' => 106,
|
382
|
+
'current-using' => 107,
|
383
|
+
'current-waiting' => 108,
|
384
|
+
'current-watching' => 109,
|
385
|
+
'pause-time-left' => 110,
|
386
|
+
'pause' => 111,
|
387
|
+
'total-jobs' => 112,
|
388
|
+
}
|
389
|
+
tube_attrs.each_pair do |key, value|
|
390
|
+
sanitized_key = @strategy.send(:stats_method_name, key)
|
391
|
+
@tube.expects(sanitized_key).times(8).returns(value)
|
392
|
+
end
|
393
|
+
@tube.expects(:exists?).times(tube_attrs.length * 4).returns(true)
|
394
|
+
verify_attrs(@tube, tube_attrs)
|
395
|
+
end
|
396
|
+
|
397
|
+
|
398
|
+
should 'not try to match on non-matachable attributes' do
|
399
|
+
# Called once to refresh state before checking match
|
400
|
+
@tube.expects(:exists?).once.returns(true)
|
401
|
+
|
402
|
+
# Attribute matcher on pause calls pause_time
|
403
|
+
@tube.expects(:pause_time).once.returns(5)
|
404
|
+
|
405
|
+
# Would be called but skipped because of expectation on exists?
|
406
|
+
@tube.expects(:connection).never
|
407
|
+
@tube.expects(:stats).never
|
408
|
+
|
409
|
+
# Should never be called
|
410
|
+
%w[clear delete kick pause peek put reserve].each do |method|
|
411
|
+
@tube.expects(method).never
|
412
|
+
end
|
413
|
+
|
414
|
+
assert @strategy.tube_matches?(@tube, {
|
415
|
+
:clear => true,
|
416
|
+
:connection => 'foo',
|
417
|
+
:delete => 'bar',
|
418
|
+
:exists? => 'baz',
|
419
|
+
:kick => true,
|
420
|
+
:name => @tube_name,
|
421
|
+
:pause => 5,
|
422
|
+
:peek => true,
|
423
|
+
:put => true,
|
424
|
+
:reserve => true,
|
425
|
+
:stats => 'boom',
|
426
|
+
})
|
427
|
+
end
|
428
|
+
|
429
|
+
|
430
|
+
should 'not match tube that no longer exists' do
|
431
|
+
client.transmit("ignore #{@tube_name}")
|
432
|
+
refute @tube.exists?
|
433
|
+
refute @strategy.tube_matches?(@tube)
|
434
|
+
end
|
435
|
+
|
436
|
+
|
437
|
+
should 'match against updated tube stats' do
|
438
|
+
assert @strategy.tube_matches?(@tube, 'current-using' => 0)
|
439
|
+
client.transmit("use #{@tube_name}")
|
440
|
+
refute @strategy.tube_matches?(@tube, 'current-using' => 0)
|
441
|
+
assert @strategy.tube_matches?(@tube, 'current-using' => 1)
|
442
|
+
end
|
443
|
+
|
444
|
+
|
445
|
+
end
|
446
|
+
|
447
|
+
|
448
|
+
context 'tubes' do
|
449
|
+
|
450
|
+
should 'implement #tubes' do
|
451
|
+
assert @strategy.respond_to?(:tubes)
|
452
|
+
assert_kind_of Enumerable, @strategy.tubes
|
453
|
+
begin
|
454
|
+
@strategy.tubes.each do
|
455
|
+
break
|
456
|
+
end
|
457
|
+
rescue NotImplementedError
|
458
|
+
raise 'Expected subclass of Strategy, BeanCounter::Strategy::StalkClimberStrategy, to provide #tubes enumerator'
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
463
|
+
|
464
|
+
|
465
|
+
context '#test_tube' do
|
466
|
+
|
467
|
+
should 'return default test tube unless set otherwise' do
|
468
|
+
assert_equal BeanCounter::Strategy::StalkClimberStrategy::TEST_TUBE, @strategy.send(:test_tube)
|
469
|
+
@strategy.test_tube = new_tube = 'bean_counter_stalk_climber_test_new'
|
470
|
+
assert_equal new_tube, @strategy.send(:test_tube)
|
471
|
+
@strategy.test_tube = nil
|
472
|
+
assert_equal BeanCounter::Strategy::StalkClimberStrategy::TEST_TUBE, @strategy.send(:test_tube)
|
473
|
+
end
|
474
|
+
|
475
|
+
end
|
476
|
+
|
477
|
+
|
478
|
+
def verify_attrs(strategy_object, attrs)
|
479
|
+
method = strategy_object.is_a?(StalkClimber::Job) ? :job_matches? : :tube_matches?
|
480
|
+
attrs.each_pair do |key, value|
|
481
|
+
sanitized_key = @strategy.send(:stats_method_name, key)
|
482
|
+
assert(
|
483
|
+
@strategy.send(method, strategy_object, key => value),
|
484
|
+
"Expected #{key} (#{strategy_object.send(sanitized_key)}) to match #{value}"
|
485
|
+
)
|
486
|
+
assert(
|
487
|
+
@strategy.send(method, strategy_object, key => (value - 5)..(value + 1)),
|
488
|
+
"Expected #{key} (#{strategy_object.send(sanitized_key)}) to match #{value} +/-5"
|
489
|
+
)
|
490
|
+
refute(
|
491
|
+
@strategy.send(method, strategy_object, key => value - 1),
|
492
|
+
"Expected #{key} (#{strategy_object.send(sanitized_key)}) to not match #{value}"
|
493
|
+
)
|
494
|
+
refute(
|
495
|
+
@strategy.send(method, strategy_object, key => (value + 100)..(value + 200)),
|
496
|
+
"Expected #{key} (#{strategy_object.send(sanitized_key)}) to not match #{value + 100}..#{value + 200}"
|
497
|
+
)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
end
|