/spec/socket_spec.rb
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