ffi-rxs 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/AUTHORS.txt +21 -0
- data/Gemfile +3 -0
- data/README.rdoc +86 -0
- data/Rakefile +6 -0
- data/ext/README +5 -0
- data/ffi-rxs.gemspec +25 -0
- data/lib/ffi-rxs/constants.rb +104 -0
- data/lib/ffi-rxs/constants.rb~ +100 -0
- data/lib/ffi-rxs/context.rb +153 -0
- data/lib/ffi-rxs/context.rb~ +155 -0
- data/lib/ffi-rxs/device.rb~ +28 -0
- data/lib/ffi-rxs/exceptions.rb +47 -0
- data/lib/ffi-rxs/exceptions.rb~ +47 -0
- data/lib/ffi-rxs/libc.rb +19 -0
- data/lib/ffi-rxs/libc.rb~ +19 -0
- data/lib/ffi-rxs/libxs.rb +156 -0
- data/lib/ffi-rxs/libxs.rb~ +156 -0
- data/lib/ffi-rxs/message.rb +282 -0
- data/lib/ffi-rxs/message.rb~ +282 -0
- data/lib/ffi-rxs/poll.rb +212 -0
- data/lib/ffi-rxs/poll.rb~ +212 -0
- data/lib/ffi-rxs/poll_items.rb +120 -0
- data/lib/ffi-rxs/poll_items.rb~ +120 -0
- data/lib/ffi-rxs/socket.rb +659 -0
- data/lib/ffi-rxs/socket.rb~ +659 -0
- data/lib/ffi-rxs/util.rb +105 -0
- data/lib/ffi-rxs/util.rb~ +105 -0
- data/lib/ffi-rxs/version.rb +3 -0
- data/lib/ffi-rxs/version.rb~ +3 -0
- data/lib/ffi-rxs.rb +74 -0
- data/spec/context_spec.rb +138 -0
- data/spec/message_spec.rb +128 -0
- data/spec/multipart_spec.rb +108 -0
- data/spec/nonblocking_recv_spec.rb +309 -0
- data/spec/poll_spec.rb +168 -0
- data/spec/pushpull_spec.rb +113 -0
- data/spec/reqrep_spec.rb +66 -0
- data/spec/socket_spec.rb +496 -0
- data/spec/spec_helper.rb +57 -0
- metadata +126 -0
data/lib/ffi-rxs/util.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
module XS
|
3
|
+
|
4
|
+
# These methods don't belong to any specific class. They get included
|
5
|
+
# in the #Context, #Socket and #Poller classes.
|
6
|
+
#
|
7
|
+
module Util
|
8
|
+
|
9
|
+
# Returns true when +rc+ is greater than or equal to 0, false otherwise.
|
10
|
+
#
|
11
|
+
# We use the >= test because xs_poll() returns the number of sockets
|
12
|
+
# that had a read or write event triggered. So, a >= 0 result means
|
13
|
+
# it succeeded.
|
14
|
+
#
|
15
|
+
def self.resultcode_ok? rc
|
16
|
+
rc >= 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the +errno+ as set by the libxs library.
|
20
|
+
#
|
21
|
+
def self.errno
|
22
|
+
LibXS.xs_errno
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a string corresponding to the currently set #errno. These
|
26
|
+
# error strings are defined by libxs.
|
27
|
+
#
|
28
|
+
def self.error_string
|
29
|
+
LibXS.xs_strerror(errno).read_string
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns an array of the form [major, minor, patch] to represent the
|
33
|
+
# version of libxs.
|
34
|
+
#
|
35
|
+
# Class method! Invoke as: XS::Util.version
|
36
|
+
#
|
37
|
+
def self.version
|
38
|
+
major = FFI::MemoryPointer.new :int
|
39
|
+
minor = FFI::MemoryPointer.new :int
|
40
|
+
patch = FFI::MemoryPointer.new :int
|
41
|
+
LibXS.xs_version major, minor, patch
|
42
|
+
[major.read_int, minor.read_int, patch.read_int]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Attempts to bind to a random tcp port on +host+ up to +max_tries+
|
46
|
+
# times. Returns the port number upon success or nil upon failure.
|
47
|
+
#
|
48
|
+
def self.bind_to_random_tcp_port host = '127.0.0.1', max_tries = 500
|
49
|
+
tries = 0
|
50
|
+
rc = -1
|
51
|
+
|
52
|
+
while !resultcode_ok?(rc) && tries < max_tries
|
53
|
+
tries += 1
|
54
|
+
random = random_port
|
55
|
+
rc = socket.bind "tcp://#{host}:#{random}"
|
56
|
+
end
|
57
|
+
|
58
|
+
resultcode_ok?(rc) ? random : nil
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# generate a random port between 10_000 and 65534
|
65
|
+
def self.random_port
|
66
|
+
rand(55534) + 10_000
|
67
|
+
end
|
68
|
+
|
69
|
+
# :doc:
|
70
|
+
# Called by most library methods to verify there were no errors during
|
71
|
+
# operation. If any are found, raise the appropriate #XSError.
|
72
|
+
#
|
73
|
+
# When no error is found, this method returns +true+ which is behavior
|
74
|
+
# used internally by #send and #recv.
|
75
|
+
#
|
76
|
+
def error_check source, result_code
|
77
|
+
if -1 == result_code
|
78
|
+
raise_error source, result_code
|
79
|
+
end
|
80
|
+
|
81
|
+
# used by Socket::send/recv, ignored by others
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def raise_error source, result_code
|
86
|
+
if 'xs_init' == source || 'xs_socket' == source
|
87
|
+
raise ContextError.new source, result_code, XS::Util.errno, XS::Util.error_string
|
88
|
+
|
89
|
+
elsif ['xs_msg_init', 'xs_msg_init_data', 'xs_msg_copy', 'xs_msg_move'].include?(source)
|
90
|
+
raise MessageError.new source, result_code, XS::Util.errno, XS::Util.error_string
|
91
|
+
|
92
|
+
else
|
93
|
+
puts "else"
|
94
|
+
raise XSError.new source, result_code, -1,
|
95
|
+
"Source [#{source}] does not match any xs_* strings, rc [#{result_code}], errno [#{XS::Util.errno}], error_string [#{XS::Util.error_string}]"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def eagain?
|
100
|
+
EAGAIN == XS::Util.errno
|
101
|
+
end
|
102
|
+
|
103
|
+
end # module Util
|
104
|
+
|
105
|
+
end # module XS
|
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
module XS
|
3
|
+
|
4
|
+
# These methods don't belong to any specific class. They get included
|
5
|
+
# in the #Context, #Socket and #Poller classes.
|
6
|
+
#
|
7
|
+
module Util
|
8
|
+
|
9
|
+
# Returns true when +rc+ is greater than or equal to 0, false otherwise.
|
10
|
+
#
|
11
|
+
# We use the >= test because xs_poll() returns the number of sockets
|
12
|
+
# that had a read or write event triggered. So, a >= 0 result means
|
13
|
+
# it succeeded.
|
14
|
+
#
|
15
|
+
def self.resultcode_ok? rc
|
16
|
+
rc >= 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the +errno+ as set by the libxs library.
|
20
|
+
#
|
21
|
+
def self.errno
|
22
|
+
LibXS.xs_errno
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a string corresponding to the currently set #errno. These
|
26
|
+
# error strings are defined by libxs.
|
27
|
+
#
|
28
|
+
def self.error_string
|
29
|
+
LibXS.xs_strerror(errno).read_string
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns an array of the form [major, minor, patch] to represent the
|
33
|
+
# version of libzmq.
|
34
|
+
#
|
35
|
+
# Class method! Invoke as: XS::Util.version
|
36
|
+
#
|
37
|
+
def self.version
|
38
|
+
major = FFI::MemoryPointer.new :int
|
39
|
+
minor = FFI::MemoryPointer.new :int
|
40
|
+
patch = FFI::MemoryPointer.new :int
|
41
|
+
LibXS.xs_version major, minor, patch
|
42
|
+
[major.read_int, minor.read_int, patch.read_int]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Attempts to bind to a random tcp port on +host+ up to +max_tries+
|
46
|
+
# times. Returns the port number upon success or nil upon failure.
|
47
|
+
#
|
48
|
+
def self.bind_to_random_tcp_port host = '127.0.0.1', max_tries = 500
|
49
|
+
tries = 0
|
50
|
+
rc = -1
|
51
|
+
|
52
|
+
while !resultcode_ok?(rc) && tries < max_tries
|
53
|
+
tries += 1
|
54
|
+
random = random_port
|
55
|
+
rc = socket.bind "tcp://#{host}:#{random}"
|
56
|
+
end
|
57
|
+
|
58
|
+
resultcode_ok?(rc) ? random : nil
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# generate a random port between 10_000 and 65534
|
65
|
+
def self.random_port
|
66
|
+
rand(55534) + 10_000
|
67
|
+
end
|
68
|
+
|
69
|
+
# :doc:
|
70
|
+
# Called by most library methods to verify there were no errors during
|
71
|
+
# operation. If any are found, raise the appropriate #XSError.
|
72
|
+
#
|
73
|
+
# When no error is found, this method returns +true+ which is behavior
|
74
|
+
# used internally by #send and #recv.
|
75
|
+
#
|
76
|
+
def error_check source, result_code
|
77
|
+
if -1 == result_code
|
78
|
+
raise_error source, result_code
|
79
|
+
end
|
80
|
+
|
81
|
+
# used by Socket::send/recv, ignored by others
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def raise_error source, result_code
|
86
|
+
if 'xs_init' == source || 'xs_socket' == source
|
87
|
+
raise ContextError.new source, result_code, XS::Util.errno, XS::Util.error_string
|
88
|
+
|
89
|
+
elsif ['xs_msg_init', 'xs_msg_init_data', 'xs_msg_copy', 'xs_msg_move'].include?(source)
|
90
|
+
raise MessageError.new source, result_code, XS::Util.errno, XS::Util.error_string
|
91
|
+
|
92
|
+
else
|
93
|
+
puts "else"
|
94
|
+
raise XSError.new source, result_code, -1,
|
95
|
+
"Source [#{source}] does not match any xs_* strings, rc [#{result_code}], errno [#{XS::Util.errno}], error_string [#{XS::Util.error_string}]"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def eagain?
|
100
|
+
EAGAIN == XS::Util.errno
|
101
|
+
end
|
102
|
+
|
103
|
+
end # module Util
|
104
|
+
|
105
|
+
end # module XS
|
data/lib/ffi-rxs.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module XS
|
3
|
+
|
4
|
+
# :stopdoc:
|
5
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
6
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
7
|
+
# :startdoc:
|
8
|
+
|
9
|
+
# Returns the version string for the library.
|
10
|
+
#
|
11
|
+
def self.version
|
12
|
+
@version ||= File.read(path('version.txt')).strip
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the library path for the module. If any arguments are given,
|
16
|
+
# they will be joined to the end of the libray path using
|
17
|
+
# <tt>File.join</tt>.
|
18
|
+
#
|
19
|
+
def self.libpath( *args, &block )
|
20
|
+
rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
21
|
+
if block
|
22
|
+
begin
|
23
|
+
$LOAD_PATH.unshift LIBPATH
|
24
|
+
rv = block.call
|
25
|
+
ensure
|
26
|
+
$LOAD_PATH.shift
|
27
|
+
end
|
28
|
+
end
|
29
|
+
return rv
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the lpath for the module. If any arguments are given,
|
33
|
+
# they will be joined to the end of the path using
|
34
|
+
# <tt>File.join</tt>.
|
35
|
+
#
|
36
|
+
def self.path( *args, &block )
|
37
|
+
rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
38
|
+
if block
|
39
|
+
begin
|
40
|
+
$LOAD_PATH.unshift PATH
|
41
|
+
rv = block.call
|
42
|
+
ensure
|
43
|
+
$LOAD_PATH.shift
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return rv
|
47
|
+
end
|
48
|
+
|
49
|
+
# Utility method used to require all files ending in .rb that lie in the
|
50
|
+
# directory below this file that has the same name as the filename passed
|
51
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
52
|
+
# the _filename_ does not have to be equivalent to the directory.
|
53
|
+
#
|
54
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
55
|
+
dir ||= ::File.basename(fname, '.*')
|
56
|
+
search_me = ::File.expand_path(
|
57
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
58
|
+
|
59
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
60
|
+
end
|
61
|
+
|
62
|
+
end # module XS
|
63
|
+
|
64
|
+
# some code is conditionalized based upon what ruby engine we are
|
65
|
+
# executing
|
66
|
+
|
67
|
+
RBX = defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ ? true : false
|
68
|
+
|
69
|
+
require 'ffi' unless RBX
|
70
|
+
|
71
|
+
# the order of files is important
|
72
|
+
%w(libc libxs constants util exceptions context message socket poll_items poll).each do |file|
|
73
|
+
require XS.libpath(['ffi-rxs', file])
|
74
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
$: << "." # added for ruby 1.9.2 compatibilty; it doesn't include the current directory on the load path anymore
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
4
|
+
|
5
|
+
module XS
|
6
|
+
|
7
|
+
|
8
|
+
describe Context do
|
9
|
+
|
10
|
+
context "when initializing with factory method #create" do
|
11
|
+
include APIHelper
|
12
|
+
|
13
|
+
it "should set the :pointer accessor to non-nil" do
|
14
|
+
ctx = Context.create
|
15
|
+
ctx.pointer.should_not be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set the :context accessor to non-nil" do
|
19
|
+
ctx = Context.create
|
20
|
+
ctx.context.should_not be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should set the :pointer and :context accessors to the same value" do
|
24
|
+
ctx = Context.create
|
25
|
+
ctx.pointer.should == ctx.context
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should define a finalizer on this object" do
|
29
|
+
ObjectSpace.should_receive(:define_finalizer)
|
30
|
+
ctx = Context.create
|
31
|
+
end
|
32
|
+
end # context initializing
|
33
|
+
|
34
|
+
|
35
|
+
context "when initializing with #new" do
|
36
|
+
include APIHelper
|
37
|
+
|
38
|
+
it "should set the :pointer accessor to non-nil" do
|
39
|
+
ctx = Context.new
|
40
|
+
ctx.pointer.should_not be_nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should set the :context accessor to non-nil" do
|
44
|
+
ctx = Context.new
|
45
|
+
ctx.context.should_not be_nil
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should set the :pointer and :context accessors to the same value" do
|
49
|
+
ctx = Context.new
|
50
|
+
ctx.pointer.should == ctx.context
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should define a finalizer on this object" do
|
54
|
+
ObjectSpace.should_receive(:define_finalizer)
|
55
|
+
ctx = Context.new
|
56
|
+
end
|
57
|
+
end # context initializing
|
58
|
+
|
59
|
+
context "when setting context options" do
|
60
|
+
include APIHelper
|
61
|
+
|
62
|
+
it "gets EINVAL when option name is not recognized" do
|
63
|
+
ctx = Context.new
|
64
|
+
rc = ctx.setctxopt(XS::IDENTITY, 10)
|
65
|
+
Util.resultcode_ok?(rc).should be_false
|
66
|
+
XS::Util.errno.should == XS::EINVAL
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when setting IO_THREADS context option" do
|
71
|
+
include APIHelper
|
72
|
+
|
73
|
+
it "should return unsuccessful code for zero io threads" do
|
74
|
+
ctx = Context.new
|
75
|
+
rc = ctx.setctxopt(XS::IO_THREADS, 0)
|
76
|
+
Util.resultcode_ok?(rc).should be_false
|
77
|
+
XS::Util.errno.should == XS::EINVAL
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return unsuccessful code for negative io threads" do
|
81
|
+
ctx = Context.new
|
82
|
+
rc = ctx.setctxopt(XS::IO_THREADS, -1)
|
83
|
+
Util.resultcode_ok?(rc).should be_false
|
84
|
+
XS::Util.errno.should == XS::EINVAL
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return successful code for positive io threads" do
|
88
|
+
ctx = Context.new
|
89
|
+
rc = ctx.setctxopt(XS::IO_THREADS, 10)
|
90
|
+
Util.resultcode_ok?(rc).should be_true
|
91
|
+
end
|
92
|
+
end # context set IO_THREADS
|
93
|
+
|
94
|
+
|
95
|
+
context "when setting MAX_SOCKETS context option" do
|
96
|
+
include APIHelper
|
97
|
+
|
98
|
+
it "should return successful code for zero max sockets" do
|
99
|
+
ctx = Context.new
|
100
|
+
rc = ctx.setctxopt(XS::MAX_SOCKETS, 0)
|
101
|
+
Util.resultcode_ok?(rc).should be_true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should return unsuccessful code for negative max sockets" do
|
105
|
+
ctx = Context.new
|
106
|
+
rc = ctx.setctxopt(XS::MAX_SOCKETS, -1)
|
107
|
+
Util.resultcode_ok?(rc).should be_false
|
108
|
+
XS::Util.errno.should == XS::EINVAL
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should return successful code for positive max sockets" do
|
112
|
+
ctx = Context.new
|
113
|
+
rc = ctx.setctxopt(XS::MAX_SOCKETS, 100)
|
114
|
+
Util.resultcode_ok?(rc).should be_true
|
115
|
+
end
|
116
|
+
end # context set MAX_SOCKETS
|
117
|
+
|
118
|
+
context "when terminating" do
|
119
|
+
it "should call xs_term to terminate the library's context" do
|
120
|
+
ctx = Context.new # can't use a shared context here because we are terminating it!
|
121
|
+
LibXS.should_receive(:xs_term).with(ctx.pointer).and_return(0)
|
122
|
+
ctx.terminate
|
123
|
+
end
|
124
|
+
end # context terminate
|
125
|
+
|
126
|
+
|
127
|
+
context "when allocating a socket" do
|
128
|
+
it "should return nil when allocation fails" do
|
129
|
+
ctx = Context.new
|
130
|
+
LibXS.stub!(:xs_socket => nil)
|
131
|
+
ctx.socket(XS::REQ).should be_nil
|
132
|
+
end
|
133
|
+
end # context socket
|
134
|
+
|
135
|
+
end # describe Context
|
136
|
+
|
137
|
+
|
138
|
+
end # module XS
|
@@ -0,0 +1,128 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
3
|
+
|
4
|
+
module XS
|
5
|
+
|
6
|
+
|
7
|
+
describe Message do
|
8
|
+
|
9
|
+
context "when initializing with an argument" do
|
10
|
+
|
11
|
+
it "calls xs_msg_init_data()" do
|
12
|
+
LibXS.should_receive(:xs_msg_init_data)
|
13
|
+
message = Message.new "text"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should *not* define a finalizer on this object" do
|
17
|
+
ObjectSpace.should_not_receive(:define_finalizer)
|
18
|
+
Message.new "text"
|
19
|
+
end
|
20
|
+
end # context initializing with arg
|
21
|
+
|
22
|
+
context "when initializing *without* an argument" do
|
23
|
+
|
24
|
+
it "calls xs_msg_init()" do
|
25
|
+
LibXS.should_receive(:xs_msg_init).and_return(0)
|
26
|
+
message = Message.new
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should *not* define a finalizer on this object" do
|
30
|
+
ObjectSpace.should_not_receive(:define_finalizer)
|
31
|
+
Message.new "text"
|
32
|
+
end
|
33
|
+
end # context initializing with arg
|
34
|
+
|
35
|
+
|
36
|
+
context "#copy_in_string" do
|
37
|
+
it "calls xs_msg_init_data()" do
|
38
|
+
message = Message.new "text"
|
39
|
+
|
40
|
+
LibXS.should_receive(:xs_msg_init_data)
|
41
|
+
message.copy_in_string("new text")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "correctly finds the length of binary data by ignoring encoding" do
|
45
|
+
message = Message.new
|
46
|
+
message.copy_in_string("\x83\x6e\x04\x00\x00\x44\xd1\x81")
|
47
|
+
message.size.should == 8
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
context "#copy" do
|
53
|
+
it "calls xs_msg_copy()" do
|
54
|
+
message = Message.new "text"
|
55
|
+
copy = Message.new
|
56
|
+
|
57
|
+
LibXS.should_receive(:xs_msg_copy)
|
58
|
+
copy.copy(message)
|
59
|
+
end
|
60
|
+
end # context copy
|
61
|
+
|
62
|
+
|
63
|
+
context "#move" do
|
64
|
+
it "calls xs_msg_move()" do
|
65
|
+
message = Message.new "text"
|
66
|
+
copy = Message.new
|
67
|
+
|
68
|
+
LibXS.should_receive(:xs_msg_move)
|
69
|
+
copy.move(message)
|
70
|
+
end
|
71
|
+
end # context move
|
72
|
+
|
73
|
+
|
74
|
+
context "#size" do
|
75
|
+
it "calls xs_msg_size()" do
|
76
|
+
message = Message.new "text"
|
77
|
+
|
78
|
+
LibXS.should_receive(:xs_msg_size)
|
79
|
+
message.size
|
80
|
+
end
|
81
|
+
end # context size
|
82
|
+
|
83
|
+
|
84
|
+
context "#data" do
|
85
|
+
it "calls xs_msg_data()" do
|
86
|
+
message = Message.new "text"
|
87
|
+
|
88
|
+
LibXS.should_receive(:xs_msg_data)
|
89
|
+
message.data
|
90
|
+
end
|
91
|
+
end # context data
|
92
|
+
|
93
|
+
|
94
|
+
context "#close" do
|
95
|
+
it "calls xs_msg_close() the first time" do
|
96
|
+
message = Message.new "text"
|
97
|
+
|
98
|
+
LibXS.should_receive(:xs_msg_close)
|
99
|
+
message.close
|
100
|
+
end
|
101
|
+
|
102
|
+
it "*does not* call xs_msg_close() on subsequent invocations" do
|
103
|
+
message = Message.new "text"
|
104
|
+
message.close
|
105
|
+
|
106
|
+
LibXS.should_not_receive(:xs_msg_close)
|
107
|
+
message.close
|
108
|
+
end
|
109
|
+
end # context close
|
110
|
+
|
111
|
+
end # describe Message
|
112
|
+
|
113
|
+
|
114
|
+
describe ManagedMessage do
|
115
|
+
|
116
|
+
context "when initializing with an argument" do
|
117
|
+
|
118
|
+
it "should define a finalizer on this object" do
|
119
|
+
ObjectSpace.should_receive(:define_finalizer)
|
120
|
+
ManagedMessage.new "text"
|
121
|
+
end
|
122
|
+
end # context initializing
|
123
|
+
|
124
|
+
|
125
|
+
end # describe ManagedMessage
|
126
|
+
|
127
|
+
|
128
|
+
end # module XS
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
2
|
+
|
3
|
+
module XS
|
4
|
+
describe Socket do
|
5
|
+
context "multipart messages" do
|
6
|
+
before(:all) { @ctx = Context.new }
|
7
|
+
after(:all) { @ctx.terminate }
|
8
|
+
|
9
|
+
context "without identity" do
|
10
|
+
include APIHelper
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
@rep = Socket.new(@ctx.pointer, XS::REP)
|
14
|
+
port = bind_to_random_tcp_port(@rep)
|
15
|
+
|
16
|
+
@req = Socket.new(@ctx.pointer, XS::REQ)
|
17
|
+
@req.connect("tcp://127.0.0.1:#{port}")
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
@req.close
|
22
|
+
@rep.close
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be delivered between REQ and REP returning an array of strings" do
|
26
|
+
req_data, rep_data = [ "1", "2" ], [ "2", "3" ]
|
27
|
+
|
28
|
+
@req.send_strings(req_data)
|
29
|
+
strings = []
|
30
|
+
rc = @rep.recv_strings(strings)
|
31
|
+
strings.should == req_data
|
32
|
+
|
33
|
+
@rep.send_strings(rep_data)
|
34
|
+
strings = []
|
35
|
+
rc = @req.recv_strings(strings)
|
36
|
+
strings.should == rep_data
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be delivered between REQ and REP returning an array of messages" do
|
40
|
+
req_data, rep_data = [ "1", "2" ], [ "2", "3" ]
|
41
|
+
|
42
|
+
@req.send_strings(req_data)
|
43
|
+
messages = []
|
44
|
+
rc = @rep.recvmsgs(messages)
|
45
|
+
messages.each_with_index do |message, index|
|
46
|
+
message.copy_out_string.should == req_data[index]
|
47
|
+
end
|
48
|
+
|
49
|
+
@rep.send_strings(rep_data)
|
50
|
+
messages = []
|
51
|
+
rc = @req.recvmsgs(messages)
|
52
|
+
messages.each_with_index do |message, index|
|
53
|
+
message.copy_out_string.should == rep_data[index]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with identity" do
|
59
|
+
include APIHelper
|
60
|
+
|
61
|
+
before(:each) do # was :all
|
62
|
+
@rep = Socket.new(@ctx.pointer, XS::XREP)
|
63
|
+
port = bind_to_random_tcp_port(@rep)
|
64
|
+
|
65
|
+
@req = Socket.new(@ctx.pointer, XS::REQ)
|
66
|
+
@req.identity = 'foo'
|
67
|
+
@req.connect("tcp://127.0.0.1:#{port}")
|
68
|
+
end
|
69
|
+
|
70
|
+
after(:each) do # was :all
|
71
|
+
@req.close
|
72
|
+
@rep.close
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should be delivered between REQ and REP returning an array of strings with an empty string as the envelope delimiter" do
|
76
|
+
req_data, rep_data = "hello", [ @req.identity, "", "ok" ]
|
77
|
+
|
78
|
+
@req.send_string(req_data)
|
79
|
+
strings = []
|
80
|
+
rc = @rep.recv_strings(strings)
|
81
|
+
strings.should == [ @req.identity, "", "hello" ]
|
82
|
+
|
83
|
+
@rep.send_strings(rep_data)
|
84
|
+
string = ''
|
85
|
+
rc = @req.recv_string(string)
|
86
|
+
string.should == rep_data.last
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should be delivered between REQ and REP returning an array of messages with an empty string as the envelope delimiter" do
|
90
|
+
req_data, rep_data = "hello", [ @req.identity, "", "ok" ]
|
91
|
+
|
92
|
+
@req.send_string(req_data)
|
93
|
+
msgs = []
|
94
|
+
rc = @rep.recvmsgs(msgs)
|
95
|
+
msgs[0].copy_out_string.should == @req.identity
|
96
|
+
msgs[1].copy_out_string.should == ""
|
97
|
+
msgs[2].copy_out_string.should == "hello"
|
98
|
+
|
99
|
+
@rep.send_strings(rep_data)
|
100
|
+
msgs = []
|
101
|
+
rc = @req.recvmsgs(msgs)
|
102
|
+
msgs[0].copy_out_string.should == rep_data.last
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|