perfectqueue 0.8.44.1 → 0.8.45

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.
@@ -15,21 +15,21 @@ describe Backend::RDBCompatBackend do
15
15
  it 'backward compatibility 1' do
16
16
  backend.db["INSERT INTO test_tasks (id, timeout, data, created_at, resource) VALUES (?, ?, ?, ?, ?)", "merge_type.1339801200", 1339801201, {'url'=>nil}.to_json, 1339801201, "1"].insert
17
17
  ts = backend.acquire(60, 1, {:now=>1339801203})
18
- ts.should_not == nil
18
+ expect(ts).not_to eq(nil)
19
19
  t = ts[0]
20
- t.data.should == {'url'=>nil}
21
- t.type.should == 'merge_type'
22
- t.key.should == 'merge_type.1339801200'
20
+ expect(t.data).to eq({'url'=>nil})
21
+ expect(t.type).to eq('merge_type')
22
+ expect(t.key).to eq('merge_type.1339801200')
23
23
  end
24
24
 
25
25
  it 'backward compatibility 2' do
26
26
  backend.db["INSERT INTO test_tasks (id, timeout, data, created_at, resource) VALUES (?, ?, ?, ?, ?)", "query.379474", 1339801201, {'query_id'=>32}.to_json, 1339801201, nil].insert
27
27
  ts = backend.acquire(60, 1, {:now=>1339801203})
28
- ts.should_not == nil
28
+ expect(ts).not_to eq(nil)
29
29
  t = ts[0]
30
- t.data.should == {'query_id'=>32}
31
- t.type.should == 'query'
32
- t.key.should == 'query.379474'
30
+ expect(t.data).to eq({'query_id'=>32})
31
+ expect(t.type).to eq('query')
32
+ expect(t.key).to eq('query.379474')
33
33
  end
34
34
 
35
35
  it 'resource limit' do
@@ -41,25 +41,25 @@ describe Backend::RDBCompatBackend do
41
41
  queue.submit("test_5", 'user02', {}, :now=>time, :user=>'u2', :max_running=>2)
42
42
 
43
43
  task1 = queue.poll(:now=>time+10)
44
- task1.should_not == nil
45
- task1.type.should == 'user01'
44
+ expect(task1).not_to eq(nil)
45
+ expect(task1.type).to eq('user01')
46
46
 
47
47
  task2 = queue.poll(:now=>time+10)
48
- task2.should_not == nil
49
- task2.type.should == 'user02'
48
+ expect(task2).not_to eq(nil)
49
+ expect(task2.type).to eq('user02')
50
50
 
51
51
  task3 = queue.poll(:now=>time+10)
52
- task3.should_not == nil
53
- task3.type.should == 'user01'
52
+ expect(task3).not_to eq(nil)
53
+ expect(task3.type).to eq('user01')
54
54
 
55
55
  task4 = queue.poll(:now=>time+10)
56
- task4.should == nil
56
+ expect(task4).to eq(nil)
57
57
 
58
58
  task1.finish!
59
59
 
60
60
  task5 = queue.poll(:now=>time+10)
61
- task5.should_not == nil
62
- task5.type.should == 'user01'
61
+ expect(task5).not_to eq(nil)
62
+ expect(task5.type).to eq('user01')
63
63
  end
64
64
 
65
65
  it 'gzip data compression' do
@@ -67,8 +67,470 @@ describe Backend::RDBCompatBackend do
67
67
  queue.submit("test", 'user01', {'data'=>'test'}, :now=>time, :user=>'u1', :max_running=>2, :compression=>'gzip')
68
68
 
69
69
  task1 = queue.poll(:now=>time+10)
70
- task1.should_not == nil
71
- task1.data.should == {'data'=>'test'}
70
+ expect(task1).not_to eq(nil)
71
+ expect(task1.data).to eq({'data'=>'test'})
72
72
  end
73
73
  end
74
74
 
