PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

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