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
data/lib/sidejob/port.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
1
3
|
module SideJob
|
2
4
|
# Represents an input or output port from a Job
|
3
5
|
class Port
|
4
6
|
# Returned by {#read} and {#default} to indicate no data
|
5
|
-
class None
|
7
|
+
class None; end
|
6
8
|
|
7
9
|
attr_reader :job, :type, :name
|
8
10
|
|
@@ -39,11 +41,10 @@ module SideJob
|
|
39
41
|
size > 0 || default?
|
40
42
|
end
|
41
43
|
|
42
|
-
# Returns the port default value.
|
43
|
-
# @return [
|
44
|
+
# Returns the port default value. See {.decode_data} for details about the return value.
|
45
|
+
# @return [Delegator, None] The default value on the port or {SideJob::Port::None} if none
|
44
46
|
def default
|
45
|
-
|
46
|
-
val ? parse_json(val) : None
|
47
|
+
self.class.decode_data SideJob.redis.hget("#{@job.redis_key}:#{type}ports:default", @name)
|
47
48
|
end
|
48
49
|
|
49
50
|
# Returns if the port has a default value.
|
@@ -58,36 +59,73 @@ module SideJob
|
|
58
59
|
if val == None
|
59
60
|
SideJob.redis.hdel "#{@job.redis_key}:#{type}ports:default", @name
|
60
61
|
else
|
61
|
-
SideJob.redis.hset "#{@job.redis_key}:#{type}ports:default", @name, val
|
62
|
+
SideJob.redis.hset "#{@job.redis_key}:#{type}ports:default", @name, self.class.encode_data(val)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the connected port channels.
|
67
|
+
# @return [Array<String>] List of port channels
|
68
|
+
def channels
|
69
|
+
JSON.parse(SideJob.redis.hget("#{@job.redis_key}:#{type}ports:channels", @name)) rescue []
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set the channels connected to the port.
|
73
|
+
# @param channels [Array<String>] Port channels
|
74
|
+
def channels=(channels)
|
75
|
+
SideJob.redis.multi do |multi|
|
76
|
+
if channels && channels.length > 0
|
77
|
+
multi.hset "#{@job.redis_key}:#{type}ports:channels", @name, channels.to_json
|
78
|
+
else
|
79
|
+
multi.hdel "#{@job.redis_key}:#{type}ports:channels", @name
|
80
|
+
end
|
81
|
+
|
82
|
+
if type == :in
|
83
|
+
channels.each do |chan|
|
84
|
+
multi.sadd "channel:#{chan}", @job.id
|
85
|
+
end
|
86
|
+
end
|
62
87
|
end
|
63
88
|
end
|
64
89
|
|
65
|
-
# Write data to the port. If port in an input port, runs the job.
|
90
|
+
# Write data to the port. If port in an input port, runs the job, otherwise run the parent job.
|
66
91
|
# @param data [Object] JSON encodable data to write to the port
|
67
92
|
def write(data)
|
68
|
-
|
69
|
-
if
|
93
|
+
options = (Thread.current[:sidejob_port_group] || {})[:options] || {}
|
94
|
+
# For {SideJob::Worker#for_inputs}, if this option is set, we set the port default instead of pushing to the port
|
95
|
+
if options[:set_default]
|
70
96
|
self.default = data
|
71
97
|
else
|
72
|
-
SideJob.redis.rpush redis_key, data
|
98
|
+
SideJob.redis.rpush redis_key, self.class.encode_data(data)
|
99
|
+
end
|
100
|
+
|
101
|
+
# run job if inport otherwise run parent
|
102
|
+
@job.run(parent: type != :in) unless options[:notify] == false
|
103
|
+
|
104
|
+
log(write: [ { port: self, data: [data] } ]) unless options[:log] == false
|
105
|
+
|
106
|
+
if type == :out
|
107
|
+
channels.each do |chan|
|
108
|
+
SideJob.publish chan, data
|
109
|
+
end
|
73
110
|
end
|
74
|
-
@job.run(parent: type != :in) # run job if inport otherwise run parent
|
75
|
-
log(write: [ { port: self, data: [data] } ])
|
76
111
|
end
|
77
112
|
|
78
|
-
# Reads the oldest data from the port.
|
79
|
-
#
|
113
|
+
# Reads the oldest data from the port. See {.decode_data} for details about the wrapped return value.
|
114
|
+
# Returns the {#default} if there is no port data and there is a default.
|
115
|
+
# Returns {SideJob::Port::None} if there is no port data and no default.
|
116
|
+
# @return [Delegator, None] First data from port or {SideJob::Port::None} if there is no data and no default
|
80
117
|
def read
|
118
|
+
options = (Thread.current[:sidejob_port_group] || {})[:options] || {}
|
81
119
|
data = SideJob.redis.lpop(redis_key)
|
82
120
|
if data
|
83
|
-
data =
|
121
|
+
data = self.class.decode_data(data)
|
84
122
|
elsif default?
|
85
123
|
data = default
|
86
124
|
else
|
87
125
|
return None
|
88
126
|
end
|
89
127
|
|
90
|
-
log(read: [ { port: self, data: [data] } ])
|
128
|
+
log(read: [ { port: self, data: [data] } ]) unless options[:log] == false || data.sidejob_options['log'] == false
|
91
129
|
|
92
130
|
data
|
93
131
|
end
|
@@ -135,9 +173,18 @@ module SideJob
|
|
135
173
|
end
|
136
174
|
end
|
137
175
|
|
138
|
-
data.map! {|x|
|
176
|
+
data.map! {|x| self.class.decode_data(x)}
|
139
177
|
if data.length > 0
|
140
178
|
log(read: [{ port: self, data: data }], write: ports.map { |port| {port: port, data: data} })
|
179
|
+
|
180
|
+
# Publish to destination channels
|
181
|
+
ports.each do |port|
|
182
|
+
if port.type == :out
|
183
|
+
port.channels.each do |chan|
|
184
|
+
data.each { |x| SideJob.publish chan, x }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
141
188
|
end
|
142
189
|
|
143
190
|
# Run the port job or parent only if something was changed
|
@@ -171,53 +218,109 @@ module SideJob
|
|
171
218
|
redis_key.hash
|
172
219
|
end
|
173
220
|
|
174
|
-
#
|
175
|
-
|
176
|
-
|
221
|
+
# Creates a group for port reads and write.
|
222
|
+
# All events inside the block are combined into a single logged event.
|
223
|
+
# Nested groups are not logged until the outermost group closes.
|
224
|
+
# Can pass additional options that are used for port read/writes inside the group.
|
225
|
+
# The default for all options is nil which means to inherit the current option value or its default.
|
226
|
+
# @param log [Boolean] If false, do not log the writing or reading of the data (default true)
|
227
|
+
# @param notify [Boolean] If false, do not notify (run) the port's job
|
228
|
+
# @param set_default [Boolean] If true, instead of writing to port, set default value
|
229
|
+
def self.group(log: nil, notify: nil, set_default: nil, &block)
|
230
|
+
previous_group = if Thread.current[:sidejob_port_group]
|
231
|
+
Thread.current[:sidejob_port_group].dup
|
232
|
+
else
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
|
177
236
|
Thread.current[:sidejob_port_group] ||= {read: {}, write: {}} # port -> [data]
|
237
|
+
|
238
|
+
options = if previous_group && previous_group[:options]
|
239
|
+
previous_group[:options].dup
|
240
|
+
else
|
241
|
+
{}
|
242
|
+
end
|
243
|
+
options[:log] = log unless log.nil?
|
244
|
+
options[:notify] = notify unless notify.nil?
|
245
|
+
options[:set_default] = set_default unless set_default.nil?
|
246
|
+
Thread.current[:sidejob_port_group][:options] = options
|
247
|
+
|
178
248
|
yield
|
179
249
|
ensure
|
180
|
-
if
|
181
|
-
|
182
|
-
|
250
|
+
if ! previous_group
|
251
|
+
group = Thread.current[:sidejob_port_group]
|
252
|
+
if group && (group[:read].length > 0 || group[:write].length > 0)
|
253
|
+
log_entry = {}
|
254
|
+
%i{read write}.each do |type|
|
255
|
+
log_entry[type] = group[type].map do |port, data|
|
256
|
+
x = {job: port.job.id, data: data}
|
257
|
+
x[:"#{port.type}port"] = port.name
|
258
|
+
x
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
SideJob.log log_entry
|
263
|
+
end
|
183
264
|
end
|
265
|
+
Thread.current[:sidejob_port_group] = previous_group
|
184
266
|
end
|
185
267
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
268
|
+
# Encodes data as JSON with the current SideJob context.
|
269
|
+
# @param data [Object] JSON encodable data
|
270
|
+
# @return [String] The encoded JSON value
|
271
|
+
def self.encode_data(data)
|
272
|
+
encoded = { data: data }
|
273
|
+
encoded[:context] = Thread.current[:sidejob_context] if Thread.current[:sidejob_context]
|
274
|
+
if Thread.current[:sidejob_port_group] && Thread.current[:sidejob_port_group][:options]
|
275
|
+
encoded[:options] = Thread.current[:sidejob_port_group][:options]
|
276
|
+
end
|
277
|
+
encoded.to_json
|
278
|
+
end
|
190
279
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
280
|
+
# Decodes data encoded with {.encode_data}.
|
281
|
+
# The value is returned as a Delegator object that behaves mostly like the underlying value.
|
282
|
+
# Use {Delegator#__getobj__} to get directly at the underlying value.
|
283
|
+
# The returned delegator object has a sidejob_context method that returns the SideJob context
|
284
|
+
# and a sidejob_options method that returns the data options.
|
285
|
+
# @param data [String, nil] Data to decode
|
286
|
+
# @return [Delegator, None] The decoded value or {SideJob::Port::None} if data is nil
|
287
|
+
def self.decode_data(data)
|
288
|
+
if data
|
289
|
+
data = JSON.parse(data)
|
290
|
+
klass = Class.new(SimpleDelegator) do
|
291
|
+
# Allow comparing two SimpleDelegator objects
|
292
|
+
def ==(obj)
|
293
|
+
return self.__getobj__ == obj.__getobj__ if obj.is_a?(SimpleDelegator)
|
294
|
+
super
|
295
|
+
end
|
296
|
+
end
|
297
|
+
klass.send(:define_method, :sidejob_context) do
|
298
|
+
data['context'] || {}
|
197
299
|
end
|
300
|
+
klass.send(:define_method, :sidejob_options) do
|
301
|
+
data['options'] || {}
|
302
|
+
end
|
303
|
+
klass.new(data['data'])
|
304
|
+
else
|
305
|
+
None
|
198
306
|
end
|
199
|
-
|
200
|
-
SideJob.log log_entry
|
201
307
|
end
|
202
308
|
|
309
|
+
private
|
310
|
+
|
203
311
|
def log(data)
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
312
|
+
if Thread.current[:sidejob_port_group]
|
313
|
+
%i{read write}.each do |type|
|
314
|
+
(data[type] || []).each do |x|
|
315
|
+
Thread.current[:sidejob_port_group][type][x[:port]] ||= []
|
316
|
+
Thread.current[:sidejob_port_group][type][x[:port]].concat JSON.parse(x[:data].to_json) # serialize/deserialize to do a deep copy
|
317
|
+
end
|
318
|
+
end
|
319
|
+
else
|
320
|
+
SideJob::Port.group do
|
321
|
+
log(data)
|
209
322
|
end
|
210
323
|
end
|
211
|
-
if ! Thread.current[:sidejob_port_group]
|
212
|
-
self.class._really_log(entry)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
# Wrapper around JSON.parse to also handle primitive types.
|
217
|
-
# @param data [String] Data to parse
|
218
|
-
# @return [Object, nil]
|
219
|
-
def parse_json(data)
|
220
|
-
JSON.parse("[#{data}]")[0]
|
221
324
|
end
|
222
325
|
|
223
326
|
# Check if the port exists, dynamically creating it if it does not exist and a * port exists for the job
|
@@ -5,6 +5,11 @@ module SideJob
|
|
5
5
|
# For simplicity, a job is allowed to be queued multiple times in the Sidekiq queue
|
6
6
|
# Only when it gets pulled out to be run, i.e. here, we decide if we want to actually run it
|
7
7
|
class ServerMiddleware
|
8
|
+
class << self
|
9
|
+
# If true, we do not rescue or log errors and instead propagate errors (useful for testing)
|
10
|
+
attr_accessor :raise_errors
|
11
|
+
end
|
12
|
+
|
8
13
|
# Called by sidekiq as a server middleware to handle running a worker
|
9
14
|
# @param worker [SideJob::Worker]
|
10
15
|
# @param msg [Hash] Sidekiq message format
|
@@ -29,7 +34,7 @@ module SideJob
|
|
29
34
|
if token
|
30
35
|
begin
|
31
36
|
SideJob.redis.del "#{@worker.redis_key}:lock:worker"
|
32
|
-
SideJob.
|
37
|
+
SideJob.context(job: @worker.id) do
|
33
38
|
case @worker.status
|
34
39
|
when 'queued'
|
35
40
|
run_worker { yield }
|
@@ -82,7 +87,11 @@ module SideJob
|
|
82
87
|
@worker.terminate
|
83
88
|
else
|
84
89
|
# normal run
|
85
|
-
|
90
|
+
|
91
|
+
# if ran_at is not set, then this is the first run of the job, so call the startup method if it exists
|
92
|
+
@worker.startup if @worker.respond_to?(:startup) && ! SideJob.redis.exists("#{@worker.redis_key}:ran_at")
|
93
|
+
|
94
|
+
SideJob.redis.set "#{@worker.redis_key}:ran_at", SideJob.timestamp
|
86
95
|
@worker.status = 'running'
|
87
96
|
yield
|
88
97
|
@worker.status = 'completed' if @worker.status == 'running'
|
@@ -101,8 +110,11 @@ module SideJob
|
|
101
110
|
end
|
102
111
|
|
103
112
|
def add_exception(exception)
|
104
|
-
|
105
|
-
|
113
|
+
if SideJob::ServerMiddleware.raise_errors
|
114
|
+
raise exception
|
115
|
+
else
|
116
|
+
SideJob.log exception
|
117
|
+
end
|
106
118
|
end
|
107
119
|
end
|
108
120
|
end
|
data/lib/sidejob/testing.rb
CHANGED
@@ -31,22 +31,15 @@ module SideJob
|
|
31
31
|
def run_inline(errors: true, queue: true, args: [])
|
32
32
|
self.status = 'queued' if queue
|
33
33
|
|
34
|
-
|
34
|
+
worker_info = JSON.parse(SideJob.redis.get("#{redis_key}:worker"))
|
35
|
+
worker = worker_info['class'].constantize.new
|
35
36
|
worker.jid = id
|
36
|
-
SideJob::ServerMiddleware.
|
37
|
+
SideJob::ServerMiddleware.raise_errors = errors
|
38
|
+
SideJob::ServerMiddleware.new.call(worker, {}, worker_info['queue']) do
|
37
39
|
worker.perform(*args)
|
38
40
|
end
|
39
|
-
|
40
|
-
|
41
|
-
SideJob.logs.each do |event|
|
42
|
-
if event['error']
|
43
|
-
exception = RuntimeError.exception(event['error'])
|
44
|
-
exception.set_backtrace(event['backtrace'])
|
45
|
-
raise exception
|
46
|
-
end
|
47
|
-
end
|
48
|
-
raise "Job #{id} failed but cannot find error log"
|
49
|
-
end
|
41
|
+
ensure
|
42
|
+
SideJob::ServerMiddleware.raise_errors = false
|
50
43
|
end
|
51
44
|
end
|
52
45
|
end
|
data/lib/sidejob/version.rb
CHANGED
data/lib/sidejob/worker.rb
CHANGED
@@ -16,6 +16,9 @@ module SideJob
|
|
16
16
|
multi.del "workers:#{queue}"
|
17
17
|
multi.hmset "workers:#{queue}", @registry.map {|key, val| [key, val.to_json]}.flatten(1) if @registry.size > 0
|
18
18
|
end
|
19
|
+
SideJob::Port.group(log: false) do
|
20
|
+
SideJob.publish "/sidejob/workers/#{queue}", @registry
|
21
|
+
end
|
19
22
|
end
|
20
23
|
|
21
24
|
# Returns the configuration registered for a worker.
|
@@ -83,7 +86,7 @@ module SideJob
|
|
83
86
|
return unless inputs.length > 0
|
84
87
|
ports = inputs.map {|name| input(name)}
|
85
88
|
loop do
|
86
|
-
SideJob::Port.
|
89
|
+
SideJob::Port.group do
|
87
90
|
info = ports.map {|port| [ port.size > 0, port.default? ] }
|
88
91
|
|
89
92
|
return unless info.any? {|x| x[0] || x[1]} # Nothing to do if there's no data to read
|
@@ -97,11 +100,8 @@ module SideJob
|
|
97
100
|
last_default = get(:for_inputs) || []
|
98
101
|
return unless defaults != last_default
|
99
102
|
set({for_inputs: defaults})
|
100
|
-
|
101
|
-
Thread.current[:sidejob_port_write_default] = true
|
103
|
+
SideJob::Port.group(set_default: true) do
|
102
104
|
yield *defaults
|
103
|
-
ensure
|
104
|
-
Thread.current[:sidejob_port_write_default] = nil
|
105
105
|
end
|
106
106
|
return
|
107
107
|
else
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Channels'do
|
4
|
+
it 'Global pubsub via channels works correctly' do
|
5
|
+
job1 = SideJob.queue('testq', 'TestSum')
|
6
|
+
job2 = SideJob.queue('testq', 'TestSum')
|
7
|
+
job3 = SideJob.queue('testq', 'TestSum')
|
8
|
+
job4 = SideJob.queue('testq', 'TestSum')
|
9
|
+
|
10
|
+
job1.input(:ready).channels = [ '/test/ready' ]
|
11
|
+
job1.input(:in).channels = ['/test/in']
|
12
|
+
job1.output(:sum).channels = ['/test/chan1']
|
13
|
+
|
14
|
+
job2.input(:ready).channels = [ '/test/ready' ]
|
15
|
+
job2.input(:in).channels = ['/test/chan1']
|
16
|
+
job2.output(:sum).channels = ['/test/chan2']
|
17
|
+
|
18
|
+
job3.input(:ready).channels = [ '/test/ready' ]
|
19
|
+
job3.input(:in).channels = ['/test/chan1']
|
20
|
+
job3.output(:sum).channels = ['/test/chan2']
|
21
|
+
|
22
|
+
job4.input(:ready).channels = [ '/test/ready' ]
|
23
|
+
job4.input(:in).channels = ['/test/chan2']
|
24
|
+
|
25
|
+
[1,2,4].each {|x| SideJob.publish '/test/in', x}
|
26
|
+
SideJob.publish '/test/ready', true
|
27
|
+
|
28
|
+
job1.run_inline
|
29
|
+
job2.run_inline
|
30
|
+
job3.run_inline
|
31
|
+
job4.run_inline
|
32
|
+
|
33
|
+
expect(job4.status).to eq 'completed'
|
34
|
+
expect(job4.output(:sum).read).to eq(14)
|
35
|
+
end
|
36
|
+
end
|
data/spec/sidejob/job_spec.rb
CHANGED
@@ -65,11 +65,97 @@ describe SideJob::Job do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
describe '#status=' do
|
68
|
-
|
68
|
+
before do
|
69
69
|
@job = SideJob.queue('testq', 'TestWorker')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'sets status' do
|
70
73
|
@job.status = 'newstatus'
|
71
74
|
expect(@job.status).to eq 'newstatus'
|
72
75
|
end
|
76
|
+
|
77
|
+
it 'publishes a message with status change' do
|
78
|
+
expect(SideJob).to receive(:publish).with("/sidejob/job/#{@job.id}", {status: 'newstatus'})
|
79
|
+
@job.status = 'newstatus'
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'does not publish a message if status does not change' do
|
83
|
+
@job.status = 'newstatus'
|
84
|
+
expect(SideJob).not_to receive(:publish)
|
85
|
+
@job.status = 'newstatus'
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'does not publish a message if worker has status_publish: false' do
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#aliases' do
|
94
|
+
before do
|
95
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'returns empty array if no aliases' do
|
99
|
+
expect(@job.aliases).to eq []
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns all aliases' do
|
103
|
+
@job.add_alias('abc')
|
104
|
+
@job.add_alias('xyz')
|
105
|
+
expect(@job.aliases).to match_array ['abc', 'xyz']
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#add_alias' do
|
110
|
+
before do
|
111
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
112
|
+
@job.add_alias('abc')
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'adds an alias' do
|
116
|
+
expect(SideJob.redis.smembers("#{@job.redis_key}:aliases")).to eq ['abc']
|
117
|
+
expect(SideJob.redis.hgetall('jobs:aliases')).to eq({'abc' => @job.id.to_s})
|
118
|
+
@job.add_alias('xyz')
|
119
|
+
expect(SideJob.redis.smembers("#{@job.redis_key}:aliases")).to match_array ['abc', 'xyz']
|
120
|
+
expect(SideJob.redis.hgetall('jobs:aliases')).to eq({'abc' => @job.id.to_s, 'xyz' => @job.id.to_s})
|
121
|
+
expect(@job.aliases).to match_array ['abc', 'xyz']
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'does nothing if name is already alias' do
|
125
|
+
@job.add_alias('abc')
|
126
|
+
expect(SideJob.redis.smembers("#{@job.redis_key}:aliases")).to eq ['abc']
|
127
|
+
expect(SideJob.redis.hgetall('jobs:aliases')).to eq({'abc' => @job.id.to_s})
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'raises error if name is an alias for another job' do
|
131
|
+
@job2 = SideJob.queue('testq', 'TestWorker')
|
132
|
+
expect { @job2.alias_as('abc') }.to raise_error
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'raises error if name is invalid' do
|
136
|
+
expect { @job.add_alias('1234') }.to raise_error
|
137
|
+
expect { @job.add_alias('!') }.to raise_error
|
138
|
+
expect { @job.add_alias('') }.to raise_error
|
139
|
+
expect { @job.add_alias(nil) }.to raise_error
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '#remove_alias' do
|
144
|
+
before do
|
145
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
146
|
+
@job.add_alias('abc')
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'removes an existing alias' do
|
150
|
+
@job.add_alias('xyz')
|
151
|
+
expect(@job.aliases).to match_array ['abc', 'xyz']
|
152
|
+
@job.remove_alias('xyz')
|
153
|
+
expect(@job.aliases).to match_array ['abc']
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'throws an error if alias does not exist' do
|
157
|
+
expect { @job.remove_alias('xyz') }.to raise_error
|
158
|
+
end
|
73
159
|
end
|
74
160
|
|
75
161
|
describe '#run' do
|
@@ -133,7 +219,7 @@ describe SideJob::Job do
|
|
133
219
|
end
|
134
220
|
|
135
221
|
it 'throws error and immediately sets status to terminated if job class is unregistered' do
|
136
|
-
SideJob.redis.del "workers:#{@job.
|
222
|
+
SideJob.redis.del "workers:#{@job.info[:queue]}"
|
137
223
|
expect { @job.run }.to raise_error
|
138
224
|
expect(@job.status).to eq 'terminated'
|
139
225
|
end
|
@@ -258,7 +344,7 @@ describe SideJob::Job do
|
|
258
344
|
|
259
345
|
it 'queues with by string set to self' do
|
260
346
|
child = @job.queue('testq', 'TestWorker', name: 'child')
|
261
|
-
expect(child.
|
347
|
+
expect(child.info[:created_by]).to eq "job:#{@job.id}"
|
262
348
|
end
|
263
349
|
end
|
264
350
|
|
@@ -371,8 +457,15 @@ describe SideJob::Job do
|
|
371
457
|
expect(@job.exists?).to be false
|
372
458
|
end
|
373
459
|
|
460
|
+
it 'publishes a message when deleted' do
|
461
|
+
@job.status = 'terminated'
|
462
|
+
expect(@job).to receive(:publish).with({deleted: true})
|
463
|
+
expect(@job.delete).to be true
|
464
|
+
end
|
465
|
+
|
374
466
|
it 'recursively deletes jobs' do
|
375
467
|
child = SideJob.queue('testq', 'TestWorker', parent: @job, name: 'child')
|
468
|
+
child.add_alias('myjob')
|
376
469
|
expect(@job.status).to eq('queued')
|
377
470
|
expect(child.status).to eq('queued')
|
378
471
|
expect(SideJob.redis {|redis| redis.keys('job:*').length}).to be > 0
|
@@ -382,6 +475,7 @@ describe SideJob::Job do
|
|
382
475
|
expect(SideJob.redis {|redis| redis.keys('job:*').length}).to be(0)
|
383
476
|
expect(@job.exists?).to be false
|
384
477
|
expect(child.exists?).to be false
|
478
|
+
expect(SideJob.find('myjob')).to be nil
|
385
479
|
end
|
386
480
|
|
387
481
|
it 'deletes data on input and output ports' do
|
@@ -401,6 +495,15 @@ describe SideJob::Job do
|
|
401
495
|
child.delete
|
402
496
|
expect(@job.children.size).to eq 0
|
403
497
|
end
|
498
|
+
|
499
|
+
it 'removes any aliases' do
|
500
|
+
@job.add_alias 'job'
|
501
|
+
@job.status = 'terminated'
|
502
|
+
expect(SideJob.find('job')).to eq @job
|
503
|
+
@job.delete
|
504
|
+
expect(SideJob.find('job')).to be nil
|
505
|
+
expect(SideJob.redis.hget('jobs:aliases', 'job')).to be nil
|
506
|
+
end
|
404
507
|
end
|
405
508
|
|
406
509
|
# Tests are identical for input and output port methods
|
@@ -471,6 +574,15 @@ describe SideJob::Job do
|
|
471
574
|
expect(@job.send("#{type}put", :myport).read).to eq 'new'
|
472
575
|
end
|
473
576
|
|
577
|
+
it 'sets port channels' do
|
578
|
+
@channels = ['abc', 'xyz']
|
579
|
+
@job.send("#{type}ports=", {myport: {channels: @channels}})
|
580
|
+
expect(@job.send("#{type}put", :myport).channels).to match_array(@channels)
|
581
|
+
@channels.each do |chan|
|
582
|
+
expect(SideJob.redis.smembers("channel:#{chan}")).to eq [@job.id.to_s]
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
474
586
|
it 'deletes no longer used ports' do
|
475
587
|
@job.send("#{type}ports=", {myport: {}})
|
476
588
|
@job.send("#{type}put", :myport).write 123
|
@@ -481,16 +593,28 @@ describe SideJob::Job do
|
|
481
593
|
end
|
482
594
|
end
|
483
595
|
|
484
|
-
describe '#
|
596
|
+
describe '#info' do
|
485
597
|
before do
|
486
598
|
now = Time.now
|
487
599
|
allow(Time).to receive(:now) { now }
|
600
|
+
@job = SideJob.queue('testq', 'TestWorker', args: [123], by: 'me')
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'returns all basic job info' do
|
604
|
+
expect(@job.info).to eq({queue: 'testq', class: 'TestWorker', args: [123],
|
605
|
+
created_by: 'me', created_at: SideJob.timestamp, ran_at: nil})
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
describe '#state' do
|
610
|
+
before do
|
488
611
|
@job = SideJob.queue('testq', 'TestWorker')
|
489
612
|
end
|
490
613
|
|
491
|
-
it 'returns job state
|
614
|
+
it 'returns all job state' do
|
492
615
|
@job.set({abc: 123})
|
493
|
-
|
616
|
+
@job.set({xyz: 456})
|
617
|
+
expect(@job.state).to eq({'abc' => 123, 'xyz' => 456})
|
494
618
|
end
|
495
619
|
end
|
496
620
|
|
@@ -518,7 +642,7 @@ describe SideJob::Job do
|
|
518
642
|
|
519
643
|
it 'always returns the latest value' do
|
520
644
|
expect(@job.get(:field3)).to eq 123
|
521
|
-
SideJob.
|
645
|
+
SideJob.find(@job.id).set(field3: 789)
|
522
646
|
expect(@job.get(:field3)).to eq 789
|
523
647
|
end
|
524
648
|
end
|
@@ -543,7 +667,7 @@ describe SideJob::Job do
|
|
543
667
|
3.times do |i|
|
544
668
|
@job.set key: [i]
|
545
669
|
expect(@job.get(:key)).to eq [i]
|
546
|
-
expect(JSON.parse(SideJob.redis.hget(@job.redis_key, 'key'))).to eq [i]
|
670
|
+
expect(JSON.parse(SideJob.redis.hget("#{@job.redis_key}:state", 'key'))).to eq [i]
|
547
671
|
end
|
548
672
|
end
|
549
673
|
|
@@ -622,4 +746,13 @@ describe SideJob::Job do
|
|
622
746
|
expect(SideJob.redis.exists("#{@job.redis_key}:lock")).to be true
|
623
747
|
end
|
624
748
|
end
|
749
|
+
|
750
|
+
describe '#publish' do
|
751
|
+
it 'calls SideJob.publish' do
|
752
|
+
@job = SideJob.queue('testq', 'TestWorker')
|
753
|
+
message = {abc: 123}
|
754
|
+
expect(SideJob).to receive(:publish).with("/sidejob/job/#{@job.id}", message)
|
755
|
+
@job.publish(message)
|
756
|
+
end
|
757
|
+
end
|
625
758
|
end
|