75
+ describe Backend::RDBCompatBackend do
76
+ let (:now){ Time.now.to_i }
77
+ let (:client){ double('client') }
78
+ let (:table){ 'test_queues' }
79
+ let (:config){ {url: 'mysql://root:@localhost/perfectqueue_test', table: table} }
80
+ let (:db) do
81
+ d = Backend::RDBCompatBackend.new(client, config)
82
+ s = d.db
83
+ s.tables.each{|t| s.drop_table(t) }
84
+ d.init_database({})
85
+ d
86
+ end
87
+
88
+ context '.new' do
89
+ let (:client){ double('client') }
90
+ let (:table){ double('table') }
91
+ it 'raises error unless url' do
92
+ expect{Backend::RDBCompatBackend.new(client, {})}.to raise_error(ConfigError)
93
+ end
94
+ it 'raises error unless table' do
95
+ expect{Backend::RDBCompatBackend.new(client, {url: ''})}.to raise_error(ConfigError)
96
+ end
97
+ it 'supports mysql' do
98
+ expect(Backend::RDBCompatBackend.new(client, config)).to be_an_instance_of(Backend::RDBCompatBackend)
99
+ expect(db.instance_variable_get(:@sql)).to include('max_running')
100
+ end
101
+ it 'doesn\'t support postgres' do
102
+ config = {url: 'postgres://localhost', table: table}
103
+ expect{Backend::RDBCompatBackend.new(client, config)}.to raise_error(ConfigError)
104
+ end
105
+ it 'with use_connection_pooling' do
106
+ config = {url: 'mysql://root:@localhost/perfectqueue_test', table: table, use_connection_pooling: true}
107
+ db = Backend::RDBCompatBackend.new(client, config)
108
+ expect(db.instance_variable_get(:@use_connection_pooling)).to eq true
109
+ end
110
+ it 'disable_resource_limit' do
111
+ config = {url: 'mysql://root:@localhost/perfectqueue_test', table: table, disable_resource_limit: true}
112
+ db = Backend::RDBCompatBackend.new(client, config)
113
+ expect(db.instance_variable_get(:@sql)).not_to include('max_running')
114
+ end
115
+ end
116
+
117
+ context '#init_database' do
118
+ let (:db) do
119
+ d = Backend::RDBCompatBackend.new(client, config)
120
+ s = d.db
121
+ s.tables.each{|t| s.drop_table(t) }
122
+ d
123
+ end
124
+ it 'creates the table' do
125
+ db.init_database({})
126
+ end
127
+ it 'raises DatabaseError if already exists' do
128
+ expect(STDERR).to receive(:puts)
129
+ db.init_database({})
130
+ expect{db.init_database({})}.to raise_error(Sequel::DatabaseError)
131
+ end
132
+ it 'drops the table if force: true' do
133
+ db.init_database({})
134
+ db.init_database({force: true})
135
+ end
136
+ end
137
+
138
+ context '#get_task_metadata' do
139
+ before do
140
+ db.submit('key', 'test', nil, {})
141
+ end
142
+ it 'fetches a metadata' do
143
+ expect(db.get_task_metadata('key', {})).to be_an_instance_of(TaskMetadata)
144
+ end
145
+ it 'raises error if non exist key' do
146
+ expect(STDERR).to receive(:puts)
147
+ expect{db.get_task_metadata('nonexistent', {})}.to raise_error(NotFoundError)
148
+ end
149
+ end
150
+
151
+ context '#preempt' do
152
+ subject { db.preempt(nil, nil, nil) }
153
+ it { expect{ subject }.to raise_error(NotSupportedError) }
154
+ end
155
+
156
+ context '#list' do
157
+ before do
158
+ db.submit('key', 'test', nil, {})
159
+ end
160
+ it 'lists a metadata' do
161
+ db.list({}) do |x|
162
+ expect(x).to be_an_instance_of(TaskWithMetadata)
163
+ expect(x.key).to eq('key')
164
+ end
165
+ end
166
+ end
167
+
168
+ context '#submit' do
169
+ it 'adds task' do
170
+ db.submit('key', 'test', nil, {})
171
+ end
172
+ it 'gzip' do
173
+ db.submit('key', 'test', nil, {compression: 'gzip'})
174
+ end
175
+ end
176
+
177
+ context '#acquire' do
178
+ let (:key){ 'key' }
179
+ let (:task_token){ Backend::RDBCompatBackend::Token.new(key) }
180
+ let (:alive_time){ 42 }
181
+ let (:max_acquire){ 42 }
182
+ context 'no tasks' do
183
+ it 'returns nil' do
184
+ expect(db.acquire(alive_time, max_acquire, {})).to be_nil
185
+ end
186
+ end
187
+ context 'some tasks' do
188
+ before do
189
+ db.submit(key, 'test', nil, {})
190
+ end
191
+ it 'returns a task' do
192
+ ary = db.acquire(alive_time, max_acquire, {})
193
+ expect(ary).to be_an_instance_of(Array)
194
+ expect(ary.size).to eq(1)
195
+ expect(ary[0]).to be_an_instance_of(AcquiredTask)
196
+ end
197
+ end
198
+ context 'some tasks' do
199
+ let :t0 do now - 100 end
200
+ before do
201
+ db.submit('key1', 'test1', nil, {now: t0})
202
+ db.submit('key2', 'test2', nil, {now: t0})
203
+ db.submit('key3', 'test3', nil, {now: t0})
204
+ end
205
+ it 'returns 3 tasks' do
206
+ now0 = Time.at(t0)
207
+ expect(now0).to receive(:to_time).exactly(3).times.and_call_original
208
+ db.list({}) do |task|
209
+ expect(task.timeout).to eq now0.to_time
210
+ end
211
+ ary = db.acquire(alive_time, max_acquire, {})
212
+ expect(ary).to be_an_instance_of(Array)
213
+ expect(ary.size).to eq(3)
214
+ expect(ary[0]).to be_an_instance_of(AcquiredTask)
215
+ expect(ary[1]).to be_an_instance_of(AcquiredTask)
216
+ expect(ary[2]).to be_an_instance_of(AcquiredTask)
217
+
218
+ now1 = Time.at(now + alive_time)
219
+ expect(now1).to receive(:to_time).exactly(3).times.and_call_original
220
+ db.list({}){|task| expect(task.timeout).to eq now1.to_time }
221
+ end
222
+ it 'returns 2 tasks' do
223
+ db.instance_variable_set(:@prefetch_break_types, 'test2')
224
+ ary = db.acquire(alive_time, max_acquire, {})
225
+ expect(ary).to be_an_instance_of(Array)
226
+ expect(ary.size).to eq(2)
227
+ expect(ary[0]).to be_an_instance_of(AcquiredTask)
228
+ expect(ary[1]).to be_an_instance_of(AcquiredTask)
229
+ end
230
+ end
231
+ end
232
+
233
+ context '#cancel_request' do
234
+ let (:key){ 'key' }
235
+ let (:task_token){ Backend::RDBCompatBackend::Token.new(key) }
236
+ let (:retention_time) { 42 }
237
+ let (:delete_timeout){ now + retention_time }
238
+ let (:options){ {now: now} }
239
+ context 'have a task' do
240
+ before do
241
+ db.submit(key, 'test', nil, {})
242
+ end
243
+ it 'returns nil' do
244
+ expect(db.cancel_request(key, options)).to be_nil
245
+ row = db.db.fetch("SELECT created_at FROM `#{table}` WHERE id=? LIMIT 1", key).first
246
+ expect(row[:created_at]).to eq 0
247
+ end
248
+ end
249
+ context 'already finished' do
250
+ before do
251
+ expect(STDERR).to receive(:puts)
252
+ db.submit(key, 'test', nil, {})
253
+ db.finish(task_token, retention_time, options)
254
+ end
255
+ it 'raises AlreadyFinishedError' do
256
+ expect{db.cancel_request(key, options)}.to raise_error(AlreadyFinishedError)
257
+ end
258
+ end
259
+ end
260
+
261
+ context '#force_finish' do
262
+ let (:key){ double('key') }
263
+ let (:token){ double('token') }
264
+ let (:retention_time){ double('retention_time') }
265
+ let (:options){ double('options') }
266
+ let (:ret){ double('ret') }
267
+ before { expect(Backend::RDBCompatBackend::Token).to receive(:new).with(key).and_return(token) }
268
+ it 'calls #finish' do
269
+ expect(db).to receive(:finish).with(token, retention_time, options).exactly(:once).and_return(ret)
270
+ expect(db.force_finish(key, retention_time, options)).to eq ret
271
+ end
272
+ end
273
+
274
+ context '#finish' do
275
+ let (:key){ 'key' }
276
+ let (:task_token){ Backend::RDBCompatBackend::Token.new(key) }
277
+ let (:retention_time) { 42 }
278
+ let (:delete_timeout){ now + retention_time }
279
+ let (:options){ {now: now} }
280
+ context 'have the task' do
281
+ before do
282
+ db.submit(key, 'test', nil, {})
283
+ expect(db.db).to receive(:[]).with(kind_of(String), delete_timeout, key).and_call_original
284
+ end
285
+ it 'returns nil' do
286
+ expect(db.finish(task_token, retention_time, options)).to be_nil
287
+ row = db.db.fetch("SELECT created_at FROM `#{table}` WHERE id=? LIMIT 1", key).first
288
+ expect(row[:created_at]).to be_nil
289
+ end
290
+ end
291
+ context 'already finished' do
292
+ it 'raises IdempotentAlreadyFinishedError' do
293
+ expect(STDERR).to receive(:puts)
294
+ expect{db.finish(task_token, retention_time, options)}.to raise_error(IdempotentAlreadyFinishedError)
295
+ end
296
+ end
297
+ end
298
+
299
+ context '#heartbeat' do
300
+ let (:key){ 'key' }
301
+ let (:task_token){ Backend::RDBCompatBackend::Token.new(key) }
302
+ let (:retention_time) { 42 }
303
+ let (:delete_timeout){ now + retention_time }
304
+ let (:options){ {now: now} }
305
+ before{ allow(STDERR).to receive(:puts) }
306
+ context 'have a queueuled task' do
307
+ before do
308
+ db.submit(key, 'test', nil, {})
309
+ end
310
+ it 'returns nil if next_run_time is not updated' do
311
+ expect(db.heartbeat(task_token, 0, {now: now})).to be_nil
312
+ end
313
+ it 'returns nil even if next_run_time is updated' do
314
+ expect(db.heartbeat(task_token, 1, {})).to be_nil
315
+ end
316
+ end
317
+ context 'no tasks' do
318
+ it 'raises PreemptedError' do
319
+ expect{db.heartbeat(task_token, 0, {})}.to raise_error(PreemptedError)
320
+ end
321
+ end
322
+ context 'finished task' do
323
+ before do
324
+ db.submit(key, 'test', nil, {})
325
+ db.finish(task_token, retention_time, options)
326
+ end
327
+ it 'raises PreemptedError' do
328
+ expect{db.heartbeat(task_token, 0, {})}.to raise_error(PreemptedError)
329
+ end
330
+ end
331
+ context 'canceled task' do
332
+ before do
333
+ db.submit(key, 'test', nil, {})
334
+ db.cancel_request(key, options)
335
+ end
336
+ it 'returns nil' do
337
+ expect( db.heartbeat(task_token, 0, {}) ).to be_nil
338
+ end
339
+ end
340
+ end
341
+
342
+ context '#connect' do
343
+ context 'normal' do
344
+ it 'returns now' do
345
+ expect(db.__send__(:connect){ }).to eq(now)
346
+ end
347
+ end
348
+ context 'error' do
349
+ it 'returns block result' do
350
+ expect(RuntimeError).to receive(:new).exactly(Backend::RDBCompatBackend::MAX_RETRY).and_call_original
351
+ allow(STDERR).to receive(:puts)
352
+ allow(db).to receive(:sleep)
353
+ expect do
354
+ db.__send__(:connect) do
355
+ raise RuntimeError.new('try restarting transaction')
356
+ end
357
+ end.to raise_error(RuntimeError)
358
+ end
359
+ end
360
+ end
361
+
362
+ context '#create_attributes' do
363
+ let (:data){ Hash.new }
364
+ let (:row) do
365
+ r = double('row')
366
+ allow(r).to receive(:[]){|k| data[k] }
367
+ r
368
+ end
369
+ it 'returns a hash consisting the data of the row' do
370
+ data[:timezone] = timezone = double('timezone')
371
+ data[:delay] = delay = double('delay')
372
+ data[:cron] = cron = double('cron')
373
+ data[:next_time] = next_time = double('next_time')
374
+ data[:timeout] = timeout = double('timeout')
375
+ data[:data] = '{"type":"foo.bar","a":"b"}'
376
+ data[:id] = 'hoge'
377
+ expect(db.__send__(:create_attributes, now, row)).to eq(
378
+ status: :finished,
379
+ created_at: nil,
380
+ data: {"a"=>"b"},
381
+ user: nil,
382
+ timeout: timeout,
383
+ max_running: nil,
384
+ type: 'foo.bar',
385
+ message: nil,
386
+ node: nil,
387
+ compression: nil,
388
+ )
389
+ end
390
+ it 'returns {} if data\'s JSON is broken' do
391
+ data[:data] = '}{'
392
+ data[:id] = 'foo.bar.baz'
393
+ expect(db.__send__(:create_attributes, now, row)).to eq(
394
+ status: :finished,
395
+ created_at: nil,
396
+ data: {},
397
+ user: nil,
398
+ timeout: nil,
399
+ max_running: nil,
400
+ type: 'foo',
401
+ message: nil,
402
+ node: nil,
403
+ compression: nil,
404
+ )
405
+ end
406
+ it 'uses id[/\A[^.]*/] if type is empty string' do
407
+ data[:data] = '{"type":""}'
408
+ data[:id] = 'foo.bar.baz'
409
+ expect(db.__send__(:create_attributes, now, row)).to eq(
410
+ status: :finished,
411
+ created_at: nil,
412
+ data: {},
413
+ user: nil,
414
+ timeout: nil,
415
+ max_running: nil,
416
+ type: 'foo',
417
+ message: nil,
418
+ node: nil,
419
+ compression: nil,
420
+ )
421
+ end
422
+ it 'uses id[/\A[^.]*/] if type is nil' do
423
+ data[:id] = 'foo.bar.baz'
424
+ expect(db.__send__(:create_attributes, now, row)).to eq(
425
+ status: :finished,
426
+ created_at: nil,
427
+ data: {},
428
+ user: nil,
429
+ timeout: nil,
430
+ max_running: nil,
431
+ type: 'foo',
432
+ message: nil,
433
+ node: nil,
434
+ compression: nil,
435
+ )
436
+ end
437
+ end
438
+
439
+ context '#connect_locked' do
440
+ let (:ret){ double('ret') }
441
+ before do
442
+ end
443
+ it 'ensures to unlock on error with use_connection_pooling' do
444
+ #expect(STDERR).to receive(:puts)
445
+ config = {url: 'mysql://root:@localhost/perfectqueue_test', table: table, use_connection_pooling: true}
446
+ db1 = Backend::RDBCompatBackend.new(client, config)
447
+ #expect{ db.__send__(:connect_locked){ raise } }.to raise_error(RuntimeError)
448
+ db1.__send__(:connect_locked){ ret }
449
+ stub_const('PerfectQueue::Backend::RDBCompatBackend::LOCK_WAIT_TIMEOUT', 5)
450
+ db2 = Backend::RDBCompatBackend.new(client, config)
451
+ Timeout.timeout(3) do
452
+ expect( db2.__send__(:connect_locked){ ret }).to eq ret
453
+ end
454
+ end
455
+ end
456
+
457
+ context '#create_attributes' do
458
+ let (:data){ {data: '{"type":"foo"}'} }
459
+ let (:timeout){ double('timeout') }
460
+ let (:row) do
461
+ r = double('row')
462
+ allow(r).to receive(:[]){|k| data[k] }
463
+ r
464
+ end
465
+ context 'created_at is nil' do
466
+ it 'returns a hash consisting the data of the row' do
467
+ data[:resource] = user = double('user')
468
+ data[:max_running] = max_running = double('max_running')
469
+ data[:cron] = cron = double('cron')
470
+ data[:next_time] = next_time = double('next_time')
471
+ data[:timeout] = timeout
472
+ data[:data] = '{"type":"foo.bar","a":"b"}'
473
+ data[:id] = 'hoge'
474
+ expect(db.__send__(:create_attributes, now, row)).to eq(
475
+ status: TaskStatus::FINISHED,
476
+ created_at: nil,
477
+ data: {"a"=>"b"},
478
+ type: 'foo.bar',
479
+ user: user,
480
+ timeout: timeout,
481
+ max_running: max_running,
482
+ message: nil,
483
+ node: nil,
484
+ compression: nil,
485
+ )
486
+ end
487
+ it 'returns {} if data\'s JSON is broken' do
488
+ data[:data] = '}{'
489
+ data[:id] = 'foo.bar.baz'
490
+ r = db.__send__(:create_attributes, now, row)
491
+ expect(r[:type]).to eq 'foo'
492
+ end
493
+ it 'uses id[/\A[^.]*/] if type is empty string' do
494
+ data[:data] = '{"type":""}'
495
+ data[:id] = 'foo.bar.baz'
496
+ r = db.__send__(:create_attributes, now, row)
497
+ expect(r[:type]).to eq 'foo'
498
+ end
499
+ it 'uses id[/\A[^.]*/] if type is nil' do
500
+ data[:id] = 'foo.bar.baz'
501
+ r = db.__send__(:create_attributes, now, row)
502
+ expect(r[:type]).to eq 'foo'
503
+ end
504
+ context 'created_at is nil' do
505
+ it 'status is :finished' do
506
+ data[:created_at] = nil
507
+ r = db.__send__(:create_attributes, now, row)
508
+ expect(r[:status]).to eq TaskStatus::FINISHED
509
+ end
510
+ end
511
+ end
512
+ context 'created_at is 0' do
513
+ it 'status is :cancel_requested' do
514
+ data[:created_at] = 0
515
+ r = db.__send__(:create_attributes, now, row)
516
+ expect(r[:status]).to eq TaskStatus::CANCEL_REQUESTED
517
+ end
518
+ end
519
+ context 'created_at > 0' do
520
+ context 'timeout' do
521
+ it 'status is :waiting' do
522
+ data[:created_at] = 1
523
+ data[:timeout] = 0
524
+ r = db.__send__(:create_attributes, now, row)
525
+ expect(r[:status]).to eq TaskStatus::WAITING
526
+ end
527
+ end
528
+ it 'status is :running' do
529
+ data[:created_at] = 1
530
+ data[:timeout] = now+100
531
+ r = db.__send__(:create_attributes, now, row)
532
+ expect(r[:status]).to eq TaskStatus::RUNNING
533
+ end
534
+ end
535
+ end
536
+ end