bean_counter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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