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
@@ -1,69 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::MiddlewareStack do
|
4
|
-
it 'returns a default instance' do
|
5
|
-
stacks = (1..100).map { described_class.default }
|
6
|
-
expect(stacks.uniq.length).to eq(1)
|
7
|
-
end
|
8
|
-
|
9
|
-
describe '#around_deserialization' do
|
10
|
-
it 'works without any handlers added' do
|
11
|
-
stack = described_class.new
|
12
|
-
prepare_result = stack.around_deserialization(nil, 'msg-123', '{"body":"some text"}') { :prepared }
|
13
|
-
expect(prepare_result).to eq(:prepared)
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'works with an entire stack' do
|
17
|
-
called = []
|
18
|
-
handler = double('Some middleware')
|
19
|
-
allow(handler).to receive(:around_deserialization){|*a, &blk|
|
20
|
-
called << a
|
21
|
-
blk.call
|
22
|
-
}
|
23
|
-
|
24
|
-
stack = described_class.new
|
25
|
-
stack << handler
|
26
|
-
stack << double('Object that does not handle around_deserialization')
|
27
|
-
stack << handler
|
28
|
-
stack << handler
|
29
|
-
result = stack.around_deserialization(nil, 'msg-123', '{"body":"some text"}') { :foo }
|
30
|
-
expect(result).to eq(:foo)
|
31
|
-
expect(called).not_to be_empty
|
32
|
-
|
33
|
-
expect(called).to eq([
|
34
|
-
[nil, "msg-123", "{\"body\":\"some text\"}"],
|
35
|
-
[nil, "msg-123", "{\"body\":\"some text\"}"],
|
36
|
-
[nil, "msg-123", "{\"body\":\"some text\"}"]]
|
37
|
-
)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
describe '#around_execution' do
|
42
|
-
it 'works without any handlers added' do
|
43
|
-
stack = described_class.new
|
44
|
-
prepare_result = stack.around_execution(nil, nil) { :prepared }
|
45
|
-
expect(prepare_result).to eq(:prepared)
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'works with an entire stack' do
|
49
|
-
called = []
|
50
|
-
handler = double('Some middleware')
|
51
|
-
allow(handler).to receive(:around_execution){|*a, &blk|
|
52
|
-
called << a
|
53
|
-
blk.call
|
54
|
-
}
|
55
|
-
|
56
|
-
stack = described_class.new
|
57
|
-
stack << handler
|
58
|
-
stack << handler
|
59
|
-
stack << double('Object that does not handle around_execution')
|
60
|
-
stack << handler
|
61
|
-
|
62
|
-
result = stack.around_execution(:some_job, :some_context) { :executed }
|
63
|
-
expect(result).to eq(:executed)
|
64
|
-
expect(called).not_to be_empty
|
65
|
-
|
66
|
-
expect(called).to eq([[:some_job, :some_context], [:some_job, :some_context], [:some_job, :some_context]])
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,118 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::Serializer do
|
4
|
-
describe '.default' do
|
5
|
-
it 'returns the same Serializer instance' do
|
6
|
-
instances = (1..1000).map{ described_class.default }
|
7
|
-
instances.uniq!
|
8
|
-
expect(instances).to be_one
|
9
|
-
|
10
|
-
the_instance = instances[0]
|
11
|
-
expect(the_instance).to respond_to(:serialize)
|
12
|
-
expect(the_instance).to respond_to(:unserialize)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe '#serialize' do
|
17
|
-
|
18
|
-
it 'serializes a Job that has no to_h support without its kwargs' do
|
19
|
-
class JobWithoutToHash
|
20
|
-
end
|
21
|
-
job = JobWithoutToHash.new
|
22
|
-
expect(described_class.new.serialize(job)).to eq("{\"_job_class\":\"JobWithoutToHash\",\"_job_params\":null}")
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'serializes a Struct along with its members and the class name' do
|
26
|
-
class SomeJob < Struct.new :one, :two
|
27
|
-
end
|
28
|
-
|
29
|
-
job = SomeJob.new(123, [456])
|
30
|
-
|
31
|
-
expect(described_class.new.serialize(job)).to eq("{\"_job_class\":\"SomeJob\",\"_job_params\":{\"one\":123,\"two\":[456]}}")
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'adds _execute_after when the value is given' do
|
35
|
-
class ThirdJob < Struct.new :one, :two
|
36
|
-
end
|
37
|
-
|
38
|
-
job = ThirdJob.new(123, [456])
|
39
|
-
res = described_class.new.serialize(job, Time.now.to_i + 1500)
|
40
|
-
parsed = JSON.load(res)
|
41
|
-
|
42
|
-
expect(parsed["_execute_after"]).to be_within(10).of(Time.now.to_i + 1500)
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'raises an exception if the object is of an anonymous class' do
|
46
|
-
s = Struct.new(:foo)
|
47
|
-
o = s.new(1)
|
48
|
-
expect {
|
49
|
-
described_class.new.serialize(o)
|
50
|
-
}.to raise_error(described_class::AnonymousJobClass)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'is able to roundtrip a job with a parameter' do
|
55
|
-
require 'ks'
|
56
|
-
|
57
|
-
class LeJob < Ks.strict(:some_data)
|
58
|
-
end
|
59
|
-
|
60
|
-
job = LeJob.new(some_data: 123)
|
61
|
-
|
62
|
-
subject = described_class.new
|
63
|
-
|
64
|
-
serialized = subject.serialize(job)
|
65
|
-
restored = subject.unserialize(serialized)
|
66
|
-
|
67
|
-
expect(restored).to be_kind_of(LeJob)
|
68
|
-
expect(restored.some_data).to eq(123)
|
69
|
-
end
|
70
|
-
|
71
|
-
describe '#unserialize' do
|
72
|
-
it 'wraps the job with a Resubmit when the _execute_after key hints that it is too early' do
|
73
|
-
class EvenSimplerJob; end
|
74
|
-
|
75
|
-
timestamp_way_in_the_future = Time.now.to_i + (60 * 60 * 24 * 3)
|
76
|
-
blob = '{"_job_class": "EvenSimplerJob", "_execute_after": %d}' % timestamp_way_in_the_future
|
77
|
-
built_job = described_class.new.unserialize(blob)
|
78
|
-
|
79
|
-
expect(built_job).to be_kind_of(Sqewer::Resubmit)
|
80
|
-
expect(built_job.execute_after).to eq(timestamp_way_in_the_future)
|
81
|
-
|
82
|
-
embedded_job = built_job.job
|
83
|
-
expect(embedded_job).to be_kind_of(EvenSimplerJob)
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'builds a job without keyword arguments if its constructor does not need any kwargs' do
|
87
|
-
class EvenSimplerJob; end
|
88
|
-
|
89
|
-
blob = '{"_job_class": "EvenSimplerJob"}'
|
90
|
-
built_job = described_class.new.unserialize(blob)
|
91
|
-
|
92
|
-
expect(built_job).to be_kind_of(EvenSimplerJob)
|
93
|
-
|
94
|
-
blob = '{"_job_class": "EvenSimplerJob", "_job_params": null}'
|
95
|
-
built_job = described_class.new.unserialize(blob)
|
96
|
-
|
97
|
-
expect(built_job).to be_kind_of(EvenSimplerJob)
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'raises an error if the job does not accept the keyword arguments given in the ticket' do
|
101
|
-
class MicroJob; end
|
102
|
-
blob = '{"_job_class": "MicroJob", "_job_params":{"foo": 1}}'
|
103
|
-
expect {
|
104
|
-
described_class.new.unserialize(blob)
|
105
|
-
}.to raise_error(ArgumentError)
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'instantiates the job with keyword arguments' do
|
109
|
-
OtherValidJob = Ks.strict(:foo)
|
110
|
-
|
111
|
-
blob = '{"_job_class": "OtherValidJob", "_job_params": {"foo": 1}}'
|
112
|
-
built_job = described_class.new.unserialize(blob)
|
113
|
-
|
114
|
-
expect(built_job).to be_kind_of(OtherValidJob)
|
115
|
-
expect(built_job.foo).to eq(1)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
@@ -1,69 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::SimpleJob do
|
4
|
-
it 'raises a clear error for an unknown attribute' do
|
5
|
-
example_class = Class.new do
|
6
|
-
attr_accessor :foo, :bar
|
7
|
-
include Sqewer::SimpleJob
|
8
|
-
end
|
9
|
-
|
10
|
-
expect {
|
11
|
-
example_class.new(zoo: 1, bar: 2)
|
12
|
-
}.to raise_error(/Unknown attribute \:zoo for/)
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'uses defined accessors to provide decent string representation' do
|
16
|
-
example_class = Class.new do
|
17
|
-
attr_accessor :foo, :bar
|
18
|
-
include Sqewer::SimpleJob
|
19
|
-
end
|
20
|
-
|
21
|
-
job = example_class.new(foo: 1, bar: 2)
|
22
|
-
expect(job.inspect).to include('Class')
|
23
|
-
expect(job.inspect).to include('{:foo=>1, :bar=>2}')
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'uses inspectable_attributes to limit the scope of .inspect' do
|
27
|
-
example_class = Class.new do
|
28
|
-
attr_accessor :foo, :bar
|
29
|
-
def inspectable_attributes
|
30
|
-
[:foo]
|
31
|
-
end
|
32
|
-
include Sqewer::SimpleJob
|
33
|
-
end
|
34
|
-
|
35
|
-
job = example_class.new(foo: 1, bar: 2)
|
36
|
-
expect(job.inspect).to include('Class')
|
37
|
-
expect(job.inspect).to include('{:foo=>1}')
|
38
|
-
expect(job.inspect).not_to include('bar')
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'provides for a keyword argument constructor and a to_h method' do
|
42
|
-
example_class = Class.new do
|
43
|
-
attr_accessor :foo, :bar
|
44
|
-
include Sqewer::SimpleJob
|
45
|
-
end
|
46
|
-
|
47
|
-
string_repr = example_class.to_s
|
48
|
-
|
49
|
-
new_instance = example_class.new(foo: 1, bar: 2)
|
50
|
-
|
51
|
-
expect(new_instance.foo).to eq(1)
|
52
|
-
expect(new_instance.bar).to eq(2)
|
53
|
-
|
54
|
-
hash_repr = new_instance.to_h
|
55
|
-
expect(hash_repr).to eq({foo: 1, bar: 2})
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
it 'raises if arguments are forgotten' do
|
60
|
-
example_class = Class.new do
|
61
|
-
attr_accessor :foo, :bar
|
62
|
-
include Sqewer::SimpleJob
|
63
|
-
end
|
64
|
-
|
65
|
-
expect {
|
66
|
-
example_class.new(foo: 1)
|
67
|
-
}.to raise_error('Missing job attribute :bar')
|
68
|
-
end
|
69
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::Submitter do
|
4
|
-
describe '.default' do
|
5
|
-
it 'returns a set up Submitter with the configured Connection and Serializer' do
|
6
|
-
expect(ENV).to receive(:fetch).with('SQS_QUEUE_URL').and_return('https://some-queue.aws.com')
|
7
|
-
|
8
|
-
s = described_class.default
|
9
|
-
expect(s.connection).to respond_to(:send_message)
|
10
|
-
expect(s.serializer).to respond_to(:serialize)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
describe '#initialize' do
|
15
|
-
it 'creates a Submitter that you can submit jobs through' do
|
16
|
-
fake_serializer = double('Some serializer')
|
17
|
-
allow(fake_serializer).to receive(:serialize) {|object_to_serialize|
|
18
|
-
expect(object_to_serialize).not_to be_nil
|
19
|
-
'serialized-object-data'
|
20
|
-
}
|
21
|
-
|
22
|
-
fake_connection = double('Some SQS connection')
|
23
|
-
expect(fake_connection).to receive(:send_message).at_least(5).times.with('serialized-object-data', {})
|
24
|
-
|
25
|
-
subject = described_class.new(fake_connection, fake_serializer)
|
26
|
-
5.times { subject.submit!(:some_object) }
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'passes the keyword arguments to send_message on the connection' do
|
30
|
-
fake_serializer = double('Some serializer')
|
31
|
-
allow(fake_serializer).to receive(:serialize) {|object_to_serialize|
|
32
|
-
expect(object_to_serialize).not_to be_nil
|
33
|
-
'serialized-object-data'
|
34
|
-
}
|
35
|
-
|
36
|
-
fake_connection = double('Some SQS connection')
|
37
|
-
expect(fake_connection).to receive(:send_message).with('serialized-object-data', {delay_seconds: 5})
|
38
|
-
|
39
|
-
subject = described_class.new(fake_connection, fake_serializer)
|
40
|
-
subject.submit!(:some_object, delay_seconds: 5)
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'handles the massively delayed execution by clamping the delay_seconds to the SQS maximum, and saving the _execute_after' do
|
44
|
-
fake_serializer = double('Some serializer')
|
45
|
-
allow(fake_serializer).to receive(:serialize) {|object_to_serialize, timestamp_seconds|
|
46
|
-
|
47
|
-
delay_by = Time.now.to_i + 4585659855
|
48
|
-
expect(timestamp_seconds).to be_within(20).of(delay_by)
|
49
|
-
|
50
|
-
expect(object_to_serialize).not_to be_nil
|
51
|
-
'serialized-object-data'
|
52
|
-
}
|
53
|
-
|
54
|
-
fake_connection = double('Some SQS connection')
|
55
|
-
expect(fake_connection).to receive(:send_message).with('serialized-object-data', {delay_seconds: 899})
|
56
|
-
|
57
|
-
subject = described_class.new(fake_connection, fake_serializer)
|
58
|
-
subject.submit!(:some_object, delay_seconds: 4585659855)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
data/spec/sqewer/worker_spec.rb
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer::Worker, :sqs => true do
|
4
|
-
let(:test_logger) {
|
5
|
-
$stderr.sync = true
|
6
|
-
ENV['SHOW_TEST_LOGS'] ? Logger.new($stderr) : Logger.new(StringIO.new(''))
|
7
|
-
}
|
8
|
-
|
9
|
-
it 'has all the necessary attributes' do
|
10
|
-
attrs = [:logger, :connection, :serializer, :middleware_stack,
|
11
|
-
:execution_context_class, :submitter_class, :num_threads]
|
12
|
-
default_worker = described_class.default
|
13
|
-
attrs.each do | attr_name |
|
14
|
-
expect(default_worker).to respond_to(attr_name)
|
15
|
-
expect(default_worker.public_send(attr_name)).not_to be_nil
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'supports .default' do
|
20
|
-
default_worker = described_class.default
|
21
|
-
expect(default_worker).to respond_to(:start)
|
22
|
-
expect(default_worker).to respond_to(:stop)
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'instantiates a new worker object on every call to .default' do
|
26
|
-
workers = (1..10).map { described_class.default }
|
27
|
-
expect(workers.uniq.length).to eq(10)
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'instantiates a Logger to STDERR by default' do
|
31
|
-
expect(Logger).to receive(:new).with(STDERR)
|
32
|
-
worker = described_class.new
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'can go through the full cycle of initialize, start, stop, start, stop' do
|
36
|
-
worker = described_class.new(logger: test_logger)
|
37
|
-
worker.start
|
38
|
-
worker.stop
|
39
|
-
worker.start
|
40
|
-
worker.stop
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'raises a state exception if being stopped without being started' do
|
44
|
-
worker = described_class.new
|
45
|
-
expect {
|
46
|
-
worker.stop
|
47
|
-
}.to raise_error(/Cannot change state/)
|
48
|
-
end
|
49
|
-
|
50
|
-
context 'when the job payload cannot be unserialized from JSON due to invalid syntax' do
|
51
|
-
it 'is able to cope with an exception when the job class is unknown (one of generic exceptions)' do
|
52
|
-
client = Aws::SQS::Client.new
|
53
|
-
client.send_message(queue_url: ENV.fetch('SQS_QUEUE_URL'), message_body: '{"foo":')
|
54
|
-
|
55
|
-
worker = described_class.new(logger: test_logger)
|
56
|
-
|
57
|
-
worker.start
|
58
|
-
sleep 2
|
59
|
-
worker.stop
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
context 'when the job cannot be instantiated due to an unknown class' do
|
64
|
-
it 'is able to cope with an exception when the job class is unknown (one of generic exceptions)' do
|
65
|
-
payload = JSON.dump({_job_class: 'UnknownJobClass', _job_params: {arg1: 'some value'}})
|
66
|
-
|
67
|
-
client = Aws::SQS::Client.new
|
68
|
-
client.send_message(queue_url: ENV.fetch('SQS_QUEUE_URL'), message_body: payload)
|
69
|
-
|
70
|
-
worker = described_class.new(logger: test_logger)
|
71
|
-
|
72
|
-
worker.start
|
73
|
-
sleep 2
|
74
|
-
worker.stop
|
75
|
-
|
76
|
-
# expect(logger_output).to include('uninitialized constant UnknownJobClass')
|
77
|
-
# expect(logger_output).to include('Stopping (clean shutdown)')
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
context 'with a job that spawns another job' do
|
82
|
-
it 'sets up the processing pipeline so that jobs can execute in sequence (with threads)' do
|
83
|
-
class SecondaryJob
|
84
|
-
def run
|
85
|
-
File.open(File.join(Dir.tmpdir, 'secondary-job-run'),'w') {}
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
class InitialJob
|
90
|
-
def run(executor)
|
91
|
-
File.open(File.join(Dir.tmpdir, 'initial-job-run'),'w') {}
|
92
|
-
executor.submit!(SecondaryJob.new)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
payload = JSON.dump({_job_class: 'InitialJob'})
|
97
|
-
client = Aws::SQS::Client.new
|
98
|
-
client.send_message(queue_url: ENV.fetch('SQS_QUEUE_URL'), message_body: payload)
|
99
|
-
|
100
|
-
worker = described_class.new(logger: test_logger, num_threads: 8)
|
101
|
-
|
102
|
-
worker.start
|
103
|
-
|
104
|
-
begin
|
105
|
-
wait_for { File.exist?(File.join(Dir.tmpdir, 'initial-job-run')) }.to eq(true)
|
106
|
-
wait_for { File.exist?(File.join(Dir.tmpdir, 'secondary-job-run')) }.to eq(true)
|
107
|
-
ensure
|
108
|
-
worker.stop
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
data/spec/sqewer_module_spec.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require_relative 'spec_helper'
|
2
|
-
|
3
|
-
describe Sqewer do
|
4
|
-
it 'provides a #submit!() method that is a shortcut to the default submitter' do
|
5
|
-
fake_submitter = double('Submitter')
|
6
|
-
expect(Sqewer::Submitter).to receive(:default) { fake_submitter }
|
7
|
-
|
8
|
-
first_job = double('Job1')
|
9
|
-
second_job = double('Job2')
|
10
|
-
|
11
|
-
expect(fake_submitter).to receive(:submit!).with(first_job, second_job, {})
|
12
|
-
Sqewer.submit!(first_job, second_job)
|
13
|
-
end
|
14
|
-
end
|