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.
@@ -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
@@ -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.logs(clear: true)
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.get(:created_at)).to eq(SideJob.timestamp)
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.get(:args)).to eq [1,2]
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.get(:created_by)).to eq 'test:sidejob'
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 'adds a timestamp to log entries' do
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
- describe '.logs' do
149
- before do
152
+ it 'can log exceptions' do
150
153
  now = Time.now
151
154
  allow(Time).to receive(:now) { now }
152
- SideJob.log({abc: 123})
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 'returns and clears all logs' do
156
- expect(SideJob.logs).to eq([{'abc' => 123, 'timestamp' => SideJob.timestamp}])
157
- SideJob.log({xyz: 456})
158
- expect(SideJob.logs).to eq([{'xyz' => 456, 'timestamp' => SideJob.timestamp}])
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 'returns and leaves logs' do
162
- expect(SideJob.logs(clear: false)).to eq([{'abc' => 123, 'timestamp' => SideJob.timestamp}])
163
- SideJob.log({xyz: 456})
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 '.log_context' do
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 metadata to logs within the group' do
176
- SideJob.log_context(data1: 1, data2: 2) do
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 metadata to logs outside of the group' do
183
- SideJob.log_context(data1: 1, data2: 2) {}
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.log_context(data1: 1) do
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.log_context(data2: 2) do
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
- expect(SideJob.logs).to eq([{'data1' => 1, 'timestamp' => SideJob.timestamp, 'x' => 1},
199
- {'data1' => 1, 'data2' => 2, 'timestamp' => SideJob.timestamp, 'x' => 2},
200
- {'data1' => 1, 'timestamp' => SideJob.timestamp, 'x' => 3},
201
- {'timestamp' => SideJob.timestamp, 'x' => 4},
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.2)
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.2)
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 some limited info for now
15
+ # only provide version for now
16
16
  get '/' do
17
- { version: '1.0' }.to_json
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
- { entries: port.entries }
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.log_context(params['log_context'] || {}) do
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.2
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-05-08 00:00:00.000000000 Z
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