zerg_support 0.0.9 → 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +2 -0
- data/Manifest +7 -2
- data/lib/zerg_support/event_machine/connection_mocks.rb +12 -12
- data/lib/zerg_support/event_machine/protocol_adapter.rb +53 -0
- data/lib/zerg_support/process.rb +1 -2
- data/lib/zerg_support/{event_machine → protocols}/frame_protocol.rb +16 -11
- data/lib/zerg_support/{event_machine → protocols}/object_protocol.rb +5 -5
- data/lib/zerg_support/socket_factory.rb +198 -0
- data/lib/zerg_support/sockets/protocol_adapter.rb +59 -0
- data/lib/zerg_support/sockets/socket_mocks.rb +60 -0
- data/lib/zerg_support.rb +7 -8
- data/test/test_frame_protocol.rb +48 -19
- data/test/test_object_protocol.rb +6 -4
- data/test/test_process.rb +5 -0
- data/test/test_socket_factory.rb +93 -0
- data/test/test_spawn.rb +1 -0
- data/zerg_support.gemspec +7 -8
- metadata +20 -8
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
CHANGELOG
|
2
2
|
lib/zerg_support/event_machine/connection_mocks.rb
|
3
|
-
lib/zerg_support/event_machine/
|
4
|
-
lib/zerg_support/event_machine/object_protocol.rb
|
3
|
+
lib/zerg_support/event_machine/protocol_adapter.rb
|
5
4
|
lib/zerg_support/gems.rb
|
6
5
|
lib/zerg_support/open_ssh.rb
|
7
6
|
lib/zerg_support/process.rb
|
7
|
+
lib/zerg_support/protocols/frame_protocol.rb
|
8
|
+
lib/zerg_support/protocols/object_protocol.rb
|
9
|
+
lib/zerg_support/socket_factory.rb
|
10
|
+
lib/zerg_support/sockets/protocol_adapter.rb
|
11
|
+
lib/zerg_support/sockets/socket_mocks.rb
|
8
12
|
lib/zerg_support/spawn.rb
|
9
13
|
lib/zerg_support.rb
|
10
14
|
LICENSE
|
@@ -19,4 +23,5 @@ test/test_gems.rb
|
|
19
23
|
test/test_object_protocol.rb
|
20
24
|
test/test_open_ssh.rb
|
21
25
|
test/test_process.rb
|
26
|
+
test/test_socket_factory.rb
|
22
27
|
test/test_spawn.rb
|
@@ -1,6 +1,9 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Zerg::Support::EventMachine
|
3
|
+
|
1
4
|
# Mocks the sending end of an EventMachine connection.
|
2
5
|
# The sent data is concatenated in a string available by calling #string.
|
3
|
-
class
|
6
|
+
class SendMock
|
4
7
|
attr_reader :string
|
5
8
|
|
6
9
|
def initialize
|
@@ -10,17 +13,17 @@ class Zerg::Support::EventMachine::SendMock
|
|
10
13
|
def send_data(data)
|
11
14
|
@string << data
|
12
15
|
end
|
13
|
-
end
|
16
|
+
end # class SendMock
|
14
17
|
|
15
18
|
# Mocks the receiving end of an EventMachine connection.
|
16
19
|
# The data to be received is passed as an array of strings to the constructor.
|
17
20
|
# Calling #replay mocks receiving the data.
|
18
|
-
class
|
21
|
+
class ReceiveMock
|
19
22
|
attr_accessor :strings
|
20
23
|
attr_accessor :objects
|
21
24
|
|
22
25
|
def initialize(strings = [''])
|
23
|
-
@strings = strings
|
26
|
+
@strings = strings.kind_of?(String) ? [strings] : strings
|
24
27
|
@objects = []
|
25
28
|
end
|
26
29
|
|
@@ -30,17 +33,14 @@ class Zerg::Support::EventMachine::ReceiveMock
|
|
30
33
|
self
|
31
34
|
end
|
32
35
|
|
33
|
-
#:nodoc:
|
34
|
-
def receive__object(object)
|
35
|
-
@objects << object
|
36
|
-
end
|
37
|
-
|
38
36
|
# Declares the name of the object to be received. For instance, a frame
|
39
37
|
# protocol would use :frame for name. This generates a receive_frame method,
|
40
38
|
# and a frames accessor.
|
41
39
|
def self.object_name(name)
|
42
|
-
|
40
|
+
define_method(:"receive_#{name}") { |object| @objects << object }
|
43
41
|
return if name == :object
|
44
|
-
alias_method "#{name}s"
|
42
|
+
alias_method :"#{name}s", :objects
|
45
43
|
end
|
46
|
-
end
|
44
|
+
end # class ReceiveMock
|
45
|
+
|
46
|
+
end # namespace Zerg::Support::EventMachine
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Zerg::Support::EventMachine
|
3
|
+
|
4
|
+
# Adapts generic Protocol modules for easy inclusion in EventMachine connection
|
5
|
+
# classes / modules.
|
6
|
+
module ProtocolAdapter
|
7
|
+
def self.adapter_module(protocol_module, object_name = nil)
|
8
|
+
unless object_name
|
9
|
+
object_name = /(^|:)([^:]*)Protocol$/.
|
10
|
+
match(protocol_module.name)[2].split(/(?=[A-Z])/).join('_').downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
state_class = Class.new StateBase
|
14
|
+
state_class.send :include, protocol_module
|
15
|
+
state_class.class_eval do
|
16
|
+
# Called by the protocol when an entire object is available.
|
17
|
+
define_method :"received_#{object_name}" do |object|
|
18
|
+
@target.send :"receive_#{object_name}", object
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
adapter = Module.new
|
23
|
+
adapter.module_eval do
|
24
|
+
# Called by Event Machine when TCP stream data is available.
|
25
|
+
define_method :receive_data do |data|
|
26
|
+
@zerg_protocol_adapter_state ||= state_class.new self
|
27
|
+
@zerg_protocol_adapter_state.received_bytes data
|
28
|
+
end
|
29
|
+
|
30
|
+
# Called by the Event Machine user to send data.
|
31
|
+
define_method :"send_#{object_name}" do |object|
|
32
|
+
@zerg_protocol_adapter_state ||= state_class.new self
|
33
|
+
@zerg_protocol_adapter_state.send :"send_#{object_name}", object
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
adapter
|
38
|
+
end
|
39
|
+
|
40
|
+
# Base class for the adapter state.
|
41
|
+
class StateBase
|
42
|
+
def initialize(target)
|
43
|
+
@target = target
|
44
|
+
end
|
45
|
+
|
46
|
+
# Called by the protocol when an entire object is available.
|
47
|
+
def send_bytes(data)
|
48
|
+
@target.send_data data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end # namespace Zerg::Support::EventMachine
|
data/lib/zerg_support/process.rb
CHANGED
@@ -127,11 +127,10 @@ module Zerg::Support::Process
|
|
127
127
|
self.kill_primitive pid, false
|
128
128
|
Thread.new(pid) do |victim_pid|
|
129
129
|
Kernel.sleep 0.2
|
130
|
-
self.kill_primitive
|
130
|
+
self.kill_primitive(pid, true) rescue nil
|
131
131
|
end
|
132
132
|
rescue
|
133
133
|
# we probably don't have the right to kill the process
|
134
|
-
print "#{$!.class.name}: #{$!}\n"
|
135
134
|
end
|
136
135
|
end
|
137
136
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
#:nodoc: namespace
|
2
|
-
module Zerg::Support::
|
2
|
+
module Zerg::Support::Protocols
|
3
3
|
|
4
|
-
#
|
5
|
-
module FrameProtocol
|
6
|
-
|
7
|
-
def
|
4
|
+
# Protocol for sending and receiving discrete-sized frames over TCP streams.
|
5
|
+
module FrameProtocol
|
6
|
+
# Called when data is available from the TCP stream.
|
7
|
+
def received_bytes(data)
|
8
8
|
@frame_protocol_varsize ||= ''
|
9
9
|
|
10
10
|
i = 0
|
@@ -22,7 +22,7 @@ module FrameProtocol
|
|
22
22
|
return if @frame_protocol_buffer.nil?
|
23
23
|
break if @frame_protocol_bytes_left > data.size - i
|
24
24
|
|
25
|
-
|
25
|
+
received_frame @frame_protocol_buffer + data[i, @frame_protocol_bytes_left]
|
26
26
|
@frame_protocol_varsize, @frame_protocol_buffer = '', nil
|
27
27
|
i += @frame_protocol_bytes_left
|
28
28
|
end
|
@@ -32,14 +32,19 @@ module FrameProtocol
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# Override to process incoming frames.
|
35
|
-
def
|
35
|
+
def received_frame(frame_data); end
|
36
36
|
|
37
|
-
# Sends a frame via the underlying
|
37
|
+
# Sends a frame via the underlying TCP stream.
|
38
38
|
def send_frame(frame_data)
|
39
|
-
|
40
|
-
send_data encoded_length + frame_data
|
39
|
+
send_bytes FrameProtocol.encode_frame(frame_data)
|
41
40
|
end
|
42
41
|
|
42
|
+
# :nodoc: Encodes frame data into data to be sent across a TCP wire.
|
43
|
+
def self.encode_frame(frame_data)
|
44
|
+
encoded_length = FrameProtocol.encode_natural(frame_data.length)
|
45
|
+
encoded_length + frame_data
|
46
|
+
end
|
47
|
+
|
43
48
|
#:nodoc: Encodes a natural (non-negative) integer into a string.
|
44
49
|
def self.encode_natural(number)
|
45
50
|
string = ''
|
@@ -65,4 +70,4 @@ module FrameProtocol
|
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
68
|
-
end
|
73
|
+
end # namespace Zerg::Support::Protocols
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
#:nodoc: namespace
|
4
|
-
module Zerg::Support::
|
4
|
+
module Zerg::Support::Protocols
|
5
5
|
|
6
6
|
# Event Machine protocol for sending serializable objects.
|
7
7
|
module ObjectProtocol
|
@@ -13,12 +13,12 @@ module ObjectProtocol
|
|
13
13
|
end
|
14
14
|
|
15
15
|
#:nodoc: Processes an incoming frame and de-serializes the object in it.
|
16
|
-
def
|
17
|
-
|
16
|
+
def received_frame(frame_data)
|
17
|
+
received_object YAML.load(frame_data)
|
18
18
|
end
|
19
19
|
|
20
20
|
# Override to process incoming objects.
|
21
|
-
def
|
21
|
+
def received_object(object); end
|
22
22
|
end
|
23
23
|
|
24
|
-
end
|
24
|
+
end # namespace Zerg::Support::Protocols
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# :nodoc: namespace
|
4
|
+
module Zerg::Support
|
5
|
+
|
6
|
+
module SocketFactory
|
7
|
+
# Splits a host:port IP address into its components. IPv6 addresses welcome.
|
8
|
+
#
|
9
|
+
# The port will be nil if the IP address doesn't contain it.
|
10
|
+
def self.split_address(address)
|
11
|
+
port_match = /(^|[^:])\:([^:].*)$/.match(address)
|
12
|
+
if port_match
|
13
|
+
port = port_match[2]
|
14
|
+
host = address[0, address.length - port.length - 1]
|
15
|
+
[(host.empty? ? nil : host), port]
|
16
|
+
else
|
17
|
+
[address, nil]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# The host from a host:port IP address.
|
22
|
+
#
|
23
|
+
# Empty string if the IP address does not contain a host (e.g. :3000)
|
24
|
+
def self.host_from_address(address)
|
25
|
+
address and split_address(address)[0]
|
26
|
+
end
|
27
|
+
|
28
|
+
# The port from a host:port IP address.
|
29
|
+
#
|
30
|
+
# nil if the IP address does not contain a port (e.g. localhost)
|
31
|
+
def self.port_from_address(address)
|
32
|
+
address and (port_string = split_address(address)[1]) and port_string.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
# True for options requesting a connecting (as opposed to listening) socket.
|
36
|
+
def self.outbound?(options)
|
37
|
+
[:out_port, :out_host, :out_addr].any? { |k| options[k] }
|
38
|
+
end
|
39
|
+
|
40
|
+
# The host from a host:port IP address, in a form suitable for bind().
|
41
|
+
def self.bind_host(options)
|
42
|
+
host_from_address(options[:in_addr]) or options[:in_host] or '0.0.0.0'
|
43
|
+
end
|
44
|
+
|
45
|
+
# The port from a host:port IP address, in a form suitable for bind().
|
46
|
+
def self.bind_port(options)
|
47
|
+
port_from_address(options[:in_addr]) or options[:in_port] or 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# An address suitable for bind() based on the options.
|
51
|
+
def self.bind_socket_address(options)
|
52
|
+
Socket::pack_sockaddr_in bind_port(options), bind_host(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
# The host from a host:port IP address, in a form suitable for connect().
|
56
|
+
def self.connect_host(options)
|
57
|
+
options[:out_host] or host_from_address(options[:out_addr]) or 'localhost'
|
58
|
+
end
|
59
|
+
|
60
|
+
# The port from a host:port IP address, in a form suitable for connect().
|
61
|
+
def self.connect_port(options)
|
62
|
+
port_from_address(options[:out_addr]) or options[:out_port]
|
63
|
+
end
|
64
|
+
|
65
|
+
# True if the options indicate TCP should be used, false for UDP.
|
66
|
+
def self.tcp?(options)
|
67
|
+
options[:tcp] or !options[:udp]
|
68
|
+
end
|
69
|
+
|
70
|
+
# The socket() type based on the options.
|
71
|
+
def self.socket_type(options)
|
72
|
+
tcp?(options) ? Socket::SOCK_STREAM : Socket::SOCK_DGRAM
|
73
|
+
end
|
74
|
+
|
75
|
+
# Sets socket flags (via setsockopt) based on the options.
|
76
|
+
def self.set_options(socket, options)
|
77
|
+
if options[:no_delay]
|
78
|
+
if tcp?(options)
|
79
|
+
socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
|
80
|
+
end
|
81
|
+
socket.sync = true
|
82
|
+
end
|
83
|
+
|
84
|
+
if options[:reuse_addr]
|
85
|
+
socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
|
86
|
+
end
|
87
|
+
|
88
|
+
unless options[:reverse_lookup]
|
89
|
+
if socket.respond_to? :do_not_reverse_lookup
|
90
|
+
socket.do_not_reverse_lookup = true
|
91
|
+
else
|
92
|
+
# work around until the patch below actually gets committed:
|
93
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2346
|
94
|
+
BasicSocket.do_not_reverse_lookup = true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Hacks a socket's accept method so that new sockets have the given flags set.
|
100
|
+
#
|
101
|
+
# The flags are set in a similar manner to set_options.
|
102
|
+
def self.set_options_on_accept_sockets(socket, options)
|
103
|
+
socket.instance_variable_set :@zerg_support_factory_options, options
|
104
|
+
def socket.accept(*args)
|
105
|
+
sock, addr = super
|
106
|
+
Zerg::Support::SocketFactory.set_options sock,
|
107
|
+
@zerg_support_factory_options
|
108
|
+
return sock, addr
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Sugar-coat the socket's listen() call with a default value for its argument.
|
113
|
+
def self.sugar_socket_listen(socket)
|
114
|
+
def socket.listen(*args)
|
115
|
+
args = [1000] if args.empty?
|
116
|
+
super(*args)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Binds a socket to an address based on the options.
|
121
|
+
def self.bind(socket, options)
|
122
|
+
socket.bind bind_socket_address(options)
|
123
|
+
socket
|
124
|
+
end
|
125
|
+
|
126
|
+
# New inbound socket based on the options.
|
127
|
+
def self.new_inbound_socket(options)
|
128
|
+
socket = Socket.new Socket::AF_INET, socket_type(options), Socket::PF_UNSPEC
|
129
|
+
set_options socket, options
|
130
|
+
set_options_on_accept_sockets socket, options
|
131
|
+
sugar_socket_listen socket
|
132
|
+
bind socket, options
|
133
|
+
end
|
134
|
+
|
135
|
+
# Retrieves possible IP addresses to connect to based on the given options.
|
136
|
+
#
|
137
|
+
# The retrieval is done via setsockopt.
|
138
|
+
def self.addr_infos(options)
|
139
|
+
Socket.getaddrinfo connect_host(options), connect_port(options),
|
140
|
+
Socket::AF_INET, socket_type(options)
|
141
|
+
end
|
142
|
+
|
143
|
+
# New outbound socket based on an address obtained from getaddrinfo().
|
144
|
+
def self.new_outbound_socket_with_addr_info(addr_info)
|
145
|
+
Socket.new addr_info[4], addr_info[5], addr_info[6]
|
146
|
+
end
|
147
|
+
|
148
|
+
# Connects a socket to an address obtained from getaddrinfo().
|
149
|
+
#
|
150
|
+
# Returns the socket for success, or nil if the connection failed.
|
151
|
+
def self.connect_with_addr_info(socket, addr_info)
|
152
|
+
begin
|
153
|
+
socket.connect Socket.pack_sockaddr_in(addr_info[1], addr_info[3])
|
154
|
+
socket
|
155
|
+
rescue
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# New outbound socket based on the options.
|
161
|
+
def self.new_outbound_socket(options)
|
162
|
+
addr_infos = self.addr_infos options
|
163
|
+
addr_infos.each do |addr_info|
|
164
|
+
socket = new_outbound_socket_with_addr_info addr_info
|
165
|
+
set_options socket, options
|
166
|
+
return socket if connect_with_addr_info socket, addr_info
|
167
|
+
socket.close rescue nil
|
168
|
+
end
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# Kitchen-sink socket creation method. The following options are supported:
|
173
|
+
# tcp:: forces the use of TCP (overrides the udp option)
|
174
|
+
# udp:: selects UDP (the default is TCP)
|
175
|
+
# out_port:: the port to connect an outgoing socket to
|
176
|
+
# out_host:: the host to connect an outgoing socket to
|
177
|
+
# out_addr:: the host:port address to connect an outgoing socket to; the
|
178
|
+
# host and port override out_port and out_host, which can be used
|
179
|
+
# in conjunction with out_addr to provide default values
|
180
|
+
# in_port:: the port to bind a listening socket to
|
181
|
+
# in_host:: the host to bind a listening socket to
|
182
|
+
# in_addr:: the host:port address to bind a listening socket to; the host
|
183
|
+
# and port override in_port and in_host, which can be used in
|
184
|
+
# conjunction with in_addr to provide default values
|
185
|
+
# no_delay:: disables Nagles' algorithm
|
186
|
+
# reuse_addr:: allows binding other sockets to this socket's address
|
187
|
+
# reverse_lookup:: enables reverse lookups on connections; this is the Ruby
|
188
|
+
# default, but ZergSupport changes it for performance
|
189
|
+
def self.socket(options)
|
190
|
+
if outbound? options
|
191
|
+
new_outbound_socket options
|
192
|
+
else
|
193
|
+
new_inbound_socket options
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end # class SocketFactory
|
197
|
+
|
198
|
+
end # namespace Zerg::Support
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Zerg::Support::Sockets
|
3
|
+
|
4
|
+
# Adapts generic Protocol modules to modules that can extend sockets.
|
5
|
+
module ProtocolAdapter
|
6
|
+
def self.adapter_module(protocol_module, object_name = nil)
|
7
|
+
unless object_name
|
8
|
+
object_name = /(^|:)([^:]*)Protocol$/.
|
9
|
+
match(protocol_module.name)[2].split(/(?=[A-Z])/).join('_').downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
state_class = Class.new StateBase
|
13
|
+
state_class.send :include, protocol_module
|
14
|
+
state_class.class_eval do
|
15
|
+
# Called by the protocol when an entire object is available.
|
16
|
+
define_method :"received_#{object_name}" do |object|
|
17
|
+
@recv_object_buffer << object
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
adapter = Module.new
|
22
|
+
adapter.module_eval do
|
23
|
+
# Receives an object from a socket.
|
24
|
+
define_method :"recv_#{object_name}" do
|
25
|
+
@zerg_protocol_adapter_state ||= state_class.new self
|
26
|
+
while @zerg_protocol_adapter_state.recv_object_buffer.length == 0
|
27
|
+
data = recv 65536
|
28
|
+
return nil if data.length == 0 # Other side closed socket.
|
29
|
+
@zerg_protocol_adapter_state.received_bytes data
|
30
|
+
end
|
31
|
+
@zerg_protocol_adapter_state.recv_object_buffer.shift
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sends an object across a socket.
|
35
|
+
define_method :"send_#{object_name}" do |object|
|
36
|
+
@zerg_protocol_adapter_state ||= state_class.new self
|
37
|
+
@zerg_protocol_adapter_state.send :"send_#{object_name}", object
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
adapter
|
42
|
+
end
|
43
|
+
|
44
|
+
# Base class for the adapter state.
|
45
|
+
class StateBase
|
46
|
+
attr_reader :recv_object_buffer
|
47
|
+
def initialize(target)
|
48
|
+
@recv_object_buffer = []
|
49
|
+
@target = target
|
50
|
+
end
|
51
|
+
|
52
|
+
# Called by the protocol when an entire object is available.
|
53
|
+
def send_bytes(data)
|
54
|
+
@target.send data, 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end # namespace Zerg::Support::Sockets
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Zerg::Support::Sockets
|
3
|
+
|
4
|
+
# Mocks the sending end of a Socket connection.
|
5
|
+
# The sent data is concatenated in a string available by calling #string.
|
6
|
+
class SendMock
|
7
|
+
attr_reader :string
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@string = ''
|
11
|
+
end
|
12
|
+
|
13
|
+
def send(data, flags)
|
14
|
+
@string << data
|
15
|
+
end
|
16
|
+
end # class SendMock
|
17
|
+
|
18
|
+
# Mocks the receiving end of a Socket connection.
|
19
|
+
# The data to be received is passed as an array of strings to the constructor.
|
20
|
+
class ReceiveMock
|
21
|
+
attr_accessor :strings
|
22
|
+
attr_accessor :objects
|
23
|
+
|
24
|
+
def initialize(strings = [''])
|
25
|
+
@strings = strings.kind_of?(String) ? [strings] : strings
|
26
|
+
@objects = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def recv(byte_limit)
|
30
|
+
bytes = @strings.shift
|
31
|
+
return '' unless bytes
|
32
|
+
if bytes.length > byte_limit
|
33
|
+
@strings.unshift bytes[byte_limit, bytes.length]
|
34
|
+
bytes = bytes[0, byte_limit]
|
35
|
+
end
|
36
|
+
bytes
|
37
|
+
end
|
38
|
+
|
39
|
+
# Declares the name of the object to be received. For instance, a frame
|
40
|
+
# protocol would use :frame for name. This generates a receive_frame method,
|
41
|
+
# and a frames accessor.
|
42
|
+
def self.object_name(name)
|
43
|
+
# Calls recv_object until the bytes buffer is drained.
|
44
|
+
define_method :replay do
|
45
|
+
while @strings.length > 0
|
46
|
+
@objects << self.send(:"recv_#{name}")
|
47
|
+
end
|
48
|
+
loop do
|
49
|
+
object = self.send(:"recv_#{name}")
|
50
|
+
break unless object
|
51
|
+
@objects << object
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
return if name == :object
|
56
|
+
alias_method :"#{name}s", :objects
|
57
|
+
end
|
58
|
+
end # class ReceiveMock
|
59
|
+
|
60
|
+
end # namespace Zerg::Support::Sockets
|
data/lib/zerg_support.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# :nodoc
|
1
|
+
# :nodoc: namespace
|
2
2
|
module Zerg
|
3
3
|
end
|
4
4
|
|
@@ -10,12 +10,11 @@ require 'rubygems'
|
|
10
10
|
require 'zerg_support/gems.rb'
|
11
11
|
require 'zerg_support/process.rb'
|
12
12
|
require 'zerg_support/open_ssh.rb'
|
13
|
+
require 'zerg_support/socket_factory.rb'
|
13
14
|
require 'zerg_support/spawn.rb'
|
14
|
-
|
15
|
-
# TODO(victor): document this
|
16
|
-
module Zerg::Support::EventMachine
|
17
|
-
end
|
18
|
-
|
19
15
|
require 'zerg_support/event_machine/connection_mocks.rb'
|
20
|
-
require 'zerg_support/event_machine/
|
21
|
-
require 'zerg_support/
|
16
|
+
require 'zerg_support/event_machine/protocol_adapter.rb'
|
17
|
+
require 'zerg_support/protocols/frame_protocol.rb'
|
18
|
+
require 'zerg_support/protocols/object_protocol.rb'
|
19
|
+
require 'zerg_support/sockets/protocol_adapter.rb'
|
20
|
+
require 'zerg_support/sockets/socket_mocks.rb'
|
data/test/test_frame_protocol.rb
CHANGED
@@ -2,23 +2,12 @@ require 'zerg_support'
|
|
2
2
|
|
3
3
|
require 'test/unit'
|
4
4
|
|
5
|
-
|
6
|
-
FP = Zerg::Support::
|
7
|
-
|
8
|
-
# Send mock for frames.
|
9
|
-
class SendFramesMock < Zerg::Support::EventMachine::SendMock
|
10
|
-
include FP
|
11
|
-
end
|
12
|
-
|
13
|
-
# Receive mock for frames.
|
14
|
-
class ReceiveFramesMock < Zerg::Support::EventMachine::ReceiveMock
|
15
|
-
include FP
|
16
|
-
object_name :frame
|
17
|
-
end
|
5
|
+
module FrameProtocolTestMethods
|
6
|
+
FP = Zerg::Support::Protocols::FrameProtocol
|
18
7
|
|
19
8
|
def setup
|
20
9
|
super
|
21
|
-
@send_mock = SendFramesMock.new
|
10
|
+
@send_mock = self.class::SendFramesMock.new
|
22
11
|
end
|
23
12
|
|
24
13
|
def teardown
|
@@ -38,12 +27,13 @@ class TestFrameProtocol < Test::Unit::TestCase
|
|
38
27
|
i += sublen
|
39
28
|
end
|
40
29
|
in_strings << in_string[i..-1] if i < in_string.length
|
41
|
-
out_frames =
|
30
|
+
out_frames =
|
31
|
+
self.class::ReceiveFramesMock.new(@send_mock.string).replay.frames
|
42
32
|
assert_equal frames, out_frames
|
43
33
|
end
|
44
34
|
|
45
35
|
def test_empty_frame
|
46
|
-
continuous_data_test ['']
|
36
|
+
continuous_data_test ['']
|
47
37
|
end
|
48
38
|
|
49
39
|
def test_byte_frame
|
@@ -88,7 +78,7 @@ class TestFrameProtocol < Test::Unit::TestCase
|
|
88
78
|
ex_frames = [s2_frame] * s2_count
|
89
79
|
|
90
80
|
# build cut points in a string
|
91
|
-
s2_points = [
|
81
|
+
s2_points = [1, 2, 3, 4, 5, 127, 128, 8190, 16381, 16382, 16383]
|
92
82
|
cut_points = []
|
93
83
|
0.upto(s2_count - 1) do |i|
|
94
84
|
cut_points += s2_points.map { |p| p + i * s2_string.length }
|
@@ -101,7 +91,8 @@ class TestFrameProtocol < Test::Unit::TestCase
|
|
101
91
|
packets = [0...cut_points[i], cut_points[i]...cut_points[j],
|
102
92
|
cut_points[j]...cut_points[k], cut_points[k]..-1].
|
103
93
|
map { |r| send_string[r] }
|
104
|
-
assert_equal ex_frames,
|
94
|
+
assert_equal ex_frames,
|
95
|
+
self.class::ReceiveFramesMock.new(packets).replay.frames
|
105
96
|
end
|
106
97
|
end
|
107
98
|
end
|
@@ -115,5 +106,43 @@ class TestFrameProtocol < Test::Unit::TestCase
|
|
115
106
|
assert_equal entry.last, FP.encode_natural(entry.first)
|
116
107
|
assert_equal entry.first, FP.decode_natural(entry.last)
|
117
108
|
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class FrameProtocolEventMachineTest < Test::Unit::TestCase
|
113
|
+
include FrameProtocolTestMethods
|
114
|
+
|
115
|
+
ProtocolAdapter = Zerg::Support::EventMachine::ProtocolAdapter
|
116
|
+
FP = Zerg::Support::Protocols::FrameProtocol
|
117
|
+
FPAdapter = ProtocolAdapter.adapter_module FP
|
118
|
+
|
119
|
+
# Send mock for frames.
|
120
|
+
class SendFramesMock < Zerg::Support::EventMachine::SendMock
|
121
|
+
include FPAdapter
|
122
|
+
end
|
123
|
+
|
124
|
+
# Receive mock for frames.
|
125
|
+
class ReceiveFramesMock < Zerg::Support::EventMachine::ReceiveMock
|
126
|
+
include FPAdapter
|
127
|
+
object_name :frame
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class FrameProtocolSocketsTest < Test::Unit::TestCase
|
132
|
+
include FrameProtocolTestMethods
|
133
|
+
|
134
|
+
ProtocolAdapter = Zerg::Support::Sockets::ProtocolAdapter
|
135
|
+
FP = Zerg::Support::Protocols::FrameProtocol
|
136
|
+
FPAdapter = ProtocolAdapter.adapter_module FP
|
137
|
+
|
138
|
+
# Send mock for frames.
|
139
|
+
class SendFramesMock < Zerg::Support::Sockets::SendMock
|
140
|
+
include FPAdapter
|
141
|
+
end
|
142
|
+
|
143
|
+
# Receive mock for frames.
|
144
|
+
class ReceiveFramesMock < Zerg::Support::Sockets::ReceiveMock
|
145
|
+
include FPAdapter
|
146
|
+
object_name :frame
|
118
147
|
end
|
119
|
-
end
|
148
|
+
end
|
@@ -3,13 +3,15 @@ require 'test/unit'
|
|
3
3
|
require 'zerg_support'
|
4
4
|
|
5
5
|
class ObjectProtocolTest < Test::Unit::TestCase
|
6
|
-
|
6
|
+
ProtocolAdapter = Zerg::Support::EventMachine::ProtocolAdapter
|
7
|
+
OP = Zerg::Support::Protocols::ObjectProtocol
|
8
|
+
OPAdapter = ProtocolAdapter.adapter_module OP
|
7
9
|
|
8
10
|
class SendObjectMock < Zerg::Support::EventMachine::SendMock
|
9
|
-
include
|
11
|
+
include OPAdapter
|
10
12
|
end
|
11
13
|
class ReceiveObjectMock < Zerg::Support::EventMachine::ReceiveMock
|
12
|
-
include
|
14
|
+
include OPAdapter
|
13
15
|
object_name :object
|
14
16
|
end
|
15
17
|
|
@@ -52,4 +54,4 @@ class ObjectProtocolTest < Test::Unit::TestCase
|
|
52
54
|
"1234567890",
|
53
55
|
{:command => 'run', :sequence => [1, nil, 2, true, "Q\n"]})
|
54
56
|
end
|
55
|
-
end
|
57
|
+
end
|
data/test/test_process.rb
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'zerg_support'
|
4
|
+
|
5
|
+
|
6
|
+
class SocketFactoryTest < Test::Unit::TestCase
|
7
|
+
SF = Zerg::Support::SocketFactory
|
8
|
+
|
9
|
+
OP = Zerg::Support::Protocols::ObjectProtocol
|
10
|
+
OPAdapter = Zerg::Support::Sockets::ProtocolAdapter.adapter_module OP
|
11
|
+
|
12
|
+
def setup
|
13
|
+
Thread.abort_on_exception = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_host_from_address
|
21
|
+
assert_equal nil, SF.host_from_address(nil)
|
22
|
+
assert_equal nil, SF.host_from_address(':9000')
|
23
|
+
assert_equal '127.0.0.1', SF.host_from_address('127.0.0.1')
|
24
|
+
assert_equal '127.0.0.1', SF.host_from_address('127.0.0.1:1234')
|
25
|
+
assert_equal 'fe80::1%lo0', SF.host_from_address('fe80::1%lo0')
|
26
|
+
assert_equal 'fe80::1%lo0', SF.host_from_address('fe80::1%lo0:19020')
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_port_from_address
|
30
|
+
assert_equal nil, SF.port_from_address(nil)
|
31
|
+
assert_equal 9000, SF.port_from_address(':9000')
|
32
|
+
assert_equal nil, SF.port_from_address('127.0.0.1')
|
33
|
+
assert_equal 1234, SF.port_from_address('127.0.0.1:1234')
|
34
|
+
assert_equal nil, SF.port_from_address('fe80::1%lo0')
|
35
|
+
assert_equal 19020, SF.port_from_address('fe80::1%lo0:19020')
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_inbound
|
39
|
+
assert !SF.outbound?(:in_port => 1)
|
40
|
+
assert !SF.outbound?(:in_host => '1')
|
41
|
+
assert !SF.outbound?(:in_addr => '1')
|
42
|
+
assert SF.outbound?(:out_port => 1)
|
43
|
+
assert SF.outbound?(:out_host => '1')
|
44
|
+
assert SF.outbound?(:out_addr => '1')
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_tcp
|
48
|
+
assert SF.tcp?({})
|
49
|
+
assert SF.tcp?(:tcp => true)
|
50
|
+
assert !SF.tcp?(:udp => true)
|
51
|
+
end
|
52
|
+
|
53
|
+
def _test_connection(server_options, client_options = nil)
|
54
|
+
client_options ||= server_options
|
55
|
+
test_port = 31996
|
56
|
+
|
57
|
+
cli_gold = { :request_type => 1, :request_name => "moo" }
|
58
|
+
srv_gold = { :response_type => 2, :response_value => [true, 314] }
|
59
|
+
cli_hash = nil
|
60
|
+
|
61
|
+
# Server thread.
|
62
|
+
Thread.new do
|
63
|
+
server = SF.socket({:in_addr => ":#{test_port}"}.merge server_options)
|
64
|
+
server.listen
|
65
|
+
serv_client, client_addrinfo = server.accept
|
66
|
+
serv_client.extend OPAdapter
|
67
|
+
cli_hash = serv_client.recv_object
|
68
|
+
serv_client.send_object srv_gold
|
69
|
+
serv_client.close
|
70
|
+
|
71
|
+
server.close
|
72
|
+
end
|
73
|
+
|
74
|
+
# Client.
|
75
|
+
client = SF.socket({:out_addr => "localhost:#{test_port}"}.
|
76
|
+
merge(client_options))
|
77
|
+
client.extend OPAdapter
|
78
|
+
client.send_object cli_gold
|
79
|
+
srv_hash = client.recv_object
|
80
|
+
client.close
|
81
|
+
|
82
|
+
# Checks
|
83
|
+
assert_equal cli_gold, cli_hash, "Client -> server failed"
|
84
|
+
assert_equal srv_gold, srv_hash, "Server -> client failed"
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_connection
|
88
|
+
_test_connection({:no_delay => true})
|
89
|
+
_test_connection({:reuse_addr => true})
|
90
|
+
|
91
|
+
# TODO(costan): fix UDP at some point
|
92
|
+
end
|
93
|
+
end
|
data/test/test_spawn.rb
CHANGED
data/zerg_support.gemspec
CHANGED
@@ -2,27 +2,26 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{zerg_support}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Victor Costan"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-06-02}
|
10
10
|
s.description = %q{Support libraries used by Zergling.Net deployment code.}
|
11
11
|
s.email = %q{victor@zergling.net}
|
12
|
-
s.extra_rdoc_files = ["CHANGELOG", "lib/zerg_support/event_machine/connection_mocks.rb", "lib/zerg_support/event_machine/frame_protocol.rb", "lib/zerg_support/
|
13
|
-
s.files = ["CHANGELOG", "lib/zerg_support/event_machine/connection_mocks.rb", "lib/zerg_support/event_machine/frame_protocol.rb", "lib/zerg_support/
|
14
|
-
s.has_rdoc = true
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/zerg_support/event_machine/connection_mocks.rb", "lib/zerg_support/event_machine/protocol_adapter.rb", "lib/zerg_support/gems.rb", "lib/zerg_support/open_ssh.rb", "lib/zerg_support/process.rb", "lib/zerg_support/protocols/frame_protocol.rb", "lib/zerg_support/protocols/object_protocol.rb", "lib/zerg_support/socket_factory.rb", "lib/zerg_support/sockets/protocol_adapter.rb", "lib/zerg_support/sockets/socket_mocks.rb", "lib/zerg_support/spawn.rb", "lib/zerg_support.rb", "LICENSE", "README"]
|
13
|
+
s.files = ["CHANGELOG", "lib/zerg_support/event_machine/connection_mocks.rb", "lib/zerg_support/event_machine/protocol_adapter.rb", "lib/zerg_support/gems.rb", "lib/zerg_support/open_ssh.rb", "lib/zerg_support/process.rb", "lib/zerg_support/protocols/frame_protocol.rb", "lib/zerg_support/protocols/object_protocol.rb", "lib/zerg_support/socket_factory.rb", "lib/zerg_support/sockets/protocol_adapter.rb", "lib/zerg_support/sockets/socket_mocks.rb", "lib/zerg_support/spawn.rb", "lib/zerg_support.rb", "LICENSE", "Manifest", "Rakefile", "README", "RUBYFORGE", "test/fork_tree.rb", "test/test_connection_mocks.rb", "test/test_frame_protocol.rb", "test/test_gems.rb", "test/test_object_protocol.rb", "test/test_open_ssh.rb", "test/test_process.rb", "test/test_socket_factory.rb", "test/test_spawn.rb", "zerg_support.gemspec"]
|
15
14
|
s.homepage = %q{http://www.zergling.net}
|
16
15
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Zerg_support", "--main", "README"]
|
17
16
|
s.require_paths = ["lib"]
|
18
17
|
s.rubyforge_project = %q{zerglings}
|
19
|
-
s.rubygems_version = %q{1.3.
|
18
|
+
s.rubygems_version = %q{1.3.4}
|
20
19
|
s.summary = %q{Support libraries used by Zergling.Net deployment code.}
|
21
|
-
s.test_files = ["test/test_connection_mocks.rb", "test/test_frame_protocol.rb", "test/test_gems.rb", "test/test_object_protocol.rb", "test/test_open_ssh.rb", "test/test_process.rb", "test/test_spawn.rb"]
|
20
|
+
s.test_files = ["test/test_connection_mocks.rb", "test/test_frame_protocol.rb", "test/test_gems.rb", "test/test_object_protocol.rb", "test/test_open_ssh.rb", "test/test_process.rb", "test/test_socket_factory.rb", "test/test_spawn.rb"]
|
22
21
|
|
23
22
|
if s.respond_to? :specification_version then
|
24
23
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
-
s.specification_version =
|
24
|
+
s.specification_version = 3
|
26
25
|
|
27
26
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
27
|
s.add_development_dependency(%q<echoe>, [">= 3.0.2"])
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zerg_support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: "0.1"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Costan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-06-02 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -51,11 +51,15 @@ extensions: []
|
|
51
51
|
extra_rdoc_files:
|
52
52
|
- CHANGELOG
|
53
53
|
- lib/zerg_support/event_machine/connection_mocks.rb
|
54
|
-
- lib/zerg_support/event_machine/
|
55
|
-
- lib/zerg_support/event_machine/object_protocol.rb
|
54
|
+
- lib/zerg_support/event_machine/protocol_adapter.rb
|
56
55
|
- lib/zerg_support/gems.rb
|
57
56
|
- lib/zerg_support/open_ssh.rb
|
58
57
|
- lib/zerg_support/process.rb
|
58
|
+
- lib/zerg_support/protocols/frame_protocol.rb
|
59
|
+
- lib/zerg_support/protocols/object_protocol.rb
|
60
|
+
- lib/zerg_support/socket_factory.rb
|
61
|
+
- lib/zerg_support/sockets/protocol_adapter.rb
|
62
|
+
- lib/zerg_support/sockets/socket_mocks.rb
|
59
63
|
- lib/zerg_support/spawn.rb
|
60
64
|
- lib/zerg_support.rb
|
61
65
|
- LICENSE
|
@@ -63,11 +67,15 @@ extra_rdoc_files:
|
|
63
67
|
files:
|
64
68
|
- CHANGELOG
|
65
69
|
- lib/zerg_support/event_machine/connection_mocks.rb
|
66
|
-
- lib/zerg_support/event_machine/
|
67
|
-
- lib/zerg_support/event_machine/object_protocol.rb
|
70
|
+
- lib/zerg_support/event_machine/protocol_adapter.rb
|
68
71
|
- lib/zerg_support/gems.rb
|
69
72
|
- lib/zerg_support/open_ssh.rb
|
70
73
|
- lib/zerg_support/process.rb
|
74
|
+
- lib/zerg_support/protocols/frame_protocol.rb
|
75
|
+
- lib/zerg_support/protocols/object_protocol.rb
|
76
|
+
- lib/zerg_support/socket_factory.rb
|
77
|
+
- lib/zerg_support/sockets/protocol_adapter.rb
|
78
|
+
- lib/zerg_support/sockets/socket_mocks.rb
|
71
79
|
- lib/zerg_support/spawn.rb
|
72
80
|
- lib/zerg_support.rb
|
73
81
|
- LICENSE
|
@@ -82,10 +90,13 @@ files:
|
|
82
90
|
- test/test_object_protocol.rb
|
83
91
|
- test/test_open_ssh.rb
|
84
92
|
- test/test_process.rb
|
93
|
+
- test/test_socket_factory.rb
|
85
94
|
- test/test_spawn.rb
|
86
95
|
- zerg_support.gemspec
|
87
96
|
has_rdoc: true
|
88
97
|
homepage: http://www.zergling.net
|
98
|
+
licenses: []
|
99
|
+
|
89
100
|
post_install_message:
|
90
101
|
rdoc_options:
|
91
102
|
- --line-numbers
|
@@ -111,9 +122,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
122
|
requirements: []
|
112
123
|
|
113
124
|
rubyforge_project: zerglings
|
114
|
-
rubygems_version: 1.3.
|
125
|
+
rubygems_version: 1.3.4
|
115
126
|
signing_key:
|
116
|
-
specification_version:
|
127
|
+
specification_version: 3
|
117
128
|
summary: Support libraries used by Zergling.Net deployment code.
|
118
129
|
test_files:
|
119
130
|
- test/test_connection_mocks.rb
|
@@ -122,4 +133,5 @@ test_files:
|
|
122
133
|
- test/test_object_protocol.rb
|
123
134
|
- test/test_open_ssh.rb
|
124
135
|
- test/test_process.rb
|
136
|
+
- test/test_socket_factory.rb
|
125
137
|
- test/test_spawn.rb
|