spec/channel_spec.rb in agent-0.1.0 vs spec/channel_spec.rb in agent-0.9.0

- old
+ new

@@ -1,145 +1,216 @@ -require "helper" +require "spec_helper" describe Agent::Channel do - # http://golang.org/doc/go_spec.html#Channel_types - include Agent - let(:c) { Channel.new(:name => "spec", :type => String) } - - it "should have a name" do - lambda { Channel.new(:type => String) }.should raise_error(Channel::NoName) - c.name.should == "spec" - end - - it "should respond to close" do - lambda { c.close }.should_not raise_error - c.closed?.should be_true - end - - it "should respond to closed?" do - c.closed?.should be_false - c.close - c.closed?.should be_true - end - - context "deadlock" do - it "should deadlock on single thread" do - c = Channel.new(:name => "deadlock", :type => String) - lambda { c.receive }.should raise_error + context "naming" do + it "should not be required" do + c = nil + lambda { c = channel!(String) }.should_not raise_error c.close end - it "should not deadlock with multiple threads" do - c = Channel.new(:name => "deadlock", :type => String) - Thread.new { sleep(0.1); c.push "hi" } - lambda { c.receive }.should_not raise_error + it "be able to be set" do + c = channel!(String, :name => "gibberish") + c.name.should == "gibberish" c.close end end context "direction" do # A channel provides a mechanism for two concurrently executing functions to # synchronize execution and communicate by passing a value of a specified element # type. The value of an uninitialized channel is nil. it "should support send only" do - c = Channel.new(:name => "spec", :direction => :send, :type => String, :size => 3) + c = channel!(String, 3, :direction => :send) lambda { c << "hello" }.should_not raise_error lambda { c.push "hello" }.should_not raise_error lambda { c.send "hello" }.should_not raise_error - lambda { c.pop }.should raise_error Channel::InvalidDirection - lambda { c.receive }.should raise_error Channel::InvalidDirection + c.direction.should == :send + lambda { c.pop }.should raise_error Agent::Errors::InvalidDirection + lambda { c.receive }.should raise_error Agent::Errors::InvalidDirection + c.close end it "should support receive only" do - c = Channel.new(:name => "spec", :direction => :receive, :type => String) + c = channel!(String, :direction => :receive) - lambda { c << "hello" }.should raise_error Channel::InvalidDirection - lambda { c.push "hello" }.should raise_error Channel::InvalidDirection - lambda { c.send "hello" }.should raise_error Channel::InvalidDirection + lambda { c << "hello" }.should raise_error Agent::Errors::InvalidDirection + lambda { c.push "hello" }.should raise_error Agent::Errors::InvalidDirection + lambda { c.send "hello" }.should raise_error Agent::Errors::InvalidDirection + c.direction.should == :receive + # timeout blocking receive calls - lambda { Timeout::timeout(0.1) { c.pop } }.should raise_error(Timeout::Error) - lambda { Timeout::timeout(0.1) { c.receive } }.should raise_error(Timeout::Error) + timed_out = false + select! do |s| + s.case(c, :receive){} + s.timeout(0.1){ timed_out = true } + end + timed_out.should == true + c.close end it "should default to bi-directional communication" do + c = channel!(String, 1) lambda { c.send "hello" }.should_not raise_error lambda { c.receive }.should_not raise_error + + c.direction.should == :bidirectional end + + it "should be able to be dup'd as a uni-directional channel" do + c = channel!(String, 1) + + send_only = c.as_send_only + send_only.direction.should == :send + + receive_only = c.as_receive_only + receive_only.direction.should == :receive + + send_only.send("nifty") + receive_only.receive[0].should == "nifty" + end end + context "closing" do + before do + @c = channel!(String) + end + + it "not raise an error the first time it is called" do + lambda { @c.close }.should_not raise_error + @c.closed?.should be_true + end + + it "should raise an error the second time it is called" do + @c.close + lambda { @c.close }.should raise_error(Agent::Errors::ChannelClosed) + end + + it "should respond to closed?" do + @c.closed?.should be_false + @c.close + @c.closed?.should be_true + end + + it "should return that a receive was a failure when a channel is closed while being read from" do + go!{ sleep 0.01; @c.close } + _, ok = @c.receive + ok.should be_false + end + + it "should raise an error when sending to a channel that has already been closed" do + @c.close + lambda { @c.send("a") }.should raise_error(Agent::Errors::ChannelClosed) + end + + it "should raise an error when receiving from a channel that has already been closed" do + @c.close + lambda { @c.receive }.should raise_error(Agent::Errors::ChannelClosed) + end + end + + context "deadlock" do + before do + @c = channel!(String) + end + + it "should deadlock on single thread", :vm => :ruby do + lambda { @c.receive }.should raise_error + end + + it "should not deadlock with multiple threads" do + go!{ sleep(0.1); @c.push "hi" } + lambda { @c.receive }.should_not raise_error + end + end + context "typed" do it "should create a typed channel" do - lambda { Channel.new(:name => "spec") }.should raise_error Channel::Untyped - lambda { Channel.new(:name => "spec", :type => Integer) }.should_not raise_error + lambda { channel! }.should raise_error Agent::Errors::Untyped + c = nil + lambda { c = channel!(Integer) }.should_not raise_error + c.close end it "should reject messages of invalid type" do - lambda { c.send 1 }.should raise_error(Channel::InvalidType) + c = channel!(String) + go!{ c.receive } + lambda { c.send 1 }.should raise_error(Agent::Errors::InvalidType) lambda { c.send "hello" }.should_not raise_error - c.receive + c.close end end - context "transport" do - it "should default to memory transport" do - c.transport.should == Agent::Transport::Queue - end + context "buffering" do + # The capacity, in number of elements, sets the size of the buffer in the channel. + # If the capacity is greater than zero, the channel is buffered: provided the + # buffer is not full, sends can succeed without blocking. If the capacity is zero + # or absent, the communication succeeds only when both a sender and receiver are ready. - context "channels of channels" do - # One of the most important properties of Go is that a channel is a first-class - # value that can be allocated and passed around like any other. A common use of - # this property is to implement safe, parallel demultiplexing. - # - http://golang.org/doc/effective_go.html#chan_of_chan + it "should default to unbuffered" do + n = Time.now + c = channel!(String) - it "should be a first class, serializable value" do - lambda { Marshal.dump(c) }.should_not raise_error - lambda { Marshal.load(Marshal.dump(c)).is_a? Channel }.should_not raise_error - end + go!{ sleep(0.15); c.send("hello") } + c.receive[0].should == "hello" - it "should be able to pass as a value on a different channel" do - c.send "hello" - - cm = Marshal.load(Marshal.dump(c)) - cm.receive.should == "hello" - end + (Time.now - n).should be_within(0.05).of(0.15) end - context "capacity" do - # The capacity, in number of elements, sets the size of the buffer in the channel. - # If the capacity is greater than zero, the channel is asynchronous: provided the - # buffer is not full, sends can succeed without blocking. If the capacity is zero - # or absent, the communication succeeds only when both a sender and receiver are ready. + it "should support buffered" do + c = channel!(String, 2) + r = [] - it "should default to synchronous communication" do - c = Channel.new(:name => "buffered", :type => String) + c.send "hello 1" + c.send "hello 2" - c.send "hello" - c.receive.should == "hello" - lambda { Timeout::timeout(0.1) { c.receive } }.should raise_error(Timeout::Error) + select! do |s| + s.case(c, :send, "hello 3") + s.timeout(0.1) + end - c.close + c.receive[0].should == "hello 1" + c.receive[0].should == "hello 2" + select! do |s| + s.case(c, :receive){|v| r.push(v) } + s.timeout(0.1) end - it "should support asynchronous communication with buffered capacity" do - c = Channel.new(:name => "buffered", :type => String, :size => 2) + c.close + end + end - c.send "hello 1" - c.send "hello 2" - lambda { Timeout::timeout(0.1) { c.send "hello 3" } }.should raise_error(Timeout::Error) + context "channels of channels" do + before do + @c = channel!(String, 1) + end - c.receive.should == "hello 1" - c.receive.should == "hello 2" - lambda { Timeout::timeout(0.1) { c.receive } }.should raise_error(Timeout::Error) + after do + @c.close unless @c.closed? + end - c.close - end + # One of the most important properties of Go is that a channel is a first-class + # value that can be allocated and passed around like any other. A common use of + # this property is to implement safe, parallel demultiplexing. + # - http://golang.org/doc/effective_go.html#chan_of_chan + + it "should be a first class, serializable value" do + lambda { Marshal.dump(@c) }.should_not raise_error + lambda { Marshal.load(Marshal.dump(@c)).is_a?(Agent::Channel) }.should_not raise_error + end + + it "should be able to pass as a value on a different channel" do + @c.send "hello" + + cm = Marshal.load(Marshal.dump(@c)) + cm.receive[0].should == "hello" end end end