ffi-rxs 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,496 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+ module XS
5
+
6
+
7
+ describe Socket do
8
+
9
+ socket_types = [XS::REQ, XS::REP, XS::DEALER, XS::ROUTER, XS::PUB, XS::SUB, XS::PUSH, XS::PULL, XS::PAIR, XS::XPUB, XS::XSUB]
10
+
11
+ context "when initializing" do
12
+ before(:all) { @ctx = Context.new }
13
+ after(:all) { @ctx.terminate }
14
+
15
+
16
+ it "should raise an error for a nil context" do
17
+ lambda { Socket.new(FFI::Pointer.new(0), XS::REQ) }.should raise_exception(XS::ContextError)
18
+ end
19
+
20
+ it "works with a Context#pointer as the context_ptr" do
21
+ lambda do
22
+ s = Socket.new(@ctx.pointer, XS::REQ)
23
+ s.close
24
+ end.should_not raise_exception(XS::ContextError)
25
+ end
26
+
27
+ it "works with a Context instance as the context_ptr" do
28
+ lambda do
29
+ s = Socket.new(@ctx, XS::SUB)
30
+ s.close
31
+ end.should_not raise_exception(XS::ContextError)
32
+ end
33
+
34
+
35
+ socket_types.each do |socket_type|
36
+
37
+ it "should not raise an error for a [#{XS::SocketTypeNameMap[socket_type]}] socket type" do
38
+ sock = nil
39
+ lambda { sock = Socket.new(@ctx.pointer, socket_type) }.should_not raise_error
40
+ sock.close
41
+ end
42
+ end # each socket_type
43
+
44
+ it "should set the :socket accessor to the raw socket allocated by libxs" do
45
+ socket = mock('socket')
46
+ socket.stub!(:null? => false)
47
+ LibXS.should_receive(:xs_socket).and_return(socket)
48
+
49
+ sock = Socket.new(@ctx.pointer, XS::REQ)
50
+ sock.socket.should == socket
51
+ end
52
+
53
+ it "should define a finalizer on this object" do
54
+ ObjectSpace.should_receive(:define_finalizer).at_least(1)
55
+ sock = Socket.new(@ctx.pointer, XS::REQ)
56
+ sock.close
57
+ end
58
+ end # context initializing
59
+
60
+
61
+ context "calling close" do
62
+ before(:all) { @ctx = Context.new }
63
+ after(:all) { @ctx.terminate }
64
+
65
+ it "should call LibXS.close only once" do
66
+ sock = Socket.new @ctx.pointer, XS::REQ
67
+ raw_socket = sock.socket
68
+
69
+ LibXS.should_receive(:close).with(raw_socket)
70
+ sock.close
71
+ sock.close
72
+ LibXS.close raw_socket # *really close it otherwise the context will block indefinitely
73
+ end
74
+ end # context calling close
75
+
76
+
77
+ context "identity=" do
78
+ before(:all) { @ctx = Context.new }
79
+ after(:all) { @ctx.terminate }
80
+
81
+ it "fails to set identity for identities in excess of 255 bytes" do
82
+ sock = Socket.new @ctx.pointer, XS::REQ
83
+
84
+ sock.identity = ('a' * 256)
85
+ sock.identity.should == ''
86
+ sock.close
87
+ end
88
+
89
+ it "fails to set identity for identities of length 0" do
90
+ sock = Socket.new @ctx.pointer, XS::REQ
91
+
92
+ sock.identity = ''
93
+ sock.identity.should == ''
94
+ sock.close
95
+ end
96
+
97
+ it "sets the identity for identities of 1 byte" do
98
+ sock = Socket.new @ctx.pointer, XS::REQ
99
+
100
+ sock.identity = 'a'
101
+ sock.identity.should == 'a'
102
+ sock.close
103
+ end
104
+
105
+ it "set the identity identities of 255 bytes" do
106
+ sock = Socket.new @ctx.pointer, XS::REQ
107
+
108
+ sock.identity = ('a' * 255)
109
+ sock.identity.should == ('a' * 255)
110
+ sock.close
111
+ end
112
+
113
+ it "should convert numeric identities to strings" do
114
+ sock = Socket.new @ctx.pointer, XS::REQ
115
+
116
+ sock.identity = 7
117
+ sock.identity.should == '7'
118
+ sock.close
119
+ end
120
+ end # context identity=
121
+
122
+
123
+ socket_types.each do |socket_type|
124
+
125
+ context "#setsockopt for a #{XS::SocketTypeNameMap[socket_type]} socket" do
126
+ before(:all) { @ctx = Context.new }
127
+ after(:all) { @ctx.terminate }
128
+
129
+ let(:socket) do
130
+ Socket.new @ctx.pointer, socket_type
131
+ end
132
+
133
+ after(:each) do
134
+ socket.close
135
+ end
136
+
137
+
138
+ context "using option XS::IDENTITY" do
139
+ it "should set the identity given any string under 255 characters" do
140
+ length = 4
141
+ (1..255).each do |length|
142
+ identity = 'a' * length
143
+ socket.setsockopt XS::IDENTITY, identity
144
+
145
+ array = []
146
+ rc = socket.getsockopt(XS::IDENTITY, array)
147
+ rc.should == 0
148
+ array[0].should == identity
149
+ end
150
+ end
151
+
152
+ it "returns -1 given a string 256 characters or longer" do
153
+ identity = 'a' * 256
154
+ array = []
155
+ rc = socket.setsockopt(XS::IDENTITY, identity)
156
+ rc.should == -1
157
+ end
158
+ end # context using option XS::IDENTITY
159
+
160
+
161
+ context "using option XS::SUBSCRIBE" do
162
+ if XS::SUB == socket_type
163
+ it "returns 0 for a SUB socket" do
164
+ rc = socket.setsockopt(XS::SUBSCRIBE, "topic.string")
165
+ rc.should == 0
166
+ end
167
+ else
168
+ it "returns -1 for non-SUB sockets" do
169
+ rc = socket.setsockopt(XS::SUBSCRIBE, "topic.string")
170
+ rc.should == -1
171
+ end
172
+ end
173
+ end # context using option XS::SUBSCRIBE
174
+
175
+
176
+ context "using option XS::UNSUBSCRIBE" do
177
+ if XS::SUB == socket_type
178
+ it "returns 0 given a topic string that was previously subscribed" do
179
+ socket.setsockopt XS::SUBSCRIBE, "topic.string"
180
+ rc = socket.setsockopt(XS::UNSUBSCRIBE, "topic.string")
181
+ rc.should == 0
182
+ end
183
+
184
+ else
185
+ it "returns -1 for non-SUB sockets" do
186
+ rc = socket.setsockopt(XS::UNSUBSCRIBE, "topic.string")
187
+ rc.should == -1
188
+ end
189
+ end
190
+ end # context using option XS::UNSUBSCRIBE
191
+
192
+
193
+ context "using option XS::AFFINITY" do
194
+ it "should set the affinity value given a positive value" do
195
+ affinity = 3
196
+ socket.setsockopt XS::AFFINITY, affinity
197
+ array = []
198
+ rc = socket.getsockopt(XS::AFFINITY, array)
199
+ rc.should == 0
200
+ array[0].should == affinity
201
+ end
202
+ end # context using option XS::AFFINITY
203
+
204
+
205
+ context "using option XS::RATE" do
206
+ it "should set the multicast send rate given a positive value" do
207
+ rate = 200
208
+ socket.setsockopt XS::RATE, rate
209
+ array = []
210
+ rc = socket.getsockopt(XS::RATE, array)
211
+ rc.should == 0
212
+ array[0].should == rate
213
+ end
214
+
215
+ it "returns -1 given a negative value" do
216
+ rate = -200
217
+ rc = socket.setsockopt XS::RATE, rate
218
+ rc.should == -1
219
+ end
220
+ end # context using option XS::RATE
221
+
222
+
223
+ context "using option XS::RECOVERY_IVL" do
224
+ it "should set the multicast recovery buffer measured in seconds given a positive value" do
225
+ rate = 200
226
+ socket.setsockopt XS::RECOVERY_IVL, rate
227
+ array = []
228
+ rc = socket.getsockopt(XS::RECOVERY_IVL, array)
229
+ rc.should == 0
230
+ array[0].should == rate
231
+ end
232
+
233
+ it "returns -1 given a negative value" do
234
+ rate = -200
235
+ rc = socket.setsockopt XS::RECOVERY_IVL, rate
236
+ rc.should == -1
237
+ end
238
+ end # context using option XS::RECOVERY_IVL
239
+
240
+
241
+ context "using option XS::SNDBUF" do
242
+ it "should set the OS send buffer given a positive value" do
243
+ size = 100
244
+ socket.setsockopt XS::SNDBUF, size
245
+ array = []
246
+ rc = socket.getsockopt(XS::SNDBUF, array)
247
+ rc.should == 0
248
+ array[0].should == size
249
+ end
250
+ end # context using option XS::SNDBUF
251
+
252
+
253
+ context "using option XS::RCVBUF" do
254
+ it "should set the OS receive buffer given a positive value" do
255
+ size = 100
256
+ socket.setsockopt XS::RCVBUF, size
257
+ array = []
258
+ rc = socket.getsockopt(XS::RCVBUF, array)
259
+ rc.should == 0
260
+ array[0].should == size
261
+ end
262
+ end # context using option XS::RCVBUF
263
+
264
+
265
+ context "using option XS::LINGER" do
266
+ it "should set the socket message linger option measured in milliseconds given a positive value" do
267
+ value = 200
268
+ socket.setsockopt XS::LINGER, value
269
+ array = []
270
+ rc = socket.getsockopt(XS::LINGER, array)
271
+ rc.should == 0
272
+ array[0].should == value
273
+ end
274
+
275
+ it "should set the socket message linger option to 0 for dropping packets" do
276
+ value = 0
277
+ socket.setsockopt XS::LINGER, value
278
+ array = []
279
+ rc = socket.getsockopt(XS::LINGER, array)
280
+ rc.should == 0
281
+ array[0].should == value
282
+ end
283
+
284
+ if (defined?(XS::XSUB) && XS::XSUB == socket_type) or
285
+ (defined?(XS::SUB) && XS::SUB == socket_type)
286
+ it "should default to a value of 0" do
287
+ value = 0
288
+ array = []
289
+ rc = socket.getsockopt(XS::LINGER, array)
290
+ rc.should == 0
291
+ array[0].should == value
292
+ end
293
+ else
294
+ it "should default to a value of -1" do
295
+ value = -1
296
+ array = []
297
+ rc = socket.getsockopt(XS::LINGER, array)
298
+ rc.should == 0
299
+ array[0].should == value
300
+ end
301
+ end
302
+ end # context using option XS::LINGER
303
+
304
+
305
+ context "using option XS::RECONNECT_IVL" do
306
+ it "should set the time interval for reconnecting disconnected sockets measured in milliseconds given a positive value" do
307
+ value = 200
308
+ socket.setsockopt XS::RECONNECT_IVL, value
309
+ array = []
310
+ rc = socket.getsockopt(XS::RECONNECT_IVL, array)
311
+ rc.should == 0
312
+ array[0].should == value
313
+ end
314
+
315
+ it "should default to a value of 100" do
316
+ value = 100
317
+ array = []
318
+ rc = socket.getsockopt(XS::RECONNECT_IVL, array)
319
+ rc.should == 0
320
+ array[0].should == value
321
+ end
322
+ end # context using option XS::RECONNECT_IVL
323
+
324
+
325
+ context "using option XS::BACKLOG" do
326
+ it "should set the maximum number of pending socket connections given a positive value" do
327
+ value = 200
328
+ socket.setsockopt XS::BACKLOG, value
329
+ array = []
330
+ rc = socket.getsockopt(XS::BACKLOG, array)
331
+ rc.should == 0
332
+ array[0].should == value
333
+ end
334
+
335
+ it "should default to a value of 100" do
336
+ value = 100
337
+ array = []
338
+ rc = socket.getsockopt(XS::BACKLOG, array)
339
+ rc.should == 0
340
+ array[0].should == value
341
+ end
342
+ end # context using option XS::BACKLOG
343
+ end # context #setsockopt
344
+
345
+
346
+ context "#getsockopt for a #{XS::SocketTypeNameMap[socket_type]} socket" do
347
+ before(:all) { @ctx = Context.new }
348
+ after(:all) { @ctx.terminate }
349
+
350
+ let(:socket) do
351
+ Socket.new @ctx.pointer, socket_type
352
+ end
353
+
354
+ after(:each) do
355
+ socket.close
356
+ end
357
+
358
+ if RUBY_PLATFORM =~ /linux|darwin/
359
+ # this spec doesn't work on Windows; hints welcome
360
+
361
+ context "using option XS::FD" do
362
+ it "should return an FD as a positive integer" do
363
+ array = []
364
+ rc = socket.getsockopt(XS::FD, array)
365
+ rc.should == 0
366
+ array[0].should be_a(Fixnum)
367
+ end
368
+
369
+ it "returns a valid FD that is accepted by the system poll() function" do
370
+ # Use FFI to wrap the C library function +poll+ so that we can execute it
371
+ # on the 0mq file descriptor. If it returns 0, then it succeeded and the FD
372
+ # is valid!
373
+ module LibSocket
374
+ extend FFI::Library
375
+ # figures out the correct libc for each platform including Windows
376
+ library = ffi_lib(FFI::Library::LIBC).first
377
+
378
+ find_type(:nfds_t) rescue typedef(:uint32, :nfds_t)
379
+
380
+ attach_function :poll, [:pointer, :nfds_t, :int], :int
381
+
382
+ class PollFD < FFI::Struct
383
+ layout :fd, :int,
384
+ :events, :short,
385
+ :revents, :short
386
+ end
387
+ end # module LibSocket
388
+
389
+ array = []
390
+ rc = socket.getsockopt(XS::FD, array)
391
+ rc.should be_zero
392
+ fd = array[0]
393
+
394
+ # setup the BSD poll_fd struct
395
+ pollfd = LibSocket::PollFD.new
396
+ pollfd[:fd] = fd
397
+ pollfd[:events] = 0
398
+ pollfd[:revents] = 0
399
+
400
+ rc = LibSocket.poll(pollfd, 1, 0)
401
+ rc.should be_zero
402
+ end
403
+ end
404
+
405
+ end # posix platform
406
+
407
+ context "using option XS::EVENTS" do
408
+ it "should return a mask of events as a Fixnum" do
409
+ array = []
410
+ rc = socket.getsockopt(XS::EVENTS, array)
411
+ rc.should == 0
412
+ array[0].should be_a(Fixnum)
413
+ end
414
+ end
415
+
416
+ context "using option XS::TYPE" do
417
+ it "should return the socket type" do
418
+ array = []
419
+ rc = socket.getsockopt(XS::TYPE, array)
420
+ rc.should == 0
421
+ array[0].should == socket_type
422
+ end
423
+ end
424
+ end # context #getsockopt
425
+
426
+ end # each socket_type
427
+
428
+
429
+ describe "Mapping socket EVENTS to POLLIN and POLLOUT" do
430
+ include APIHelper
431
+
432
+ shared_examples_for "pubsub sockets where" do
433
+ it "SUB socket that received a message always has POLLIN set" do
434
+ events = []
435
+ rc = @sub.getsockopt(XS::EVENTS, events)
436
+ rc.should == 0
437
+ events[0].should == XS::POLLIN
438
+ end
439
+
440
+ it "PUB socket always has POLLOUT set" do
441
+ events = []
442
+ rc = @pub.getsockopt(XS::EVENTS, events)
443
+ rc.should == 0
444
+ events[0].should == XS::POLLOUT
445
+ end
446
+
447
+ it "PUB socket never has POLLIN set" do
448
+ events = []
449
+ rc = @pub.getsockopt(XS::EVENTS, events)
450
+ rc.should == 0
451
+ events[0].should_not == XS::POLLIN
452
+ end
453
+
454
+ it "SUB socket never has POLLOUT set" do
455
+ events = []
456
+ rc = @sub.getsockopt(XS::EVENTS, events)
457
+ rc.should == 0
458
+ events[0].should_not == XS::POLLOUT
459
+ end
460
+ end # shared example for pubsub
461
+
462
+
463
+ context "when SUB connects and PUB binds" do
464
+
465
+ before(:each) do
466
+ @ctx = Context.new
467
+
468
+ @sub = @ctx.socket XS::SUB
469
+ rc = @sub.setsockopt XS::SUBSCRIBE, ''
470
+
471
+ @pub = @ctx.socket XS::PUB
472
+ port = bind_to_random_tcp_port(@pub)
473
+ rc = @sub.connect "tcp://127.0.0.1:#{port}"
474
+ sleep 0.5
475
+
476
+ rc = @pub.send_string('test')
477
+ sleep 0.2
478
+ end
479
+
480
+ it_behaves_like "pubsub sockets where"
481
+ end # context SUB binds PUB connects
482
+
483
+
484
+ after(:each) do
485
+ @sub.close
486
+ @pub.close
487
+ # must call close on *every* socket before calling terminate otherwise it blocks indefinitely
488
+ @ctx.terminate
489
+ end
490
+
491
+ end # describe 'events mapping to pollin and pollout'
492
+
493
+ end # describe Socket
494
+
495
+
496
+ end # module XS
@@ -0,0 +1,57 @@
1
+ # Execute 'rake spec' from the main directory to run all specs.
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib ffi-rxs]))
5
+
6
+ Thread.abort_on_exception = true
7
+
8
+ module APIHelper
9
+ def stub_libxs
10
+ @err_str_mock = mock("error string")
11
+
12
+ LibXS.stub!(
13
+ :xs_init => 0,
14
+ :xs_errno => 0,
15
+ :xs_sterror => @err_str_mock
16
+ )
17
+ end
18
+
19
+ # generate a random port between 10_000 and 65534
20
+ def random_port
21
+ rand(55534) + 10_000
22
+ end
23
+
24
+ def bind_to_random_tcp_port socket, max_tries = 500
25
+ tries = 0
26
+ rc = -1
27
+
28
+ while !XS::Util.resultcode_ok?(rc) && tries < max_tries
29
+ tries += 1
30
+ random = random_port
31
+ rc = socket.bind(local_transport_string(random))
32
+ end
33
+
34
+ random
35
+ end
36
+
37
+ def connect_to_random_tcp_port socket, max_tries = 500
38
+ tries = 0
39
+ rc = -1
40
+
41
+ while !XS::Util.resultcode_ok?(rc) && tries < max_tries
42
+ tries += 1
43
+ random = random_port
44
+ rc = socket.connect(local_transport_string(random))
45
+ end
46
+
47
+ random
48
+ end
49
+
50
+ def local_transport_string(port)
51
+ "tcp://127.0.0.1:#{port}"
52
+ end
53
+
54
+ def assert_ok(rc)
55
+ raise "Failed with rc [#{rc}] and errno [#{XS::Util.errno}], msg [#{XS::Util.error_string}]! #{caller(0)}" unless rc >= 0
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi-rxs
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Duncan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi
16
+ requirement: &83395810 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *83395810
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &83413290 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.6'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *83413290
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &83412730 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *83412730
47
+ description: ! 'This gem wraps the Crossroads I/O networking library using the ruby
48
+ FFI (foreign
49
+
50
+ function interface). It''s a pure ruby wrapper so this gem can be loaded
51
+
52
+ and run by any ruby runtime that supports FFI. That''s all of them:
53
+
54
+ MRI 1.9.x, Rubinius and JRuby.'
55
+ email:
56
+ - celldee@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - .gitignore
62
+ - AUTHORS.txt
63
+ - Gemfile
64
+ - README.rdoc
65
+ - Rakefile
66
+ - ext/README
67
+ - ffi-rxs.gemspec
68
+ - lib/ffi-rxs.rb
69
+ - lib/ffi-rxs/constants.rb
70
+ - lib/ffi-rxs/constants.rb~
71
+ - lib/ffi-rxs/context.rb
72
+ - lib/ffi-rxs/context.rb~
73
+ - lib/ffi-rxs/device.rb~
74
+ - lib/ffi-rxs/exceptions.rb
75
+ - lib/ffi-rxs/exceptions.rb~
76
+ - lib/ffi-rxs/libc.rb
77
+ - lib/ffi-rxs/libc.rb~
78
+ - lib/ffi-rxs/libxs.rb
79
+ - lib/ffi-rxs/libxs.rb~
80
+ - lib/ffi-rxs/message.rb
81
+ - lib/ffi-rxs/message.rb~
82
+ - lib/ffi-rxs/poll.rb
83
+ - lib/ffi-rxs/poll.rb~
84
+ - lib/ffi-rxs/poll_items.rb
85
+ - lib/ffi-rxs/poll_items.rb~
86
+ - lib/ffi-rxs/socket.rb
87
+ - lib/ffi-rxs/socket.rb~
88
+ - lib/ffi-rxs/util.rb
89
+ - lib/ffi-rxs/util.rb~
90
+ - lib/ffi-rxs/version.rb
91
+ - lib/ffi-rxs/version.rb~
92
+ - spec/context_spec.rb
93
+ - spec/message_spec.rb
94
+ - spec/multipart_spec.rb
95
+ - spec/nonblocking_recv_spec.rb
96
+ - spec/poll_spec.rb
97
+ - spec/pushpull_spec.rb
98
+ - spec/reqrep_spec.rb
99
+ - spec/socket_spec.rb
100
+ - spec/spec_helper.rb
101
+ homepage: http://github.com/celldee/ffi-rxs
102
+ licenses: []
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 1.8.6
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: This gem wraps the Crossroads I/O networking library using Ruby FFI (foreign
125
+ function interface).
126
+ test_files: []