backburner-allq 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +29 -0
  4. data/CHANGELOG.md +133 -0
  5. data/CONTRIBUTING.md +37 -0
  6. data/Gemfile +4 -0
  7. data/HOOKS.md +99 -0
  8. data/LICENSE +22 -0
  9. data/README.md +658 -0
  10. data/Rakefile +17 -0
  11. data/TODO +4 -0
  12. data/backburner-allq.gemspec +26 -0
  13. data/bin/backburner +7 -0
  14. data/circle.yml +3 -0
  15. data/deploy.sh +3 -0
  16. data/examples/custom.rb +25 -0
  17. data/examples/demo.rb +60 -0
  18. data/examples/god.rb +46 -0
  19. data/examples/hooked.rb +87 -0
  20. data/examples/retried.rb +31 -0
  21. data/examples/simple.rb +43 -0
  22. data/examples/stress.rb +31 -0
  23. data/lib/backburner.rb +75 -0
  24. data/lib/backburner/allq_wrapper.rb +317 -0
  25. data/lib/backburner/async_proxy.rb +25 -0
  26. data/lib/backburner/cli.rb +53 -0
  27. data/lib/backburner/configuration.rb +48 -0
  28. data/lib/backburner/connection.rb +157 -0
  29. data/lib/backburner/helpers.rb +193 -0
  30. data/lib/backburner/hooks.rb +53 -0
  31. data/lib/backburner/job.rb +118 -0
  32. data/lib/backburner/logger.rb +53 -0
  33. data/lib/backburner/performable.rb +95 -0
  34. data/lib/backburner/queue.rb +145 -0
  35. data/lib/backburner/tasks.rb +54 -0
  36. data/lib/backburner/version.rb +3 -0
  37. data/lib/backburner/worker.rb +221 -0
  38. data/lib/backburner/workers/forking.rb +52 -0
  39. data/lib/backburner/workers/simple.rb +29 -0
  40. data/lib/backburner/workers/threading.rb +163 -0
  41. data/lib/backburner/workers/threads_on_fork.rb +263 -0
  42. data/test/async_proxy_test.rb +36 -0
  43. data/test/back_burner_test.rb +88 -0
  44. data/test/connection_test.rb +179 -0
  45. data/test/fixtures/hooked.rb +122 -0
  46. data/test/fixtures/test_fork_jobs.rb +72 -0
  47. data/test/fixtures/test_forking_jobs.rb +56 -0
  48. data/test/fixtures/test_jobs.rb +87 -0
  49. data/test/fixtures/test_queue_settings.rb +14 -0
  50. data/test/helpers/templogger.rb +22 -0
  51. data/test/helpers_test.rb +278 -0
  52. data/test/hooks_test.rb +112 -0
  53. data/test/job_test.rb +185 -0
  54. data/test/logger_test.rb +44 -0
  55. data/test/performable_test.rb +88 -0
  56. data/test/queue_test.rb +69 -0
  57. data/test/test_helper.rb +128 -0
  58. data/test/worker_test.rb +157 -0
  59. data/test/workers/forking_worker_test.rb +181 -0
  60. data/test/workers/simple_worker_test.rb +350 -0
  61. data/test/workers/threading_worker_test.rb +104 -0
  62. data/test/workers/threads_on_fork_worker_test.rb +484 -0
  63. metadata +217 -0
