PageRenderTime 88ms CodeModel.GetById 16ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 1ms

/spec/socket_spec.rb

https://github.com/paav-o/ffi-rzmq
Ruby | 632 lines | 493 code | 131 blank | 8 comment | 96 complexity | b3b5f55399b41f38147b9c039ff0e107 MD5 | raw file
  1
  2require File.join(File.dirname(__FILE__), %w[spec_helper])
  3
  4module ZMQ
  5
  6
  7  describe Socket do
  8    include APIHelper
  9
 10    socket_types = if LibZMQ.version2?
 11      [ZMQ::REQ, ZMQ::REP, ZMQ::DEALER, ZMQ::ROUTER, ZMQ::PUB, ZMQ::SUB, ZMQ::PUSH, ZMQ::PULL, ZMQ::PAIR]
 12    elsif LibZMQ.version3?
 13      [ZMQ::REQ, ZMQ::REP, ZMQ::DEALER, ZMQ::ROUTER, ZMQ::PUB, ZMQ::SUB, ZMQ::PUSH, ZMQ::PULL, ZMQ::PAIR, ZMQ::XPUB, ZMQ::XSUB]
 14    end
 15
 16    context "when initializing" do
 17      before(:all) { @ctx = Context.new }
 18      after(:all) { @ctx.terminate }
 19
 20
 21      it "should raise an error for a nil context" do
 22        lambda { Socket.new(FFI::Pointer.new(0), ZMQ::REQ) }.should raise_exception(ZMQ::ContextError)
 23      end
 24
 25      it "works with a Context#pointer as the context_ptr" do
 26        lambda do
 27          s = Socket.new(@ctx.pointer, ZMQ::REQ)
 28          s.close
 29        end.should_not raise_exception(ZMQ::ContextError)
 30      end
 31
 32      it "works with a Context instance as the context_ptr" do
 33        lambda do
 34          s = Socket.new(@ctx, ZMQ::SUB)
 35          s.close
 36        end.should_not raise_exception(ZMQ::ContextError)
 37      end
 38
 39
 40      socket_types.each do |socket_type|
 41
 42        it "should not raise an error for a [#{ZMQ::SocketTypeNameMap[socket_type]}] socket type" do
 43          sock = nil
 44          lambda { sock = Socket.new(@ctx.pointer, socket_type) }.should_not raise_error
 45          sock.close
 46        end
 47      end # each socket_type
 48
 49      it "should set the :socket accessor to the raw socket allocated by libzmq" do
 50        socket = mock('socket')
 51        socket.stub!(:null? => false)
 52        LibZMQ.should_receive(:zmq_socket).and_return(socket)
 53
 54        sock = Socket.new(@ctx.pointer, ZMQ::REQ)
 55        sock.socket.should == socket
 56      end
 57
 58      it "should define a finalizer on this object" do
 59        ObjectSpace.should_receive(:define_finalizer).at_least(1)
 60        sock = Socket.new(@ctx.pointer, ZMQ::REQ)
 61        sock.close
 62      end
 63    end # context initializing
 64
 65
 66    context "calling close" do
 67      before(:all) { @ctx = Context.new }
 68      after(:all) { @ctx.terminate }
 69
 70      it "should call LibZMQ.close only once" do
 71        sock = Socket.new @ctx.pointer, ZMQ::REQ
 72        raw_socket = sock.socket
 73
 74        LibZMQ.should_receive(:close).with(raw_socket)
 75        sock.close
 76        sock.close
 77        LibZMQ.close raw_socket # *really close it otherwise the context will block indefinitely
 78      end
 79    end # context calling close
 80
 81
 82
 83    context "identity=" do
 84      before(:all) { @ctx = Context.new }
 85      after(:all) { @ctx.terminate }
 86
 87      it "fails to set identity for identities in excess of 255 bytes" do
 88        sock = Socket.new @ctx.pointer, ZMQ::REQ
 89
 90        sock.identity = ('a' * 256)
 91        sock.identity.should == ''
 92        sock.close
 93      end
 94
 95      it "fails to set identity for identities of length 0" do
 96        sock = Socket.new @ctx.pointer, ZMQ::REQ
 97
 98        sock.identity = ''
 99        sock.identity.should == ''
