sqewer 5.0.3 → 5.0.4
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/.gitignore +54 -0
- data/Gemfile +3 -23
- data/Rakefile +3 -39
- data/lib/sqewer/extensions/appsignal_wrapper.rb +4 -0
- data/lib/sqewer/version.rb +1 -1
- data/lib/sqewer/worker.rb +5 -1
- data/sqewer.gemspec +39 -126
- metadata +41 -123
- data/spec/spec_helper.rb +0 -36
- data/spec/sqewer/active_job_spec.rb +0 -113
- data/spec/sqewer/atomic_counter_spec.rb +0 -15
- data/spec/sqewer/cli_app.rb +0 -13
- data/spec/sqewer/cli_spec.rb +0 -83
- data/spec/sqewer/connection_spec.rb +0 -145
- data/spec/sqewer/execution_context_spec.rb +0 -43
- data/spec/sqewer/middleware_stack_spec.rb +0 -69
- data/spec/sqewer/serializer_spec.rb +0 -118
- data/spec/sqewer/simple_job_spec.rb +0 -69
- data/spec/sqewer/submitter_spec.rb +0 -61
- data/spec/sqewer/worker_spec.rb +0 -112
- data/spec/sqewer_module_spec.rb +0 -14
data/spec/spec_helper.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
-
|
4
|
-
require 'rspec'
|
5
|
-
require 'rspec/wait'
|
6
|
-
require 'dotenv'
|
7
|
-
require 'aws-sdk'
|
8
|
-
require 'simplecov'
|
9
|
-
Dotenv.load
|
10
|
-
|
11
|
-
SimpleCov.start
|
12
|
-
require 'sqewer'
|
13
|
-
|
14
|
-
RSpec.configure do |config|
|
15
|
-
config.order = 'random'
|
16
|
-
config.around :each do | example |
|
17
|
-
if example.metadata[:sqs]
|
18
|
-
queue_name = 'sqewer-test-queue-%s' % SecureRandom.hex(6)
|
19
|
-
client = Aws::SQS::Client.new
|
20
|
-
resp = client.create_queue(queue_name: queue_name)
|
21
|
-
ENV['SQS_QUEUE_URL'] = resp.queue_url
|
22
|
-
|
23
|
-
example.run
|
24
|
-
|
25
|
-
# Sometimes the queue is already deleted before the example completes. If the test has passed,
|
26
|
-
# we do not really care whether this invocation raises an exception about a non-existent queue since
|
27
|
-
# all we care about is the queue _being gone_ at the end of the example.
|
28
|
-
client.delete_queue(queue_url: ENV.fetch('SQS_QUEUE_URL')) rescue Aws::SQS::Errors::NonExistentQueue
|
29
|
-
|
30
|
-
ENV.delete('SQS_QUEUE_URL')
|
31
|
-
else
|
32
|
-
example.run
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
@@ -1,113 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
require 'securerandom'
|
3
|
-
require 'active_job'
|
4
|
-
require 'active_record'
|
5
|
-
require 'global_id'
|
6
|
-
require_relative '../../lib/sqewer/extensions/active_job_adapter'
|
7
|
-
|
8
|
-
class CreateFileJob < ActiveJob::Base
|
9
|
-
def perform(file)
|
10
|
-
File.open(file, 'w') {}
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class DeleteFileJob < ActiveJob::Base
|
15
|
-
def perform(file)
|
16
|
-
File.unlink(file)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class ActivateUser < ActiveJob::Base
|
21
|
-
def perform(user)
|
22
|
-
user.active = true
|
23
|
-
user.save!
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
GlobalID.app = 'test-app'
|
28
|
-
class User < ActiveRecord::Base
|
29
|
-
include GlobalID::Identification
|
30
|
-
end
|
31
|
-
|
32
|
-
describe ActiveJob::QueueAdapters::SqewerAdapter, :sqs => true do
|
33
|
-
let(:file) { File.join(Dir.tmpdir, "file_active_job_test_1") }
|
34
|
-
let(:client) { ::Aws::SQS::Client.new }
|
35
|
-
|
36
|
-
after :all do
|
37
|
-
# Ensure database files get killed afterwards
|
38
|
-
File.unlink(ActiveRecord::Base.connection_config[:database]) rescue nil
|
39
|
-
end
|
40
|
-
|
41
|
-
before :all do
|
42
|
-
ActiveJob::Base.queue_adapter = ActiveJob::QueueAdapters::SqewerAdapter
|
43
|
-
|
44
|
-
test_seed_name = SecureRandom.hex(4)
|
45
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ('master_db_%s.sqlite3' % test_seed_name))
|
46
|
-
|
47
|
-
ActiveRecord::Migration.suppress_messages do
|
48
|
-
ActiveRecord::Schema.define(:version => 1) do
|
49
|
-
create_table :users do |t|
|
50
|
-
t.string :email, :null => true
|
51
|
-
t.boolean :active, default: false
|
52
|
-
t.timestamps :null => false
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
before do
|
59
|
-
@queue_url_hash = { queue_url: ENV['SQS_QUEUE_URL'] }
|
60
|
-
end
|
61
|
-
|
62
|
-
it "sends job to the queue" do
|
63
|
-
CreateFileJob.perform_later(file)
|
64
|
-
resp = client.get_queue_attributes(@queue_url_hash.merge(attribute_names: ["ApproximateNumberOfMessages"]))
|
65
|
-
expect(resp.attributes["ApproximateNumberOfMessages"].to_i).to eq(1)
|
66
|
-
end
|
67
|
-
|
68
|
-
it "correctly serializes the job into a Sqewer job" do
|
69
|
-
job = CreateFileJob.perform_later(file)
|
70
|
-
resp = client.receive_message(@queue_url_hash)
|
71
|
-
serialized_job = JSON.parse(resp.messages.last.body)
|
72
|
-
|
73
|
-
expect(serialized_job["_job_class"]).to eq("ActiveJob::QueueAdapters::SqewerAdapter::Performable")
|
74
|
-
expect(serialized_job["_job_params"]["job"]["job_id"]).to eq(job.job_id)
|
75
|
-
end
|
76
|
-
|
77
|
-
it "executes job from the queue" do
|
78
|
-
file_delayed = "#{file}_delayed"
|
79
|
-
CreateFileJob.perform_later(file)
|
80
|
-
CreateFileJob.perform_later(file_delayed)
|
81
|
-
w = Sqewer::Worker.default
|
82
|
-
w.start
|
83
|
-
begin
|
84
|
-
wait_for { File.exist?(file) }.to eq(true)
|
85
|
-
File.unlink(file)
|
86
|
-
|
87
|
-
wait_for { File.exist?(file_delayed) }.to eq(true)
|
88
|
-
DeleteFileJob.set(wait: 5.seconds).perform_later(file_delayed)
|
89
|
-
|
90
|
-
sleep 6
|
91
|
-
|
92
|
-
expect(File.exist?(file_delayed)).to eq(false)
|
93
|
-
ensure
|
94
|
-
w.stop
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
it "serializes and deserializes active record using GlobalID" do
|
99
|
-
user = User.create(email: 'test@wetransfer.com')
|
100
|
-
expect(user.active).to eq(false)
|
101
|
-
ActivateUser.perform_later(user)
|
102
|
-
w = Sqewer::Worker.default
|
103
|
-
begin
|
104
|
-
w.start
|
105
|
-
sleep 4
|
106
|
-
user.reload
|
107
|
-
expect(user.active).to eq(true)
|
108
|
-
ensure
|
109
|
-
w.stop
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::AtomicCounter do
|
4
|
-
it 'is atomic' do
|
5
|
-
c = described_class.new
|
6
|
-
expect(c.to_i).to be_zero
|
7
|
-
|
8
|
-
threads = (1..64).map do
|
9
|
-
Thread.new { sleep(rand); c.increment! }
|
10
|
-
end
|
11
|
-
threads.map(&:join)
|
12
|
-
|
13
|
-
expect(c.to_i).to eq(threads.length)
|
14
|
-
end
|
15
|
-
end
|
data/spec/sqewer/cli_app.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
require_relative '../../lib/sqewer'
|
2
|
-
|
3
|
-
class MyJob
|
4
|
-
include Sqewer::SimpleJob
|
5
|
-
attr_accessor :first_name
|
6
|
-
attr_accessor :last_name
|
7
|
-
|
8
|
-
def run(executor)
|
9
|
-
File.open("#{SecureRandom.hex(3)}-result", 'w') {|f| f << [first_name, last_name].join }
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
Sqewer::CLI.start
|
data/spec/sqewer/cli_spec.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::CLI, :sqs => true, :wait => {timeout: 120} do
|
4
|
-
after :each do
|
5
|
-
Dir.glob('*-result').each{|path| File.unlink(path) }
|
6
|
-
end
|
7
|
-
|
8
|
-
describe 'with a mock Worker' do
|
9
|
-
it 'uses just three methods' do
|
10
|
-
mock_worker = Class.new do
|
11
|
-
def self.start; end
|
12
|
-
def self.stop; end
|
13
|
-
def self.debug_thread_information!; end
|
14
|
-
end
|
15
|
-
|
16
|
-
worker_pid = fork do
|
17
|
-
Sqewer::CLI.start(mock_worker)
|
18
|
-
end
|
19
|
-
sleep 1
|
20
|
-
|
21
|
-
begin
|
22
|
-
Process.kill('INFO', worker_pid) # Calls debug_thread_information!
|
23
|
-
rescue ArgumentError, Errno::ENOTSUP # on Linux
|
24
|
-
end
|
25
|
-
Process.kill('TERM', worker_pid) # Terminates the worker
|
26
|
-
|
27
|
-
wait_for {
|
28
|
-
_, status = Process.wait2(worker_pid)
|
29
|
-
expect(status.exitstatus).to be_zero # Must have quit cleanly
|
30
|
-
}
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe 'runs the commandline app, executes jobs and then quits cleanly' do
|
35
|
-
it 'on a USR1 signal' do
|
36
|
-
submitter = Sqewer::Connection.default
|
37
|
-
|
38
|
-
pid = fork { exec("ruby #{__dir__}/cli_app.rb") }
|
39
|
-
|
40
|
-
Thread.new do
|
41
|
-
20.times do
|
42
|
-
j = {"_job_class" => 'MyJob', "_job_params" => {first_name: 'John', last_name: 'Doe'}}
|
43
|
-
submitter.send_message(JSON.dump(j))
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
sleep 8
|
48
|
-
wait_for {
|
49
|
-
Process.kill("USR1", pid)
|
50
|
-
_, status = Process.wait2(pid)
|
51
|
-
expect(status.exitstatus).to be_zero # Must have quit cleanly
|
52
|
-
}
|
53
|
-
|
54
|
-
generated_files = Dir.glob('*-result')
|
55
|
-
expect(generated_files).not_to be_empty
|
56
|
-
generated_files.each{|path| File.unlink(path) }
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'on a TERM signal' do
|
60
|
-
submitter = Sqewer::Connection.default
|
61
|
-
|
62
|
-
pid = fork { exec("ruby #{__dir__}/cli_app.rb") }
|
63
|
-
|
64
|
-
Thread.new do
|
65
|
-
20.times do
|
66
|
-
j = {"_job_class" => 'MyJob', "_job_params" => {first_name: 'John', last_name: 'Doe'}}
|
67
|
-
submitter.send_message(JSON.dump(j))
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
sleep 8
|
72
|
-
wait_for {
|
73
|
-
Process.kill("TERM", pid)
|
74
|
-
_, status = Process.wait2(pid)
|
75
|
-
expect(status.exitstatus).to be_zero # Must have quit cleanly
|
76
|
-
}
|
77
|
-
|
78
|
-
generated_files = Dir.glob('*-result')
|
79
|
-
expect(generated_files).not_to be_empty
|
80
|
-
generated_files.each{|path| File.unlink(path) }
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,145 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::Connection do
|
4
|
-
describe '.default' do
|
5
|
-
it 'returns a new Connection with the SQS queue location picked from SQS_QUEUE_URL envvar' do
|
6
|
-
expect(ENV).to receive(:fetch).with('SQS_QUEUE_URL').and_return('https://aws-fake-queue.com')
|
7
|
-
default = described_class.default
|
8
|
-
expect(default).to be_kind_of(described_class)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
describe '#send_message' do
|
13
|
-
it 'sends the message to the SQS client created with the URL given to the constructor' do
|
14
|
-
fake_sqs_client = double('Client')
|
15
|
-
expect(Aws::SQS::Client).to receive(:new) { fake_sqs_client }
|
16
|
-
expect(fake_sqs_client).to receive(:send_message_batch).and_return(double(failed: []))
|
17
|
-
|
18
|
-
conn = described_class.new('https://fake-queue.com')
|
19
|
-
expect(conn).to receive(:send_multiple_messages).and_call_original
|
20
|
-
conn.send_message('abcdef')
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'passes keyword args to Aws::SQS::Client' do
|
24
|
-
fake_sqs_client = double('Client')
|
25
|
-
expect(Aws::SQS::Client).to receive(:new) { fake_sqs_client }
|
26
|
-
expect(fake_sqs_client).to receive(:send_message_batch).and_return(double(failed: []))
|
27
|
-
|
28
|
-
conn = described_class.new('https://fake-queue.com')
|
29
|
-
expect(conn).to receive(:send_multiple_messages).and_call_original
|
30
|
-
conn.send_message('abcdef', delay_seconds: 5)
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'retries on networking errors'
|
34
|
-
end
|
35
|
-
|
36
|
-
describe '#send_multiple_messages' do
|
37
|
-
it 'sends 100 messages' do
|
38
|
-
fake_sqs_client = double('Client')
|
39
|
-
expect(Aws::SQS::Client).to receive(:new) { fake_sqs_client }
|
40
|
-
expect(fake_sqs_client).to receive(:send_message_batch).exactly(11).times {|kwargs|
|
41
|
-
expect(kwargs[:queue_url]).to eq("https://fake-queue.com")
|
42
|
-
expect(kwargs[:entries]).to be_kind_of(Array)
|
43
|
-
|
44
|
-
entries = kwargs[:entries]
|
45
|
-
expect(entries.length).to be <= 10 # At most 10 messages per batch
|
46
|
-
entries.each do | entry |
|
47
|
-
expect(entry[:id]).to be_kind_of(String)
|
48
|
-
expect(entry[:message_body]).to be_kind_of(String)
|
49
|
-
expect(entry[:message_body]).to match(/Hello/)
|
50
|
-
end
|
51
|
-
double(failed: [])
|
52
|
-
}
|
53
|
-
|
54
|
-
conn = described_class.new('https://fake-queue.com')
|
55
|
-
conn.send_multiple_messages do | b|
|
56
|
-
102.times { b.send_message("Hello - #{SecureRandom.uuid}") }
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'raises an exception if any message fails sending' do
|
61
|
-
fake_sqs_client = double('Client')
|
62
|
-
expect(Aws::SQS::Client).to receive(:new) { fake_sqs_client }
|
63
|
-
expect(fake_sqs_client).to receive(:send_message_batch) {|kwargs|
|
64
|
-
double(failed: [double(message: 'Something went wrong at AWS')])
|
65
|
-
}
|
66
|
-
|
67
|
-
conn = described_class.new('https://fake-queue.com')
|
68
|
-
expect {
|
69
|
-
conn.send_multiple_messages do | b|
|
70
|
-
102.times { b.send_message("Hello - #{SecureRandom.uuid}") }
|
71
|
-
end
|
72
|
-
}.to raise_error(/messages failed to send/)
|
73
|
-
end
|
74
|
-
|
75
|
-
it 'retries on networking errors'
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
describe '#delete_message' do
|
80
|
-
it 'deletes a single message'
|
81
|
-
end
|
82
|
-
|
83
|
-
describe '#delete_multiple_messages' do
|
84
|
-
it 'deletes 100 messages' do
|
85
|
-
fake_sqs_client = double('Client')
|
86
|
-
expect(Aws::SQS::Client).to receive(:new) { fake_sqs_client }
|
87
|
-
expect(fake_sqs_client).to receive(:delete_message_batch).exactly(11).times {|kwargs|
|
88
|
-
expect(kwargs[:queue_url]).to eq("https://fake-queue.com")
|
89
|
-
expect(kwargs[:entries]).to be_kind_of(Array)
|
90
|
-
|
91
|
-
entries = kwargs[:entries]
|
92
|
-
expect(entries.length).to be <= 10 # At most 10 messages per batch
|
93
|
-
entries.each do | entry |
|
94
|
-
expect(entry[:id]).to be_kind_of(String)
|
95
|
-
expect(entry[:receipt_handle]).to be_kind_of(String)
|
96
|
-
end
|
97
|
-
double(failed: [])
|
98
|
-
}
|
99
|
-
|
100
|
-
conn = described_class.new('https://fake-queue.com')
|
101
|
-
conn.delete_multiple_messages do | b|
|
102
|
-
102.times { b.delete_message(SecureRandom.uuid) }
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'raises an exception if any message fails sending' do
|
107
|
-
fake_sqs_client = double('Client')
|
108
|
-
expect(Aws::SQS::Client).to receive(:new) { fake_sqs_client }
|
109
|
-
expect(fake_sqs_client).to receive(:delete_message_batch) {|kwargs|
|
110
|
-
double(failed: [double(message: 'Something went wrong at AWS')])
|
111
|
-
}
|
112
|
-
|
113
|
-
conn = described_class.new('https://fake-queue.com')
|
114
|
-
expect {
|
115
|
-
conn.delete_multiple_messages do | b|
|
116
|
-
102.times { b.delete_message(SecureRandom.uuid) }
|
117
|
-
end
|
118
|
-
}.to raise_error(/messages failed to delete/)
|
119
|
-
end
|
120
|
-
|
121
|
-
it 'retries on networking errors'
|
122
|
-
end
|
123
|
-
|
124
|
-
describe '#receive_messages' do
|
125
|
-
it 'uses the batched receive feature' do
|
126
|
-
s = described_class.new('https://fake-queue')
|
127
|
-
|
128
|
-
fake_sqs_client = double('Client')
|
129
|
-
expect(Aws::SQS::Client).to receive(:new) { fake_sqs_client }
|
130
|
-
|
131
|
-
fake_messages = (1..5).map {
|
132
|
-
double(receipt_handle: SecureRandom.hex(4), body: SecureRandom.random_bytes(128))
|
133
|
-
}
|
134
|
-
fake_response = double(messages: fake_messages)
|
135
|
-
|
136
|
-
expect(fake_sqs_client).to receive(:receive_message).with({:queue_url=>"https://fake-queue", :wait_time_seconds=>5,
|
137
|
-
:max_number_of_messages=>10}).and_return(fake_response)
|
138
|
-
|
139
|
-
messages = s.receive_messages
|
140
|
-
expect(messages.length).to eq(5)
|
141
|
-
end
|
142
|
-
|
143
|
-
it 'retries on networking errors'
|
144
|
-
end
|
145
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::ExecutionContext do
|
4
|
-
it 'offers a submit! that goes through the given Submitter argument' do
|
5
|
-
fake_submitter = double('Submitter')
|
6
|
-
expect(fake_submitter).to receive(:submit!).with(:fake_job, {})
|
7
|
-
|
8
|
-
subject = described_class.new(fake_submitter)
|
9
|
-
subject.submit!(:fake_job)
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'offers arbitrary key/value storage' do
|
13
|
-
fake_submitter = double('Submitter')
|
14
|
-
subject = described_class.new(fake_submitter)
|
15
|
-
|
16
|
-
subject['foo'] = 123
|
17
|
-
expect(subject['foo']).to eq(123)
|
18
|
-
expect(subject[:foo]).to eq(123)
|
19
|
-
expect(subject.fetch(:foo)).to eq(123)
|
20
|
-
|
21
|
-
expect {
|
22
|
-
subject.fetch(:bar)
|
23
|
-
}.to raise_error(KeyError)
|
24
|
-
|
25
|
-
default_value = subject.fetch(:bar) { 123 }
|
26
|
-
expect(default_value).to eq(123)
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'returns the NullLogger from #logger if no logger was passed to the constructor' do
|
30
|
-
fake_submitter = double('Submitter')
|
31
|
-
|
32
|
-
subject = described_class.new(fake_submitter)
|
33
|
-
expect(subject.logger).to eq(Sqewer::NullLogger)
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'offers access to the given "logger" extra param if it was given to the constructor' do
|
37
|
-
fake_submitter = double('Submitter')
|
38
|
-
fake_logger = double('Logger')
|
39
|
-
|
40
|
-
subject = described_class.new(fake_submitter, {'logger' => fake_logger})
|
41
|
-
expect(subject.logger).to eq(fake_logger)
|
42
|
-
end
|
43
|
-
end
|