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