100        sock.close
101      end
102
103      it "sets the identity for identities of 1 byte" do
104        sock = Socket.new @ctx.pointer, ZMQ::REQ
105
106        sock.identity = 'a'
107        sock.identity.should == 'a'
108        sock.close
109      end
110
111      it "set the identity identities of 255 bytes" do
112        sock = Socket.new @ctx.pointer, ZMQ::REQ
113
114        sock.identity = ('a' * 255)
115        sock.identity.should == ('a' * 255)
116        sock.close
117      end
118
119      it "should convert numeric identities to strings" do
120        sock = Socket.new @ctx.pointer, ZMQ::REQ
121
122        sock.identity = 7
123        sock.identity.should == '7'
124        sock.close
125      end
126    end # context identity=
127
128
129
130    socket_types.each do |socket_type|
131
132      context "#setsockopt for a #{ZMQ::SocketTypeNameMap[socket_type]} socket" do
133        before(:all) { @ctx = Context.new }
134        after(:all) { @ctx.terminate }
135
136        let(:socket) do
137          Socket.new @ctx.pointer, socket_type
138        end
139
140        after(:each) do
141          socket.close
142        end
143
144
145        context "using option ZMQ::IDENTITY" do
146          it "should set the identity given any string under 255 characters" do
147            length = 4
148            (1..255).each do |length|
149              identity = 'a' * length
150              socket.setsockopt ZMQ::IDENTITY, identity
151
152              array = []
153              rc = socket.getsockopt(ZMQ::IDENTITY, array)
154              rc.should == 0
155              array[0].should == identity
156            end
157          end
158
159          it "returns -1 given a string 256 characters or longer" do
160            identity = 'a' * 256
161            array = []
162            rc = socket.setsockopt(ZMQ::IDENTITY, identity)
163            rc.should == -1
164          end
165        end # context using option ZMQ::IDENTITY
166
167
168        if version2?
169
170          context "using option ZMQ::HWM" do
171            it "should set the high water mark given a positive value" do
172              hwm = 4
173              socket.setsockopt ZMQ::HWM, hwm
174              array = []
175              rc = socket.getsockopt(ZMQ::HWM, array)
176              rc.should == 0
177              array[0].should == hwm
178            end
179          end # context using option ZMQ::HWM
180
181
182          context "using option ZMQ::SWAP" do
183            it "should set the swap value given a positive value" do
184              swap = 10_000
185              socket.setsockopt ZMQ::SWAP, swap
186              array = []
187              rc = socket.getsockopt(ZMQ::SWAP, array)
188              rc.should == 0
189              array[0].should == swap
190            end
191
192            it "returns -1 given a negative value" do
193              swap = -10_000
194              rc = socket.setsockopt(ZMQ::SWAP, swap)
195              rc.should == -1
196            end
197          end # context using option ZMQ::SWP
198
199
200          context "using option ZMQ::MCAST_LOOP" do
201            it "should enable the multicast loopback given a 1 (true) value" do
202              socket.setsockopt ZMQ::MCAST_LOOP, 1
203              array = []
204              rc = socket.getsockopt(ZMQ::MCAST_LOOP, array)
205              rc.should == 0
206              array[0].should be_true
207            end
208
209            it "should disable the multicast loopback given a 0 (false) value" do
210              socket.setsockopt ZMQ::MCAST_LOOP, 0
211              array = []
212              rc = socket.getsockopt(ZMQ::MCAST_LOOP, array)
213              rc.should == 0
214              array[0].should be_false
215            end
216          end # context using option ZMQ::MCAST_LOOP
217
218
219          context "using option ZMQ::RECOVERY_IVL_MSEC" do
220            it "should set the time interval for saving messages measured in milliseconds given a positive value" do
221              value = 200
222              socket.setsockopt ZMQ::RECOVERY_IVL_MSEC, value
223              array = []
224              rc = socket.getsockopt(ZMQ::RECOVERY_IVL_MSEC, array)
225              rc.should == 0
226              array[0].should == value
227            end
228
229            it "should default to a value of -1" do
230              value = -1
231              array = []
232              rc = socket.getsockopt(ZMQ::RECOVERY_IVL_MSEC, array)
233              rc.should == 0
234              array[0].should == value
235            end
236          end # context using option ZMQ::RECOVERY_IVL_MSEC
237
238        else # version3 or higher
239
240          context "using option ZMQ::IPV4ONLY" do
241            it "should enable use of IPV6 sockets when set to 0" do
242              value = 0
243              socket.setsockopt ZMQ::IPV4ONLY, value
244              array = []
245              rc = socket.getsockopt(ZMQ::IPV4ONLY, array)
246              rc.should == 0
247              array[0].should == value
248            end
249
250            it "should default to a value of 1" do
251              value = 1
252              array = []
253              rc = socket.getsockopt(ZMQ::IPV4ONLY, array)
254              rc.should == 0
255              array[0].should == value
256            end
257
258            it "returns -1 given a negative value" do
259              value = -1
260              rc = socket.setsockopt ZMQ::IPV4ONLY, value
261              rc.should == -1
262            end
263
264            it "returns -1 given a value > 1" do
265              value = 2
266              rc = socket.setsockopt ZMQ::IPV4ONLY, value
267              rc.should == -1
268            end
269          end # context using option ZMQ::IPV4ONLY
270
271
272        end # version2? if/else block
273
274
275        context "using option ZMQ::SUBSCRIBE" do
276          if ZMQ::SUB == socket_type
277            it "returns 0 for a SUB socket" do
278              rc = socket.setsockopt(ZMQ::SUBSCRIBE, "topic.string")
279              rc.should == 0
280            end
281          else
282            it "returns -1 for non-SUB sockets" do
283              rc = socket.setsockopt(ZMQ::SUBSCRIBE, "topic.string")
284              rc.should == -1
285            end
286          end
287        end # context using option ZMQ::SUBSCRIBE
288
289
290        context "using option ZMQ::UNSUBSCRIBE" do
291          if ZMQ::SUB == socket_type
292            it "returns 0 given a topic string that was previously subscribed" do
293              socket.setsockopt ZMQ::SUBSCRIBE, "topic.string"
294              rc = socket.setsockopt(ZMQ::UNSUBSCRIBE, "topic.string")
295              rc.should == 0
296            end
297
298          else
299            it "returns -1 for non-SUB sockets" do
300              rc = socket.setsockopt(ZMQ::UNSUBSCRIBE, "topic.string")
301              rc.should == -1
302            end
303          end
304        end # context using option ZMQ::UNSUBSCRIBE
305
306
307        context "using option ZMQ::AFFINITY" do
308          it "should set the affinity value given a positive value" do
309            affinity = 3
310            socket.setsockopt ZMQ::AFFINITY, affinity
311            array = []
312            rc = socket.getsockopt(ZMQ::AFFINITY, array)
313            rc.should == 0
314            array[0].should == affinity
315          end
316        end # context using option ZMQ::AFFINITY
317
318
319        context "using option ZMQ::RATE" do
320          it "should set the multicast send rate given a positive value" do
321            rate = 200
322            socket.setsockopt ZMQ::RATE, rate
323            array = []
324            rc = socket.getsockopt(ZMQ::RATE, array)
325            rc.should == 0
326            array[0].should == rate
327          end
328
329          it "returns -1 given a negative value" do
330            rate = -200
331            rc = socket.setsockopt ZMQ::RATE, rate
332            rc.should == -1
333          end
334        end # context using option ZMQ::RATE
335
336
337        context "using option ZMQ::RECOVERY_IVL" do
338          it "should set the multicast recovery buffer measured in seconds given a positive value" do
339            rate = 200
340            socket.setsockopt ZMQ::RECOVERY_IVL, rate
341            array = []
342            rc = socket.getsockopt(ZMQ::RECOVERY_IVL, array)
343            rc.should == 0
344            array[0].should == rate
345          end
346
347          it "returns -1 given a negative value" do
348            rate = -200
349            rc = socket.setsockopt ZMQ::RECOVERY_IVL, rate
350            rc.should == -1
351          end
352        end # context using option ZMQ::RECOVERY_IVL
353
354
355        context "using option ZMQ::SNDBUF" do
356          it "should set the OS send buffer given a positive value" do
357            size = 100
358            socket.setsockopt ZMQ::SNDBUF, size
359            array = []
360            rc = socket.getsockopt(ZMQ::SNDBUF, array)
361            rc.should == 0
362            array[0].should == size
363          end
364        end # context using option ZMQ::SNDBUF
365
366
367        context "using option ZMQ::RCVBUF" do
368          it "should set the OS receive buffer given a positive value" do
369            size = 100
370            socket.setsockopt ZMQ::RCVBUF, size
371            array = []
372            rc = socket.getsockopt(ZMQ::RCVBUF, array)
373            rc.should == 0
374            array[0].should == size
375          end
376        end # context using option ZMQ::RCVBUF
377
378
379        context "using option ZMQ::LINGER" do
380          it "should set the socket message linger option measured in milliseconds given a positive value" do
381            value = 200
382            socket.setsockopt ZMQ::LINGER, value
383            array = []
384            rc = socket.getsockopt(ZMQ::LINGER, array)
385            rc.should == 0
386            array[0].should == value
387          end
388
389          it "should set the socket message linger option to 0 for dropping packets" do
390            value = 0
391            socket.setsockopt ZMQ::LINGER, value
392            array = []
393            rc = socket.getsockopt(ZMQ::LINGER, array)
394            rc.should == 0
395            array[0].should == value
396          end
397
398          if (ZMQ::SUB == socket_type) && version3? || (defined?(ZMQ::XSUB) && ZMQ::XSUB == socket_type)
399            it "should default to a value of 0" do
400              value = 0
401              array = []
402              rc = socket.getsockopt(ZMQ::LINGER, array)
403              rc.should == 0
404              array[0].should == value
405            end
406          else
407            it "should default to a value of -1" do
408              value = -1
409              array = []
410              rc = socket.getsockopt(ZMQ::LINGER, array)
411              rc.should == 0
412              array[0].should == value
413            end
414          end
415        end # context using option ZMQ::LINGER
416
417
418        context "using option ZMQ::RECONNECT_IVL" do
419          it "should set the time interval for reconnecting disconnected sockets measured in milliseconds given a positive value" do
420            value = 200
421            socket.setsockopt ZMQ::RECONNECT_IVL, value
422            array = []
423            rc = socket.getsockopt(ZMQ::RECONNECT_IVL, array)
424            rc.should == 0
425            array[0].should == value
426          end
427
428          it "should default to a value of 100" do
429            value = 100
430            array = []
431            rc = socket.getsockopt(ZMQ::RECONNECT_IVL, array)
432            rc.should == 0
433            array[0].should == value
434          end
435        end # context using option ZMQ::RECONNECT_IVL
436
437
438        context "using option ZMQ::BACKLOG" do
439          it "should set the maximum number of pending socket connections given a positive value" do
440            value = 200
441            socket.setsockopt ZMQ::BACKLOG, value
442            array = []
443            rc = socket.getsockopt(ZMQ::BACKLOG, array)
444            rc.should == 0
445            array[0].should == value
446          end
447
448          it "should default to a value of 100" do
449            value = 100
450            array = []
451            rc = socket.getsockopt(ZMQ::BACKLOG, array)
452            rc.should == 0
453            array[0].should == value
454          end
455        end # context using option ZMQ::BACKLOG
456
457      end # context #setsockopt
458
459
460      context "#getsockopt for a #{ZMQ::SocketTypeNameMap[socket_type]} socket" do
461        before(:all) { @ctx = Context.new }
462        after(:all) { @ctx.terminate }
463
464        let(:socket) do
465          Socket.new @ctx.pointer, socket_type
466        end
467
468        after(:each) do
469          socket.close
470        end
471
472        if RUBY_PLATFORM =~ /linux|darwin/
473          # this spec doesn't work on Windows; hints welcome
474
475          context "using option ZMQ::FD" do
476            it "should return an FD as a positive integer" do
477              array = []
478              rc = socket.getsockopt(ZMQ::FD, array)
479              rc.should == 0
480              array[0].should be_a(Fixnum)
481            end
482
483            it "returns a valid FD that is accepted by the system poll() function" do
484              # Use FFI to wrap the C library function +poll+ so that we can execute it
485              # on the 0mq file descriptor. If it returns 0, then it succeeded and the FD
486              # is valid!
487              module LibSocket
488                extend FFI::Library
489                # figures out the correct libc for each platform including Windows
490                library = ffi_lib(FFI::Library::LIBC).first
491
492                find_type(:nfds_t) rescue typedef(:uint32, :nfds_t)
493
494                attach_function :poll, [:pointer, :nfds_t, :int], :int
495
496                class PollFD < FFI::Struct
497                  layout :fd,    :int,
498                  :events, :short,
499                  :revents, :short
500                end
501              end # module LibSocket
502
503              array = []
504              rc = socket.getsockopt(ZMQ::FD, array)
505              rc.should be_zero
506              fd = array[0]
507
508              # setup the BSD poll_fd struct
509              pollfd = LibSocket::PollFD.new
510              pollfd[:fd] = fd
511              pollfd[:events] = 0
512              pollfd[:revents] = 0
513
514              rc = LibSocket.poll(pollfd, 1, 0)
515              rc.should be_zero
516            end
517          end
518
519        end # posix platform
520
521        context "using option ZMQ::EVENTS" do
522          it "should return a mask of events as a Fixnum" do
523            array = []
524            rc = socket.getsockopt(ZMQ::EVENTS, array)
525            rc.should == 0
526            array[0].should be_a(Fixnum)
527          end
528        end
529
530        context "using option ZMQ::TYPE" do
531          it "should return the socket type" do
532            array = []
533            rc = socket.getsockopt(ZMQ::TYPE, array)
534            rc.should == 0
535            array[0].should == socket_type
536          end
537        end
538      end # context #getsockopt
539
540    end # each socket_type
541
542
543    describe "Mapping socket EVENTS to POLLIN and POLLOUT" do
544      include APIHelper
545
546      shared_examples_for "pubsub sockets where" do
547        it "SUB socket that received a message always has POLLIN set" do
548          events = []
549          rc = @sub.getsockopt(ZMQ::EVENTS, events)
550          rc.should == 0
551          events[0].should == ZMQ::POLLIN
552        end
553
554        it "PUB socket always has POLLOUT set" do
555          events = []
556          rc = @pub.getsockopt(ZMQ::EVENTS, events)
557          rc.should == 0
558          events[0].should == ZMQ::POLLOUT
559        end
560
561        it "PUB socket never has POLLIN set" do
562          events = []
563          rc = @pub.getsockopt(ZMQ::EVENTS, events)
564          rc.should == 0
565          events[0].should_not == ZMQ::POLLIN
566        end
567
568        it "SUB socket never has POLLOUT set" do
569          events = []
570          rc = @sub.getsockopt(ZMQ::EVENTS, events)
571          rc.should == 0
572          events[0].should_not == ZMQ::POLLOUT
573        end
574      end # shared example for pubsub
575
576      context "when SUB binds and PUB connects" do
577
578        before(:each) do
579          @ctx = Context.new
580          poller_setup
581
582          endpoint = "inproc://socket_test"
583          @sub = @ctx.socket ZMQ::SUB
584          rc = @sub.setsockopt ZMQ::SUBSCRIBE, ''
585          rc.should == 0
586
587          @pub = @ctx.socket ZMQ::PUB
588          @sub.bind(endpoint)
589          connect_to_inproc(@pub, endpoint)
590
591          @pub.send_string('test')
592        end
593
594        #it_behaves_like "pubsub sockets where" # see Jira LIBZMQ-270
595      end # context SUB binds PUB connects
596
597      context "when SUB connects and PUB binds" do
598
599        before(:each) do
600          @ctx = Context.new
601          poller_setup
602
603          endpoint = "inproc://socket_test"
604          @sub = @ctx.socket ZMQ::SUB
605          rc = @sub.setsockopt ZMQ::SUBSCRIBE, ''
606
607          @pub = @ctx.socket ZMQ::PUB
608          @pub.bind(endpoint)
609          connect_to_inproc(@sub, endpoint)
610
611          poll_it_for_read(@sub) do
612            rc = @pub.send_string('test')
613          end
614        end
615
616        it_behaves_like "pubsub sockets where"
617      end # context SUB binds PUB connects
618
619
620      after(:each) do
621        @sub.close
622        @pub.close
623        # must call close on *every* socket before calling terminate otherwise it blocks indefinitely
624        @ctx.terminate
625      end
626
627    end # describe 'events mapping to pollin and pollout'
628
629  end # describe Socket
630
631
632end # module ZMQ