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