sidejob 4.0.2 → 4.1.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 +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
|