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.
@@ -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