sidejob 3.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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +61 -0
- data/README.md +128 -0
- data/bin/build +11 -0
- data/bin/console +7 -0
- data/lib/sidejob/job.rb +425 -0
- data/lib/sidejob/port.rb +206 -0
- data/lib/sidejob/server_middleware.rb +117 -0
- data/lib/sidejob/testing.rb +54 -0
- data/lib/sidejob/version.rb +4 -0
- data/lib/sidejob/worker.rb +133 -0
- data/lib/sidejob.rb +116 -0
- data/sidejob.gemspec +21 -0
- data/spec/integration/fib_spec.rb +51 -0
- data/spec/integration/sum_spec.rb +39 -0
- data/spec/sidejob/job_spec.rb +543 -0
- data/spec/sidejob/port_spec.rb +452 -0
- data/spec/sidejob/server_middleware_spec.rb +254 -0
- data/spec/sidejob/testing_spec.rb +108 -0
- data/spec/sidejob/worker_spec.rb +201 -0
- data/spec/sidejob_spec.rb +182 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/test_sum.rb +17 -0
- data/spec/support/test_worker.rb +6 -0
- metadata +140 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# fibonacci calculation by recursively spawning jobs
|
4
|
+
class TestFib
|
5
|
+
include SideJob::Worker
|
6
|
+
register(
|
7
|
+
inports: {
|
8
|
+
n: {mode: :memory}
|
9
|
+
},
|
10
|
+
outports: {
|
11
|
+
num: {}
|
12
|
+
}
|
13
|
+
)
|
14
|
+
def perform
|
15
|
+
suspend unless input(:n).data?
|
16
|
+
n = input(:n).read
|
17
|
+
|
18
|
+
if n <= 2
|
19
|
+
output(:num).write 1
|
20
|
+
else
|
21
|
+
job1 = child(:job1)
|
22
|
+
if ! job1
|
23
|
+
job1 = queue('testq', 'TestFib', name: :job1)
|
24
|
+
job1.input(:n).write n-1
|
25
|
+
end
|
26
|
+
|
27
|
+
job2 = child(:job2)
|
28
|
+
if ! job2
|
29
|
+
job2 = queue('testq', 'TestFib', name: :job2)
|
30
|
+
job2.input(:n).write n-2
|
31
|
+
end
|
32
|
+
|
33
|
+
if job1.status != 'completed' || job2.status != 'completed'
|
34
|
+
suspend
|
35
|
+
else
|
36
|
+
output(:num).write (job1.output(:num).read + job2.output(:num).read)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe TestFib do
|
43
|
+
it 'calculates fibonacci correctly' do
|
44
|
+
job = SideJob.queue('testq', 'TestFib')
|
45
|
+
job.input(:n).write 6 # 1, 1, 2, 3, 5, 8
|
46
|
+
SideJob::Worker.drain_queue
|
47
|
+
job.reload
|
48
|
+
expect(job.status).to eq 'completed'
|
49
|
+
expect(job.output(:num).read).to eq(8)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestSumFlow
|
4
|
+
include SideJob::Worker
|
5
|
+
register(
|
6
|
+
inports: {},
|
7
|
+
outports: {
|
8
|
+
out: {},
|
9
|
+
}
|
10
|
+
)
|
11
|
+
def perform
|
12
|
+
sum = child(:sum)
|
13
|
+
if ! sum
|
14
|
+
queue('testq', 'TestSum', name: :sum)
|
15
|
+
suspend
|
16
|
+
else
|
17
|
+
if get(:sent)
|
18
|
+
output(:out).write sum.output(:sum).read
|
19
|
+
else
|
20
|
+
sum.input(:in).write 5
|
21
|
+
sum.input(:in).write 6
|
22
|
+
sum.input(:ready).write 1
|
23
|
+
set(sent: 1)
|
24
|
+
sum.run
|
25
|
+
suspend
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe TestSumFlow do
|
32
|
+
it 'calls child job to sum numbers' do
|
33
|
+
job = SideJob.queue('testq', 'TestSumFlow')
|
34
|
+
SideJob::Worker.drain_queue
|
35
|
+
job.reload
|
36
|
+
expect(job.status).to eq 'completed'
|
37
|
+
expect(job.output(:out).read).to eq(11)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,543 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SideJob::Job do
|
4
|
+
describe '#==, #eql?' do
|
5
|
+
it 'two jobs with the same id are eq' do
|
6
|
+
expect(SideJob::Job.new('123')).to eq(SideJob::Job.new('123'))
|
7
|
+
expect(SideJob::Job.new('123')).to eql(SideJob::Job.new('123'))
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'two jobs with different id are not eq' do
|
11
|
+
expect(SideJob::Job.new('123')).not_to eq(SideJob::Job.new('456'))
|
12
|
+
expect(SideJob::Job.new('123')).not_to eql(SideJob::Job.new('456'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#hash' do
|
17
|
+
it 'uses hash of the job id and can be used as hash keys' do
|
18
|
+
job = SideJob::Job.new('abc')
|
19
|
+
expect(job.hash).to eq('abc'.hash)
|
20
|
+
h = {}
|
21
|
+
h[job] = 1
|
22
|
+
job2 = SideJob::Job.new('abc')
|
23
|
+
expect(job.hash).to eq(job2.hash)
|
24
|
+
h[job2] = 3
|
25
|
+
expect(h.keys.length).to be(1)
|
26
|
+
expect(h[job]).to be(3)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#to_s' do
|
31
|
+
it 'returns the redis key' do
|
32
|
+
job = SideJob::Job.new('abc')
|
33
|
+
expect(job.to_s).to eq 'job:abc'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#exists?' do
|
38
|
+
it 'returns true if job exists' do
|
39
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
40
|
+
expect(@job.exists?).to be true
|
41
|
+
end
|
42
|
+
it 'returns false if job does not exist' do
|
43
|
+
expect(SideJob::Job.new('job').exists?).to be false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#log' do
|
48
|
+
before do
|
49
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
50
|
+
@entry = {'abc' => [1,2]}
|
51
|
+
@entry_job = @entry.merge({job: @job.id})
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'logs to SideJob.log if logger not set' do
|
55
|
+
expect(SideJob).to receive(:log).with @entry_job
|
56
|
+
@job.log(@entry)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'logs to job logger if set' do
|
60
|
+
expect(SideJob).not_to receive(:log).with @entry_job
|
61
|
+
@job.logger = Class.new
|
62
|
+
expect(@job.logger).to receive(:log).with @entry_job
|
63
|
+
@job.log(@entry)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#group_port_logs' do
|
68
|
+
before do
|
69
|
+
@job = SideJob.queue('testq', 'TestWorker', inports: {port1: {}})
|
70
|
+
@port = @job.input(:port1)
|
71
|
+
now = Time.now
|
72
|
+
allow(Time).to receive(:now) { now }
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'does not generate a log if no logs occur' do
|
76
|
+
@job.group_port_logs {}
|
77
|
+
expect(SideJob.logs.length).to eq 0
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'passes through logs not generated by port read/write' do
|
81
|
+
@job.group_port_logs do
|
82
|
+
@job.log({'test' => 123})
|
83
|
+
expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'test' => 123, 'job' => @job.id}]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'merges logs from port reads and writes' do
|
88
|
+
@job.group_port_logs do
|
89
|
+
@port.write 'hello'
|
90
|
+
@port.write 2
|
91
|
+
end
|
92
|
+
expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => @job.id,
|
93
|
+
'read' => [],
|
94
|
+
'write' => [{'job' => @job.id, 'inport' => 'port1', 'data' => ['hello', 2]}]}]
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'does not write out a log entry until the end of outermost group' do
|
98
|
+
@job.group_port_logs do
|
99
|
+
@port.write 'hello'
|
100
|
+
@job.group_port_logs do
|
101
|
+
@port.write 2
|
102
|
+
end
|
103
|
+
expect(SideJob.logs.length).to eq 0
|
104
|
+
end
|
105
|
+
expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => @job.id,
|
106
|
+
'read' => [],
|
107
|
+
'write' => [{'job' => @job.id, 'inport' => 'port1', 'data' => ['hello', 2]}]}]
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'can overwrite job for log attribution' do
|
111
|
+
@job.group_port_logs(job: 1234) do
|
112
|
+
@port.write 'hello'
|
113
|
+
@port.read
|
114
|
+
end
|
115
|
+
expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => 1234,
|
116
|
+
'read' => [{'job' => @job.id, 'inport' => 'port1', 'data' => ['hello']}],
|
117
|
+
'write' => [{'job' => @job.id, 'inport' => 'port1', 'data' => ['hello']}]}]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'can include arbitrary metadata' do
|
121
|
+
@job.group_port_logs(user: 'test') do
|
122
|
+
@port.write 'hello'
|
123
|
+
@port.read
|
124
|
+
end
|
125
|
+
expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => @job.id, 'user' => 'test',
|
126
|
+
'read' => [{'job' => @job.id, 'inport' => 'port1', 'data' => ['hello']}],
|
127
|
+
'write' => [{'job' => @job.id, 'inport' => 'port1', 'data' => ['hello']}]}]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#status' do
|
132
|
+
it 'retrieves status' do
|
133
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
134
|
+
expect(@job.status).to eq 'queued'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#status=' do
|
139
|
+
it 'sets status' do
|
140
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
141
|
+
@job.status = 'newstatus'
|
142
|
+
expect(@job.status).to eq 'newstatus'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#terminate' do
|
147
|
+
before do
|
148
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'sets the status to terminating' do
|
152
|
+
@job.terminate
|
153
|
+
expect(@job.status).to eq 'terminating'
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'does nothing if status is terminated' do
|
157
|
+
@job.status = 'terminated'
|
158
|
+
@job.terminate
|
159
|
+
expect(@job.status).to eq 'terminated'
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'throws error and immediately sets status to terminated if job class is unregistered' do
|
163
|
+
SideJob.redis.del 'workers:testq'
|
164
|
+
expect { @job.terminate }.to raise_error
|
165
|
+
expect(@job.status).to eq 'terminated'
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'queues the job for termination run' do
|
169
|
+
expect {
|
170
|
+
@job.terminate
|
171
|
+
}.to change {Sidekiq::Stats.new.enqueued}.by(1)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'by default does not terminate children' do
|
175
|
+
child = SideJob.queue('testq', 'TestWorker', parent: @job, name: 'child')
|
176
|
+
expect(child.status).to eq 'queued'
|
177
|
+
@job.terminate
|
178
|
+
expect(child.status).to eq 'queued'
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'can recursively terminate' do
|
182
|
+
5.times {|i| SideJob.queue('testq', 'TestWorker', parent: @job, name: "child#{i}") }
|
183
|
+
@job.terminate(recursive: true)
|
184
|
+
@job.children.each_value do |child|
|
185
|
+
expect(child.status).to eq 'terminating'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe '#run' do
|
191
|
+
before do
|
192
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
193
|
+
end
|
194
|
+
|
195
|
+
%w{queued running suspended completed failed}.each do |status|
|
196
|
+
it "queues the job if status is #{status}" do
|
197
|
+
expect {
|
198
|
+
@job.status = status
|
199
|
+
@job.run
|
200
|
+
expect(@job.status).to eq 'queued'
|
201
|
+
}.to change {Sidekiq::Stats.new.enqueued}.by(1)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
%w{terminating terminated}.each do |status|
|
206
|
+
it "does not queue the job if status is #{status}" do
|
207
|
+
expect {
|
208
|
+
@job.status = status
|
209
|
+
@job.run
|
210
|
+
expect(@job.status).to eq status
|
211
|
+
}.to change {Sidekiq::Stats.new.enqueued}.by(0)
|
212
|
+
end
|
213
|
+
|
214
|
+
it "queues the job if status is #{status} and force=true" do
|
215
|
+
expect {
|
216
|
+
@job.status = status
|
217
|
+
@job.run(force: true)
|
218
|
+
expect(@job.status).to eq 'queued'
|
219
|
+
}.to change {Sidekiq::Stats.new.enqueued}.by(1)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'throws error and immediately sets status to terminated if job class is unregistered' do
|
224
|
+
SideJob.redis.del "workers:#{@job.get(:queue)}"
|
225
|
+
expect { @job.run }.to raise_error
|
226
|
+
expect(@job.status).to eq 'terminated'
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'can schedule a job to run at a specific time using a float' do
|
230
|
+
time = Time.now.to_f + 10000
|
231
|
+
expect { @job.run(at: time) }.to change {Sidekiq::Stats.new.scheduled_size}.by(1)
|
232
|
+
expect(Sidekiq::ScheduledSet.new.find_job(@job.id).at).to eq(Time.at(time))
|
233
|
+
expect(@job.status).to eq 'queued'
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'can schedule a job to run at a specific time using a Time' do
|
237
|
+
time = Time.now + 1000
|
238
|
+
expect { @job.run(at: time) }.to change {Sidekiq::Stats.new.scheduled_size}.by(1)
|
239
|
+
expect(Sidekiq::ScheduledSet.new.find_job(@job.id).at).to eq(Time.at(time.to_f))
|
240
|
+
expect(@job.status).to eq 'queued'
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'can schedule a job to run in a specific time' do
|
244
|
+
now = Time.now
|
245
|
+
allow(Time).to receive(:now) { now }
|
246
|
+
expect { @job.run(wait: 100) }.to change {Sidekiq::Stats.new.scheduled_size}.by(1)
|
247
|
+
expect(Sidekiq::ScheduledSet.new.find_job(@job.id).at).to eq(Time.at(now.to_f + 100))
|
248
|
+
expect(@job.status).to eq 'queued'
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'raises error if job no longer exists' do
|
252
|
+
job2 = SideJob.find(@job.id)
|
253
|
+
job2.status = 'terminated'
|
254
|
+
expect(job2.delete).to be true
|
255
|
+
expect { @job.run }.to raise_error
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#child' do
|
260
|
+
it 'returns nil for missing child' do
|
261
|
+
job = SideJob.queue('testq', 'TestWorker')
|
262
|
+
expect(job.child('child')).to be nil
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'returns child by name' do
|
266
|
+
job = SideJob.queue('testq', 'TestWorker')
|
267
|
+
child = SideJob.queue('testq', 'TestWorker', parent: job, name: 'child')
|
268
|
+
expect(job.child('child')).to eq child
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe '#children, #parent' do
|
273
|
+
it 'can get children and parent jobs' do
|
274
|
+
parent = SideJob.queue('testq', 'TestWorker')
|
275
|
+
child = SideJob.queue('testq', 'TestWorker', parent: parent, name: 'child')
|
276
|
+
expect(parent.children).to eq('child' => child)
|
277
|
+
expect(child.parent).to eq(parent)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe '#ancestors' do
|
282
|
+
it 'returns empty array if no parent' do
|
283
|
+
job = SideJob.queue('testq', 'TestWorker')
|
284
|
+
expect(job.ancestors).to eq([])
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'returns entire job tree' do
|
288
|
+
j1 = SideJob.queue('testq', 'TestWorker')
|
289
|
+
j2 = SideJob.queue('testq', 'TestWorker', parent: j1, name: 'child')
|
290
|
+
j3 = SideJob.queue('testq', 'TestWorker', parent: j2, name: 'child')
|
291
|
+
j4 = SideJob.queue('testq', 'TestWorker', parent: j3, name: 'child')
|
292
|
+
expect(j4.ancestors).to eq([j3, j2, j1])
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe '#terminated?' do
|
297
|
+
before do
|
298
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'returns false if job status is not terminated' do
|
302
|
+
expect(@job.terminated?).to be false
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'returns true if job status is terminated' do
|
306
|
+
@job.status = 'terminated'
|
307
|
+
expect(@job.terminated?).to be true
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'returns false if child job is not terminated' do
|
311
|
+
@job.status = 'terminated'
|
312
|
+
SideJob.queue('testq', 'TestWorker', parent: @job, name: 'child')
|
313
|
+
expect(@job.terminated?).to be false
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'returns true if child job is terminated' do
|
317
|
+
@job.status = 'terminated'
|
318
|
+
child = SideJob.queue('testq', 'TestWorker', parent: @job, name: 'child')
|
319
|
+
child.status = 'terminated'
|
320
|
+
expect(@job.terminated?).to be true
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
describe '#delete' do
|
325
|
+
before do
|
326
|
+
@job = SideJob.queue('testq', 'TestWorker', inports: {in1: {}}, outports: {out1: {}})
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'does not delete non-terminated jobs' do
|
330
|
+
expect(@job.delete).to be false
|
331
|
+
expect(@job.exists?).to be true
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'deletes terminated jobs' do
|
335
|
+
@job.status = 'terminated'
|
336
|
+
expect(@job.delete).to be true
|
337
|
+
expect(@job.exists?).to be false
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'recursively deletes jobs' do
|
341
|
+
child = SideJob.queue('testq', 'TestWorker', parent: @job, name: 'child')
|
342
|
+
expect(@job.status).to eq('queued')
|
343
|
+
expect(child.status).to eq('queued')
|
344
|
+
expect(SideJob.redis {|redis| redis.keys('job:*').length}).to be > 0
|
345
|
+
@job.status = 'terminated'
|
346
|
+
child.status = 'terminated'
|
347
|
+
@job.delete
|
348
|
+
expect(SideJob.redis {|redis| redis.keys('job:*').length}).to be(0)
|
349
|
+
expect(@job.exists?).to be false
|
350
|
+
expect(child.exists?).to be false
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'deletes data on input and output ports' do
|
354
|
+
@job.input(:in1).write 'data'
|
355
|
+
@job.output(:out1).write 'data'
|
356
|
+
expect(@job.input(:in1).size).to be 1
|
357
|
+
expect(@job.output(:out1).size).to be 1
|
358
|
+
@job.status = 'terminated'
|
359
|
+
@job.delete
|
360
|
+
expect(SideJob.redis {|redis| redis.keys('job:*').length}).to be(0)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Tests are identical for input and output port methods
|
365
|
+
%i{in out}.each do |type|
|
366
|
+
describe "##{type}put" do
|
367
|
+
it "returns a cached #{type}put port" do
|
368
|
+
spec = {}
|
369
|
+
spec[:"#{type}ports"] = {port: {}}
|
370
|
+
@job = SideJob.queue('testq', 'TestWorker', **spec)
|
371
|
+
expect(@job.send("#{type}put", :port)).to eq(SideJob::Port.new(@job, type, :port))
|
372
|
+
expect(@job.send("#{type}put", :port)).to be(@job.send("#{type}put", :port))
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'raises error on unknown port' do
|
376
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
377
|
+
expect { @job.send("#{type}put", :unknown) }.to raise_error
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'can dynamically create ports' do
|
381
|
+
spec = {}
|
382
|
+
spec[:"#{type}ports"] = {'*' => {mode: :memory, default: 123}}
|
383
|
+
@job = SideJob.queue('testq', 'TestWorker', **spec)
|
384
|
+
expect(@job.send("#{type}ports").size).to eq 0
|
385
|
+
port = @job.send("#{type}put", :newport)
|
386
|
+
expect(@job.send("#{type}ports").size).to eq 1
|
387
|
+
expect(port.mode).to eq :memory
|
388
|
+
expect(port.default).to eq 123
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
describe "##{type}ports" do
|
393
|
+
it "returns all #{type}put ports" do
|
394
|
+
@job = SideJob.queue('testq', 'TestWorker', inports: { port1: {} }, outports: { port1: {} })
|
395
|
+
expect(@job.send("#{type}ports")).to eq([SideJob::Port.new(@job, type, :port1)])
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
describe "##{type}ports=" do
|
400
|
+
before do
|
401
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'can specify ports with options' do
|
405
|
+
expect(@job.send("#{type}ports").size).to eq 0
|
406
|
+
@job.send("#{type}ports=", {myport: {mode: :memory, default: 'def'}})
|
407
|
+
expect(@job.send("#{type}ports").size).to eq 1
|
408
|
+
expect(@job.send("#{type}ports").map(&:name)).to include(:myport)
|
409
|
+
expect(@job.send("#{type}put", :myport).mode).to eq :memory
|
410
|
+
expect(@job.send("#{type}put", :myport).default).to eq 'def'
|
411
|
+
end
|
412
|
+
|
413
|
+
it 'merges ports with the worker configuration' do
|
414
|
+
allow(@job).to receive(:config) { {"#{type}ports" => {'port1' => {}, 'port2' => {'mode' => 'memory'}}}}
|
415
|
+
@job.send("#{type}ports=", {port2: {mode: :queue}, port3: {}})
|
416
|
+
expect(@job.send("#{type}ports").size).to eq 3
|
417
|
+
expect(@job.send("#{type}ports").all? {|port| port.options == {mode: :queue}}).to be true
|
418
|
+
end
|
419
|
+
|
420
|
+
it 'can change existing port mode while keeping data intact' do
|
421
|
+
@job.send("#{type}ports=", {myport: {}})
|
422
|
+
@job.send("#{type}put", :myport).write 'data'
|
423
|
+
@job.send("#{type}ports=", {myport: {mode: :memory, default: 'def'}})
|
424
|
+
expect(@job.send("#{type}ports").size).to eq 1
|
425
|
+
expect(@job.send("#{type}put", :myport).mode).to eq :memory
|
426
|
+
expect(@job.send("#{type}put", :myport).default).to eq 'def'
|
427
|
+
expect(@job.send("#{type}put", :myport).read).to eq 'data'
|
428
|
+
expect(@job.send("#{type}put", :myport).read).to eq 'def'
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'deletes no longer used ports' do
|
432
|
+
@job.send("#{type}ports=", {myport: {}})
|
433
|
+
@job.send("#{type}put", :myport).write 123
|
434
|
+
@job.send("#{type}ports=", {})
|
435
|
+
expect(@job.send("#{type}ports").map(&:name)).not_to include(:myport)
|
436
|
+
expect { @job.send("#{type}put", :myport) }.to raise_error
|
437
|
+
end
|
438
|
+
|
439
|
+
it 'can specify port data' do
|
440
|
+
@job.send("#{type}ports=", {myport: {data: [1,'abc']}})
|
441
|
+
expect(@job.send("#{type}put", :myport).read).to eq 1
|
442
|
+
expect(@job.send("#{type}put", :myport).read).to eq 'abc'
|
443
|
+
end
|
444
|
+
|
445
|
+
it 'can clear port data' do
|
446
|
+
@job.send("#{type}ports=", {myport: {}})
|
447
|
+
@job.send("#{type}put", :myport).write 'data'
|
448
|
+
expect(@job.send("#{type}put", :myport).size).to eq 1
|
449
|
+
@job.send("#{type}ports=", {myport: {data: []}})
|
450
|
+
expect(@job.send("#{type}put", :myport).size).to eq 0
|
451
|
+
end
|
452
|
+
|
453
|
+
it 'overwrites existing data' do
|
454
|
+
@job.send("#{type}ports=", {myport: {}})
|
455
|
+
@job.send("#{type}put", :myport).write 'data'
|
456
|
+
expect(@job.send("#{type}put", :myport).size).to eq 1
|
457
|
+
@job.send("#{type}ports=", {myport: {data: [1,'abc']}})
|
458
|
+
expect(@job.send("#{type}put", :myport).size).to eq 2
|
459
|
+
expect(@job.send("#{type}put", :myport).read).to eq 1
|
460
|
+
expect(@job.send("#{type}put", :myport).read).to eq 'abc'
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'groups logs' do
|
464
|
+
now = Time.now
|
465
|
+
allow(Time).to receive(:now) { now }
|
466
|
+
@job.send("#{type}ports=", {myport: {data: [1,'abc']}})
|
467
|
+
expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => @job.id, 'read' => [], 'write' => [{'job' => @job.id, "#{type}port" => 'myport', 'data' => [1,'abc']}]}]
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
describe '#get' do
|
473
|
+
before do
|
474
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
475
|
+
SideJob.redis.hset 'jobs', @job.id, { queue: 'testq', class: 'TestWorker', field1: 'value1', field2: [1,2], field3: 123 }.to_json
|
476
|
+
@job.reload
|
477
|
+
end
|
478
|
+
|
479
|
+
it 'returns a value from job state using symbol key' do
|
480
|
+
expect(@job.get(:field3)).to eq 123
|
481
|
+
end
|
482
|
+
|
483
|
+
it 'returns a value from job state using string key' do
|
484
|
+
expect(@job.get('field1')).to eq 'value1'
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'returns nil for missing value' do
|
488
|
+
expect(@job.get(:missing)).to be nil
|
489
|
+
end
|
490
|
+
|
491
|
+
it 'can retrieve complex objects in job state' do
|
492
|
+
expect(@job.get(:field2)).to eq [1, 2]
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'caches the state' do
|
496
|
+
expect(@job.get(:field3)).to eq 123
|
497
|
+
SideJob.redis.hmset @job.redis_key, :field3, '789'
|
498
|
+
expect(@job.get(:field3)).to eq 123
|
499
|
+
end
|
500
|
+
|
501
|
+
it 'raises error if job no longer exists and state is not cached' do
|
502
|
+
@job.reload
|
503
|
+
job2 = SideJob.find(@job.id)
|
504
|
+
job2.status = 'terminated'
|
505
|
+
job2.delete
|
506
|
+
expect { @job.get(:key) }.to raise_error
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'does not raise error if job no longer exists but state is cached' do
|
510
|
+
@job.get(:foo)
|
511
|
+
job2 = SideJob.find(@job.id)
|
512
|
+
job2.status = 'terminated'
|
513
|
+
job2.delete
|
514
|
+
expect { @job.get(:key) }.not_to raise_error
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
describe '#reload' do
|
519
|
+
before do
|
520
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
521
|
+
end
|
522
|
+
|
523
|
+
it 'clears the job state cache' do
|
524
|
+
expect(@job.get(:field1)).to be nil
|
525
|
+
SideJob.redis.hset 'jobs', @job.id, {queue: 'testq', class: 'TestWorker', field1: 789}.to_json
|
526
|
+
@job.reload
|
527
|
+
expect(@job.get(:field1)).to eq 789
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
describe '#config' do
|
532
|
+
before do
|
533
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
534
|
+
end
|
535
|
+
|
536
|
+
it 'returns and caches worker configuration' do
|
537
|
+
expect(SideJob::Worker).to receive(:config).with('testq', 'TestWorker').once.and_call_original
|
538
|
+
@job.reload
|
539
|
+
@job.config
|
540
|
+
@job.config # test cached
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|