rails-queue 1.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/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
|