creeper 1.0.9 → 2.0.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.
- data/.gitignore +1 -0
- data/.rvmrc +48 -0
- data/Gemfile +17 -1
- data/Guardfile +32 -0
- data/Rakefile +9 -1
- data/bin/creeper +10 -58
- data/bin/creeperctl +74 -0
- data/config.ru +18 -0
- data/creeper.gemspec +19 -9
- data/lib/creeper.rb +108 -413
- data/lib/creeper/beanstalk_connection.rb +35 -0
- data/lib/creeper/cli.rb +225 -0
- data/lib/creeper/client.rb +93 -0
- data/lib/creeper/core_ext.rb +54 -0
- data/lib/creeper/exception_handler.rb +30 -0
- data/lib/creeper/extensions/action_mailer.rb +33 -0
- data/lib/creeper/extensions/active_record.rb +30 -0
- data/lib/creeper/extensions/generic_proxy.rb +26 -0
- data/lib/creeper/fetch.rb +94 -0
- data/lib/creeper/legacy.rb +46 -0
- data/lib/creeper/logging.rb +46 -0
- data/lib/creeper/manager.rb +164 -0
- data/lib/creeper/middleware/chain.rb +100 -0
- data/lib/creeper/middleware/server/active_record.rb +13 -0
- data/lib/creeper/middleware/server/logging.rb +31 -0
- data/lib/creeper/middleware/server/retry_jobs.rb +79 -0
- data/lib/creeper/middleware/server/timeout.rb +21 -0
- data/lib/creeper/paginator.rb +31 -0
- data/lib/creeper/processor.rb +116 -0
- data/lib/creeper/rails.rb +21 -0
- data/lib/creeper/redis_connection.rb +28 -0
- data/lib/creeper/testing.rb +44 -0
- data/lib/creeper/util.rb +45 -0
- data/lib/creeper/version.rb +1 -1
- data/lib/creeper/web.rb +248 -0
- data/lib/creeper/worker.rb +62 -313
- data/spec/dummy/.gitignore +15 -0
- data/spec/dummy/Gemfile +51 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/images/rails.png +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/work_controller.rb +71 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/mailers/user_mailer.rb +9 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/post.rb +8 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/user_mailer/greetings.html.erb +3 -0
- data/spec/dummy/app/views/work/index.html.erb +1 -0
- data/spec/dummy/app/workers/fast_worker.rb +10 -0
- data/spec/dummy/app/workers/hard_worker.rb +11 -0
- data/spec/dummy/app/workers/lazy_worker.rb +12 -0
- data/spec/dummy/app/workers/suicidal_worker.rb +33 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +68 -0
- data/spec/dummy/config/boot.rb +6 -0
- data/spec/dummy/config/creeper.yml +9 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/creeper.rb +8 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +13 -0
- data/spec/dummy/db/migrate/20120123214055_create_posts.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/tasks/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/index.html +241 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/dummy/vendor/plugins/.gitkeep +0 -0
- data/spec/lib/creeper/cli_spec.rb +208 -0
- data/spec/lib/creeper/client_spec.rb +110 -0
- data/spec/lib/creeper/exception_handler_spec.rb +110 -0
- data/spec/lib/creeper/processor_spec.rb +92 -0
- data/spec/lib/creeper/testing_spec.rb +105 -0
- data/spec/lib/creeper_spec.rb +54 -120
- data/spec/spec_helper.rb +81 -7
- data/spec/support/config.yml +9 -0
- data/spec/support/fake_env.rb +0 -0
- data/spec/support/workers/base_worker.rb +11 -0
- data/spec/support/workers/my_worker.rb +4 -0
- data/spec/support/workers/queued_worker.rb +5 -0
- data/spec/support/workers/real_worker.rb +10 -0
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/javascripts/application.js +49 -0
- data/web/assets/javascripts/vendor/bootstrap.js +12 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
- data/web/assets/javascripts/vendor/jquery.js +9266 -0
- data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
- data/web/assets/stylesheets/application.css +6 -0
- data/web/assets/stylesheets/layout.css +26 -0
- data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
- data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
- data/web/views/_paging.slim +15 -0
- data/web/views/_summary.slim +9 -0
- data/web/views/_workers.slim +14 -0
- data/web/views/index.slim +10 -0
- data/web/views/layout.slim +37 -0
- data/web/views/poll.slim +3 -0
- data/web/views/queue.slim +15 -0
- data/web/views/queues.slim +19 -0
- data/web/views/retries.slim +31 -0
- data/web/views/retry.slim +52 -0
- data/web/views/scheduled.slim +27 -0
- metadata +341 -23
- data/lib/creeper/celluloid_ext.rb +0 -42
- data/lib/creeper/creep.rb +0 -25
- data/lib/creeper/err_logger.rb +0 -37
- data/lib/creeper/launcher.rb +0 -44
- data/lib/creeper/out_logger.rb +0 -39
- data/spec/lib/creeper/session_spec.rb +0 -15
- data/spec/lib/creeper/worker_spec.rb +0 -21
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'creeper/client'
|
|
3
|
+
require 'creeper/worker'
|
|
4
|
+
|
|
5
|
+
describe Creeper::Client do
|
|
6
|
+
|
|
7
|
+
context 'with mock beanstalk and redis' do
|
|
8
|
+
before do
|
|
9
|
+
@beanstalk = double('beanstalk')
|
|
10
|
+
@connection = double('connection')
|
|
11
|
+
def @beanstalk.on_tube(tube); yield @connection if block_given?; end
|
|
12
|
+
Creeper.instance_variable_set(:@beanstalk, @beanstalk)
|
|
13
|
+
@redis = double('redis')
|
|
14
|
+
def @redis.multi; [yield] * 2 if block_given?; end
|
|
15
|
+
def @redis.set(*); true; end
|
|
16
|
+
def @redis.sadd(*); true; end
|
|
17
|
+
def @redis.srem(*); true; end
|
|
18
|
+
def @redis.get(*); nil; end
|
|
19
|
+
def @redis.del(*); nil; end
|
|
20
|
+
def @redis.incrby(*); nil; end
|
|
21
|
+
def @redis.setex(*); true; end
|
|
22
|
+
def @redis.expire(*); true; end
|
|
23
|
+
def @redis.watch(*); true; end
|
|
24
|
+
def @redis.with_connection; yield self; end
|
|
25
|
+
def @redis.with; yield self; end
|
|
26
|
+
def @redis.exec; true; end
|
|
27
|
+
Creeper.instance_variable_set(:@redis, @redis)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
after do
|
|
31
|
+
Creeper.instance_variable_set(:@beanstalk, nil)
|
|
32
|
+
Creeper.instance_variable_set(:@redis, nil)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'raises ArgumentError with invalid params' do
|
|
36
|
+
expect do
|
|
37
|
+
Creeper::Client.push('foo', 1)
|
|
38
|
+
end.to raise_error(ArgumentError)
|
|
39
|
+
|
|
40
|
+
expect do
|
|
41
|
+
Creeper::Client.push('foo', :class => 'Foo', :noargs => [1, 2])
|
|
42
|
+
end.to raise_error(ArgumentError)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'pushes messages to beanstalk' do
|
|
46
|
+
@beanstalk.should_receive(:on_tube).with('foo').and_yield(@connection)
|
|
47
|
+
@connection.should_receive(:put).with(%q(["foo",{"retry":true,"queue":"foo","class":"MyWorker","args":[1,2]}])).and_return(24)
|
|
48
|
+
pushed = Creeper::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => [1, 2])
|
|
49
|
+
expect(pushed).to eq(24)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'has default options' do
|
|
53
|
+
expect(Creeper::Worker::ClassMethods::DEFAULT_OPTIONS).to eq(MyWorker.get_creeper_options)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'handles perform_async' do
|
|
57
|
+
@beanstalk.should_receive(:on_tube).with('default').and_yield(@connection)
|
|
58
|
+
@connection.should_receive(:put).with(%q(["default",{"retry":true,"queue":"default","class":"MyWorker","args":[1,2]}])).and_return(24)
|
|
59
|
+
pushed = MyWorker.perform_async(1, 2)
|
|
60
|
+
expect(pushed).to eq(24)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'handles perform_async on failure' do
|
|
64
|
+
@beanstalk.should_receive(:on_tube).with('default').and_yield(@connection)
|
|
65
|
+
@connection.should_receive(:put).with(%q(["default",{"retry":true,"queue":"default","class":"MyWorker","args":[1,2]}])).and_return(nil)
|
|
66
|
+
pushed = MyWorker.perform_async(1, 2)
|
|
67
|
+
expect(pushed).to be_nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'enqueues messages to beanstalk' do
|
|
71
|
+
@beanstalk.should_receive(:on_tube).with('default').and_yield(@connection)
|
|
72
|
+
@connection.should_receive(:put).with(%q(["default",{"retry":true,"queue":"default","class":"MyWorker","args":[1,2]}])).and_return(24)
|
|
73
|
+
pushed = Creeper::Client.enqueue(MyWorker, 1, 2)
|
|
74
|
+
expect(pushed).to eq(24)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'enqueues messages to beanstalk using legacy method' do
|
|
78
|
+
@beanstalk.should_receive(:on_tube).with('my.worker').and_yield(@connection)
|
|
79
|
+
@connection.should_receive(:put).with(%q(["my.worker",{"retry":true,"queue":"my.worker","args":[1,2],"class":"MyWorker","delay":0,"priority":65536,"time_to_run":120}]), 65536, 0, 120).and_return(24)
|
|
80
|
+
pushed = Creeper.enqueue('my.worker', 1, 2)
|
|
81
|
+
expect(pushed).to eq(24)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'enqueues to the named queue' do
|
|
85
|
+
@beanstalk.should_receive(:on_tube).with(:flimflam).and_yield(@connection)
|
|
86
|
+
@connection.should_receive(:put).with(%q(["flimflam",{"retry":true,"queue":"flimflam","timeout":1,"class":"QueuedWorker","args":[1,2]}])).and_return(24)
|
|
87
|
+
pushed = QueuedWorker.perform_async(1, 2)
|
|
88
|
+
expect(pushed).to eq(24)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'retrieves queues' do
|
|
92
|
+
@redis.should_receive(:smembers).with('queues').and_return(['bob'])
|
|
93
|
+
expect(Creeper::Client.registered_queues).to eq(['bob'])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'retrieves workers' do
|
|
97
|
+
@redis.should_receive(:smembers).with('workers').and_return(['bob'])
|
|
98
|
+
expect(Creeper::Client.registered_workers).to eq(['bob'])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe 'inheritance' do
|
|
104
|
+
it 'should inherit creeper options' do
|
|
105
|
+
expect(AWorker.get_creeper_options['retry']).to eq('base')
|
|
106
|
+
expect(BWorker.get_creeper_options['retry']).to eq('b')
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'creeper'
|
|
3
|
+
require 'creeper/exception_handler'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require 'logger'
|
|
6
|
+
|
|
7
|
+
describe Creeper::ExceptionHandler do
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
stub_const('ExceptionHandlerTestException', Class.new(StandardError))
|
|
11
|
+
stub_const('TEST_EXCEPTION', ExceptionHandlerTestException.new("Something didn't work!"))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Component
|
|
15
|
+
include Creeper::Util
|
|
16
|
+
|
|
17
|
+
def invoke_exception(args)
|
|
18
|
+
raise TEST_EXCEPTION
|
|
19
|
+
rescue ExceptionHandlerTestException => e
|
|
20
|
+
handle_exception(e,args)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "with mock logger" do
|
|
25
|
+
before do
|
|
26
|
+
@old_logger = Creeper.logger
|
|
27
|
+
@str_logger = StringIO.new
|
|
28
|
+
Creeper.logger = Logger.new(@str_logger)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
after do
|
|
32
|
+
Creeper.logger = @old_logger
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "logs the exception to Creeper.logger" do
|
|
36
|
+
Component.new.invoke_exception(:a => 1)
|
|
37
|
+
@str_logger.rewind
|
|
38
|
+
log = @str_logger.readlines
|
|
39
|
+
expect(log[0]).to match(/a=>1/) # didn't include the context
|
|
40
|
+
expect(log[1]).to match(/Something didn't work!/) # didn't include the exception message
|
|
41
|
+
expect(log[2]).to match(/spec\/lib\/creeper\/exception_handler_spec\.rb/) # didn't include the backtrace
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "with fake Airbrake" do
|
|
46
|
+
before do
|
|
47
|
+
::Airbrake = double('Airbrake')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
after do
|
|
51
|
+
Object.send(:remove_const, "Airbrake") # HACK should probably inject Airbrake etc into this class in the future
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "notifies Airbrake" do
|
|
55
|
+
::Airbrake.should_receive(:notify).with(TEST_EXCEPTION,:parameters => { :a => 1 }).and_return(nil)
|
|
56
|
+
Component.new.invoke_exception(:a => 1)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "with fake ExceptionNotifier" do
|
|
61
|
+
before do
|
|
62
|
+
::ExceptionNotifier = Module.new
|
|
63
|
+
::ExceptionNotifier::Notifier = double('ExceptionNotifier::Notifier')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
after do
|
|
67
|
+
Object.send(:remove_const, "ExceptionNotifier")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "notifies ExceptionNotifier" do
|
|
71
|
+
mail = double('mail')
|
|
72
|
+
mail.should_receive(:deliver).and_return(nil)
|
|
73
|
+
::ExceptionNotifier::Notifier.should_receive(:background_exception_notification).with(TEST_EXCEPTION, :data => { :message => { :b => 2 } }).and_return(mail)
|
|
74
|
+
Component.new.invoke_exception(:b => 2)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe "with fake Exceptional" do
|
|
79
|
+
before do
|
|
80
|
+
::Exceptional = Class.new do
|
|
81
|
+
|
|
82
|
+
def self.context(msg)
|
|
83
|
+
@msg = msg
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.check_context
|
|
87
|
+
@msg
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
::Exceptional::Config = double('Exceptional::Config')
|
|
92
|
+
::Exceptional::Remote = double('Exceptional::Remote')
|
|
93
|
+
::Exceptional::ExceptionData = double('Exceptional::ExceptionData')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
after do
|
|
97
|
+
Object.send(:remove_const, "Exceptional")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "notifies Exceptional" do
|
|
101
|
+
::Exceptional::Config.should_receive(:should_send_to_api?).and_return(true)
|
|
102
|
+
exception_data = double('exception_data')
|
|
103
|
+
::Exceptional::Remote.should_receive(:error).with(exception_data).and_return(nil)
|
|
104
|
+
::Exceptional::ExceptionData.should_receive(:new).with(TEST_EXCEPTION).and_return(exception_data)
|
|
105
|
+
Component.new.invoke_exception(:c => 3)
|
|
106
|
+
expect(::Exceptional.check_context).to eq({:c => 3}) # did not record arguments properly
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'creeper/processor'
|
|
3
|
+
|
|
4
|
+
describe Creeper::Processor do
|
|
5
|
+
|
|
6
|
+
context 'with mock setup' do
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
stub_const('TestException', Class.new(StandardError))
|
|
10
|
+
stub_const('TEST_EXCEPTION', TestException.new("kerboom!"))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
let(:boss) { double('boss') }
|
|
14
|
+
let(:processor) { ::Creeper::Processor.new(boss) }
|
|
15
|
+
|
|
16
|
+
let(:job) { double('job') }
|
|
17
|
+
let(:conn) { double('conn') }
|
|
18
|
+
|
|
19
|
+
before do
|
|
20
|
+
$invokes = 0
|
|
21
|
+
Celluloid.logger = nil
|
|
22
|
+
Creeper.redis = REDIS
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class MockWorker
|
|
26
|
+
include Creeper::Worker
|
|
27
|
+
def perform(args)
|
|
28
|
+
raise TEST_EXCEPTION if args == 'boom'
|
|
29
|
+
args.pop if args.is_a? Array
|
|
30
|
+
$invokes += 1
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'processes as expected' do
|
|
35
|
+
msg = Creeper.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] })
|
|
36
|
+
|
|
37
|
+
conn.should_receive(:close).and_return(true)
|
|
38
|
+
job.should_receive(:id).and_return(1)
|
|
39
|
+
job.should_receive(:delete).and_return(true)
|
|
40
|
+
boss.should_receive(:processor_done!).with(processor).and_return(nil)
|
|
41
|
+
|
|
42
|
+
processor.process(msg, 'default', job, conn)
|
|
43
|
+
expect($invokes).to eq(1)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'passes exceptions to ExceptionHandler' do
|
|
47
|
+
msg = Creeper.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
|
|
48
|
+
|
|
49
|
+
conn.should_receive(:close).and_return(true)
|
|
50
|
+
job.should_receive(:id).and_return(1)
|
|
51
|
+
job.should_receive(:bury).and_return(true)
|
|
52
|
+
|
|
53
|
+
expect do
|
|
54
|
+
processor.process(msg, 'default', job, conn)
|
|
55
|
+
end.to raise_error(TestException)
|
|
56
|
+
|
|
57
|
+
expect($invokes).to eq(0)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 're-raises exceptions after handling' do
|
|
61
|
+
msg = Creeper.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
|
|
62
|
+
re_raise = false
|
|
63
|
+
|
|
64
|
+
conn.should_receive(:close).and_return(true)
|
|
65
|
+
job.should_receive(:id).and_return(1)
|
|
66
|
+
job.should_receive(:bury).and_return(true)
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
processor.process(msg, 'default', job, conn)
|
|
70
|
+
rescue TestException
|
|
71
|
+
re_raise = true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
expect(re_raise).to be_true # does not re-raise exceptions after handling
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'does not modify original arguments' do
|
|
78
|
+
msg = { 'class' => MockWorker.to_s, 'args' => [['myarg']] }
|
|
79
|
+
msgstr = Creeper.dump_json(msg)
|
|
80
|
+
|
|
81
|
+
conn.should_receive(:close).and_return(true)
|
|
82
|
+
job.should_receive(:id).and_return(1)
|
|
83
|
+
job.should_receive(:delete).and_return(true)
|
|
84
|
+
boss.should_receive(:processor_done!).with(processor).and_return(nil)
|
|
85
|
+
|
|
86
|
+
processor.process(msgstr, 'default', job, conn)
|
|
87
|
+
expect(msg['args']).to eq([['myarg']])
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'creeper'
|
|
3
|
+
require 'creeper/worker'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require 'action_mailer'
|
|
6
|
+
require 'creeper/rails'
|
|
7
|
+
require 'creeper/extensions/action_mailer'
|
|
8
|
+
require 'creeper/extensions/active_record'
|
|
9
|
+
|
|
10
|
+
Creeper.hook_rails!
|
|
11
|
+
|
|
12
|
+
describe Creeper::Worker do
|
|
13
|
+
|
|
14
|
+
context 'creeper testing' do
|
|
15
|
+
class PerformError < RuntimeError; end
|
|
16
|
+
|
|
17
|
+
class DirectWorker
|
|
18
|
+
include Creeper::Worker
|
|
19
|
+
def perform(a, b)
|
|
20
|
+
a + b
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class EnqueuedWorker
|
|
25
|
+
include Creeper::Worker
|
|
26
|
+
def perform(a, b)
|
|
27
|
+
a + b
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class StoredWorker
|
|
32
|
+
include Creeper::Worker
|
|
33
|
+
def perform(error)
|
|
34
|
+
raise PerformError if error
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class FooMailer < ActionMailer::Base
|
|
39
|
+
def bar(str)
|
|
40
|
+
str
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class FooModel < ActiveRecord::Base
|
|
45
|
+
def bar(str)
|
|
46
|
+
str
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
before do
|
|
51
|
+
load 'creeper/testing.rb'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
after do
|
|
55
|
+
# Undo override
|
|
56
|
+
Creeper::Worker::ClassMethods.class_eval do
|
|
57
|
+
remove_method :client_push
|
|
58
|
+
alias_method :client_push, :client_push_old
|
|
59
|
+
remove_method :client_push_old
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'stubs the async call' do
|
|
64
|
+
expect(DirectWorker.jobs.size).to eq(0)
|
|
65
|
+
expect(DirectWorker.perform_async(1, 2)).to be_true
|
|
66
|
+
expect(DirectWorker.jobs.size).to eq(1)
|
|
67
|
+
expect(DirectWorker.perform_in(10, 1, 2)).to be_true
|
|
68
|
+
expect(DirectWorker.jobs.size).to eq(2)
|
|
69
|
+
expect(DirectWorker.perform_at(10, 1, 2)).to be_true
|
|
70
|
+
expect(DirectWorker.jobs.size).to eq(3)
|
|
71
|
+
expect(DirectWorker.jobs.last['at']).to be_within(0.1).of(10.seconds.from_now.to_f)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'stubs the delay call on mailers' do
|
|
75
|
+
expect(Creeper::Extensions::DelayedMailer.jobs.size).to eq(0)
|
|
76
|
+
FooMailer.delay.bar('hello!')
|
|
77
|
+
expect(Creeper::Extensions::DelayedMailer.jobs.size).to eq(1)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'stubs the delay call on models' do
|
|
81
|
+
expect(Creeper::Extensions::DelayedModel.jobs.size).to eq(0)
|
|
82
|
+
FooModel.delay.bar('hello!')
|
|
83
|
+
expect(Creeper::Extensions::DelayedModel.jobs.size).to eq(1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'stubs the enqueue call' do
|
|
87
|
+
expect(EnqueuedWorker.jobs.size).to eq(0)
|
|
88
|
+
expect(Creeper::Client.enqueue(EnqueuedWorker, 1, 2)).to be_true
|
|
89
|
+
expect(EnqueuedWorker.jobs.size).to eq(1)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'executes all stored jobs' do
|
|
93
|
+
expect(StoredWorker.perform_async(false)).to be_true
|
|
94
|
+
expect(StoredWorker.perform_async(true)).to be_true
|
|
95
|
+
|
|
96
|
+
expect(StoredWorker.jobs.size).to eq(2)
|
|
97
|
+
expect do
|
|
98
|
+
StoredWorker.drain
|
|
99
|
+
end.to raise_error(PerformError)
|
|
100
|
+
expect(StoredWorker.jobs.size).to eq(0)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
data/spec/lib/creeper_spec.rb
CHANGED
|
@@ -1,131 +1,65 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
+
require 'creeper/cli'
|
|
2
3
|
|
|
3
4
|
describe Creeper do
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
it "work a job and do it up" do
|
|
10
|
-
val = rand(999999)
|
|
11
|
-
Creeper.job('my.job') { |args| $result = args['val'] }
|
|
12
|
-
Creeper.enqueue('my.job', :val => val)
|
|
13
|
-
w = Creeper::Worker.new
|
|
14
|
-
w.stub(:exception_message)
|
|
15
|
-
w.stub(:log)
|
|
16
|
-
w.prepare
|
|
17
|
-
w.work_one_job
|
|
18
|
-
val.should == $result
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
it "invoke error handler when defined" do
|
|
22
|
-
with_an_error_handler
|
|
23
|
-
Creeper.job('my.job') { |args| fail }
|
|
24
|
-
Creeper.enqueue('my.job', :foo => 123)
|
|
25
|
-
w = Creeper::Worker.new
|
|
26
|
-
w.stub(:exception_message)
|
|
27
|
-
w.stub(:log)
|
|
28
|
-
w.prepare
|
|
29
|
-
w.work_one_job
|
|
30
|
-
$handled.should_not == nil
|
|
31
|
-
'my.job'.should == $job_name
|
|
32
|
-
{'foo' => 123}.should == $job_args
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "should be compatible with legacy error handlers" do
|
|
36
|
-
exception = StandardError.new("Oh my, the job has failed!")
|
|
37
|
-
Creeper.error { |e| $handled = e }
|
|
38
|
-
Creeper.job('my.job') { |args| raise exception }
|
|
39
|
-
Creeper.enqueue('my.job')
|
|
40
|
-
w = Creeper::Worker.new
|
|
41
|
-
w.stub(:exception_message)
|
|
42
|
-
w.stub(:log)
|
|
43
|
-
w.prepare
|
|
44
|
-
w.work_one_job
|
|
45
|
-
exception.should == $handled
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it "continue working when error handler not defined" do
|
|
49
|
-
Creeper.error { |e| $handled = false }
|
|
50
|
-
Creeper.job('my.job') { fail }
|
|
51
|
-
Creeper.enqueue('my.job')
|
|
52
|
-
w = Creeper::Worker.new
|
|
53
|
-
w.stub(:exception_message)
|
|
54
|
-
w.stub(:log)
|
|
55
|
-
w.prepare
|
|
56
|
-
w.work_one_job
|
|
57
|
-
false.should == $handled
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it "exception raised one second before beanstalk ttr reached" do
|
|
61
|
-
with_an_error_handler
|
|
62
|
-
Creeper.job('my.job') { sleep(3); $handled = "didn't time out" }
|
|
63
|
-
Creeper.enqueue('my.job', {}, :ttr => 2)
|
|
64
|
-
w = Creeper::Worker.new
|
|
65
|
-
w.stub(:exception_message)
|
|
66
|
-
w.stub(:log)
|
|
67
|
-
w.prepare
|
|
68
|
-
w.work_one_job
|
|
69
|
-
$handled.should == "didn't time out"
|
|
70
|
-
end
|
|
6
|
+
it 'is able to process a real job' do
|
|
7
|
+
pending 'still need to figure out how to test the daemon'
|
|
8
|
+
let(:cli) { Creeper::CLI.instance }
|
|
71
9
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
10
|
+
before do
|
|
11
|
+
$rd, $wr = IO.pipe
|
|
12
|
+
$rd.sync, $wr.sync = true
|
|
13
|
+
if @pid = fork
|
|
14
|
+
# parent
|
|
15
|
+
$wr.close
|
|
16
|
+
else
|
|
17
|
+
# child
|
|
18
|
+
$rd.close
|
|
19
|
+
cli.parse(['creeper', '-r', './spec/support/fake_env.rb'])
|
|
20
|
+
sleep 5
|
|
21
|
+
begin
|
|
22
|
+
cli.run
|
|
23
|
+
rescue SystemExit
|
|
24
|
+
Process.kill(:KILL, $$)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
83
28
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
29
|
+
after do
|
|
30
|
+
if @pid
|
|
31
|
+
Process.kill(:TERM, @pid)
|
|
32
|
+
done = nil
|
|
33
|
+
# 8.times do
|
|
34
|
+
# begin
|
|
35
|
+
# Process.getpgid(@pid)
|
|
36
|
+
# rescue Errno::ESRCH
|
|
37
|
+
# done = true
|
|
38
|
+
# break
|
|
39
|
+
# end
|
|
40
|
+
# sleep 1
|
|
41
|
+
# end
|
|
42
|
+
# expect(done).to be_true
|
|
43
|
+
sleep 1
|
|
44
|
+
Process.kill(:KILL, @pid)
|
|
45
|
+
child_pid, status = Process.waitpid2(-1, Process::WNOHANG)
|
|
46
|
+
expect(child_pid).to eq(@pid)
|
|
47
|
+
# expect(status).to be_success
|
|
95
48
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
w = Creeper::Worker.new
|
|
101
|
-
w.stub(:exception_message)
|
|
102
|
-
w.stub(:log)
|
|
103
|
-
w.prepare
|
|
104
|
-
w.work_one_job
|
|
105
|
-
true.should == $handled
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
it "before filter invokes error handler when defined" do
|
|
109
|
-
with_an_error_handler
|
|
110
|
-
Creeper.before { |name| fail }
|
|
111
|
-
Creeper.job('my.job') { }
|
|
112
|
-
Creeper.enqueue('my.job', :foo => 123)
|
|
113
|
-
w = Creeper::Worker.new
|
|
114
|
-
w.stub(:exception_message)
|
|
115
|
-
w.stub(:log)
|
|
116
|
-
w.prepare
|
|
117
|
-
w.work_one_job
|
|
118
|
-
$handled.should_not == nil
|
|
119
|
-
'my.job'.should == $job_name
|
|
120
|
-
{'foo' => 123}.should == $job_args
|
|
121
|
-
end
|
|
49
|
+
$rd.close rescue nil
|
|
50
|
+
$wr.close rescue nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
122
53
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
54
|
+
it 'is able to process a real job' do
|
|
55
|
+
pending 'still need to figure out how to test the daemon'
|
|
56
|
+
RealWorker.perform_async(1, 2)
|
|
57
|
+
result = nil
|
|
58
|
+
Timeout::timeout(10) do
|
|
59
|
+
result = $rd.read
|
|
60
|
+
end
|
|
61
|
+
expect(result).to eq('[1,2]')
|
|
128
62
|
end
|
|
129
63
|
end
|
|
130
|
-
|
|
64
|
+
|
|
131
65
|
end
|