diff options
-rw-r--r-- | lib/net/ssh/test.rb | 4 | ||||
-rw-r--r-- | lib/net/ssh/test/script.rb | 81 | ||||
-rw-r--r-- | lib/net/ssh/test/socket.rb | 20 |
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 |