require 'net/ssh/test/channel' require 'net/ssh/test/local_packet' 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 events << LocalPacket.new(:channel_open) { |p| channel.local_id = p[:remote_id] } if confirm events << RemotePacket.new(:channel_open_confirmation, channel.local_id, channel.remote_id, 0x20000, 0x10000) end channel end # 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 # 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 if success events << RemotePacket.new(:channel_success, channel.local_id) else events << RemotePacket.new(:channel_failure, channel.local_id) end 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) end end end; end; end