backburner 1.7.0 → 1.8.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +45 -0
- data/.github/workflows/github-release.yml +58 -0
- data/.github/workflows/release.yml +221 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile +4 -1
- data/HOOKS.md +23 -14
- data/README.md +32 -9
- data/backburner.gemspec +3 -0
- data/bin/backburner +1 -1
- data/lib/active_job/queue_adapters/backburner_adapter.rb +10 -3
- data/lib/backburner/configuration.rb +7 -0
- data/lib/backburner/connection.rb +4 -2
- data/lib/backburner/enqueue_pool.rb +88 -0
- data/lib/backburner/helpers.rb +77 -22
- data/lib/backburner/job.rb +46 -7
- data/lib/backburner/logger.rb +9 -0
- data/lib/backburner/performable.rb +1 -1
- data/lib/backburner/queue.rb +37 -19
- data/lib/backburner/version.rb +1 -1
- data/lib/backburner/worker.rb +112 -21
- data/lib/backburner/workers/forking.rb +4 -2
- data/lib/backburner/workers/simple.rb +5 -0
- data/lib/backburner/workers/threading.rb +1 -7
- data/lib/backburner/workers/threads_on_fork.rb +58 -23
- data/lib/backburner.rb +7 -1
- data/test/active_job_adapter_test.rb +24 -0
- data/test/back_burner_test.rb +13 -0
- data/test/connection_test.rb +22 -0
- data/test/fixtures/active_job_app.rb +53 -0
- data/test/fixtures/autoload/test_broken_name_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_no_method_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_require_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_runtime_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_syntax_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_timeout_job.rb +4 -0
- data/test/fixtures/autoload/test_same_name_job.rb +5 -0
- data/test/fixtures/test_fork_jobs.rb +5 -0
- data/test/fixtures/test_jobs.rb +53 -0
- data/test/fixtures/test_queue_settings.rb +41 -1
- data/test/helpers/templogger.rb +1 -1
- data/test/helpers_test.rb +184 -0
- data/test/job_test.rb +22 -0
- data/test/logger_test.rb +27 -0
- data/test/performable_test.rb +50 -0
- data/test/queue_test.rb +63 -0
- data/test/test_helper.rb +57 -0
- data/test/worker_test.rb +346 -1
- data/test/workers/forking_worker_test.rb +19 -1
- data/test/workers/simple_worker_test.rb +272 -0
- data/test/workers/threading_worker_test.rb +22 -1
- data/test/workers/threads_on_fork_worker_test.rb +279 -12
- metadata +54 -6
- data/.travis.yml +0 -29
- data/circle.yml +0 -3
|
@@ -11,4 +11,44 @@ class TestJobSettingsOverride
|
|
|
11
11
|
queue_garbage_limit 1000
|
|
12
12
|
queue_retry_limit 2
|
|
13
13
|
def self.perform; end
|
|
14
|
-
end
|
|
14
|
+
end
|
|
15
|
+
class TestTubeLimitsJob
|
|
16
|
+
include Backburner::Queue
|
|
17
|
+
queue "tube-limits"
|
|
18
|
+
queue_jobs_limit 5
|
|
19
|
+
queue_garbage_limit 100
|
|
20
|
+
queue_retry_limit 3
|
|
21
|
+
def self.perform; end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Shares its parent's tube, whose options stay the parent's
|
|
25
|
+
class TestTubeLimitsChildJob < TestTubeLimitsJob
|
|
26
|
+
queue_jobs_limit 1
|
|
27
|
+
queue_garbage_limit 7
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class TestTubeLimitsLambdaJob
|
|
31
|
+
include Backburner::Queue
|
|
32
|
+
queue lambda { |klass| "tube-limits-#{klass.name.downcase}" }
|
|
33
|
+
queue_jobs_limit 3
|
|
34
|
+
def self.perform; end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Gets a tube of its own from the inherited lambda, so its options are its own
|
|
38
|
+
class TestTubeLimitsLambdaChildJob < TestTubeLimitsLambdaJob
|
|
39
|
+
queue_jobs_limit 4
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class TestTubeFewLimitsJob
|
|
43
|
+
include Backburner::Queue
|
|
44
|
+
queue "tube-few-limits"
|
|
45
|
+
queue_retry_limit 2
|
|
46
|
+
def self.perform; end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Shares its parent's tube and fills in the limits the parent leaves unset
|
|
50
|
+
class TestTubeFewLimitsChildJob < TestTubeFewLimitsJob
|
|
51
|
+
queue_jobs_limit 3
|
|
52
|
+
queue_garbage_limit 50
|
|
53
|
+
queue_retry_limit 9
|
|
54
|
+
end
|
data/test/helpers/templogger.rb
CHANGED
data/test/helpers_test.rb
CHANGED
|
@@ -42,6 +42,19 @@ describe "Backburner::Helpers module" do
|
|
|
42
42
|
output = exception_message(RuntimeError.new("test"))
|
|
43
43
|
assert_match(/Exception RuntimeError/, output)
|
|
44
44
|
end
|
|
45
|
+
|
|
46
|
+
it "shortens the backtrace paths under a working directory that has regexp characters in its name" do
|
|
47
|
+
["jobs[1]", "jobs["].each do |name|
|
|
48
|
+
Dir.mktmpdir do |root|
|
|
49
|
+
dir = File.join(File.realpath(root), name)
|
|
50
|
+
Dir.mkdir(dir)
|
|
51
|
+
error = RuntimeError.new("test")
|
|
52
|
+
error.set_backtrace(["#{dir}/app/jobs/report.rb:3:in `perform'"])
|
|
53
|
+
output = Dir.chdir(dir) { exception_message(error) }
|
|
54
|
+
assert_equal "Exception RuntimeError -> test\n app/jobs/report.rb:3:in `perform'", output, name
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
45
58
|
end # exception_message
|
|
46
59
|
|
|
47
60
|
describe "for queue_config" do
|
|
@@ -85,6 +98,118 @@ describe "Backburner::Helpers module" do
|
|
|
85
98
|
test = lambda { "email/send_news" }
|
|
86
99
|
assert_equal "test.foo.job.email/send-news", expand_tube_name(test)
|
|
87
100
|
end #lambdas
|
|
101
|
+
|
|
102
|
+
it "removes prefix if configured as nil" do
|
|
103
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => nil, :namespace_separator => '.'))
|
|
104
|
+
|
|
105
|
+
test = stub(:queue => "email/send_news")
|
|
106
|
+
assert_equal "email/send-news", expand_tube_name(test)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "removes prefix if configured as false" do
|
|
110
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => false, :namespace_separator => '.'))
|
|
111
|
+
|
|
112
|
+
test = stub(:queue => "email/send_news")
|
|
113
|
+
assert_equal "email/send-news", expand_tube_name(test)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Tubes of 1.7.0 with an empty namespace hold queued jobs: keep their names
|
|
117
|
+
it "starts names with the separator if configured as empty" do
|
|
118
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => "", :namespace_separator => '.', :primary_queue => "backburner-jobs"))
|
|
119
|
+
|
|
120
|
+
assert_equal ".email/send-news", expand_tube_name("email/send_news")
|
|
121
|
+
assert_equal ".email/send-news", expand_tube_name(stub(:queue => "email/send_news"))
|
|
122
|
+
assert_equal ".backburner-jobs", expand_tube_name(RuntimeError)
|
|
123
|
+
assert_equal ".mailers", expand_tube_name("mailers")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# A tube name is where queued jobs wait, so a namespace that works keeps every name it gives, including
|
|
127
|
+
# the odd ones of queue names that start with the namespace ("appmail" -> "app.mail")
|
|
128
|
+
it "keeps the tube names it gives for namespaces without regexp characters other than dots" do
|
|
129
|
+
queues = lambda do |namespace, separator|
|
|
130
|
+
["default", "mailers", "production_mailers", "newsletter_sender", "email/send_news", "Admin::ReportJob",
|
|
131
|
+
"#{namespace}mail", "#{namespace}-reports", "#{namespace.tr('.', '-')}-sync", "#{namespace}#{separator}reports"]
|
|
132
|
+
end
|
|
133
|
+
{
|
|
134
|
+
["backburner.worker.queue", "."] => %w[
|
|
135
|
+
backburner.worker.queue.default backburner.worker.queue.mailers backburner.worker.queue.production-mailers
|
|
136
|
+
backburner.worker.queue.newsletter-sender backburner.worker.queue.email/send-news
|
|
137
|
+
backburner.worker.queue.admin/report-job backburner.worker.queue.mail backburner.worker.queue.-reports
|
|
138
|
+
backburner.worker.queue.-sync backburner.worker.queue.reports backburner.worker.queue.backburner-jobs
|
|
139
|
+
],
|
|
140
|
+
["backburner.worker.queue", "_"] => %w[
|
|
141
|
+
backburner.worker.queue_default backburner.worker.queue_mailers backburner.worker.queue_production-mailers
|
|
142
|
+
backburner.worker.queue_newsletter-sender backburner.worker.queue_email/send-news
|
|
143
|
+
backburner.worker.queue_admin/report-job backburner.worker.queue_mail backburner.worker.queue_-reports
|
|
144
|
+
backburner.worker.queue_-sync backburner.worker.queue_-reports backburner.worker.queue_backburner-jobs
|
|
145
|
+
],
|
|
146
|
+
["backburner", "."] => %w[
|
|
147
|
+
backburner.default backburner.mailers backburner.production-mailers backburner.newsletter-sender
|
|
148
|
+
backburner.email/send-news backburner.admin/report-job backburner.mail backburner.-reports backburner.-sync
|
|
149
|
+
backburner.reports backburner.-jobs
|
|
150
|
+
],
|
|
151
|
+
["backburner", "_"] => %w[
|
|
152
|
+
backburner_default backburner_mailers backburner_production-mailers backburner_newsletter-sender
|
|
153
|
+
backburner_email/send-news backburner_admin/report-job backburner_mail backburner_-reports backburner_-sync
|
|
154
|
+
backburner_-reports backburner_-jobs
|
|
155
|
+
],
|
|
156
|
+
["app", "."] => %w[
|
|
157
|
+
app.default app.mailers app.production-mailers app.newsletter-sender app.email/send-news app.admin/report-job
|
|
158
|
+
app.mail app.-reports app.-sync app.reports app.backburner-jobs
|
|
159
|
+
],
|
|
160
|
+
["app", "_"] => %w[
|
|
161
|
+
app_default app_mailers app_production-mailers app_newsletter-sender app_email/send-news app_admin/report-job
|
|
162
|
+
app_mail app_-reports app_-sync app_-reports app_backburner-jobs
|
|
163
|
+
],
|
|
164
|
+
["app.prod", "."] => %w[
|
|
165
|
+
app.prod.default app.prod.mailers app.prod.production-mailers app.prod.newsletter-sender
|
|
166
|
+
app.prod.email/send-news app.prod.admin/report-job app.prod.mail app.prod.-reports app.prod.-sync
|
|
167
|
+
app.prod.reports app.prod.backburner-jobs
|
|
168
|
+
],
|
|
169
|
+
["app.prod", "_"] => %w[
|
|
170
|
+
app.prod_default app.prod_mailers app.prod_production-mailers app.prod_newsletter-sender
|
|
171
|
+
app.prod_email/send-news app.prod_admin/report-job app.prod_mail app.prod_-reports app.prod_-sync
|
|
172
|
+
app.prod_-reports app.prod_backburner-jobs
|
|
173
|
+
],
|
|
174
|
+
["my-app", "."] => %w[
|
|
175
|
+
my-app.default my-app.mailers my-app.production-mailers my-app.newsletter-sender my-app.email/send-news
|
|
176
|
+
my-app.admin/report-job my-app.mail my-app.-reports my-app.-sync my-app.reports my-app.backburner-jobs
|
|
177
|
+
],
|
|
178
|
+
["my-app", "_"] => %w[
|
|
179
|
+
my-app_default my-app_mailers my-app_production-mailers my-app_newsletter-sender my-app_email/send-news
|
|
180
|
+
my-app_admin/report-job my-app_mail my-app_-reports my-app_-sync my-app_-reports my-app_backburner-jobs
|
|
181
|
+
],
|
|
182
|
+
["MyApp", "."] => %w[
|
|
183
|
+
MyApp.default MyApp.mailers MyApp.production-mailers MyApp.newsletter-sender MyApp.email/send-news
|
|
184
|
+
MyApp.admin/report-job MyApp.my-appmail MyApp.my-app-reports MyApp.my-app-sync MyApp.my-app.reports
|
|
185
|
+
MyApp.backburner-jobs
|
|
186
|
+
],
|
|
187
|
+
["MyApp", "_"] => %w[
|
|
188
|
+
MyApp_default MyApp_mailers MyApp_production-mailers MyApp_newsletter-sender MyApp_email/send-news
|
|
189
|
+
MyApp_admin/report-job MyApp_my-appmail MyApp_my-app-reports MyApp_my-app-sync MyApp_my-app-reports
|
|
190
|
+
MyApp_backburner-jobs
|
|
191
|
+
]
|
|
192
|
+
}.each do |(namespace, separator), tube_names|
|
|
193
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => namespace, :namespace_separator => separator, :primary_queue => "backburner-jobs"))
|
|
194
|
+
assert_equal tube_names, queues.call(namespace, separator).map { |queue| expand_tube_name(queue) } + [expand_tube_name(RuntimeError)],
|
|
195
|
+
[namespace, separator].inspect
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "matches a namespace that isn't a valid regexp literally" do
|
|
200
|
+
{ "app(" => ["app(.default", "app(.mail"], "jobs\\" => ["jobs\\.default", "jobs\\.mail"] }.each do |namespace, tube_names|
|
|
201
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => namespace, :namespace_separator => ".", :primary_queue => "backburner-jobs"))
|
|
202
|
+
assert_equal tube_names, [expand_tube_name("default"), expand_tube_name("#{namespace}mail")], namespace
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "uses the bare primary queue without a prefix" do
|
|
207
|
+
[nil, false].each do |namespace|
|
|
208
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => namespace, :namespace_separator => '-', :primary_queue => "backburner-jobs"))
|
|
209
|
+
assert_equal "backburner-jobs", expand_tube_name(RuntimeError), namespace.inspect
|
|
210
|
+
assert_equal "queue-name", expand_tube_name("queue_name"), namespace.inspect
|
|
211
|
+
end
|
|
212
|
+
end
|
|
88
213
|
end # expand_tube_name
|
|
89
214
|
|
|
90
215
|
describe "for alternative namespace separator" do
|
|
@@ -93,6 +218,17 @@ describe "Backburner::Helpers module" do
|
|
|
93
218
|
it "uses alternative namespace separator" do
|
|
94
219
|
assert_equal "test-queue-name", expand_tube_name("queue_name")
|
|
95
220
|
end # simple string
|
|
221
|
+
|
|
222
|
+
it "joins the namespace and queue name without a separator if configured as empty" do
|
|
223
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => "test", :namespace_separator => '', :primary_queue => "backburner-jobs"))
|
|
224
|
+
assert_equal "testqueue-name", expand_tube_name("queue_name")
|
|
225
|
+
assert_equal "testbackburner-jobs", expand_tube_name(RuntimeError)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "keeps a trailing dot in the namespace when the separator is empty" do
|
|
229
|
+
Backburner.stubs(:configuration).returns(stub(:tube_namespace => "test.", :namespace_separator => '', :primary_queue => "backburner-jobs"))
|
|
230
|
+
assert_equal "test.queue-name", expand_tube_name("queue_name")
|
|
231
|
+
end
|
|
96
232
|
end
|
|
97
233
|
|
|
98
234
|
describe "for resolve_priority method" do
|
|
@@ -147,6 +283,31 @@ describe "Backburner::Helpers module" do
|
|
|
147
283
|
it "supports default pri for null values" do
|
|
148
284
|
assert_equal 1000, resolve_priority(nil)
|
|
149
285
|
end
|
|
286
|
+
|
|
287
|
+
it "supports a priority label as the default" do
|
|
288
|
+
Backburner.configure { |config| config.default_priority = :high }
|
|
289
|
+
assert_equal 0, resolve_priority(nil)
|
|
290
|
+
assert_equal 0, resolve_priority(stub(:queue_priority => nil))
|
|
291
|
+
assert_equal 0, resolve_priority(:unknown_label)
|
|
292
|
+
assert_equal 200, resolve_priority(:low)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it "supports a custom or aliased priority label as the default" do
|
|
296
|
+
Backburner.configure { |config| config.priority_labels = { :foo => 5, :bar => 'foo' }; config.default_priority = 'bar' }
|
|
297
|
+
assert_equal 5, resolve_priority(nil)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it "leaves an unknown default label as configured" do
|
|
301
|
+
Backburner.configure { |config| config.default_priority = :unknown_label }
|
|
302
|
+
assert_equal :unknown_label, resolve_priority(nil)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it "stops at a cycle of priority labels" do
|
|
306
|
+
Backburner.configure { |config| config.priority_labels = { :a => :b, :b => 'a' }; config.default_priority = :a }
|
|
307
|
+
assert_equal :a, resolve_priority(nil), "a default label cycle is left as configured"
|
|
308
|
+
Backburner.configure { |config| config.default_priority = 1000 }
|
|
309
|
+
assert_equal 1000, resolve_priority(:b), "a job label cycle falls back to the default"
|
|
310
|
+
end
|
|
150
311
|
end # resolve_priority
|
|
151
312
|
|
|
152
313
|
describe "for resolve_respond_timeout method" do
|
|
@@ -178,6 +339,19 @@ describe "Backburner::Helpers module" do
|
|
|
178
339
|
it "supports default ttr for null values" do
|
|
179
340
|
assert_equal 300, resolve_respond_timeout(nil)
|
|
180
341
|
end
|
|
342
|
+
|
|
343
|
+
it "rounds a fractional respond_timeout up to whole seconds, from a value, a class or the default" do
|
|
344
|
+
assert_equal 1, resolve_respond_timeout(0.2)
|
|
345
|
+
assert_equal 2, resolve_respond_timeout(stub(:queue_respond_timeout => 1.5))
|
|
346
|
+
Backburner.configure { |config| config.respond_timeout = 2.5 }
|
|
347
|
+
assert_equal 3, resolve_respond_timeout(nil)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it "keeps an Integer as it is and ignores a value that isn't finite, as before" do
|
|
351
|
+
assert_equal(-5, resolve_respond_timeout(-5))
|
|
352
|
+
assert_equal 300, resolve_respond_timeout(Float::INFINITY)
|
|
353
|
+
assert_equal 300, resolve_respond_timeout(stub(:queue_respond_timeout => Float::NAN))
|
|
354
|
+
end
|
|
181
355
|
end # resolve_respond_timeout
|
|
182
356
|
|
|
183
357
|
describe "for resolve_max_job_retries method" do
|
|
@@ -218,6 +392,11 @@ describe "Backburner::Helpers module" do
|
|
|
218
392
|
end
|
|
219
393
|
after { Backburner.configure { |config| config.retry_delay = @original_retry_delay } }
|
|
220
394
|
|
|
395
|
+
it "ignores a retry_delay that isn't finite, as before" do
|
|
396
|
+
assert_equal 300, resolve_retry_delay(stub(:queue_retry_delay => Float::NAN))
|
|
397
|
+
assert_equal 300, resolve_retry_delay(Float::INFINITY)
|
|
398
|
+
end
|
|
399
|
+
|
|
221
400
|
it "supports fix num retry_delay" do
|
|
222
401
|
assert_equal 500, resolve_retry_delay(500)
|
|
223
402
|
end
|
|
@@ -240,6 +419,11 @@ describe "Backburner::Helpers module" do
|
|
|
240
419
|
it "supports default retry_delay for null values" do
|
|
241
420
|
assert_equal 300, resolve_retry_delay(nil)
|
|
242
421
|
end
|
|
422
|
+
|
|
423
|
+
it "supports a fractional retry_delay from a value or a class" do
|
|
424
|
+
assert_equal 2.5, resolve_retry_delay(2.5)
|
|
425
|
+
assert_equal 1.5, resolve_retry_delay(stub(:queue_retry_delay => 1.5))
|
|
426
|
+
end
|
|
243
427
|
end # resolve_retry_delay
|
|
244
428
|
|
|
245
429
|
describe "for resolve_retry_delay_proc method" do
|
data/test/job_test.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require File.expand_path('../test_helper', __FILE__)
|
|
2
|
+
require File.expand_path('../fixtures/test_jobs', __FILE__)
|
|
2
3
|
|
|
3
4
|
module NestedDemo
|
|
4
5
|
class TestJobC
|
|
@@ -110,6 +111,27 @@ describe "Backburner::Job module" do
|
|
|
110
111
|
assert_raises(Backburner::Job::JobNotFound) { @job.process }
|
|
111
112
|
end # error class
|
|
112
113
|
end # invalid
|
|
114
|
+
|
|
115
|
+
describe "with a class an autoloader can't load" do
|
|
116
|
+
before do
|
|
117
|
+
@task_body = { "class" => "AutoloadingDemo::TestJobGone", "args" => [56] }
|
|
118
|
+
@task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
|
|
119
|
+
@task.expects(:delete).never
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "should raise JobNotFound" do
|
|
123
|
+
@job = Backburner::Job.new(@task)
|
|
124
|
+
assert_raises(Backburner::Job::JobNotFound) { @job.process }
|
|
125
|
+
end
|
|
126
|
+
end # autoloader
|
|
127
|
+
|
|
128
|
+
describe "with a class whose file fails to load" do
|
|
129
|
+
it "should raise JobNotFound naming the cause on one line" do
|
|
130
|
+
task = stub(:body => { "class" => "TestBrokenNameJob", "args" => [56] }, :ttr => 120, :delete => true, :bury => true)
|
|
131
|
+
error = assert_raises(Backburner::Job::JobNotFound) { Backburner::Job.new(task).process }
|
|
132
|
+
assert_match(/\ATestBrokenNameJob \(NameError: uninitialized constant (TestBrokenNameJob::)?TestMissingConcern\)\z/, error.message)
|
|
133
|
+
end
|
|
134
|
+
end # broken class file
|
|
113
135
|
end # process
|
|
114
136
|
|
|
115
137
|
describe "for simple delegation method" do
|
data/test/logger_test.rb
CHANGED
|
@@ -41,4 +41,31 @@ describe "Backburner::Logger module" do
|
|
|
41
41
|
Backburner.configure { |config| config.logger = nil }
|
|
42
42
|
end
|
|
43
43
|
end # log_error
|
|
44
|
+
|
|
45
|
+
describe "for log_warn method" do
|
|
46
|
+
it "prints out to std err" do
|
|
47
|
+
output = capture_stdout { log_warn("baz") }
|
|
48
|
+
assert_equal "baz\n", output
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "can be configured to log to logger" do
|
|
52
|
+
Backburner.configure { |config| config.logger = @logger }
|
|
53
|
+
log_warn("baz")
|
|
54
|
+
assert_match(/W,.*?baz/, @strio.string)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "logs as an error to a logger without a public warn method" do
|
|
58
|
+
logged = []
|
|
59
|
+
logger = Object.new # the README asks a logger for info and error only
|
|
60
|
+
logger.define_singleton_method(:info) { |msg| logged << [:info, msg] }
|
|
61
|
+
logger.define_singleton_method(:error) { |msg| logged << [:error, msg] }
|
|
62
|
+
Backburner.configure { |config| config.logger = logger }
|
|
63
|
+
log_warn("baz")
|
|
64
|
+
assert_equal [[:error, "baz"]], logged
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
after do
|
|
68
|
+
Backburner.configure { |config| config.logger = nil }
|
|
69
|
+
end
|
|
70
|
+
end # log_warn
|
|
44
71
|
end
|
data/test/performable_test.rb
CHANGED
|
@@ -22,6 +22,24 @@ end
|
|
|
22
22
|
class AsyncInstanceMethodsTestObj < PerformableTestObj; end
|
|
23
23
|
class AsyncStaticMethodsTestObj < PerformableTestObj; end
|
|
24
24
|
|
|
25
|
+
class AsyncSettingsTestObj < TestObj
|
|
26
|
+
include Backburner::Performable
|
|
27
|
+
queue "performable-settings"
|
|
28
|
+
queue_priority 10
|
|
29
|
+
queue_respond_timeout 300
|
|
30
|
+
queue_max_job_retries 3
|
|
31
|
+
handle_asynchronously :foo
|
|
32
|
+
handle_static_asynchronously :bar
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class QueueThenPerformableTestObj < TestObj
|
|
36
|
+
include Backburner::Queue
|
|
37
|
+
queue "queue-then-performable"
|
|
38
|
+
queue_priority 20
|
|
39
|
+
queue_respond_timeout 200
|
|
40
|
+
include Backburner::Performable
|
|
41
|
+
end
|
|
42
|
+
|
|
25
43
|
|
|
26
44
|
|
|
27
45
|
describe "Backburner::Performable module" do
|
|
@@ -84,5 +102,37 @@ describe "Backburner::Performable module" do
|
|
|
84
102
|
Backburner::Worker.expects(:enqueue).with(AsyncStaticMethodsTestObj, [nil, :bar_without_async, true, false], has_entries(:pri => 5000, :queue => "garply"))
|
|
85
103
|
AsyncStaticMethodsTestObj.bar(true, false)
|
|
86
104
|
end
|
|
105
|
+
|
|
106
|
+
describe "on a class with queue settings" do
|
|
107
|
+
before { clear_jobs!("performable-settings") }
|
|
108
|
+
after { clear_jobs!("performable-settings") }
|
|
109
|
+
|
|
110
|
+
it "keeps the class's queue settings" do
|
|
111
|
+
assert_equal "performable-settings", AsyncSettingsTestObj.queue
|
|
112
|
+
assert_equal 10, AsyncSettingsTestObj.queue_priority
|
|
113
|
+
assert_equal 300, AsyncSettingsTestObj.queue_respond_timeout
|
|
114
|
+
assert_equal 3, AsyncSettingsTestObj.queue_max_job_retries
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "enqueues instance and class method calls with the class's queue settings" do
|
|
118
|
+
AsyncSettingsTestObj.new.foo(true, false)
|
|
119
|
+
AsyncSettingsTestObj.bar(false, true)
|
|
120
|
+
[[56, "foo_without_async", true, false], [nil, "bar_without_async", false, true]].each do |args|
|
|
121
|
+
pop_one_job("performable-settings") do |job, body|
|
|
122
|
+
assert_equal ["AsyncSettingsTestObj", args], [body["class"], body["args"]]
|
|
123
|
+
assert_equal [10, 300], [job.pri, job.ttr]
|
|
124
|
+
job.delete
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end # handle_asynchronously
|
|
130
|
+
|
|
131
|
+
describe "when included after Backburner::Queue" do
|
|
132
|
+
it "keeps the queue settings the class already set" do
|
|
133
|
+
assert_equal "queue-then-performable", QueueThenPerformableTestObj.queue
|
|
134
|
+
assert_equal 20, QueueThenPerformableTestObj.queue_priority
|
|
135
|
+
assert_equal 200, QueueThenPerformableTestObj.queue_respond_timeout
|
|
136
|
+
end
|
|
87
137
|
end
|
|
88
138
|
end
|
data/test/queue_test.rb
CHANGED
|
@@ -5,6 +5,33 @@ module NestedDemo
|
|
|
5
5
|
class TestJobB; include Backburner::Queue; end
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
module InheritDemo
|
|
9
|
+
class ParentJob
|
|
10
|
+
include Backburner::Queue
|
|
11
|
+
queue "inherit-parent"
|
|
12
|
+
queue_priority 10
|
|
13
|
+
queue_respond_timeout 77
|
|
14
|
+
queue_max_job_retries 4
|
|
15
|
+
queue_retry_delay 9
|
|
16
|
+
queue_retry_delay_proc lambda { |min_retry_delay, num_retries| 42 }
|
|
17
|
+
queue_jobs_limit 3
|
|
18
|
+
queue_garbage_limit 50
|
|
19
|
+
queue_retry_limit 6
|
|
20
|
+
end
|
|
21
|
+
class ChildJob < ParentJob; end
|
|
22
|
+
class GrandchildJob < ChildJob; end
|
|
23
|
+
class OverrideJob < ParentJob
|
|
24
|
+
queue "inherit-override"
|
|
25
|
+
queue_priority 20
|
|
26
|
+
end
|
|
27
|
+
class ReincludedJob < ParentJob; include Backburner::Queue; end
|
|
28
|
+
class LambdaParentJob
|
|
29
|
+
include Backburner::Queue
|
|
30
|
+
queue lambda { |klass| klass.name }
|
|
31
|
+
end
|
|
32
|
+
class LambdaChildJob < LambdaParentJob; end
|
|
33
|
+
end
|
|
34
|
+
|
|
8
35
|
describe "Backburner::Queue module" do
|
|
9
36
|
describe "contains known_queue_classes" do
|
|
10
37
|
it "has all defined known queues" do
|
|
@@ -66,4 +93,40 @@ describe "Backburner::Queue module" do
|
|
|
66
93
|
assert_equal retry_delay_proc.call(2, 1), NestedDemo::TestJobB.queue_retry_delay_proc.call(2, 1)
|
|
67
94
|
end
|
|
68
95
|
end # queue_retry_delay_proc
|
|
96
|
+
|
|
97
|
+
describe "for subclasses" do
|
|
98
|
+
settings = [:queue, :queue_priority, :queue_respond_timeout, :queue_max_job_retries, :queue_retry_delay,
|
|
99
|
+
:queue_retry_delay_proc, :queue_jobs_limit, :queue_garbage_limit, :queue_retry_limit]
|
|
100
|
+
|
|
101
|
+
it "inherits every queue setting from the superclass" do
|
|
102
|
+
settings.each do |setting|
|
|
103
|
+
refute_nil InheritDemo::ParentJob.send(setting), setting
|
|
104
|
+
assert_equal InheritDemo::ParentJob.send(setting), InheritDemo::ChildJob.send(setting), setting
|
|
105
|
+
assert_equal InheritDemo::ParentJob.send(setting), InheritDemo::GrandchildJob.send(setting), setting
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "uses its own settings over inherited ones without changing the superclass" do
|
|
110
|
+
assert_equal "inherit-override", InheritDemo::OverrideJob.queue
|
|
111
|
+
assert_equal 20, InheritDemo::OverrideJob.queue_priority
|
|
112
|
+
assert_equal 77, InheritDemo::OverrideJob.queue_respond_timeout
|
|
113
|
+
assert_equal "inherit-parent", InheritDemo::ParentJob.queue
|
|
114
|
+
assert_equal 10, InheritDemo::ParentJob.queue_priority
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "inherits the superclass settings when it includes Backburner::Queue again" do
|
|
118
|
+
assert_equal "inherit-parent", InheritDemo::ReincludedJob.queue
|
|
119
|
+
assert_equal 10, InheritDemo::ReincludedJob.queue_priority
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "calls an inherited queue lambda with the subclass" do
|
|
123
|
+
assert_equal "InheritDemo::LambdaChildJob", InheritDemo::LambdaChildJob.queue
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "registers subclasses as known queue classes, once each" do
|
|
127
|
+
[InheritDemo::ChildJob, InheritDemo::GrandchildJob, InheritDemo::OverrideJob, InheritDemo::ReincludedJob].each do |klass|
|
|
128
|
+
assert_equal 1, Backburner::Worker.known_queue_classes.count(klass), klass.name
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end # subclasses
|
|
69
132
|
end # Backburner::Queue
|
data/test/test_helper.rb
CHANGED
|
@@ -110,6 +110,34 @@ class MiniTest::Spec
|
|
|
110
110
|
connection.close if connection
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
# A beanstalkd of the test's own (on @port) that it can stop and start, for outage tests:
|
|
114
|
+
# pick_beanstalkd_port in `before`, then start_beanstalkd, and stop_beanstalkd in `after`
|
|
115
|
+
def pick_beanstalkd_port
|
|
116
|
+
@beanstalkd_bin = ENV['BEANSTALKD_BIN'] || ENV['PATH'].split(File::PATH_SEPARATOR).
|
|
117
|
+
map { |dir| File.join(dir, 'beanstalkd') }.find { |file| File.executable?(file) }
|
|
118
|
+
skip "needs a beanstalkd binary on PATH or in BEANSTALKD_BIN" unless @beanstalkd_bin
|
|
119
|
+
server = TCPServer.new('127.0.0.1', 0)
|
|
120
|
+
@port = server.addr[1]
|
|
121
|
+
server.close
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def start_beanstalkd
|
|
125
|
+
@beanstalkd_pid = Process.spawn(@beanstalkd_bin, '-l', '127.0.0.1', '-p', @port.to_s, [:out, :err] => File::NULL)
|
|
126
|
+
wait_for { (TCPSocket.new('127.0.0.1', @port).close; true) rescue false }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def stop_beanstalkd
|
|
130
|
+
return unless @beanstalkd_pid
|
|
131
|
+
Process.kill(:KILL, @beanstalkd_pid)
|
|
132
|
+
Process.wait(@beanstalkd_pid)
|
|
133
|
+
@beanstalkd_pid = nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def wait_for(seconds = 15)
|
|
137
|
+
deadline = Time.now + seconds
|
|
138
|
+
sleep 0.1 until yield || Time.now > deadline
|
|
139
|
+
end
|
|
140
|
+
|
|
113
141
|
# Simulates a broken connection for any Beaneater::Connection. Will
|
|
114
142
|
# simulate a restored connection after `reconnects_after`. This is expected
|
|
115
143
|
# to be used when ensuring a Beaneater connection is open, therefore
|
|
@@ -122,4 +150,33 @@ class MiniTest::Spec
|
|
|
122
150
|
end
|
|
123
151
|
TCPSocket.expects(:new).times(returns.size).returns(*returns)
|
|
124
152
|
end
|
|
153
|
+
|
|
154
|
+
# Whether a forked child's GC was disabled by the time the interpreter tore it down (when its
|
|
155
|
+
# at_exit handlers run). Yields the child's body, which ends as `how` says (:return, :raise, or
|
|
156
|
+
# :shutdown, which calls Kernel.exit as the TERM and INT traps do); the block forks a child that
|
|
157
|
+
# runs it and waits for the child. Fails if the GC is already off here (the child would inherit
|
|
158
|
+
# that), or if the child never ran its at_exit handlers (it left by exit!, or crashed).
|
|
159
|
+
def gc_disabled_at_child_exit?(worker, how)
|
|
160
|
+
gc_was_disabled = GC.enable # returns whether it was disabled
|
|
161
|
+
GC.disable if gc_was_disabled
|
|
162
|
+
refute gc_was_disabled, "the GC was already disabled in the test process"
|
|
163
|
+
reader, writer = IO.pipe
|
|
164
|
+
yield(lambda do
|
|
165
|
+
reader.close
|
|
166
|
+
$stdout.reopen(File::NULL)
|
|
167
|
+
$stderr.reopen(File::NULL)
|
|
168
|
+
at_exit { writer.write(GC.disable ? "disabled" : "enabled"); writer.close }
|
|
169
|
+
case how
|
|
170
|
+
when :raise then raise "the child failed"
|
|
171
|
+
when :shutdown then worker.shutdown
|
|
172
|
+
end
|
|
173
|
+
end)
|
|
174
|
+
writer.close
|
|
175
|
+
state = reader.read
|
|
176
|
+
flunk "the child never ran its at_exit handlers (it left by exit!, or crashed)" if state.empty?
|
|
177
|
+
state == "disabled"
|
|
178
|
+
ensure
|
|
179
|
+
reader.close if reader && !reader.closed?
|
|
180
|
+
writer.close if writer && !writer.closed?
|
|
181
|
+
end
|
|
125
182
|
end # MiniTest::Spec
|