sidejob 4.0.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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