spec/em-dextras/chains_spec.rb in em-dextras-0.2.0 vs spec/em-dextras/chains_spec.rb in em-dextras-0.3.0
- old
+ new
@@ -1,8 +1,14 @@
require 'spec_helper'
-describe EMDextras::Chains do
+describe EMDextras::Chains do
+ class EchoStage
+ def todo(input)
+ EMDextras::Chains::Deferrables.succeeded(input)
+ end
+ end
+
class ProduceStage
def initialize(result)
@result = result
end
@@ -22,14 +28,20 @@
end
end
class ErrorStage
def todo(input)
- EMDextras::Chains::Deferrables.failed "Failed with #{input}"
+ EMDextras::Chains::Deferrables.failed input
end
end
+ class InvalidStage
+ def todo(input)
+ "not-a-deferrable"
+ end
+ end
+
class StopStage
def todo(input)
EM.stop
EMDextras::Chains::Deferrables.succeeded input
end
@@ -41,56 +53,204 @@
@context = context
EMDextras::Chains::Deferrables.succeeded input
end
end
- let(:monitoring) { mock.as_null_object }
-
- it "should chain todo stages" do
- EM.run do
- inputs = []
+ class InterruptChainStage
+ def todo(ignored)
+ deferrable = EventMachine::DefaultDeferrable.new
+ deferrable.succeed()
+ deferrable
+ end
+ end
- EMDextras::Chains.pipe("input", monitoring, [
- SpyStage.new(inputs),
- SpyStage.new(inputs),
- StopStage.new
- ])
+ let(:monitoring) { EMDextras::Spec::Spy.new }
- inputs.should == ["input", "input"]
+ context " - when all stages succeed -" do
+ it "should chain todo stages" do
+ EM.run do
+ inputs = []
+
+ EMDextras::Chains.pipe("input", monitoring, [
+ SpyStage.new(inputs),
+ SpyStage.new(inputs),
+ StopStage.new
+ ])
+
+ inputs.should == ["input", "input"]
+ end
end
+
+ it "should return a deferrable with the result of the last step" do
+ EM.run do
+ result = EMDextras::Chains.pipe("ignored", monitoring, [
+ ProduceStage.new("out")
+ ])
+
+ result.should succeed_with("out")
+ end
+ end
end
- it "should notify monitoring of any exceptions" do
- EM.run do
- monitoring.should_receive(:inform_exception!) do
- EM.stop
+ context " - when a stage fails - " do
+ it "should fail the resulting deferrable" do
+ EM.run do
+ result = EMDextras::Chains.pipe("error", monitoring, [
+ EchoStage.new,
+ ErrorStage.new,
+ ProduceStage.new(42)
+ ])
+
+ result.should fail_with("error")
end
+ end
- EM.add_timer(2) do
- fail("timeout")
+ it "should not proceed with the chain" do
+ EM.run do
+ produced = []
+
+ result = EMDextras::Chains.pipe("in", monitoring, [
+ SpyStage.new(produced),
+ ErrorStage.new,
+ SpyStage.new(produced)
+ ])
+
+ probe_event_machine check: (Proc.new do
+ produced.should == ["in"]
+ end)
end
+ end
+ end
- EMDextras::Chains.pipe("anything", monitoring, [ErrorStage.new]);
+ context "- interruption -" do
+ it "should interrupt the chain when a stage returns an empty succeeded deferrable" do
+ EM.run do
+ input = []
+
+ EMDextras::Chains.pipe("input", monitoring, [
+ ProduceStage.new("x"),
+ InterruptChainStage.new,
+ SpyStage.new(input)
+ ])
+
+ probe_event_machine check: (Proc.new do
+ input.should == []
+ end)
+ end
end
+
+ it "should notify the monitor that the chain ended (with nil value)" do
+ EM.run do
+ input = []
+
+ monitoring = EMDextras::Spec::Spy.new
+
+ EMDextras::Chains.pipe("input", monitoring, [
+ ProduceStage.new("x"),
+ InterruptChainStage.new,
+ SpyStage.new(input)
+ ])
+
+ monitoring.received_call!(:end_of_chain!, nil)
+ end
+ end
end
- it "should pass a 'context' object if given and the stage takes one" do
- contextual_stage = ContextualStage.new
-
- EM.run do
- EMDextras::Chains.pipe("anything", monitoring, [
- contextual_stage,
- StopStage.new
- ], :context => "the context")
+ context "- monitoring -" do
+ it "should notify monitoring of any exceptions" do
+ EM.run do
+ EMDextras::Chains.pipe("anything", monitoring, [ErrorStage.new]);
- probe_event_machine :check => lambda {|x|
- contextual_stage.context.should == "the context"
- }
+ monitoring.received_call!(:inform_exception!, any_args)
+ end
end
+
+ it "should pass the context to monitoring.inform_exception if given" do
+ EM.run do
+ error_stage = ErrorStage.new
+
+ EMDextras::Chains.pipe("input", monitoring, [
+ error_stage
+ ], context: "the context")
+
+ monitoring.received_call!(:inform_exception!, "input", error_stage, "the context")
+ end
+ end
+
+ it "should notify monitoring of the end of the pipeline" do
+ EM.run do
+ monitoring = EMDextras::Spec::Spy.new
+ EMDextras::Chains.pipe("x", monitoring, [
+ ProduceStage.new("y"),
+ SpyStage.new([]),
+ ProduceStage.new("z")
+ ])
+
+ monitoring.received_call!(:end_of_chain!, "z")
+ end
+ end
+
+ it "should notify monitoring of the end of the pipeline even when a stage fails" do
+ EM.run do
+ monitoring = EMDextras::Spec::Spy.new
+
+ EMDextras::Chains.pipe("x", monitoring, [
+ ProduceStage.new("y"),
+ ErrorStage.new,
+ ProduceStage.new("z")
+ ])
+
+ monitoring.received_call!(:end_of_chain!, "y")
+ end
+ end
+
+ context 'when monitoring does not respond to end_of_chain' do
+ it 'does not to try to call that method' do
+ EM.run do
+ monitoring = EMDextras::Spec::Spy.new only_respond_to: [:this_method]
+
+ EMDextras::Chains.pipe('x', monitoring, [
+ ProduceStage.new('y'),
+ SpyStage.new([]),
+ ProduceStage.new('z')
+ ])
+
+ monitoring.not_received_call!(:end_of_chain!, 'z')
+ end
+ end
+ end
end
- context "when given a :split stage" do
+ context " - context - " do
+ it "should pass a 'context' object if given and the stage takes one" do
+ contextual_stage = ContextualStage.new
+
+ EM.run do
+ EMDextras::Chains.pipe("anything", monitoring, [
+ contextual_stage,
+ StopStage.new
+ ], :context => "the context")
+
+ probe_event_machine :check => lambda {|x|
+ contextual_stage.context.should == "the context"
+ }
+ end
+ end
+
+ it "should pass the contect object to monitoring if given" do
+ EM.run do
+ monitoring = EMDextras::Spec::Spy.new
+ EMDextras::Chains.pipe("x", monitoring, [
+ ProduceStage.new("y"),
+ ], context: "the context")
+
+ monitoring.received_call!(:end_of_chain!, "y", "the context")
+ end
+ end
+ end
+
+ context "when given a :split stage" do
context "and the input is enumberable" do
it "should invoke the next step the given number of times" do
EM.run do
final_inputs = []
@@ -103,20 +263,40 @@
final_inputs.should =~ [1,2,3]
end
end
+ it "successive splits should recursively divide the pipeline" do
+ EM.run do
+ final_inputs = []
+ intermediate_inputs = []
+
+ EMDextras::Chains.pipe("anything", monitoring, [
+ ProduceStage.new([1,2]),
+ :split,
+ SpyStage.new(intermediate_inputs),
+ ProduceStage.new([3,4]),
+ :split,
+ SpyStage.new(final_inputs),
+ StopStage.new
+ ])
+
+ intermediate_inputs.should =~ [1,2]
+ final_inputs.should =~ [3,4,3,4]
+ end
+ end
+
it "should split the given context" do
before = ContextualStage.new
after = ContextualStage.new
first_context = double("first context")
second_context = double("second context")
first_context.stub(:split).and_return second_context
- EM.run do
+ EM.run do
EMDextras::Chains.pipe("anything", monitoring, [
before,
ProduceStage.new([1,2]),
:split,
after
@@ -126,29 +306,129 @@
before.context.should == first_context
after.context.should == second_context
}
end
end
- end
+
+ it "should return a deferrable acccumulating the results of the last step" do
+ EM.run do
+ result = EMDextras::Chains.pipe("anything", monitoring, [
+ ProduceStage.new([1,2]),
+ :split,
+ SpyStage.new([])
+ ])
- context "and the input is not enumberable" do
- it "will terminate the chain and report the error as an exception" do
- EM.run do
- monitoring.should_receive(:inform_exception!) do
- EM.stop
+ result.should succeed_with([1,2])
+ end
+ end
+
+ it "should return a deferrable with the result of the last step, accumulating results for multiple splits" do
+ EM.run do
+ result = EMDextras::Chains.pipe("anything", monitoring, [
+ ProduceStage.new([1,2]),
+ :split,
+ ProduceStage.new([3,4]),
+ :split,
+ SpyStage.new([])
+ ])
+
+ result.should succeed_with([[3,4],[3,4]])
+ end
+ end
+
+ context " - corner cases -" do
+ it "should handle a split as first chain element" do
+ EM.run do
+ results = []
+ EMDextras::Chains.pipe([1,2,3], monitoring, [
+ :split,
+ SpyStage.new(results)
+ ])
+ probe_event_machine :check => (lambda {|x|
+ results.should == [1,2,3]
+ })
end
+ end
- EM.add_timer(2) do
- fail("timeout")
+ it "should return the deferrable result even if split is the first argument" do
+ EM.run do
+ res = EMDextras::Chains.pipe([1,2,3], monitoring, [
+ :split,
+ EchoStage.new
+ ])
+
+ res.should succeed_with([1,2,3])
end
+ end
+ it "should handle a split as last chain element" do
+ EM.run do
+ result = EMDextras::Chains.pipe('ignored', monitoring, [
+ ProduceStage.new([1,2,3]),
+ :split
+ ])
+
+ result.should succeed_with([1,2,3])
+ end
+ end
+ end
+
+ context " - splits and monitoring - " do
+ it "should inform monitoring that the pipeline ended only once" do
+ EM.run do
+ monitoring = EMDextras::Spec::Spy.new
+
+ EMDextras::Chains.pipe("anything", monitoring, [
+ ProduceStage.new([1,2]),
+ :split,
+ ProduceStage.new([3,4]),
+ :split,
+ SpyStage.new([])
+ ])
+
+ monitoring.received_n_calls!(1, :end_of_chain!, [[3,4],[3,4]])
+ end
+ end
+
+ it "should inform monitoring that the pipeline ended with context if given" do
+ EM.run do
+ monitoring = EMDextras::Spec::Spy.new
+
+ EMDextras::Chains.pipe("anything", monitoring, [
+ ProduceStage.new([1,2]),
+ :split,
+ ProduceStage.new([3,4]),
+ :split,
+ SpyStage.new([])
+ ], context: :the_context)
+
+ monitoring.received_n_calls!(1, :end_of_chain!, [[3,4],[3,4]], :the_context)
+ end
+ end
+ end
+ end
+
+ context "and the input is not enumberable" do
+ it "will terminate the chain and report the error as an exception" do
+ EM.run do
EMDextras::Chains.pipe("anything", monitoring, [
ProduceStage.new(:not_enumberable_input),
:split,
SpyStage.new([]),
StopStage.new
])
+ monitoring.received_call!(:inform_exception!, any_args)
end
end
+ end
+ end
+
+ context " - input validation - " do
+ it "should raise an exception when a stage doesn't return a deferrable" do
+ expect {EM.run do
+ EMDextras::Chains.pipe("the input", monitoring, [
+ InvalidStage.new
+ ])
+ end}.to raise_error(EMDextras::Chains::InvalidStage, /.*'InvalidStage'.*'the input'.*'not-a-deferrable'.*/)
end
end
end