pipeline 0.0.8 → 0.0.9
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.
- data/.gitignore +2 -1
- data/CHANGELOG +14 -0
- data/README.rdoc +31 -9
- data/Rakefile +3 -1
- data/TODO +1 -2
- data/VERSION +1 -1
- data/generators/pipeline/templates/migration.rb +3 -0
- data/lib/pipeline.rb +2 -1
- data/lib/pipeline/api_methods.rb +15 -1
- data/lib/pipeline/base.rb +204 -13
- data/lib/pipeline/core_ext/symbol_attribute.rb +16 -2
- data/lib/pipeline/core_ext/transactional_attribute.rb +18 -1
- data/lib/pipeline/errors.rb +18 -2
- data/lib/pipeline/stage/base.rb +141 -8
- data/pipeline.gemspec +13 -6
- data/spec/database_integration_helper.rb +1 -0
- data/spec/models.rb +72 -0
- data/spec/pipeline/api_methods_spec.rb +5 -4
- data/spec/pipeline/base_spec.rb +119 -80
- data/spec/pipeline/core_ext/symbol_attribute_spec.rb +1 -1
- data/spec/pipeline/core_ext/transactional_attribute_spec.rb +1 -1
- data/spec/pipeline/errors_spec.rb +1 -1
- data/spec/pipeline/stage/base_spec.rb +103 -88
- data/spec/spec_helper.rb +2 -0
- metadata +7 -4
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec/spec_helper'
|
2
2
|
|
3
3
|
module Pipeline
|
4
4
|
describe ApiMethods do
|
@@ -48,6 +48,7 @@ module Pipeline
|
|
48
48
|
describe "#resume" do
|
49
49
|
before(:each) do
|
50
50
|
@pipeline = Pipeline::Base.new
|
51
|
+
@pipeline.stub!(:resume)
|
51
52
|
Pipeline::Base.stub!(:find).with('1').and_return(@pipeline)
|
52
53
|
Delayed::Job.stub!(:enqueue)
|
53
54
|
end
|
@@ -72,10 +73,10 @@ module Pipeline
|
|
72
73
|
Pipeline.resume('1')
|
73
74
|
end
|
74
75
|
|
75
|
-
it "should
|
76
|
-
@pipeline.
|
76
|
+
it "should resume pipeline instance" do
|
77
|
+
@pipeline.should_receive(:resume)
|
77
78
|
|
78
|
-
|
79
|
+
Pipeline.resume('1')
|
79
80
|
end
|
80
81
|
|
81
82
|
end
|
data/spec/pipeline/base_spec.rb
CHANGED
@@ -1,53 +1,9 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
class FirstStage < Pipeline::Stage::Base
|
4
|
-
def run
|
5
|
-
@executed = true
|
6
|
-
end
|
7
|
-
|
8
|
-
def executed?
|
9
|
-
!!@executed
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class SecondStage < FirstStage; end # Ugly.. just so I don't have to write stub again
|
14
|
-
|
15
|
-
class IrrecoverableStage < FirstStage
|
16
|
-
def run
|
17
|
-
super
|
18
|
-
raise Pipeline::IrrecoverableError.new
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class RecoverableInputRequiredStage < FirstStage
|
23
|
-
def run
|
24
|
-
super
|
25
|
-
raise Pipeline::RecoverableError.new("message", true)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
class RecoverableStage < FirstStage
|
30
|
-
def run
|
31
|
-
super
|
32
|
-
raise Pipeline::RecoverableError.new("message")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
class GenericErrorStage < FirstStage
|
37
|
-
def run
|
38
|
-
super
|
39
|
-
raise Exception.new
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class SamplePipeline < Pipeline::Base
|
44
|
-
define_stages FirstStage >> SecondStage
|
45
|
-
end
|
1
|
+
require 'spec/spec_helper'
|
46
2
|
|
47
3
|
module Pipeline
|
48
4
|
describe Base do
|
49
5
|
|
50
|
-
|
6
|
+
context "- configuring" do
|
51
7
|
before(:each) do
|
52
8
|
class ::SamplePipeline
|
53
9
|
define_stages FirstStage >> SecondStage
|
@@ -70,7 +26,7 @@ module Pipeline
|
|
70
26
|
end
|
71
27
|
end
|
72
28
|
|
73
|
-
|
29
|
+
context "- setup" do
|
74
30
|
before(:each) do
|
75
31
|
@pipeline = SamplePipeline.new
|
76
32
|
end
|
@@ -88,15 +44,15 @@ module Pipeline
|
|
88
44
|
end
|
89
45
|
end
|
90
46
|
|
91
|
-
|
47
|
+
context "- persistence" do
|
92
48
|
before(:each) do
|
93
49
|
@pipeline = Base.new
|
94
50
|
end
|
95
51
|
|
96
52
|
it "should persist pipeline instance" do
|
97
|
-
@pipeline.
|
53
|
+
@pipeline.should be_new_record
|
98
54
|
lambda {@pipeline.save!}.should_not raise_error
|
99
|
-
@pipeline.
|
55
|
+
@pipeline.should_not be_new_record
|
100
56
|
end
|
101
57
|
|
102
58
|
it "should allow retrieval by id" do
|
@@ -149,20 +105,15 @@ module Pipeline
|
|
149
105
|
end
|
150
106
|
end
|
151
107
|
|
152
|
-
|
108
|
+
context "- execution (success)" do
|
153
109
|
before(:each) do
|
154
|
-
|
155
|
-
define_stages FirstStage >> SecondStage
|
156
|
-
end
|
157
|
-
@pipeline = ::SamplePipeline.new
|
110
|
+
@pipeline = SamplePipeline.new
|
158
111
|
end
|
159
112
|
|
160
113
|
it "should increment attempts" do
|
161
|
-
pipeline
|
162
|
-
|
163
|
-
pipeline.attempts.should ==
|
164
|
-
pipeline.perform
|
165
|
-
pipeline.attempts.should == 1
|
114
|
+
@pipeline.attempts.should == 0
|
115
|
+
@pipeline.perform
|
116
|
+
@pipeline.attempts.should == 1
|
166
117
|
end
|
167
118
|
|
168
119
|
it "should perform each stage" do
|
@@ -183,7 +134,7 @@ module Pipeline
|
|
183
134
|
end
|
184
135
|
end
|
185
136
|
|
186
|
-
|
137
|
+
context "- execution (in progress)" do
|
187
138
|
it "should set status to in_progress" do
|
188
139
|
pipeline = SamplePipeline.new
|
189
140
|
pipeline.send(:_setup)
|
@@ -193,7 +144,7 @@ module Pipeline
|
|
193
144
|
end
|
194
145
|
end
|
195
146
|
|
196
|
-
|
147
|
+
context "- execution (irrecoverable error)" do
|
197
148
|
before(:each) do
|
198
149
|
class ::SamplePipeline
|
199
150
|
define_stages FirstStage >> IrrecoverableStage
|
@@ -217,7 +168,7 @@ module Pipeline
|
|
217
168
|
end
|
218
169
|
end
|
219
170
|
|
220
|
-
|
171
|
+
context "- execution (recoverable error that doesn't require user input)" do
|
221
172
|
before(:each) do
|
222
173
|
class ::SamplePipeline
|
223
174
|
define_stages FirstStage >> RecoverableStage
|
@@ -241,7 +192,7 @@ module Pipeline
|
|
241
192
|
end
|
242
193
|
end
|
243
194
|
|
244
|
-
|
195
|
+
context "- execution (recoverable error that requires user input)" do
|
245
196
|
before(:each) do
|
246
197
|
class ::SamplePipeline
|
247
198
|
define_stages FirstStage >> RecoverableInputRequiredStage
|
@@ -265,7 +216,7 @@ module Pipeline
|
|
265
216
|
end
|
266
217
|
end
|
267
218
|
|
268
|
-
|
219
|
+
context "- execution (other errors will use failure mode to pause/cancel pipeline)" do
|
269
220
|
before(:each) do
|
270
221
|
class ::SamplePipeline
|
271
222
|
define_stages FirstStage >> GenericErrorStage
|
@@ -304,7 +255,7 @@ module Pipeline
|
|
304
255
|
end
|
305
256
|
end
|
306
257
|
|
307
|
-
|
258
|
+
context "- execution (retrying)" do
|
308
259
|
before(:each) do
|
309
260
|
class ::SamplePipeline
|
310
261
|
define_stages FirstStage >> RecoverableInputRequiredStage
|
@@ -344,15 +295,14 @@ module Pipeline
|
|
344
295
|
|
345
296
|
# Status gets updated to failed on the database (not on the current instance)
|
346
297
|
same_pipeline = SamplePipeline.find(@pipeline.id)
|
347
|
-
same_pipeline.
|
348
|
-
same_pipeline.save!
|
298
|
+
same_pipeline.update_attribute(:status, :failed)
|
349
299
|
|
350
300
|
# Retrying should fail because pipeline is now failed
|
351
301
|
lambda {@pipeline.perform}.should raise_error(InvalidStatusError, "Status is already failed")
|
352
302
|
end
|
353
303
|
end
|
354
304
|
|
355
|
-
|
305
|
+
context "- execution (state transitions)" do
|
356
306
|
before(:each) do
|
357
307
|
@pipeline = Base.new
|
358
308
|
end
|
@@ -363,42 +313,42 @@ module Pipeline
|
|
363
313
|
end
|
364
314
|
|
365
315
|
it "should execute if status is :paused (for retrying)" do
|
366
|
-
@pipeline.
|
316
|
+
@pipeline.update_attribute(:status, :paused)
|
367
317
|
|
368
318
|
@pipeline.should be_ok_to_resume
|
369
319
|
lambda {@pipeline.perform}.should_not raise_error(InvalidStatusError)
|
370
320
|
end
|
371
321
|
|
372
322
|
it "should execute if status is :retry" do
|
373
|
-
@pipeline.
|
323
|
+
@pipeline.update_attribute(:status, :retry)
|
374
324
|
|
375
325
|
@pipeline.should be_ok_to_resume
|
376
326
|
lambda {@pipeline.perform}.should_not raise_error(InvalidStatusError)
|
377
327
|
end
|
378
328
|
|
379
329
|
it "should not execute if status is :in_progress" do
|
380
|
-
@pipeline.
|
330
|
+
@pipeline.update_attribute(:status, :in_progress)
|
381
331
|
|
382
332
|
@pipeline.should_not be_ok_to_resume
|
383
333
|
lambda {@pipeline.perform}.should raise_error(InvalidStatusError, "Status is already in progress")
|
384
334
|
end
|
385
335
|
|
386
336
|
it "should not execute if status is :completed" do
|
387
|
-
@pipeline.
|
337
|
+
@pipeline.update_attribute(:status, :completed)
|
388
338
|
|
389
339
|
@pipeline.should_not be_ok_to_resume
|
390
340
|
lambda {@pipeline.perform}.should raise_error(InvalidStatusError, "Status is already completed")
|
391
341
|
end
|
392
342
|
|
393
343
|
it "should not execute if status is :failed" do
|
394
|
-
@pipeline.
|
344
|
+
@pipeline.update_attribute(:status, :failed)
|
395
345
|
|
396
346
|
@pipeline.should_not be_ok_to_resume
|
397
347
|
lambda {@pipeline.perform}.should raise_error(InvalidStatusError, "Status is already failed")
|
398
348
|
end
|
399
349
|
end
|
400
350
|
|
401
|
-
|
351
|
+
context "- cancelling" do
|
402
352
|
before(:each) do
|
403
353
|
class ::SamplePipeline
|
404
354
|
define_stages FirstStage >> RecoverableInputRequiredStage
|
@@ -417,9 +367,22 @@ module Pipeline
|
|
417
367
|
@pipeline.cancel
|
418
368
|
@pipeline.reload.status.should == :failed
|
419
369
|
end
|
370
|
+
|
371
|
+
it "should refresh object (in case it was updated after job was scheduled)" do
|
372
|
+
# Gets paused on the first time
|
373
|
+
@pipeline.save!
|
374
|
+
|
375
|
+
# Status gets updated to failed on the database (not on the current instance)
|
376
|
+
same_pipeline = SamplePipeline.find(@pipeline.id)
|
377
|
+
same_pipeline.update_attribute(:status, :failed)
|
378
|
+
|
379
|
+
# Retrying should fail because pipeline is now failed
|
380
|
+
lambda {@pipeline.cancel}.should raise_error(InvalidStatusError, "Status is already failed")
|
381
|
+
end
|
382
|
+
|
420
383
|
end
|
421
384
|
|
422
|
-
|
385
|
+
context "- cancelling (state transitions)" do
|
423
386
|
before(:each) do
|
424
387
|
@pipeline = Base.new
|
425
388
|
end
|
@@ -429,29 +392,105 @@ module Pipeline
|
|
429
392
|
end
|
430
393
|
|
431
394
|
it "should cancel if status is :paused (for retrying)" do
|
432
|
-
@pipeline.
|
395
|
+
@pipeline.update_attribute(:status, :paused)
|
433
396
|
|
434
397
|
lambda {@pipeline.cancel}.should_not raise_error(InvalidStatusError)
|
435
398
|
end
|
436
399
|
|
437
400
|
it "should not cancel if status is :in_progress" do
|
438
|
-
@pipeline.
|
401
|
+
@pipeline.update_attribute(:status, :in_progress)
|
439
402
|
|
440
403
|
lambda {@pipeline.cancel}.should raise_error(InvalidStatusError, "Status is already in progress")
|
441
404
|
end
|
442
405
|
|
443
406
|
it "should not cancel if status is :completed" do
|
444
|
-
@pipeline.
|
407
|
+
@pipeline.update_attribute(:status, :completed)
|
445
408
|
|
446
409
|
lambda {@pipeline.cancel}.should raise_error(InvalidStatusError, "Status is already completed")
|
447
410
|
end
|
448
411
|
|
449
412
|
it "should not cancel if status is :failed" do
|
450
|
-
@pipeline.
|
413
|
+
@pipeline.update_attribute(:status, :failed)
|
451
414
|
|
452
415
|
lambda {@pipeline.cancel}.should raise_error(InvalidStatusError, "Status is already failed")
|
453
416
|
end
|
454
417
|
end
|
418
|
+
|
419
|
+
context "- resuming" do
|
420
|
+
before(:each) do
|
421
|
+
class ::SamplePipeline
|
422
|
+
define_stages FirstStage >> RecoverableInputRequiredStage
|
423
|
+
end
|
424
|
+
@pipeline = SamplePipeline.new
|
425
|
+
@pipeline.perform
|
426
|
+
end
|
427
|
+
|
428
|
+
it "should refresh object (in case it was updated after job was scheduled)" do
|
429
|
+
# Gets paused on the first time
|
430
|
+
@pipeline.save!
|
431
|
+
|
432
|
+
# Status gets updated to failed on the database (not on the current instance)
|
433
|
+
same_pipeline = SamplePipeline.find(@pipeline.id)
|
434
|
+
same_pipeline.update_attribute(:status, :failed)
|
435
|
+
|
436
|
+
# Retrying should fail because pipeline is now failed
|
437
|
+
lambda {@pipeline.resume}.should raise_error(InvalidStatusError, "Status is already failed")
|
438
|
+
end
|
439
|
+
end
|
455
440
|
|
441
|
+
context "- resuming (state transitions)" do
|
442
|
+
before(:each) do
|
443
|
+
@pipeline = Base.new
|
444
|
+
end
|
445
|
+
|
446
|
+
it "should resume if status is :not_started" do
|
447
|
+
lambda {@pipeline.resume}.should_not raise_error(InvalidStatusError)
|
448
|
+
end
|
449
|
+
|
450
|
+
it "should resume if status is :paused (for retrying)" do
|
451
|
+
@pipeline.update_attribute(:status, :paused)
|
452
|
+
|
453
|
+
lambda {@pipeline.resume}.should_not raise_error(InvalidStatusError)
|
454
|
+
end
|
455
|
+
|
456
|
+
it "should not resume if status is :in_progress" do
|
457
|
+
@pipeline.update_attribute(:status, :in_progress)
|
458
|
+
|
459
|
+
lambda {@pipeline.resume}.should raise_error(InvalidStatusError, "Status is already in progress")
|
460
|
+
end
|
461
|
+
|
462
|
+
it "should not resume if status is :completed" do
|
463
|
+
@pipeline.update_attribute(:status, :completed)
|
464
|
+
|
465
|
+
lambda {@pipeline.resume}.should raise_error(InvalidStatusError, "Status is already completed")
|
466
|
+
end
|
467
|
+
|
468
|
+
it "should not resume if status is :failed" do
|
469
|
+
@pipeline.update_attribute(:status, :failed)
|
470
|
+
|
471
|
+
lambda {@pipeline.resume}.should raise_error(InvalidStatusError, "Status is already failed")
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context "- callbacks" do
|
476
|
+
before(:each) do
|
477
|
+
@pipeline = ::SamplePipeline.new
|
478
|
+
end
|
479
|
+
|
480
|
+
it "should allow callback before running the pipeline" do
|
481
|
+
@pipeline.should_receive(:before_pipeline_callback).once
|
482
|
+
@pipeline.perform
|
483
|
+
end
|
484
|
+
|
485
|
+
it "should allow callback after running the pipeline" do
|
486
|
+
@pipeline.should_receive(:after_pipeline_callback).once
|
487
|
+
@pipeline.perform
|
488
|
+
end
|
489
|
+
|
490
|
+
it "should run callback after cancelling a pipeline" do
|
491
|
+
@pipeline.should_receive(:after_pipeline_callback).once
|
492
|
+
@pipeline.cancel
|
493
|
+
end
|
494
|
+
end
|
456
495
|
end
|
457
496
|
end
|
@@ -1,20 +1,10 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
class SampleStage < Pipeline::Stage::Base
|
4
|
-
def run
|
5
|
-
@executed = true
|
6
|
-
end
|
7
|
-
|
8
|
-
def executed?
|
9
|
-
!!@executed
|
10
|
-
end
|
11
|
-
end
|
1
|
+
require 'spec/spec_helper'
|
12
2
|
|
13
3
|
module Pipeline
|
14
4
|
module Stage
|
15
5
|
describe Base do
|
16
6
|
|
17
|
-
|
7
|
+
context "- chaining" do
|
18
8
|
class Step1 < Base; end
|
19
9
|
class Step2 < Base; end
|
20
10
|
class Step3 < Base; end
|
@@ -32,23 +22,23 @@ module Pipeline
|
|
32
22
|
end
|
33
23
|
end
|
34
24
|
|
35
|
-
|
25
|
+
context "- setup" do
|
36
26
|
it "should set default name" do
|
37
27
|
Base.new.name.should == "Pipeline::Stage::Base"
|
38
|
-
SampleStage.new.name.should == "SampleStage"
|
28
|
+
::SampleStage.new.name.should == "SampleStage"
|
39
29
|
end
|
40
30
|
|
41
31
|
it "should allow overriding name at class level" do
|
42
|
-
|
43
|
-
|
32
|
+
StubStage.default_name = "My custom stage name"
|
33
|
+
StubStage.new.name.should == "My custom stage name"
|
44
34
|
|
45
|
-
|
46
|
-
|
35
|
+
StubStage.default_name = :some_symbol
|
36
|
+
StubStage.new.name.should == "some_symbol"
|
47
37
|
end
|
48
38
|
|
49
39
|
it "should allow specifying a name on creation" do
|
50
40
|
Base.new(:name => "My Name").name.should == "My Name"
|
51
|
-
|
41
|
+
StubStage.new(:name => "Customized Name").name.should == "Customized Name"
|
52
42
|
end
|
53
43
|
|
54
44
|
it "should start with status not_started" do
|
@@ -57,24 +47,28 @@ module Pipeline
|
|
57
47
|
|
58
48
|
it "should validate status" do
|
59
49
|
lambda {Base.new(:status => :something_else)}.should raise_error
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should raise error if subclass doesn't implement #run" do
|
53
|
+
lambda {Base.new.run}.should raise_error("This method must be implemented by any subclass of Pipeline::Stage::Base")
|
60
54
|
end
|
61
55
|
end
|
62
56
|
|
63
|
-
|
57
|
+
context "- persistence" do
|
64
58
|
before(:each) do
|
65
|
-
@stage =
|
59
|
+
@stage = StubStage.new
|
66
60
|
end
|
67
61
|
|
68
62
|
it "should persist stage" do
|
69
|
-
@stage.
|
63
|
+
@stage.should be_new_record
|
70
64
|
lambda {@stage.save!}.should_not raise_error
|
71
|
-
@stage.
|
65
|
+
@stage.should_not be_new_record
|
72
66
|
end
|
73
67
|
|
74
68
|
it "should allow retrieval by id" do
|
75
69
|
@stage.save!
|
76
70
|
|
77
|
-
s =
|
71
|
+
s = StubStage.find(@stage.id)
|
78
72
|
s.should === @stage
|
79
73
|
|
80
74
|
lambda {Base.find('invalid_id')}.should raise_error(ActiveRecord::RecordNotFound)
|
@@ -83,7 +77,7 @@ module Pipeline
|
|
83
77
|
it "should persist type as single table inheritance" do
|
84
78
|
@stage.save!
|
85
79
|
stage = Base.find(@stage.id)
|
86
|
-
stage.should be_an_instance_of(
|
80
|
+
stage.should be_an_instance_of(StubStage)
|
87
81
|
end
|
88
82
|
|
89
83
|
it "should belong to pipeline instance" do
|
@@ -96,9 +90,9 @@ module Pipeline
|
|
96
90
|
|
97
91
|
end
|
98
92
|
|
99
|
-
|
93
|
+
context "- execution (success)" do
|
100
94
|
before(:each) do
|
101
|
-
@stage =
|
95
|
+
@stage = StubStage.new
|
102
96
|
end
|
103
97
|
|
104
98
|
it "should update status after finished" do
|
@@ -128,89 +122,90 @@ module Pipeline
|
|
128
122
|
|
129
123
|
end
|
130
124
|
|
131
|
-
|
132
|
-
before(:each) do
|
133
|
-
@stage = SampleStage.new
|
134
|
-
@stage.stub!(:run).and_raise(StandardError.new)
|
135
|
-
end
|
136
|
-
|
125
|
+
context "- execution (failure)" do
|
137
126
|
it "should re-raise error" do
|
138
|
-
|
127
|
+
stage = FailedStage.new
|
128
|
+
lambda {stage.perform}.should raise_error
|
139
129
|
end
|
140
130
|
|
141
131
|
it "should update status on irrecoverable error" do
|
142
|
-
|
143
|
-
lambda {
|
144
|
-
|
145
|
-
|
132
|
+
stage = IrrecoverableStage.new
|
133
|
+
lambda {stage.perform}.should raise_error(IrrecoverableError)
|
134
|
+
stage.status.should == :failed
|
135
|
+
stage.reload.status.should == :failed
|
146
136
|
end
|
147
137
|
|
148
138
|
it "should update message on irrecoverable error" do
|
149
|
-
|
150
|
-
lambda {
|
151
|
-
|
152
|
-
|
139
|
+
stage = IrrecoverableStage.new
|
140
|
+
lambda {stage.perform}.should raise_error(IrrecoverableError)
|
141
|
+
stage.message.should == "message"
|
142
|
+
stage.reload.message.should == "message"
|
153
143
|
end
|
154
144
|
|
155
145
|
it "should update status on recoverable error (not requiring input)" do
|
156
|
-
|
157
|
-
lambda {
|
158
|
-
|
159
|
-
|
146
|
+
stage = RecoverableStage.new
|
147
|
+
lambda {stage.perform}.should raise_error(RecoverableError)
|
148
|
+
stage.status.should == :failed
|
149
|
+
stage.reload.status.should == :failed
|
160
150
|
end
|
161
151
|
|
162
152
|
it "should update status on recoverable error (requiring input)" do
|
163
|
-
|
164
|
-
lambda {
|
165
|
-
|
166
|
-
|
153
|
+
stage = RecoverableInputRequiredStage.new
|
154
|
+
lambda {stage.perform}.should raise_error(RecoverableError)
|
155
|
+
stage.status.should == :failed
|
156
|
+
stage.reload.status.should == :failed
|
167
157
|
end
|
168
158
|
|
169
159
|
it "should update message on recoverable error" do
|
170
|
-
|
171
|
-
lambda {
|
172
|
-
|
173
|
-
|
160
|
+
stage = RecoverableStage.new
|
161
|
+
lambda {stage.perform}.should raise_error(RecoverableError)
|
162
|
+
stage.message.should == "message"
|
163
|
+
stage.reload.message.should == "message"
|
174
164
|
end
|
175
165
|
|
176
166
|
it "should capture generic Exception" do
|
177
|
-
|
178
|
-
lambda {
|
179
|
-
|
180
|
-
|
167
|
+
stage = GenericErrorStage.new
|
168
|
+
lambda {stage.perform}.should raise_error(Exception)
|
169
|
+
stage.status.should == :failed
|
170
|
+
stage.reload.status.should == :failed
|
181
171
|
end
|
182
172
|
|
183
173
|
it "should log exception message and backtrace" do
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
174
|
+
class StageFailWithDetails < StubStage
|
175
|
+
self.default_name = "Fail With Details"
|
176
|
+
|
177
|
+
def run
|
178
|
+
super
|
179
|
+
error = StandardError.new("error message")
|
180
|
+
error.set_backtrace(['a', 'b', 'c'])
|
181
|
+
raise error
|
182
|
+
end
|
183
|
+
end
|
184
|
+
stage = StageFailWithDetails.new
|
188
185
|
|
189
|
-
|
190
|
-
|
191
|
-
lambda {
|
186
|
+
stage.logger.should_receive(:info).with("Error on stage Fail With Details: error message")
|
187
|
+
stage.logger.should_receive(:info).with("a\nb\nc")
|
188
|
+
lambda {stage.perform}.should raise_error
|
192
189
|
end
|
193
190
|
|
194
191
|
it "should refresh object (in case it was cancelled after job was scheduled)" do
|
195
192
|
# Gets failed on the first time
|
196
|
-
|
197
|
-
|
198
|
-
lambda {@stage.perform}.should raise_error(RecoverableError)
|
193
|
+
stage = RecoverableStage.create!
|
194
|
+
lambda {stage.perform}.should raise_error(RecoverableError)
|
199
195
|
|
200
196
|
# Status gets updated to completed on the database (not on the current instance)
|
201
|
-
same_stage =
|
202
|
-
same_stage.
|
203
|
-
same_stage.save!
|
197
|
+
same_stage = StubStage.find(stage.id)
|
198
|
+
same_stage.update_attribute(:status, :completed)
|
204
199
|
|
205
200
|
# Retrying should fail because stage is now completed
|
206
|
-
lambda {
|
201
|
+
lambda {stage.perform}.should raise_error(InvalidStatusError, "Status is already completed")
|
207
202
|
end
|
208
203
|
|
209
204
|
end
|
210
205
|
|
211
|
-
|
206
|
+
context "- execution (in progress)" do
|
212
207
|
it "should set status to in_progress" do
|
213
|
-
stage =
|
208
|
+
stage = StubStage.new
|
214
209
|
stage.send(:_setup)
|
215
210
|
|
216
211
|
stage.status.should == :in_progress
|
@@ -218,7 +213,7 @@ module Pipeline
|
|
218
213
|
end
|
219
214
|
|
220
215
|
it "should clear message when restarting" do
|
221
|
-
stage =
|
216
|
+
stage = StubStage.new(:message => 'some message')
|
222
217
|
stage.send(:_setup)
|
223
218
|
|
224
219
|
stage.message.should be_nil
|
@@ -226,35 +221,55 @@ module Pipeline
|
|
226
221
|
end
|
227
222
|
end
|
228
223
|
|
229
|
-
|
224
|
+
context "- execution (state transitions)" do
|
225
|
+
before(:each) do
|
226
|
+
@stage = StubStage.new
|
227
|
+
end
|
228
|
+
|
230
229
|
it "should execute if status is :not_started" do
|
231
|
-
stage
|
232
|
-
|
233
|
-
lambda {stage.perform}.should_not raise_error(InvalidStatusError)
|
230
|
+
lambda {@stage.perform}.should_not raise_error(InvalidStatusError)
|
234
231
|
end
|
235
232
|
|
236
233
|
it "should execute if status is :failed (for retrying)" do
|
237
|
-
stage
|
238
|
-
stage.send(:status=, :failed)
|
234
|
+
@stage.update_attribute(:status, :failed)
|
239
235
|
|
240
|
-
lambda {stage.perform}.should_not raise_error(InvalidStatusError)
|
236
|
+
lambda {@stage.perform}.should_not raise_error(InvalidStatusError)
|
241
237
|
end
|
242
238
|
|
243
239
|
it "should not execute if status is :in_progress" do
|
244
|
-
stage
|
245
|
-
stage.send(:status=, :in_progress)
|
240
|
+
@stage.update_attribute(:status, :in_progress)
|
246
241
|
|
247
|
-
lambda {stage.perform}.should raise_error(InvalidStatusError, "Status is already in progress")
|
242
|
+
lambda {@stage.perform}.should raise_error(InvalidStatusError, "Status is already in progress")
|
248
243
|
end
|
249
244
|
|
250
245
|
it "should not execute if status is :completed" do
|
251
|
-
stage
|
252
|
-
stage.send(:status=, :completed)
|
246
|
+
@stage.update_attribute(:status, :completed)
|
253
247
|
|
254
|
-
lambda {stage.perform}.should raise_error(InvalidStatusError, "Status is already completed")
|
248
|
+
lambda {@stage.perform}.should raise_error(InvalidStatusError, "Status is already completed")
|
255
249
|
end
|
256
250
|
end
|
257
251
|
|
252
|
+
context "- callbacks" do
|
253
|
+
before(:each) do
|
254
|
+
@stage = ::SampleStage.new
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should allow callback before running the stage" do
|
258
|
+
@stage.should_receive(:before_stage_callback).once
|
259
|
+
@stage.perform
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should allow callback after running the stage on success" do
|
263
|
+
@stage.should_receive(:after_stage_callback).once
|
264
|
+
@stage.perform
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should allow callback after running the stage on failure" do
|
268
|
+
@stage.stub!(:run).and_raise("error")
|
269
|
+
@stage.should_receive(:after_stage_callback).once
|
270
|
+
lambda {@stage.perform}.should raise_error
|
271
|
+
end
|
272
|
+
end
|
258
273
|
end
|
259
274
|
end
|
260
275
|
end
|