summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/net/ssh/test.rb4
-rw-r--r--lib/net/ssh/test/script.rb81
-rw-r--r--lib/net/ssh/test/socket.rb20
3 files changed, 100 insertions, 5 deletions
diff --git a/lib/net/ssh/test.rb b/lib/net/ssh/test.rb
index 3248982..5ecb7d5 100644
--- a/lib/net/ssh/test.rb
+++ b/lib/net/ssh/test.rb
@@ -42,6 +42,10 @@ module Net; module SSH
# end
#
# See Net::SSH::Test::Channel and Net::SSH::Test::Script for more options.
+ #
+ # Note that the Net::SSH::Test system is rather finicky yet, and can be kind
+ # of frustrating to get working. Any suggestions for improvement will be
+ # welcome!
module Test
# If a block is given, yields the script for the test socket (#socket).
# Otherwise, simply returns the socket's script. See Net::SSH::Test::Script.
diff --git a/lib/net/ssh/test/script.rb b/lib/net/ssh/test/script.rb
index 69b5d92..89ee064 100644
--- a/lib/net/ssh/test/script.rb
+++ b/lib/net/ssh/test/script.rb
@@ -4,13 +4,34 @@ require 'net/ssh/test/remote_packet'
module Net; module SSH; module Test
+ # Represents a sequence of scripted events that identify the behavior that
+ # a test expects. Methods named "sends_*" create events for packets being
+ # sent from the local to the remote host, and methods named "gets_*" create
+ # events for packets being received by the local from the remote host.
+ #
+ # A reference to a script. is generally obtained in a unit test via the
+ # Net::SSH::Test#story helper method:
+ #
+ # story do |script|
+ # channel = script.opens_channel
+ # ...
+ # end
class Script
+ # The list of scripted events. These will be Net::SSH::Test::LocalPacket
+ # and Net::SSH::Test::RemotePacket instances.
attr_reader :events
+ # Create a new, empty script.
def initialize
@events = []
end
+ # Scripts the opening of a channel by adding a local packet sending the
+ # channel open request, and if +confirm+ is true (the default), also
+ # adding a remote packet confirming the new channel.
+ #
+ # A new Net::SSH::Test::Channel instance is returned, which can be used
+ # to script additional channel operations.
def opens_channel(confirm=true)
channel = Channel.new(self)
channel.remote_id = 5555
@@ -24,14 +45,31 @@ module Net; module SSH; module Test
channel
end
- def sends(*args)
- events << LocalPacket.new(*args)
+ # A convenience method for adding an arbitrary local packet to the events
+ # list.
+ def sends(type, *args, &block)
+ events << LocalPacket.new(type, *args, &block)
end
- def gets(*args)
- events << RemotePacket.new(*args)
+ # A convenience method for adding an arbitrary remote packet to the events
+ # list.
+ def gets(type, *args)
+ events << RemotePacket.new(type, *args)
end
+ # Scripts the sending of a new channel request packet to the remote host.
+ # +channel+ should be an instance of Net::SSH::Test::Channel. +request+
+ # is a string naming the request type to send, +reply+ is a boolean
+ # indicating whether a response to this packet is required , and +data+
+ # is any additional request-specific data that this packet should send.
+ # +success+ indicates whether the response (if one is required) should be
+ # success or failure.
+ #
+ # If a reply is desired, a remote packet will also be queued, :channel_success
+ # if +success+ is true, or :channel_failure if +success+ is false.
+ #
+ # This will typically be called via Net::SSH::Test::Channel#sends_exec or
+ # Net::SSH::Test::Channel#sends_subsystem.
def sends_channel_request(channel, request, reply, data, success=true)
events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, data)
if reply
@@ -43,38 +81,73 @@ module Net; module SSH; module Test
end
end
+ # Scripts the sending of a channel data packet. +channel+ must be a
+ # Net::SSH::Test::Channel object, and +data+ is the (string) data to
+ # expect will be sent.
+ #
+ # This will typically be called via Net::SSH::Test::Channel#sends_data.
def sends_channel_data(channel, data)
events << LocalPacket.new(:channel_data, channel.remote_id, data)
end
+ # Scripts the sending of a channel EOF packet from the given
+ # Net::SSH::Test::Channel +channel+. This will typically be called via
+ # Net::SSH::Test::Channel#sends_eof.
def sends_channel_eof(channel)
events << LocalPacket.new(:channel_eof, channel.remote_id)
end
+ # Scripts the sending of a channel close packet from the given
+ # Net::SSH::Test::Channel +channel+. This will typically be called via
+ # Net::SSH::Test::Channel#sends_close.
def sends_channel_close(channel)
events << LocalPacket.new(:channel_close, channel.remote_id)
end
+ # Scripts the reception of a channel data packet from the remote host by
+ # the given Net::SSH::Test::Channel +channel+. This will typically be
+ # called via Net::SSH::Test::Channel#gets_data.
def gets_channel_data(channel, data)
events << RemotePacket.new(:channel_data, channel.local_id, data)
end
+ # Scripts the reception of a channel request packet from the remote host by
+ # the given Net::SSH::Test::Channel +channel+. This will typically be
+ # called via Net::SSH::Test::Channel#gets_exit_status.
def gets_channel_request(channel, request, reply, data)
events << RemotePacket.new(:channel_request, channel.local_id, request, reply, data)
end
+ # Scripts the reception of a channel EOF packet from the remote host by
+ # the given Net::SSH::Test::Channel +channel+. This will typically be
+ # called via Net::SSH::Test::Channel#gets_eof.
def gets_channel_eof(channel)
events << RemotePacket.new(:channel_eof, channel.local_id)
end
+ # Scripts the reception of a channel close packet from the remote host by
+ # the given Net::SSH::Test::Channel +channel+. This will typically be
+ # called via Net::SSH::Test::Channel#gets_close.
def gets_channel_close(channel)
events << RemotePacket.new(:channel_close, channel.local_id)
end
+ # By default, removes the next event in the list and returns it. However,
+ # this can also be used to non-destructively peek at the next event in the
+ # list, by passing :first as the argument.
+ #
+ # # remove the next event and return it
+ # event = script.next
+ #
+ # # peek at the next event
+ # event = script.next(:first)
def next(mode=:shift)
events.send(mode)
end
+ # Compare the given packet against the next event in the list. If there is
+ # no next event, an exception will be raised. This is called by
+ # Net::SSH::Test::Extensions::PacketStream#test_enqueue_packet.
def process(packet)
event = events.shift or raise "end of script reached, but got a packet type #{packet.read_byte}"
event.process(packet)
diff --git a/lib/net/ssh/test/socket.rb b/lib/net/ssh/test/socket.rb
index dd52541..4741255 100644
--- a/lib/net/ssh/test/socket.rb
+++ b/lib/net/ssh/test/socket.rb
@@ -5,9 +5,20 @@ require 'net/ssh/test/script'
module Net; module SSH; module Test
+ # A mock socket implementation for use in testing. It implements the minimum
+ # necessary interface for interacting with the rest of the Net::SSH::Test
+ # system.
class Socket < StringIO
- attr_reader :host, :port, :script
+ attr_reader :host, :port
+ # The Net::SSH::Test::Script object in use by this socket. This is the
+ # canonical script instance that should be used for any test depending on
+ # this socket instance.
+ attr_reader :script
+
+ # Create a new test socket. This will also instantiate a new Net::SSH::Test::Script
+ # and seed it with the necessary events to power the initialization of the
+ # connection.
def initialize
extend(Net::SSH::Transport::PacketStream)
super "SSH-2.0-Test\r\n"
@@ -20,19 +31,26 @@ module Net; module SSH; module Test
script.gets(:newkeys)
end
+ # This doesn't actually do anything, since we don't really care what gets
+ # written.
def write(data)
# black hole, because we don't actually care about what gets written
end
+ # Allows the socket to also mimic a socket factory, simply returning
+ # +self+.
def open(host, port)
@host, @port = host, port
self
end
+ # Returns a sockaddr struct for the port and host that were used when the
+ # socket was instantiated.
def getpeername
::Socket.sockaddr_in(port, host)
end
+ # Alias to #read, but never returns nil (returns an empty string instead).
def recv(n)
read(n) || ""
end