@@ -0,0 +1,112 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+ require File.expand_path('../fixtures/hooked', __FILE__)
3
+
4
+ describe "Backburner::Hooks module" do
5
+ before do
6
+ $hooked_fail_count = 0
7
+ @hooks = Backburner::Hooks
8
+ end
9
+
10
+ describe "for invoke_hook_events method" do
11
+ describe "with before_enqueue" do
12
+ it "should support successful invocation" do
13
+ out = silenced { @res = @hooks.invoke_hook_events(HookedObjectSuccess, :before_enqueue, 5, 6) }
14
+ assert_equal [nil, nil], @res
15
+ assert_match(/!!before_enqueue_foo!! \[5\, 6\]/, out)
16
+ assert_match(/!!before_enqueue_bar!! \[5\, 6\]/, out)
17
+ end
18
+
19
+ it "should support fail case" do
20
+ out = silenced { @res = @hooks.invoke_hook_events(HookedObjectBeforeEnqueueFail, :before_enqueue, 5, 6) }
21
+ assert_equal false, @res
22
+ assert_match(/!!before_enqueue_foo!! \[5\, 6\]/, out)
23
+ end
24
+ end # before_enqueue
25
+
26
+ describe "with after_enqueue" do
27
+ it "should support successful invocation" do
28
+ out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :after_enqueue, 7, 8) }
29
+ assert_match(/!!after_enqueue_foo!! \[7\, 8\]/, out)
30
+ assert_match(/!!after_enqueue_bar!! \[7\, 8\]/, out)
31
+ end
32
+
33
+ it "should support fail case" do
34
+ assert_raises(HookFailError) do
35
+ silenced { @res = @hooks.invoke_hook_events(HookedObjectAfterEnqueueFail, :after_enqueue, 5, 6) }
36
+ end
37
+ end
38
+ end # after_enqueue
39
+
40
+ describe "with before_perform" do
41
+ it "should support successful invocation" do
42
+ out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :before_perform, 1, 2) }
43
+ assert_match(/!!before_perform_foo!! \[1\, 2\]/, out)
44
+ end
45
+
46
+ it "should support fail case" do
47
+ out = silenced { @res = @hooks.invoke_hook_events(HookedObjectBeforePerformFail, :before_perform, 5, 6) }
48
+ assert_equal false, @res
49
+ assert_match(/!!before_perform_foo!! \[5\, 6\]/, out)
50
+ end
51
+ end # before_perform
52
+
53
+ describe "with after_perform" do
54
+ it "should support successful invocation" do
55
+ out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :after_perform, 3, 4) }
56
+ assert_match(/!!after_perform_foo!! \[3\, 4\]/, out)
57
+ end
58
+
59
+ it "should support fail case" do
60
+ assert_raises(HookFailError) do
61
+ silenced { @res = @hooks.invoke_hook_events(HookedObjectAfterPerformFail, :after_perform, 5, 6) }
62
+ end
63
+ end
64
+ end # after_perform
65
+
66
+ describe "with on_failure" do
67
+ it "should support successful invocation" do
68
+ out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :on_failure, RuntimeError, 10) }
69
+ assert_match(/!!on_failure_foo!! RuntimeError \[10\]/, out)
70
+ end
71
+ end # on_failure
72
+
73
+ describe 'with on_retry' do
74
+ it "should support successful invocation" do
75
+ out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :on_retry, 1, 0, 10) }
76
+ assert_match(/!!on_retry_foo!! 1 0 \[10\]/, out)
77
+ end
78
+ end
79
+
80
+ describe 'with on_bury' do
81
+ it "should support successful invocation" do
82
+ out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :on_bury, 10) }
83
+ assert_match(/!!on_bury_foo!! \[10\]/, out)
84
+ end
85
+ end
86
+
87
+ describe "with on_reconnect" do
88
+ it "should support successful invocation" do
89
+ out = silenced { @hooks.invoke_hook_events(HookedWorker.new, :on_reconnect)}
90
+ assert_match(/!!on_reconnect!!/, out)
91
+ end
92
+ end
93
+ end # invoke_hook_events
94
+
95
+ describe "for around_hook_events method" do
96
+ describe "with around_perform" do
97
+ it "should support successful invocation" do
98
+ out = silenced do
99
+ @hooks.around_hook_events(HookedObjectSuccess, :around_perform, 7, 8) {
100
+ puts "!!FIRED!!"
101
+ }
102
+ end
103
+ assert_match(/BEGIN.*?bar.*BEGIN.*cat.*FIRED.*END.*cat.*END.*bar/m, out)
104
+ assert_match(/!!BEGIN around_perform_bar!! \[7\, 8\]/, out)
105
+ assert_match(/!!BEGIN around_perform_cat!! \[7\, 8\]/, out)
106
+ assert_match(/!!FIRED!!/, out)
107
+ assert_match(/!!END around_perform_cat!! \[7\, 8\]/, out)
108
+ assert_match(/!!END around_perform_bar!! \[7\, 8\]/, out)
109
+ end
110
+ end # successful
111
+ end # around_hook_events
112
+ end # Hooks
@@ -0,0 +1,185 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ module NestedDemo
4
+ class TestJobC
5
+ def self.perform(x); puts "Performed #{x} in #{self}"; end
6
+ end
7
+
8
+ class TestJobD
9
+ include Backburner::Queue
10
+ def self.perform(x); raise RuntimeError; end
11
+ end
12
+ end
13
+
14
+ describe "Backburner::Job module" do
15
+ describe "for initialize" do
16
+ describe "with hash" do
17
+ before do
18
+ @task = stub(:body => task_body, :ttr => 120, :delete => true, :bury => true)
19
+ end
20
+
21
+ describe "with string keys" do
22
+ let(:task_body) { { "class" => "NewsletterSender", "args" => ["foo@bar.com", "bar@foo.com"] } }
23
+ it "should create job with correct task data" do
24
+ @job = Backburner::Job.new(@task)
25
+ assert_equal @task, @job.task
26
+ assert_equal ["class", "args"], @job.body.keys
27
+ assert_equal task_body["class"], @job.name
28
+ assert_equal task_body["args"], @job.args
29
+ end
30
+ end
31
+
32
+ describe "with symbol keys" do
33
+ let(:task_body) { { :class => "NewsletterSender", :args => ["foo@bar.com", "bar@foo.com"] } }
34
+ it "should create job with correct task data" do
35
+ @job = Backburner::Job.new(@task)
36
+ assert_equal @task, @job.task
37
+ assert_equal [:class, :args], @job.body.keys
38
+ assert_equal task_body[:class], @job.name
39
+ assert_equal task_body[:args], @job.args
40
+ end
41
+ end
42
+ end # with hash
43
+
44
+ describe "with json string" do
45
+ before do
46
+ @task_body = { "class" => "NewsletterSender", "args" => ["foo@bar.com", "bar@foo.com"] }
47
+ @task = stub(:body => @task_body.to_json, :ttr => 120, :delete => true, :bury => true)
48
+ end
49
+
50
+ it "should create job with correct task data" do
51
+ @job = Backburner::Job.new(@task)
52
+ assert_equal @task, @job.task
53
+ assert_equal ["class", "args"], @job.body.keys
54
+ assert_equal @task_body["class"], @job.name
55
+ assert_equal @task_body["args"], @job.args
56
+ end
57
+ end # with json
58
+
59
+ describe "with invalid string" do
60
+ before do
61
+ @task_body = "^%$*&^*"
62
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
63
+ end
64
+
65
+ it "should raise a job format exception" do
66
+ assert_raises(Backburner::Job::JobFormatInvalid) {
67
+ @job = Backburner::Job.new(@task)
68
+ }
69
+ end
70
+ end # invalid
71
+ end # initialize
72
+
73
+ describe "for process method" do
74
+ describe "with valid task" do
75
+ before do
76
+ @task_body = { "class" => "NestedDemo::TestJobC", "args" => [56] }
77
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
78
+ @task.expects(:delete).once
79
+ end
80
+
81
+ it "should process task" do
82
+ @job = Backburner::Job.new(@task)
83
+ out = silenced(1) { @job.process }
84
+ assert_match(/Performed 56 in NestedDemo::TestJobC/, out)
85
+ end # process
86
+ end # valid
87
+
88
+ describe "with invalid task" do
89
+ before do
90
+ @task_body = { "class" => "NestedDemo::TestJobD", "args" => [56] }
91
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
92
+ @task.expects(:delete).never
93
+ end
94
+
95
+ it "should raise an exception" do
96
+ @job = Backburner::Job.new(@task)
97
+ assert_raises(RuntimeError) { @job.process }
98
+ end # error invalid
99
+ end # invalid
100
+
101
+ describe "with invalid class" do
102
+ before do
103
+ @task_body = { "class" => "NestedDemo::TestJobY", "args" => [56] }
104
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
105
+ @task.expects(:delete).never
106
+ end
107
+
108
+ it "should raise an exception" do
109
+ @job = Backburner::Job.new(@task)
110
+ assert_raises(Backburner::Job::JobNotFound) { @job.process }
111
+ end # error class
112
+ end # invalid
113
+ end # process
114
+
115
+ describe "for simple delegation method" do
116
+ describe "with valid class" do
117
+ before do
118
+ @task_body = { "class" => "NestedDemo::TestJobC", "args" => [56] }
119
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
120
+ @task.expects(:bury).once
121
+ end
122
+
123
+ it "should call bury for task" do
124
+ @job = Backburner::Job.new(@task)
125
+ @job.bury
126
+ end # bury
127
+ end
128
+
129
+ describe "with invalid class" do
130
+ before do
131
+ @task_body = { "class" => "AnUnknownClass", "args" => [] }
132
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true, :release => true)
133
+ end
134
+
135
+ it "should call bury for task" do
136
+ @task.expects(:bury).once
137
+ @job = Backburner::Job.new(@task)
138
+ Backburner::Hooks.expects(:invoke_hook_events)
139
+ .with("AnUnknownClass", :on_bury, anything)
140
+ @job.bury
141
+ end
142
+
143
+ it "should call retry for task" do
144
+ @task.expects(:release).once
145
+ @job = Backburner::Job.new(@task)
146
+ Backburner::Hooks.expects(:invoke_hook_events)
147
+ .with("AnUnknownClass", :on_retry, 0, is_a(Integer), anything)
148
+ @job.retry(0, 0)
149
+ end
150
+ end
151
+ end # simple delegation
152
+
153
+ describe "timing out for various values of ttr" do
154
+ before do
155
+ @task_body = { "class" => "NestedDemo::TestJobC", "args" => [56] }
156
+ end
157
+
158
+ describe "when ttr == 0" do
159
+ it "should use 0 for the timeout" do
160
+ @task = stub(:body => @task_body, :delete => true, :ttr => 0)
161
+ @job = Backburner::Job.new(@task)
162
+ Timeout.expects(:timeout).with(0)
163
+ @job.process
164
+ end
165
+ end
166
+
167
+ describe "when ttr == 1" do
168
+ it "should use 1 for the timeout" do
169
+ @task = stub(:body => @task_body, :delete => true, :ttr => 1)
170
+ @job = Backburner::Job.new(@task)
171
+ Timeout.expects(:timeout).with(1)
172
+ @job.process
173
+ end
174
+ end
175
+
176
+ describe "when ttr > 1" do
177
+ it "should use ttr-1 for the timeout" do
178
+ @task = stub(:body => @task_body, :delete => true, :ttr => 2)
179
+ @job = Backburner::Job.new(@task)
180
+ Timeout.expects(:timeout).with(1)
181
+ @job.process
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Backburner::Logger module" do
4
+ include Backburner::Logger
5
+
6
+ before do
7
+ @strio = StringIO.new
8
+ @logger = Logger.new(@strio)
9
+ end
10
+
11
+ describe "for log_info method" do
12
+ it "prints out to std out" do
13
+ output = capture_stdout { log_info("foo") }
14
+ assert_equal "foo\n", output
15
+ end
16
+
17
+ it "can be configured to log to logger" do
18
+ Backburner.configure { |config| config.logger = @logger }
19
+ log_info("foo")
20
+ assert_match(/I,.*?foo/, @strio.string)
21
+ end
22
+
23
+ after do
24
+ Backburner.configure { |config| config.logger = nil }
25
+ end
26
+ end # log_info
27
+
28
+ describe "for log_error method" do
29
+ it "prints out to std err" do
30
+ output = capture_stdout { log_error("bar") }
31
+ assert_equal "bar\n", output
32
+ end
33
+
34
+ it "can be configured to log to logger" do
35
+ Backburner.configure { |config| config.logger = @logger }
36
+ log_error("bar")
37
+ assert_match(/E,.*?bar/, @strio.string)
38
+ end
39
+
40
+ after do
41
+ Backburner.configure { |config| config.logger = nil }
42
+ end
43
+ end # log_error
44
+ end
@@ -0,0 +1,88 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class TestObj
4
+ ID = 56
5
+ def id; ID; end
6
+ def self.find(id); TestObj.new if id == ID; end
7
+ def foo(state, state2); "bar #{state} #{state2}"; end
8
+ def self.bar(state, state2); "baz #{state} #{state2}"; end
9
+ end
10
+
11
+ class PerformableTestObj < TestObj
12
+ include Backburner::Performable
13
+ end
14
+
15
+ class AutomagicTestObj < TestObj
16
+ # Don't include Backburner::Performable because it should be automagically included
17
+ def qux(state, state2); "garply #{state} #{state2}" end
18
+ def self.garply(state, state2); "thud #{state} #{state2}" end
19
+ def qux?; "garply!" end
20
+ end
21
+
22
+ class AsyncInstanceMethodsTestObj < PerformableTestObj; end
23
+ class AsyncStaticMethodsTestObj < PerformableTestObj; end
24
+
25
+
26
+
27
+ describe "Backburner::Performable module" do
28
+ after { ENV["TEST"] = nil }
29
+
30
+ describe "for async instance method" do
31
+ it "should invoke worker enqueue" do
32
+ Backburner::Worker.expects(:enqueue).with(PerformableTestObj, [56, :foo, true, false], has_entries(:pri => 5000, :queue => "foo"))
33
+ PerformableTestObj.new.async(:pri => 5000, :queue => "foo").foo(true, false)
34
+ end
35
+ end # async instance
36
+
37
+ describe "for async class method" do
38
+ it "should invoke worker enqueue" do
39
+ Backburner::Worker.expects(:enqueue).with(PerformableTestObj, [nil, :bar, true, false], has_entries(:pri => 5000, :queue => "foo"))
40
+ PerformableTestObj.async(:pri => 5000, :queue => "foo").bar(true, false)
41
+ end
42
+ end # async class
43
+
44
+ describe "for perform class method" do
45
+ it "should work for instance" do
46
+ assert_equal "bar true false", PerformableTestObj.perform(PerformableTestObj::ID, :foo, true, false)
47
+ end # instance
48
+
49
+ it "should work for class level" do
50
+ assert_equal "baz false true", PerformableTestObj.perform(nil, :bar, false, true)
51
+ end # class
52
+ end # perform
53
+
54
+ describe "for handle_asynchronously class method" do
55
+ it "should automagically asynchronously proxy calls to the method" do
56
+ Backburner::Performable.handle_asynchronously(AutomagicTestObj, :qux, :pri => 5000, :queue => "qux")
57
+
58
+ Backburner::Worker.expects(:enqueue).with(AutomagicTestObj, [56, :qux_without_async, true, false], has_entries(:pri => 5000, :queue => "qux"))
59
+ AutomagicTestObj.new.qux(true, false)
60
+ end
61
+
62
+ it "should work for class methods, too" do
63
+ Backburner::Performable.handle_static_asynchronously(AutomagicTestObj, :garply, :pri => 5000, :queue => "garply")
64
+
65
+ Backburner::Worker.expects(:enqueue).with(AutomagicTestObj, [nil, :garply_without_async, true, false], has_entries(:pri => 5000, :queue => "garply"))
66
+ AutomagicTestObj.garply(true, false)
67
+ end
68
+
69
+ it "should correctly handle punctuation" do
70
+ Backburner::Performable.handle_asynchronously(AutomagicTestObj, :qux?)
71
+
72
+ Backburner::Worker.expects(:enqueue).with(AutomagicTestObj, [56, :qux_without_async?], {})
73
+ AutomagicTestObj.new.qux?
74
+ end
75
+
76
+ it "should be available for instance methods on any class that includes the Performable module" do
77
+ AsyncInstanceMethodsTestObj.handle_asynchronously :foo, pri: 5000, queue: 'qux'
78
+ Backburner::Worker.expects(:enqueue).with(AsyncInstanceMethodsTestObj, [56, :foo_without_async, true, false], has_entries(:pri => 5000, :queue => "qux"))
79
+ AsyncInstanceMethodsTestObj.new.foo(true, false)
80
+ end
81
+
82
+ it "should be available for class methods on any class that includes the Performable module" do
83
+ AsyncStaticMethodsTestObj.handle_static_asynchronously :bar, pri: 5000, queue: 'garply'
84
+ Backburner::Worker.expects(:enqueue).with(AsyncStaticMethodsTestObj, [nil, :bar_without_async, true, false], has_entries(:pri => 5000, :queue => "garply"))
85
+ AsyncStaticMethodsTestObj.bar(true, false)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,69 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ module NestedDemo
4
+ class TestJobA; include Backburner::Queue; end
5
+ class TestJobB; include Backburner::Queue; end
6
+ end
7
+
8
+ describe "Backburner::Queue module" do
9
+ describe "contains known_queue_classes" do
10
+ it "has all defined known queues" do
11
+ assert_contains Backburner::Worker.known_queue_classes, NestedDemo::TestJobA
12
+ assert_contains Backburner::Worker.known_queue_classes, NestedDemo::TestJobB
13
+ end
14
+ end
15
+
16
+ describe "for queue method accessor" do
17
+ it "should return the queue name" do
18
+ assert_equal Backburner.configuration.primary_queue, NestedDemo::TestJobA.queue
19
+ end
20
+ end # queue_name
21
+
22
+ describe "for queue assignment method" do
23
+ it "should allow queue name to be assigned" do
24
+ NestedDemo::TestJobB.queue("nested/job")
25
+ assert_equal "nested/job", NestedDemo::TestJobB.queue
26
+ end
27
+
28
+ it "should allow lambdas" do
29
+ NestedDemo::TestJobB.queue(lambda { |klass| klass.name })
30
+ assert_equal "NestedDemo::TestJobB", NestedDemo::TestJobB.queue
31
+ end
32
+ end # queue
33
+
34
+ describe "for queue_priority assignment method" do
35
+ it "should allow queue priority to be assigned" do
36
+ NestedDemo::TestJobB.queue_priority(1000)
37
+ assert_equal 1000, NestedDemo::TestJobB.queue_priority
38
+ end
39
+ end # queue_priority
40
+
41
+ describe "for queue_respond_timeout assignment method" do
42
+ it "should allow queue respond_timeout to be assigned" do
43
+ NestedDemo::TestJobB.queue_respond_timeout(300)
44
+ assert_equal 300, NestedDemo::TestJobB.queue_respond_timeout
45
+ end
46
+ end # queue_respond_timeout
47
+
48
+ describe "for queue_max_job_retries assignment method" do
49
+ it "should allow queue max_job_retries to be assigned" do
50
+ NestedDemo::TestJobB.queue_max_job_retries(5)
51
+ assert_equal 5, NestedDemo::TestJobB.queue_max_job_retries
52
+ end
53
+ end # queue_max_job_retries
54
+
55
+ describe "for queue_retry_delay assignment method" do
56
+ it "should allow queue retry_delay to be assigned" do
57
+ NestedDemo::TestJobB.queue_retry_delay(300)
58
+ assert_equal 300, NestedDemo::TestJobB.queue_retry_delay
59
+ end
60
+ end # queue_retry_delay
61
+
62
+ describe "for queue_retry_delay_proc assignment method" do
63
+ it "should allow queue retry_delay_proc to be assigned" do
64
+ retry_delay_proc = lambda { |x, y| x - y }
65
+ NestedDemo::TestJobB.queue_retry_delay_proc(retry_delay_proc)
66
+ assert_equal retry_delay_proc.call(2, 1), NestedDemo::TestJobB.queue_retry_delay_proc.call(2, 1)
67
+ end
68
+ end # queue_retry_delay_proc
69
+ end # Backburner::Queue