rails-queue 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +20 -0
- data/README.md +179 -0
- data/Rakefile +12 -0
- data/lib/rails-queue.rb +15 -0
- data/lib/rails/queue/action_mailer/queued_message.rb +42 -0
- data/lib/rails/queue/application.rb +17 -0
- data/lib/rails/queue/configuration.rb +12 -0
- data/lib/rails/queue/queue.rb +110 -0
- data/lib/rails/queue/railtie.rb +64 -0
- data/lib/rails/queue/version.rb +5 -0
- data/test/ci/before_script.sh +4 -0
- data/test/fixtures/async_mailer/welcome.erb +1 -0
- data/test/rails-queue_test.rb +7 -0
- data/test/rails/frameworks_test.rb +35 -0
- data/test/rails/queue/actionmailer/base_test.rb +22 -0
- data/test/rails/queue/synchronous_queue_test.rb +28 -0
- data/test/rails/queue/test_queue_test.rb +147 -0
- data/test/rails/queue/threaded_consumer_test.rb +111 -0
- data/test/rails/queue_test.rb +152 -0
- data/test/support/action_mailer_abstract_unit.rb +65 -0
- data/test/support/active_support_abstract_unit.rb +26 -0
- data/test/support/empty_bool.rb +8 -0
- data/test/support/isolation_abstract_unit.rb +305 -0
- data/test/support/mailers.rb +18 -0
- metadata +107 -0
@@ -0,0 +1 @@
|
|
1
|
+
Welcome
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "support/isolation_abstract_unit"
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module ApplicationTests
|
5
|
+
class FrameworksTest < ActiveSupport::TestCase
|
6
|
+
include ActiveSupport::Testing::Isolation
|
7
|
+
|
8
|
+
def setup
|
9
|
+
build_app
|
10
|
+
boot_rails
|
11
|
+
FileUtils.rm_rf "#{app_path}/config/environments"
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
teardown_app
|
16
|
+
end
|
17
|
+
|
18
|
+
test "uses the default queue for ActionMailer" do
|
19
|
+
require "#{app_path}/config/environment"
|
20
|
+
assert_kind_of Rails::Queue::Queue, ActionMailer::Base.queue
|
21
|
+
end
|
22
|
+
|
23
|
+
test "allows me to configure queue for ActionMailer" do
|
24
|
+
app_file "config/environments/development.rb", <<-RUBY
|
25
|
+
AppTemplate::Application.configure do
|
26
|
+
config.action_mailer.queue = Rails::Queue::TestQueue.new
|
27
|
+
end
|
28
|
+
RUBY
|
29
|
+
|
30
|
+
require "#{app_path}/config/environment"
|
31
|
+
assert_kind_of Rails::Queue::TestQueue, ActionMailer::Base.queue
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Original File: RAILS_ROOT/actionmailer/test/base_test.rb
|
2
|
+
require "support/action_mailer_abstract_unit"
|
3
|
+
require "rails-queue"
|
4
|
+
require "support/mailers"
|
5
|
+
|
6
|
+
class BaseTest < ActiveSupport::TestCase
|
7
|
+
def teardown
|
8
|
+
ActionMailer::Base.asset_host = nil
|
9
|
+
ActionMailer::Base.assets_dir = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
test "delivering message asynchronously" do
|
13
|
+
AsyncMailer.delivery_method = :test
|
14
|
+
AsyncMailer.deliveries.clear
|
15
|
+
|
16
|
+
AsyncMailer.welcome.deliver
|
17
|
+
assert_equal 0, AsyncMailer.deliveries.length
|
18
|
+
|
19
|
+
AsyncMailer.queue.drain
|
20
|
+
assert_equal 1, AsyncMailer.deliveries.length
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Original File: RAILS_ROOT/activesupport/test/queueing/synchronous_queue_test.rb
|
2
|
+
require "support/active_support_abstract_unit"
|
3
|
+
require "rails-queue"
|
4
|
+
|
5
|
+
class SynchronousQueueTest < ActiveSupport::TestCase
|
6
|
+
class Job
|
7
|
+
attr_reader :ran
|
8
|
+
def run; @ran = true end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ExceptionRaisingJob
|
12
|
+
def run; raise end
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@queue = Rails::Queue::SynchronousQueue.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_runs_jobs_immediately
|
20
|
+
job = Job.new
|
21
|
+
@queue.push job
|
22
|
+
assert job.ran
|
23
|
+
|
24
|
+
assert_raises RuntimeError do
|
25
|
+
@queue.push ExceptionRaisingJob.new
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# Original File: RAILS_ROOT/activesupport/test/queueing/test_queue_test.rb
|
2
|
+
require "support/active_support_abstract_unit"
|
3
|
+
require "rails-queue"
|
4
|
+
|
5
|
+
class TestQueueTest < ActiveSupport::TestCase
|
6
|
+
def setup
|
7
|
+
@queue = Rails::Queue::TestQueue.new
|
8
|
+
end
|
9
|
+
|
10
|
+
class ExceptionRaisingJob
|
11
|
+
def run
|
12
|
+
raise
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_drain_raises_exceptions_from_running_jobs
|
17
|
+
@queue.push ExceptionRaisingJob.new
|
18
|
+
assert_raises(RuntimeError) { @queue.drain }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_jobs
|
22
|
+
@queue.push 1
|
23
|
+
@queue.push 2
|
24
|
+
assert_equal [1,2], @queue.jobs
|
25
|
+
end
|
26
|
+
|
27
|
+
class EquivalentJob
|
28
|
+
def initialize
|
29
|
+
@initial_id = self.object_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
other.same_initial_id?(@initial_id)
|
37
|
+
end
|
38
|
+
|
39
|
+
def same_initial_id?(other_id)
|
40
|
+
other_id == @initial_id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_contents
|
45
|
+
job = EquivalentJob.new
|
46
|
+
assert @queue.empty?
|
47
|
+
@queue.push job
|
48
|
+
refute @queue.empty?
|
49
|
+
assert_equal job, @queue.pop
|
50
|
+
end
|
51
|
+
|
52
|
+
class ProcessingJob
|
53
|
+
def self.clear_processed
|
54
|
+
@processed = []
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.processed
|
58
|
+
@processed
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(object)
|
62
|
+
@object = object
|
63
|
+
end
|
64
|
+
|
65
|
+
def run
|
66
|
+
self.class.processed << @object
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_order
|
71
|
+
ProcessingJob.clear_processed
|
72
|
+
job1 = ProcessingJob.new(1)
|
73
|
+
job2 = ProcessingJob.new(2)
|
74
|
+
|
75
|
+
@queue.push job1
|
76
|
+
@queue.push job2
|
77
|
+
@queue.drain
|
78
|
+
|
79
|
+
assert_equal [1,2], ProcessingJob.processed
|
80
|
+
end
|
81
|
+
|
82
|
+
class ThreadTrackingJob
|
83
|
+
attr_reader :thread_id
|
84
|
+
|
85
|
+
def run
|
86
|
+
@thread_id = Thread.current.object_id
|
87
|
+
end
|
88
|
+
|
89
|
+
def ran?
|
90
|
+
@thread_id
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_drain
|
95
|
+
@queue.push ThreadTrackingJob.new
|
96
|
+
job = @queue.jobs.last
|
97
|
+
@queue.drain
|
98
|
+
|
99
|
+
assert @queue.empty?
|
100
|
+
assert job.ran?, "The job runs synchronously when the queue is drained"
|
101
|
+
assert_equal job.thread_id, Thread.current.object_id
|
102
|
+
end
|
103
|
+
|
104
|
+
class IdentifiableJob
|
105
|
+
def initialize(id)
|
106
|
+
@id = id
|
107
|
+
end
|
108
|
+
|
109
|
+
def ==(other)
|
110
|
+
other.same_id?(@id)
|
111
|
+
end
|
112
|
+
|
113
|
+
def same_id?(other_id)
|
114
|
+
other_id == @id
|
115
|
+
end
|
116
|
+
|
117
|
+
def run
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_queue_can_be_observed
|
122
|
+
jobs = (1..10).map do |id|
|
123
|
+
IdentifiableJob.new(id)
|
124
|
+
end
|
125
|
+
|
126
|
+
jobs.each do |job|
|
127
|
+
@queue.push job
|
128
|
+
end
|
129
|
+
|
130
|
+
assert_equal jobs, @queue.jobs
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_adding_an_unmarshallable_job
|
134
|
+
anonymous_class_instance = Struct.new(:run).new
|
135
|
+
|
136
|
+
assert_raises TypeError do
|
137
|
+
@queue.push anonymous_class_instance
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_attempting_to_add_a_reference_to_itself
|
142
|
+
job = {reference: @queue}
|
143
|
+
assert_raises TypeError do
|
144
|
+
@queue.push job
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# Original File: RAILS_ROOT/activesupport/test/queueing/threaded_consumer_test.rb
|
2
|
+
require "support/active_support_abstract_unit"
|
3
|
+
require "rails-queue"
|
4
|
+
require "active_support/log_subscriber/test_helper"
|
5
|
+
|
6
|
+
class TestThreadConsumer < ActiveSupport::TestCase
|
7
|
+
class Job
|
8
|
+
attr_reader :id
|
9
|
+
def initialize(id = 1, &block)
|
10
|
+
@id = id
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
@block.call if @block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup
|
20
|
+
@logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
|
21
|
+
@queue = Rails::Queue::Queue.new(logger: @logger)
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
@queue.drain
|
26
|
+
end
|
27
|
+
|
28
|
+
test "the jobs are executed" do
|
29
|
+
ran = false
|
30
|
+
job = Job.new { ran = true }
|
31
|
+
|
32
|
+
@queue.push job
|
33
|
+
@queue.drain
|
34
|
+
|
35
|
+
assert_equal true, ran
|
36
|
+
end
|
37
|
+
|
38
|
+
test "the jobs are not executed synchronously" do
|
39
|
+
run, ran = Queue.new, Queue.new
|
40
|
+
job = Job.new { ran.push run.pop }
|
41
|
+
|
42
|
+
@queue.consumer.start
|
43
|
+
@queue.push job
|
44
|
+
assert ran.empty?
|
45
|
+
|
46
|
+
run.push true
|
47
|
+
assert_equal true, ran.pop
|
48
|
+
end
|
49
|
+
|
50
|
+
test "shutting down the queue synchronously drains the jobs" do
|
51
|
+
ran = false
|
52
|
+
job = Job.new do
|
53
|
+
sleep 0.1
|
54
|
+
ran = true
|
55
|
+
end
|
56
|
+
|
57
|
+
@queue.consumer.start
|
58
|
+
@queue.push job
|
59
|
+
assert_equal false, ran
|
60
|
+
|
61
|
+
@queue.consumer.shutdown
|
62
|
+
assert_equal true, ran
|
63
|
+
end
|
64
|
+
|
65
|
+
test "log job that raises an exception" do
|
66
|
+
job = Job.new { raise "RuntimeError: Error!" }
|
67
|
+
|
68
|
+
@queue.push job
|
69
|
+
consume_queue @queue
|
70
|
+
|
71
|
+
assert_equal 1, @logger.logged(:error).size
|
72
|
+
assert_match "Job Error: #{job.inspect}\nRuntimeError: Error!", @logger.logged(:error).last
|
73
|
+
end
|
74
|
+
|
75
|
+
test "logger defaults to stderr" do
|
76
|
+
begin
|
77
|
+
$stderr, old_stderr = StringIO.new, $stderr
|
78
|
+
queue = Rails::Queue::Queue.new
|
79
|
+
queue.push Job.new { raise "RuntimeError: Error!" }
|
80
|
+
consume_queue queue
|
81
|
+
assert_match 'Job Error', $stderr.string
|
82
|
+
ensure
|
83
|
+
$stderr = old_stderr
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
test "test overriding exception handling" do
|
88
|
+
@queue.consumer.instance_eval do
|
89
|
+
def handle_exception(job, exception)
|
90
|
+
@last_error = exception.message
|
91
|
+
end
|
92
|
+
|
93
|
+
def last_error
|
94
|
+
@last_error
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
job = Job.new { raise "RuntimeError: Error!" }
|
99
|
+
|
100
|
+
@queue.push job
|
101
|
+
consume_queue @queue
|
102
|
+
|
103
|
+
assert_equal "RuntimeError: Error!", @queue.consumer.last_error
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def consume_queue(queue)
|
108
|
+
queue.push nil
|
109
|
+
queue.consumer.consume
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require "support/isolation_abstract_unit"
|
2
|
+
|
3
|
+
class QueueTest < ActiveSupport::TestCase
|
4
|
+
include ActiveSupport::Testing::Isolation
|
5
|
+
|
6
|
+
def setup
|
7
|
+
build_app
|
8
|
+
boot_rails
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
teardown_app
|
13
|
+
end
|
14
|
+
|
15
|
+
def app_const
|
16
|
+
@app_const ||= Class.new(Rails::Application)
|
17
|
+
end
|
18
|
+
|
19
|
+
test "the queue is a SynchronousQueue in test mode" do
|
20
|
+
app("test")
|
21
|
+
assert_kind_of Rails::Queue::SynchronousQueue, Rails.application.queue
|
22
|
+
assert_kind_of Rails::Queue::SynchronousQueue, Rails.queue
|
23
|
+
end
|
24
|
+
|
25
|
+
test "the queue is a SynchronousQueue in development mode" do
|
26
|
+
app("development")
|
27
|
+
assert_kind_of Rails::Queue::SynchronousQueue, Rails.application.queue
|
28
|
+
assert_kind_of Rails::Queue::SynchronousQueue, Rails.queue
|
29
|
+
end
|
30
|
+
|
31
|
+
class ThreadTrackingJob
|
32
|
+
def initialize
|
33
|
+
@origin = Thread.current.object_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
@target = Thread.current.object_id
|
38
|
+
end
|
39
|
+
|
40
|
+
def ran_in_different_thread?
|
41
|
+
@origin != @target
|
42
|
+
end
|
43
|
+
|
44
|
+
def ran?
|
45
|
+
@target
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
test "in development mode, an enqueued job will be processed in the same thread" do
|
50
|
+
app("development")
|
51
|
+
|
52
|
+
job = ThreadTrackingJob.new
|
53
|
+
Rails.queue.push job
|
54
|
+
sleep 0.1
|
55
|
+
|
56
|
+
assert job.ran?, "Expected job to be run"
|
57
|
+
refute job.ran_in_different_thread?, "Expected job to run in the same thread"
|
58
|
+
end
|
59
|
+
|
60
|
+
test "in test mode, an enqueued job will be processed in the same thread" do
|
61
|
+
app("test")
|
62
|
+
|
63
|
+
job = ThreadTrackingJob.new
|
64
|
+
Rails.queue.push job
|
65
|
+
sleep 0.1
|
66
|
+
|
67
|
+
assert job.ran?, "Expected job to be run"
|
68
|
+
refute job.ran_in_different_thread?, "Expected job to run in the same thread"
|
69
|
+
end
|
70
|
+
|
71
|
+
test "in production, automatically spawn a queue consumer in a background thread" do
|
72
|
+
add_to_env_config "production", <<-RUBY
|
73
|
+
config.queue = Rails::Queue::Queue.new
|
74
|
+
RUBY
|
75
|
+
|
76
|
+
app("production")
|
77
|
+
|
78
|
+
assert_nil Rails.application.config.queue_consumer
|
79
|
+
assert_kind_of Rails::Queue::ThreadedQueueConsumer, Rails.application.queue_consumer
|
80
|
+
assert_equal Rails.logger, Rails.application.queue_consumer.logger
|
81
|
+
end
|
82
|
+
|
83
|
+
test "attempting to marshal a queue will raise an exception" do
|
84
|
+
app("test")
|
85
|
+
assert_raises TypeError do
|
86
|
+
Marshal.dump Rails.queue
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def setup_custom_queue
|
91
|
+
add_to_env_config "production", <<-RUBY
|
92
|
+
require "my_queue"
|
93
|
+
config.queue = MyQueue.new
|
94
|
+
RUBY
|
95
|
+
|
96
|
+
app_file "lib/my_queue.rb", <<-RUBY
|
97
|
+
class MyQueue
|
98
|
+
def push(job)
|
99
|
+
job.run
|
100
|
+
end
|
101
|
+
end
|
102
|
+
RUBY
|
103
|
+
|
104
|
+
app("production")
|
105
|
+
end
|
106
|
+
|
107
|
+
test "a custom queue implementation can be provided" do
|
108
|
+
setup_custom_queue
|
109
|
+
|
110
|
+
assert_kind_of MyQueue, Rails.queue
|
111
|
+
|
112
|
+
job = Struct.new(:id, :ran) do
|
113
|
+
def run
|
114
|
+
self.ran = true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
job1 = job.new(1)
|
119
|
+
Rails.queue.push job1
|
120
|
+
|
121
|
+
assert_equal true, job1.ran
|
122
|
+
end
|
123
|
+
|
124
|
+
test "a custom consumer implementation can be provided" do
|
125
|
+
add_to_env_config "production", <<-RUBY
|
126
|
+
require "my_queue_consumer"
|
127
|
+
config.queue = Rails::Queue::Queue.new
|
128
|
+
config.queue_consumer = MyQueueConsumer.new
|
129
|
+
RUBY
|
130
|
+
|
131
|
+
app_file "lib/my_queue_consumer.rb", <<-RUBY
|
132
|
+
class MyQueueConsumer
|
133
|
+
attr_reader :started
|
134
|
+
|
135
|
+
def start
|
136
|
+
@started = true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
RUBY
|
140
|
+
|
141
|
+
app("production")
|
142
|
+
|
143
|
+
assert_kind_of MyQueueConsumer, Rails.application.queue_consumer
|
144
|
+
assert Rails.application.queue_consumer.started
|
145
|
+
end
|
146
|
+
|
147
|
+
test "default consumer is not used with custom queue implementation" do
|
148
|
+
setup_custom_queue
|
149
|
+
|
150
|
+
assert_nil Rails.application.queue_consumer
|
151
|
+
end
|
152
|
+
end
|