nodule 0.0.34
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.yardopts +1 -0
- data/Gemfile +5 -0
- data/README.md +100 -0
- data/Rakefile +15 -0
- data/ci_jobs/nodule-units/run.sh +25 -0
- data/examples/cat_test.rb +45 -0
- data/examples/wget.rb +36 -0
- data/lib/nodule/alarm.rb +25 -0
- data/lib/nodule/base.rb +258 -0
- data/lib/nodule/cassandra.rb +292 -0
- data/lib/nodule/console.rb +87 -0
- data/lib/nodule/line_io.rb +74 -0
- data/lib/nodule/monkeypatch.rb +8 -0
- data/lib/nodule/process.rb +386 -0
- data/lib/nodule/tempfile.rb +57 -0
- data/lib/nodule/topology.rb +195 -0
- data/lib/nodule/unixsocket.rb +54 -0
- data/lib/nodule/util.rb +56 -0
- data/lib/nodule/version.rb +3 -0
- data/lib/nodule/zeromq.rb +280 -0
- data/lib/nodule.rb +10 -0
- data/nodule.gemspec +28 -0
- data/test/helper.rb +1 -0
- data/test/nodule_cassandra_test.rb +31 -0
- data/test/nodule_console_test.rb +11 -0
- data/test/nodule_lineio_test.rb +32 -0
- data/test/nodule_process_test.rb +25 -0
- data/test/nodule_tempfile_test.rb +22 -0
- data/test/nodule_topology_test.rb +11 -0
- data/test/nodule_unixsocket_test.rb +11 -0
- data/test/nodule_util_test.rb +25 -0
- data/test/nodule_zeromq_test.rb +44 -0
- metadata +163 -0
data/lib/nodule/util.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Nodule
|
4
|
+
module Util
|
5
|
+
@seen = {}
|
6
|
+
|
7
|
+
def self.random_tcp_port(max_tries=500)
|
8
|
+
self._random_port do |port|
|
9
|
+
TCPServer.new port
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.random_udp_port(max_tries=500)
|
14
|
+
self._random_port do |port|
|
15
|
+
socket = UDPSocket.new
|
16
|
+
socket.bind("0.0.0.0", port)
|
17
|
+
socket
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Try random ports > 10_000 looking for one that's free.
|
23
|
+
# @param [Fixnum] max number of tries to find a free port
|
24
|
+
# @return [Fixnum] port number
|
25
|
+
# @yield [Fixnum] port
|
26
|
+
#
|
27
|
+
def self._random_port(max_tries=500)
|
28
|
+
tries = 0
|
29
|
+
|
30
|
+
while tries < max_tries
|
31
|
+
port = random_port
|
32
|
+
next if @seen.has_key? port
|
33
|
+
|
34
|
+
socket = begin
|
35
|
+
yield port
|
36
|
+
rescue Errno::EADDRINUSE
|
37
|
+
@seen[port] = true
|
38
|
+
tries += 1
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
43
|
+
socket.close
|
44
|
+
return port
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Return a random integer between 10_000 and 65_534
|
50
|
+
# @return [Fixnum]
|
51
|
+
#
|
52
|
+
def self.random_port
|
53
|
+
rand(55534) + 10_000
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'nodule/tempfile'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module Nodule
|
6
|
+
#
|
7
|
+
# A resource for setting up and testing ZeroMQ message flows. The most basic usage will provide
|
8
|
+
# auto-generated IPC URI's, which can be handy for testing. More advanced usage uses the built-in
|
9
|
+
# tap device to sniff messages while they're in-flight.
|
10
|
+
#
|
11
|
+
class ZeroMQ < Tempfile
|
12
|
+
attr_reader :ctx, :uri, :method, :type, :limit, :error_count
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def setsockopt(socket, option, value)
|
17
|
+
if option == :hwm && ::ZMQ::LibZMQ.version3?
|
18
|
+
rc = socket.setsockopt(::ZMQ::SNDHWM, value)
|
19
|
+
rc = socket.setsockopt(::ZMQ::RCVHWM, value) if rc > -1
|
20
|
+
else
|
21
|
+
option = ::ZMQ::HWM if option == :hwm
|
22
|
+
rc = socket.setsockopt(option, value)
|
23
|
+
end
|
24
|
+
|
25
|
+
rc
|
26
|
+
end
|
27
|
+
|
28
|
+
public
|
29
|
+
|
30
|
+
#
|
31
|
+
# @param [Hash{Symbol => Object}] opts named parameters
|
32
|
+
# @option [String] :uri either :gen/:generate or a string, :gen means generate an IPC URI,
|
33
|
+
# a string must be a valid URI.
|
34
|
+
# @option [Fixnum] :limit exit the read loop after :limit messages are received
|
35
|
+
# @option [String,Symbol] :connect create a socket and connect to the URI
|
36
|
+
# @option [String,Symbol] :bind create a socket and bind to the URI
|
37
|
+
#
|
38
|
+
# :connect and :bind are allowed at the same time and must be of the same socket type.
|
39
|
+
# ZMQ::SUB sockets that are connected/bound will subscribe to "" by default.
|
40
|
+
#
|
41
|
+
# For the rest of the options, see Hastur::Test::Resource::Base.
|
42
|
+
#
|
43
|
+
def initialize(opts)
|
44
|
+
opts[:suffix] ||= '.zmq'
|
45
|
+
|
46
|
+
super(opts)
|
47
|
+
|
48
|
+
@ctx = ::ZMQ::Context.new
|
49
|
+
@zmq_thread = nil
|
50
|
+
@error_count = 0
|
51
|
+
@sockprocs = []
|
52
|
+
@limit = nil
|
53
|
+
@timeout_started = false
|
54
|
+
@stopped = false
|
55
|
+
|
56
|
+
# Sockets cannot be used across thread boundaries, so use a ZMQ::PAIR socket both to synchronize thread
|
57
|
+
# startup and pass writes form main -> thread. The .socket method will return the PAIR socket.
|
58
|
+
@pipe_uri = "inproc://pair-#{Nodule.next_seq}"
|
59
|
+
@pipe = @ctx.socket(::ZMQ::PAIR)
|
60
|
+
@child = @ctx.socket(::ZMQ::PAIR)
|
61
|
+
setsockopt(@pipe, :hwm, 1)
|
62
|
+
setsockopt(@child, :hwm, 1)
|
63
|
+
setsockopt(@pipe, ::ZMQ::LINGER, 1.0)
|
64
|
+
setsockopt(@child, ::ZMQ::LINGER, 1.0)
|
65
|
+
@pipe.bind(@pipe_uri)
|
66
|
+
@child.connect(@pipe_uri)
|
67
|
+
|
68
|
+
case opts[:uri]
|
69
|
+
# Socket files are specified so they land in PWD, in the future we might want to specify a temp
|
70
|
+
# dir, but that has a whole different bag of issues, so stick with simple until it's needed.
|
71
|
+
when :gen, :generate
|
72
|
+
@uri = "ipc://#{@file.to_s}"
|
73
|
+
when String
|
74
|
+
@uri = opts[:uri]
|
75
|
+
else
|
76
|
+
raise ArgumentError.new "Invalid URI specifier: (#{opts[:uri].class}) '#{opts[:uri]}'"
|
77
|
+
end
|
78
|
+
|
79
|
+
if opts[:connect] and opts[:bind] and opts[:connect] != opts[:bind]
|
80
|
+
raise ArgumentError.new "ZMQ socket types must be the same when enabling :bind and :connect"
|
81
|
+
end
|
82
|
+
|
83
|
+
# only set type and create a socket if :bind or :connect is specified
|
84
|
+
# otherwise, the caller probably just wants to generate a URI, or possibly
|
85
|
+
# use a pre-created socket? (not supported yet)
|
86
|
+
if @type = (opts[:connect] || opts[:bind])
|
87
|
+
@socket = @ctx.socket(@type)
|
88
|
+
setsockopt(@socket, :hwm, 1)
|
89
|
+
setsockopt(@socket, ::ZMQ::LINGER, 1.0)
|
90
|
+
|
91
|
+
if opts[:connect]
|
92
|
+
@sockprocs << proc { @socket.connect(@uri) } # deferred
|
93
|
+
end
|
94
|
+
|
95
|
+
if opts[:bind]
|
96
|
+
@sockprocs << proc { @socket.bind(@uri) } # deferred
|
97
|
+
end
|
98
|
+
|
99
|
+
# by default, subscribe to "" on ZMQ::SUB sockets, since that's what we want
|
100
|
+
# the vast majority of the time. Also allow specifying a subscription for those
|
101
|
+
# times we want something else.
|
102
|
+
if @type == ::ZMQ::SUB
|
103
|
+
if opts[:subscribe]
|
104
|
+
@sockprocs << proc { subscribe(opts[:subscribe]) }
|
105
|
+
else
|
106
|
+
@sockprocs << proc { subscribe("") }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if opts[:limit]
|
112
|
+
@limit = opts[:limit]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def run
|
117
|
+
super
|
118
|
+
return if @sockprocs.empty?
|
119
|
+
|
120
|
+
# wrap the block in a block so errors don't simply vanish until join time
|
121
|
+
@zmq_thread = Thread.new do
|
122
|
+
Thread.current.abort_on_exception
|
123
|
+
|
124
|
+
# sockets have to be created inside the thread that uses them
|
125
|
+
@sockprocs.each { |p| p.call }
|
126
|
+
|
127
|
+
_zmq_read()
|
128
|
+
verbose "child thread #{Thread.current} shutting down"
|
129
|
+
|
130
|
+
@child.close
|
131
|
+
@socket.close if @socket
|
132
|
+
end
|
133
|
+
|
134
|
+
Thread.pass
|
135
|
+
|
136
|
+
@stopped = @zmq_thread.alive? ? false : true
|
137
|
+
end
|
138
|
+
|
139
|
+
def socket
|
140
|
+
@pipe
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# For PUB sockets only, subscribe to a prefix.
|
145
|
+
# @param [String] subscription prefix, usually ""
|
146
|
+
#
|
147
|
+
def subscribe(subscription)
|
148
|
+
@pipe.send_strings ["subscribe", subscription]
|
149
|
+
end
|
150
|
+
|
151
|
+
def done?
|
152
|
+
@stopped
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Wait for the ZMQ thread to exit on its own, mostly useful with :limit => Fixnum.
|
157
|
+
#
|
158
|
+
# This does not signal the child thread.
|
159
|
+
#
|
160
|
+
def wait(timeout=60)
|
161
|
+
countdown = timeout.to_f
|
162
|
+
|
163
|
+
while countdown > 0
|
164
|
+
if @zmq_thread and @zmq_thread.alive?
|
165
|
+
sleep 0.1
|
166
|
+
countdown = countdown - 0.1
|
167
|
+
else
|
168
|
+
break
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
super()
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# If the thread is still alive, force an exception in the thread and
|
177
|
+
# continue to do the things stop does.
|
178
|
+
#
|
179
|
+
def stop!
|
180
|
+
if @zmq_thread.alive?
|
181
|
+
STDERR.puts "force stop! called, issuing Thread.raise"
|
182
|
+
@zmq_thread.raise "force stop! called"
|
183
|
+
end
|
184
|
+
|
185
|
+
stop
|
186
|
+
wait 1
|
187
|
+
|
188
|
+
@zmq_thread.join if @zmq_thread
|
189
|
+
@pipe.close if @pipe
|
190
|
+
|
191
|
+
@stopped = true
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# send a message to the child thread telling it to exit and join the thread
|
196
|
+
#
|
197
|
+
def stop
|
198
|
+
return if @stopped
|
199
|
+
|
200
|
+
@pipe.send_strings(["exit"], 1)
|
201
|
+
|
202
|
+
Thread.pass
|
203
|
+
|
204
|
+
super
|
205
|
+
|
206
|
+
@zmq_thread.join if @zmq_thread
|
207
|
+
@pipe.close if @pipe
|
208
|
+
|
209
|
+
@stopped = true
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# Return the URI generated/provided for this resource. For tapped devices, the "front" side
|
214
|
+
# of the tap is returned.
|
215
|
+
#
|
216
|
+
def to_s
|
217
|
+
@uri
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
#
|
223
|
+
# Run a poll loop (using the zmq poller) on a 1/5 second timer, reading data
|
224
|
+
# from the socket and calling the registered procs.
|
225
|
+
# If :limit was set, will exit after that many messages are seen/processed.
|
226
|
+
# Otherwise, exits on the next iteration if the mutex is locked (which is done in stop).
|
227
|
+
# Takes no arguments, doesn't return anything meaningful.
|
228
|
+
#
|
229
|
+
def _zmq_read
|
230
|
+
return unless @socket
|
231
|
+
@poller = ::ZMQ::Poller.new
|
232
|
+
|
233
|
+
@poller.register_readable @socket
|
234
|
+
@poller.register_readable @child
|
235
|
+
|
236
|
+
# read on the socket(s) and call the registered reader blocks for every message, always using
|
237
|
+
# multipart and converting to ruby strings to avoid ZMQ::Message cleanup issues.
|
238
|
+
count = 0
|
239
|
+
@running = true
|
240
|
+
while @running
|
241
|
+
rc = @poller.poll(1)
|
242
|
+
unless rc > 0
|
243
|
+
sleep 0.01
|
244
|
+
next
|
245
|
+
end
|
246
|
+
|
247
|
+
@poller.readables.each do |sock|
|
248
|
+
rc = sock.recv_strings messages=[]
|
249
|
+
if rc > -1
|
250
|
+
if sock == @socket
|
251
|
+
count += 1
|
252
|
+
run_readers(messages, self)
|
253
|
+
# the main thread can send messages through to be resent or "exit" to shut down this thread
|
254
|
+
elsif sock == @child
|
255
|
+
if messages[0] == "exit"
|
256
|
+
verbose "Got exit message. Exiting."
|
257
|
+
@running = false
|
258
|
+
elsif messages[0] == "subscribe"
|
259
|
+
@socket.setsockopt ::ZMQ::SUBSCRIBE, messages[1]
|
260
|
+
else
|
261
|
+
@socket.send_strings messages
|
262
|
+
end
|
263
|
+
else
|
264
|
+
raise "BUG: couldn't match socket to a known socket"
|
265
|
+
end
|
266
|
+
|
267
|
+
# stop reading after a set number of messages, regardless of whether there are any more waiting
|
268
|
+
break if @limit and count >= @limit
|
269
|
+
break unless @running
|
270
|
+
else
|
271
|
+
@error_count += 1
|
272
|
+
break
|
273
|
+
end
|
274
|
+
end # @poller.readables.each
|
275
|
+
|
276
|
+
break if @limit and count >= @limit
|
277
|
+
end # while @running
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
data/lib/nodule.rb
ADDED
data/nodule.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "nodule/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "nodule"
|
7
|
+
s.version = Nodule::VERSION
|
8
|
+
s.authors = ["Al Tobey", "Noah Gibbs", "Viet Nguyen", "Jay Bhat"]
|
9
|
+
s.email = ["al@ooyala.com", "noah@ooyala.com", "viet@ooyala.com", "bhat@ooyala.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Nodule starts, stops, tests and redirects groups of processes}
|
12
|
+
s.description = %q{Nodule lets you declare Topologies of processes, which can be started or stopped together. You can also redirect sockets, set up interprocess communication, make assertions on captured packets between processes and generally monitor or change the interaction of any of your processes. Nodule is great for integration testing or for bringing up complicated interdependent sets of processes on a single host.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "nodule"
|
15
|
+
s.required_ruby_version = ">= 1.9.2"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
# specify any dependencies here; for example:
|
23
|
+
s.add_development_dependency "minitest"
|
24
|
+
# s.add_runtime_dependency "rest-client"
|
25
|
+
s.add_runtime_dependency "ffi-rzmq"
|
26
|
+
s.add_runtime_dependency "cassandra", "=0.12.2ooyala2"
|
27
|
+
s.add_runtime_dependency "rainbow"
|
28
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'nodule/cassandra'
|
6
|
+
|
7
|
+
class NoduleCassandraTest < MiniTest::Unit::TestCase
|
8
|
+
KEYSPACE = "NoduleTest"
|
9
|
+
|
10
|
+
def test_cassandra
|
11
|
+
cass = nil
|
12
|
+
assert (cass = Nodule::Cassandra.new({:keyspace => KEYSPACE, :verbose => true})),
|
13
|
+
"Can't create Nodule::Cassandra!"
|
14
|
+
|
15
|
+
cass.run
|
16
|
+
cass.create_keyspace
|
17
|
+
|
18
|
+
assert_nil cass.waitpid
|
19
|
+
|
20
|
+
assert_kind_of Cassandra, cass.client
|
21
|
+
|
22
|
+
cfdef = CassandraThrift::CfDef.new :name => "foo", :keyspace => KEYSPACE
|
23
|
+
refute_nil cass.client.add_column_family cfdef
|
24
|
+
|
25
|
+
assert File.directory?(File.join(cass.data, KEYSPACE))
|
26
|
+
|
27
|
+
cass.stop
|
28
|
+
|
29
|
+
assert File.directory?(cass.tmp) != true
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
require 'nodule/line_io'
|
7
|
+
|
8
|
+
class NoduleLineIOTest < MiniTest::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@r_pipe, @w_pipe = IO.pipe
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_stdio
|
14
|
+
io = Nodule::LineIO.new :io => @r_pipe, :run => true, :reader => :capture
|
15
|
+
|
16
|
+
@w_pipe.puts "x"
|
17
|
+
io.require_read_count 1, 10
|
18
|
+
assert_equal "x", io.output.first.chomp, "read data from pipe"
|
19
|
+
|
20
|
+
assert_equal 1, io.read_count
|
21
|
+
@w_pipe.puts "y"
|
22
|
+
io.require_read_count 2, 10
|
23
|
+
|
24
|
+
assert_equal 2, io.read_count
|
25
|
+
io.clear!
|
26
|
+
assert_equal 0, io.read_count
|
27
|
+
@w_pipe.puts "y"
|
28
|
+
|
29
|
+
io.require_read_count 1, 10
|
30
|
+
assert_equal "y", io.output.first.chomp, "read data from pipe"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
require 'nodule/process'
|
7
|
+
|
8
|
+
class NoduleProcessTest < MiniTest::Unit::TestCase
|
9
|
+
def test_process
|
10
|
+
true_bin = File.exist?("/bin/true") ? "/bin/true" : "/usr/bin/true"
|
11
|
+
p = Nodule::Process.new true_bin
|
12
|
+
p.run
|
13
|
+
p.wait 2
|
14
|
+
assert p.done?, "true exits immediately, done? should be true"
|
15
|
+
p.stop
|
16
|
+
|
17
|
+
echo = Nodule::Process.new '/bin/echo', 'foobar', :run => true
|
18
|
+
echo.wait 2
|
19
|
+
|
20
|
+
assert_equal 'foobar', echo.output.first.chomp
|
21
|
+
assert echo.done?, "true exits immediately, done? should be true"
|
22
|
+
|
23
|
+
echo.stop
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'nodule/tempfile'
|
6
|
+
|
7
|
+
class NoduleTempfileTest < MiniTest::Unit::TestCase
|
8
|
+
def test_basic
|
9
|
+
tfile = nil
|
10
|
+
assert (tfile = Nodule::Tempfile.new), "Can't create Nodule::Tempfile!"
|
11
|
+
|
12
|
+
assert_kind_of Nodule::Tempfile, tfile
|
13
|
+
assert_kind_of Nodule::Base, tfile
|
14
|
+
|
15
|
+
assert (tfile = Nodule::Tempfile.new(:directory => true)),
|
16
|
+
"Can't create Nodule::Tempfile on a directory!"
|
17
|
+
assert_kind_of Nodule::Tempfile, tfile
|
18
|
+
assert_kind_of Nodule::Base, tfile
|
19
|
+
|
20
|
+
tfile.stop
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'nodule/util'
|
6
|
+
|
7
|
+
class NoduleUtilTest < MiniTest::Unit::TestCase
|
8
|
+
def test_random_tcp_port
|
9
|
+
port = nil
|
10
|
+
assert (port = Nodule::Util.random_tcp_port),
|
11
|
+
"Can't create Nodule::Util with random TCP port!"
|
12
|
+
assert_kind_of Fixnum, port
|
13
|
+
assert port > 1024
|
14
|
+
assert port < 65536
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_random_udp_port
|
18
|
+
port = nil
|
19
|
+
assert (port = Nodule::Util.random_udp_port),
|
20
|
+
"Can't create Nodule::Util with random UDP port!"
|
21
|
+
assert_kind_of Fixnum, port
|
22
|
+
assert port > 1024
|
23
|
+
assert port < 65536
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
require 'nodule/zeromq'
|
7
|
+
|
8
|
+
class NoduleZeromqTest < MiniTest::Unit::TestCase
|
9
|
+
def test_zeromq_setup
|
10
|
+
t1 = Nodule::ZeroMQ.new(:uri => :gen)
|
11
|
+
refute_nil t1
|
12
|
+
t2 = Nodule::ZeroMQ.new(:uri => :gen, :bind => ZMQ::PUSH)
|
13
|
+
refute_nil t2
|
14
|
+
t3 = Nodule::ZeroMQ.new(:uri => t2.uri, :connect => ZMQ::PULL)
|
15
|
+
refute_nil t3
|
16
|
+
|
17
|
+
t1.stop
|
18
|
+
t2.stop
|
19
|
+
t3.stop
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_zeromq_pubsub
|
23
|
+
pub = Nodule::ZeroMQ.new(:uri => :gen, :bind => ZMQ::PUB)
|
24
|
+
refute_nil pub
|
25
|
+
sub = Nodule::ZeroMQ.new(:uri => pub.uri, :connect => ZMQ::SUB, :reader => :capture)
|
26
|
+
refute_nil sub
|
27
|
+
pub.run
|
28
|
+
refute pub.done?
|
29
|
+
sub.run
|
30
|
+
refute sub.done?
|
31
|
+
|
32
|
+
pub.socket.send_string "Hello Verld!"
|
33
|
+
5.times do
|
34
|
+
if sub.output.count > 0
|
35
|
+
assert_equal "Hello Verld!", sub.output.flatten[0]
|
36
|
+
break
|
37
|
+
end
|
38
|
+
sleep 0.1
|
39
|
+
end
|
40
|
+
|
41
|
+
pub.stop
|
42
|
+
sub.stop
|
43
|
+
end
|
44
|
+
end
|