sidejob 4.0.2 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +38 -15
- data/lib/sidejob.rb +82 -26
- data/lib/sidejob/job.rb +121 -33
- data/lib/sidejob/port.rb +153 -50
- data/lib/sidejob/server_middleware.rb +16 -4
- data/lib/sidejob/testing.rb +6 -13
- data/lib/sidejob/version.rb +1 -1
- data/lib/sidejob/worker.rb +5 -5
- data/spec/integration/channels_spec.rb +36 -0
- data/spec/sidejob/job_spec.rb +141 -8
- data/spec/sidejob/port_spec.rb +208 -68
- data/spec/sidejob/server_middleware_spec.rb +30 -15
- data/spec/sidejob/testing_spec.rb +0 -18
- data/spec/sidejob/worker_spec.rb +11 -6
- data/spec/sidejob_spec.rb +85 -33
- data/web/Gemfile.lock +2 -2
- data/web/app.rb +22 -19
- metadata +3 -2
@@ -17,14 +17,6 @@ describe 'SideJob testing helpers' do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
class TestMysteriousFailure
|
21
|
-
include SideJob::Worker
|
22
|
-
register
|
23
|
-
def perform
|
24
|
-
self.status = 'failed'
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
20
|
describe 'SideJob::Worker.drain_queue' do
|
29
21
|
it 'runs jobs' do
|
30
22
|
job = SideJob.queue('testq', 'TestSum')
|
@@ -50,11 +42,6 @@ describe 'SideJob testing helpers' do
|
|
50
42
|
expect { SideJob::Worker.drain_queue }.to raise_error(RuntimeError, 'bad error')
|
51
43
|
end
|
52
44
|
|
53
|
-
it 'raises error if worker mysteriously fails' do
|
54
|
-
job = SideJob.queue('testq', 'TestMysteriousFailure')
|
55
|
-
expect { SideJob::Worker.drain_queue }.to raise_error(RuntimeError)
|
56
|
-
end
|
57
|
-
|
58
45
|
it 'can disable raising of errors' do
|
59
46
|
job = SideJob.queue('testq', 'TestFailure')
|
60
47
|
expect { SideJob::Worker.drain_queue(errors: false) }.not_to raise_error
|
@@ -95,11 +82,6 @@ describe 'SideJob testing helpers' do
|
|
95
82
|
expect { job.run_inline }.to raise_error(RuntimeError, 'bad error')
|
96
83
|
end
|
97
84
|
|
98
|
-
it 'raises error if worker mysteriously fails' do
|
99
|
-
job = SideJob.queue('testq', 'TestMysteriousFailure')
|
100
|
-
expect { job.run_inline }.to raise_error(RuntimeError)
|
101
|
-
end
|
102
|
-
|
103
85
|
it 'can disable raising of errors' do
|
104
86
|
job = SideJob.queue('testq', 'TestFailure')
|
105
87
|
expect { job.run_inline(errors: false) }.not_to raise_error
|
data/spec/sidejob/worker_spec.rb
CHANGED
@@ -20,6 +20,11 @@ describe SideJob::Worker do
|
|
20
20
|
SideJob::Worker.register_all('q1')
|
21
21
|
expect(SideJob.redis.hget('workers:q1', 'foo')).to be nil
|
22
22
|
end
|
23
|
+
|
24
|
+
it 'publishes message containing worker registry' do
|
25
|
+
expect(SideJob).to receive(:publish).with('/sidejob/workers/q1', SideJob::Worker.registry)
|
26
|
+
SideJob::Worker.register_all('q1')
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
describe '.config' do
|
@@ -75,19 +80,19 @@ describe SideJob::Worker do
|
|
75
80
|
end
|
76
81
|
|
77
82
|
it 'logs input and output from them' do
|
78
|
-
now = Time.now
|
79
|
-
allow(Time).to receive(:now) { now }
|
80
83
|
@job.input(:in1).write 1
|
81
84
|
@job.input(:in1).write 2
|
82
85
|
@job.input(:in2).write ['a', 'b']
|
83
86
|
@job.input(:in2).write ['c', 'd']
|
84
|
-
SideJob.
|
87
|
+
expect(SideJob).to receive(:log).with({
|
88
|
+
read: [{job: @job.id, inport: :in1, data: [1]}, {job: @job.id, inport: :in2, data: [['a', 'b']]}],
|
89
|
+
write: [{job: @job.id, outport: :out1, data: [[1, 'a']]}]})
|
90
|
+
expect(SideJob).to receive(:log).with({
|
91
|
+
read: [{job: @job.id, inport: :in1, data: [2]}, {job: @job.id, inport: :in2, data: [['c', 'd']]}],
|
92
|
+
write: [{job: @job.id, outport: :out1, data: [[2, 'c']]}]})
|
85
93
|
@worker.for_inputs(:in1, :in2) do |in1, in2|
|
86
94
|
@worker.output(:out1).write [in1, in2[0]]
|
87
95
|
end
|
88
|
-
expect(SideJob.logs).to eq([{'timestamp' => SideJob.timestamp, 'read' => [{'job' => @job.id, 'inport' => 'in1', 'data' => [1]}, {'job' => @job.id, 'inport' => 'in2', 'data' => [['a', 'b']]}], 'write' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [[1, 'a']]}]},
|
89
|
-
{'timestamp' => SideJob.timestamp, 'read' => [{'job' => @job.id, 'inport' => 'in1', 'data' => [2]}, {'job' => @job.id, 'inport' => 'in2', 'data' => [['c', 'd']]}], 'write' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [[2, 'c']]}]},
|
90
|
-
])
|
91
96
|
end
|
92
97
|
|
93
98
|
it 'suspends on partial inputs' do
|
data/spec/sidejob_spec.rb
CHANGED
@@ -54,13 +54,13 @@ describe SideJob do
|
|
54
54
|
now = Time.now
|
55
55
|
allow(Time).to receive(:now) { now }
|
56
56
|
job = SideJob.queue('testq', 'TestWorker')
|
57
|
-
expect(job.
|
57
|
+
expect(job.info[:created_at]).to eq(SideJob.timestamp)
|
58
58
|
end
|
59
59
|
|
60
60
|
it 'can specify job args' do
|
61
61
|
job = SideJob.queue('testq', 'TestWorker', args: [1,2])
|
62
62
|
expect(job.status).to eq 'queued'
|
63
|
-
expect(job.
|
63
|
+
expect(job.info[:args]).to eq [1,2]
|
64
64
|
expect_any_instance_of(TestWorker).to receive(:perform).with(1, 2)
|
65
65
|
SideJob::Worker.drain_queue
|
66
66
|
end
|
@@ -109,7 +109,7 @@ describe SideJob do
|
|
109
109
|
|
110
110
|
it 'can specify a by string' do
|
111
111
|
job = SideJob.queue('testq', 'TestWorker', by: 'test:sidejob')
|
112
|
-
expect(job.
|
112
|
+
expect(job.info[:created_by]).to eq 'test:sidejob'
|
113
113
|
end
|
114
114
|
|
115
115
|
it 'defaults to empty by string' do
|
@@ -124,6 +124,12 @@ describe SideJob do
|
|
124
124
|
expect(SideJob.find(job.id)).to eq(job)
|
125
125
|
end
|
126
126
|
|
127
|
+
it 'returns a job object by alias' do
|
128
|
+
job = SideJob.queue('testq', 'TestWorker')
|
129
|
+
job.add_alias 'myjob'
|
130
|
+
expect(SideJob.find('myjob')).to eq(job)
|
131
|
+
end
|
132
|
+
|
127
133
|
it 'returns nil if the job does not exist' do
|
128
134
|
expect(SideJob.find('job')).to be_nil
|
129
135
|
end
|
@@ -136,70 +142,116 @@ describe SideJob do
|
|
136
142
|
end
|
137
143
|
|
138
144
|
describe '.log' do
|
139
|
-
it '
|
145
|
+
it 'can log arbitrary entry hash' do
|
140
146
|
now = Time.now
|
141
147
|
allow(Time).to receive(:now) { now }
|
148
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {abc: 123, timestamp: SideJob.timestamp})
|
142
149
|
SideJob.log({abc: 123})
|
143
|
-
log = SideJob.redis.rpop 'jobs:logs'
|
144
|
-
expect(JSON.parse(log)).to eq({'abc' => 123, 'timestamp' => SideJob.timestamp})
|
145
150
|
end
|
146
|
-
end
|
147
151
|
|
148
|
-
|
149
|
-
before do
|
152
|
+
it 'can log exceptions' do
|
150
153
|
now = Time.now
|
151
154
|
allow(Time).to receive(:now) { now }
|
152
|
-
|
155
|
+
exception = RuntimeError.new('err')
|
156
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {error: 'err', timestamp: SideJob.timestamp})
|
157
|
+
SideJob.log(exception)
|
153
158
|
end
|
154
159
|
|
155
|
-
it '
|
156
|
-
|
157
|
-
|
158
|
-
expect(SideJob
|
160
|
+
it 'can log string messages' do
|
161
|
+
now = Time.now
|
162
|
+
allow(Time).to receive(:now) { now }
|
163
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {message: 'hello', timestamp: SideJob.timestamp})
|
164
|
+
SideJob.log 'hello'
|
159
165
|
end
|
160
166
|
|
161
|
-
it '
|
162
|
-
|
163
|
-
SideJob.log({
|
164
|
-
expect(SideJob.logs(clear: false)).to eq([{'abc' => 123, 'timestamp' => SideJob.timestamp},
|
165
|
-
{'xyz' => 456, 'timestamp' => SideJob.timestamp},])
|
167
|
+
it 'does not generate an infinite publish loop for port subscriptions on /sidejob/log' do
|
168
|
+
job = SideJob.queue('testq', 'TestWorker', inports: {port1: {channels: ['/sidejob/log', '/']}})
|
169
|
+
SideJob.log({test: 1})
|
166
170
|
end
|
167
171
|
end
|
168
172
|
|
169
|
-
describe '.
|
173
|
+
describe '.context' do
|
170
174
|
before do
|
171
175
|
now = Time.now
|
172
176
|
allow(Time).to receive(:now) { now }
|
173
177
|
end
|
174
178
|
|
175
|
-
it 'adds
|
176
|
-
SideJob.
|
179
|
+
it 'adds data to logs within the group' do
|
180
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {data1: 1, data2: 2, abc: 123, timestamp: SideJob.timestamp})
|
181
|
+
SideJob.context(data1: 1, data2: 2) do
|
177
182
|
SideJob.log({abc: 123})
|
178
|
-
expect(SideJob.logs).to eq([{'data1' => 1, 'data2' => 2, 'abc' => 123, 'timestamp' => SideJob.timestamp}])
|
179
183
|
end
|
180
184
|
end
|
181
185
|
|
182
|
-
it 'does not add
|
183
|
-
SideJob.
|
186
|
+
it 'does not add data to logs outside of the group' do
|
187
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {abc: 123, timestamp: SideJob.timestamp})
|
188
|
+
SideJob.context(data1: 1, data2: 2) {}
|
184
189
|
SideJob.log({abc: 123})
|
185
|
-
expect(SideJob.logs).to eq([{'abc' => 123, 'timestamp' => SideJob.timestamp}])
|
186
190
|
end
|
187
191
|
|
188
192
|
it 'can be nested' do
|
189
|
-
SideJob.
|
193
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {data1: 1, timestamp: SideJob.timestamp, x: 1})
|
194
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {data1: 1, data2: 2, timestamp: SideJob.timestamp, x: 2})
|
195
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {data1: 1, timestamp: SideJob.timestamp, x: 3})
|
196
|
+
expect(SideJob).to receive(:publish).with('/sidejob/log', {timestamp: SideJob.timestamp, x: 4})
|
197
|
+
SideJob.context(data1: 1) do
|
190
198
|
SideJob.log({x: 1})
|
191
|
-
SideJob.
|
199
|
+
SideJob.context(data2: 2) do
|
192
200
|
SideJob.log({x: 2})
|
193
201
|
end
|
194
202
|
SideJob.log({x: 3})
|
195
203
|
end
|
196
204
|
SideJob.log({x: 4})
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe '.publish' do
|
209
|
+
it 'publishes message to channel ignoring hierarchy using redis pubsub' do
|
210
|
+
Timeout::timeout(3) do
|
211
|
+
subscribed = false
|
212
|
+
t = Thread.new do
|
213
|
+
redis = SideJob.redis.dup
|
214
|
+
redis.psubscribe('*') do |on|
|
215
|
+
on.psubscribe do |pattern, total|
|
216
|
+
subscribed = true
|
217
|
+
end
|
218
|
+
|
219
|
+
on.pmessage do |pattern, channel, message|
|
220
|
+
expect(JSON.parse(message)).to eq [1,2]
|
221
|
+
expect(channel).to eq '/namespace/mychannel'
|
222
|
+
redis.punsubscribe
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
Thread.pass until subscribed
|
228
|
+
SideJob.publish '/namespace/mychannel', [1,2]
|
229
|
+
t.join
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'writes to subscribed jobs' do
|
234
|
+
job = SideJob.queue('testq', 'TestWorker', inports: {myport: {channels: ['/namespace/mychannel']}, yourport: {channels: ['/namespace']}})
|
235
|
+
SideJob.publish('/namespace/mychannel', [1,2])
|
236
|
+
expect(job.input(:myport).entries).to eq [[1,2]]
|
237
|
+
expect(job.input(:yourport).entries).to eq [[1,2]]
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'includes original channel in context' do
|
241
|
+
job = SideJob.queue('testq', 'TestWorker', inports: {myport: {channels: ['/namespace']}})
|
242
|
+
SideJob.publish('/namespace/mychannel', [1,2])
|
243
|
+
data = job.input(:myport).read
|
244
|
+
expect(data).to eq [1,2]
|
245
|
+
expect(data.sidejob_context).to eq({'channel' => '/namespace/mychannel'})
|
246
|
+
end
|
197
247
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
248
|
+
it 'removes jobs that are no longer subscribed' do
|
249
|
+
job = SideJob.queue('testq', 'TestWorker', inports: {myport: {channels: ['/namespace/mychannel']}})
|
250
|
+
job.input(:myport).channels = []
|
251
|
+
expect(SideJob.redis.smembers('channel:/namespace/mychannel')).to eq [job.id.to_s]
|
252
|
+
SideJob.publish('/namespace/mychannel', [1,2])
|
253
|
+
expect(SideJob.redis.smembers('channel:/namespace/mychannel')).to eq []
|
254
|
+
expect(job.input(:myport).size).to eq 0
|
203
255
|
end
|
204
256
|
end
|
205
257
|
end
|
data/web/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
sidejob (4.0
|
4
|
+
sidejob (4.1.0)
|
5
5
|
sidekiq
|
6
6
|
|
7
7
|
GEM
|
@@ -11,7 +11,7 @@ GEM
|
|
11
11
|
timers (~> 4.0.0)
|
12
12
|
connection_pool (2.2.0)
|
13
13
|
hitimes (1.2.2)
|
14
|
-
json (1.8.
|
14
|
+
json (1.8.3)
|
15
15
|
puma (2.10.2)
|
16
16
|
rack (>= 1.1, < 2.0)
|
17
17
|
rack (1.6.0)
|
data/web/app.rb
CHANGED
@@ -12,9 +12,25 @@ class SideJob::Web < Sinatra::Base
|
|
12
12
|
200
|
13
13
|
end
|
14
14
|
|
15
|
-
# provide
|
15
|
+
# only provide version for now
|
16
16
|
get '/' do
|
17
|
-
{ version:
|
17
|
+
{ version: SideJob::VERSION }.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
# publish a message to a channel
|
21
|
+
post '/publish' do
|
22
|
+
api_call do |params|
|
23
|
+
SideJob.publish params['channel'], params['message']
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# add a log entry
|
29
|
+
post '/log' do
|
30
|
+
api_call do |params|
|
31
|
+
SideJob.log(params['entry'])
|
32
|
+
nil
|
33
|
+
end
|
18
34
|
end
|
19
35
|
|
20
36
|
# queue a new job
|
@@ -80,12 +96,13 @@ class SideJob::Web < Sinatra::Base
|
|
80
96
|
if data == SideJob::Port::None
|
81
97
|
{}
|
82
98
|
else
|
83
|
-
{ data: data }
|
99
|
+
{ data: data, context: data.sidejob_context }
|
84
100
|
end
|
85
101
|
|
86
102
|
# read all and return an array of data
|
87
103
|
when 'entries'
|
88
|
-
|
104
|
+
entries = port.entries
|
105
|
+
{ entries: entries.map {|x| { data: x.data, context: x.sidejob_context } } }
|
89
106
|
|
90
107
|
# port write
|
91
108
|
when 'write'
|
@@ -155,20 +172,6 @@ class SideJob::Web < Sinatra::Base
|
|
155
172
|
end
|
156
173
|
end
|
157
174
|
|
158
|
-
# return all logs
|
159
|
-
get '/logs' do
|
160
|
-
api_call do |params|
|
161
|
-
SideJob.logs(clear: params['clear'] || false)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
# add a log entry
|
166
|
-
post '/logs' do
|
167
|
-
api_call do |params|
|
168
|
-
SideJob.log(params['entry'])
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
175
|
private
|
173
176
|
|
174
177
|
def job_api(&block)
|
@@ -185,7 +188,7 @@ class SideJob::Web < Sinatra::Base
|
|
185
188
|
begin
|
186
189
|
request.body.rewind
|
187
190
|
params = JSON.parse(request.body.read) rescue {}
|
188
|
-
SideJob.
|
191
|
+
SideJob.context(params['context'] || {}) do
|
189
192
|
(yield params).to_json
|
190
193
|
end
|
191
194
|
rescue => e
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidejob
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Austin Che
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sidekiq
|
@@ -102,6 +102,7 @@ files:
|
|
102
102
|
- lib/sidejob/version.rb
|
103
103
|
- lib/sidejob/worker.rb
|
104
104
|
- sidejob.gemspec
|
105
|
+
- spec/integration/channels_spec.rb
|
105
106
|
- spec/integration/fib_spec.rb
|
106
107
|
- spec/integration/sum_spec.rb
|
107
108
|
- spec/sidejob/job_spec.rb
|