PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/test/rinda/test_rinda.rb

https://github.com/ruby/ruby
Ruby | 894 lines | 736 code | 149 blank | 9 comment | 41 complexity | 9285375155bf2e1558cb7520773a91bf MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. # frozen_string_literal: false
  2. require 'test/unit'
  3. require 'envutil'
  4. require 'drb/drb'
  5. require 'drb/eq'
  6. require 'rinda/ring'
  7. require 'rinda/tuplespace'
  8. require 'timeout'
  9. require 'singleton'
  10. module Rinda
  11. class MockClock
  12. include Singleton
  13. class MyTS < Rinda::TupleSpace
  14. def keeper_thread
  15. nil
  16. end
  17. def stop_keeper
  18. if @keeper
  19. @keeper.kill
  20. @keeper.join
  21. @keeper = nil
  22. end
  23. end
  24. end
  25. def initialize
  26. @now = 2
  27. @reso = 1
  28. @ts = nil
  29. @inf = 2**31 - 1
  30. end
  31. def start_keeper
  32. @now = 2
  33. @reso = 1
  34. @ts&.stop_keeper
  35. @ts = MyTS.new
  36. @ts.write([2, :now])
  37. @inf = 2**31 - 1
  38. end
  39. def stop_keeper
  40. @ts.stop_keeper
  41. end
  42. def now
  43. @now.to_f
  44. end
  45. def at(n)
  46. n
  47. end
  48. def _forward(n=nil)
  49. now ,= @ts.take([nil, :now])
  50. @now = now + n
  51. @ts.write([@now, :now])
  52. end
  53. def forward(n)
  54. while n > 0
  55. _forward(@reso)
  56. n -= @reso
  57. Thread.pass
  58. end
  59. end
  60. def rewind
  61. @ts.take([nil, :now])
  62. @ts.write([@inf, :now])
  63. @ts.take([nil, :now])
  64. @now = 2
  65. @ts.write([2, :now])
  66. end
  67. def sleep(n=nil)
  68. now ,= @ts.read([nil, :now])
  69. @ts.read([(now + n)..@inf, :now])
  70. 0
  71. end
  72. end
  73. module Time
  74. def sleep(n)
  75. @m.sleep(n)
  76. end
  77. module_function :sleep
  78. def at(n)
  79. n
  80. end
  81. module_function :at
  82. def now
  83. defined?(@m) && @m ? @m.now : 2
  84. end
  85. module_function :now
  86. def rewind
  87. @m.rewind
  88. end
  89. module_function :rewind
  90. def forward(n)
  91. @m.forward(n)
  92. end
  93. module_function :forward
  94. @m = MockClock.instance
  95. end
  96. class TupleSpace
  97. def sleep(n)
  98. Kernel.sleep(n * 0.01)
  99. end
  100. end
  101. module TupleSpaceTestModule
  102. def setup
  103. MockClock.instance.start_keeper
  104. end
  105. def teardown
  106. MockClock.instance.stop_keeper
  107. end
  108. def sleep(n)
  109. if Thread.current == Thread.main
  110. Time.forward(n)
  111. else
  112. Time.sleep(n)
  113. end
  114. end
  115. def thread_join(th)
  116. while th.alive?
  117. Kernel.sleep(0.1)
  118. sleep(1)
  119. end
  120. th.value
  121. end
  122. def test_00_tuple
  123. tuple = Rinda::TupleEntry.new([1,2,3])
  124. assert(!tuple.canceled?)
  125. assert(!tuple.expired?)
  126. assert(tuple.alive?)
  127. end
  128. def test_00_template
  129. tmpl = Rinda::Template.new([1,2,3])
  130. assert_equal(3, tmpl.size)
  131. assert_equal(3, tmpl[2])
  132. assert(tmpl.match([1,2,3]))
  133. assert(!tmpl.match([1,nil,3]))
  134. tmpl = Rinda::Template.new([/^rinda/i, nil, :hello])
  135. assert_equal(3, tmpl.size)
  136. assert(tmpl.match(['Rinda', 2, :hello]))
  137. assert(!tmpl.match(['Rinda', 2, Symbol]))
  138. assert(!tmpl.match([1, 2, :hello]))
  139. assert(tmpl.match([/^rinda/i, 2, :hello]))
  140. tmpl = Rinda::Template.new([Symbol])
  141. assert_equal(1, tmpl.size)
  142. assert(tmpl.match([:hello]))
  143. assert(tmpl.match([Symbol]))
  144. assert(!tmpl.match(['Symbol']))
  145. tmpl = Rinda::Template.new({"message"=>String, "name"=>String})
  146. assert_equal(2, tmpl.size)
  147. assert(tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
  148. assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
  149. assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
  150. assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
  151. assert_raise(Rinda::InvalidHashTupleKey) do
  152. Rinda::Template.new({:message=>String, "name"=>String})
  153. end
  154. tmpl = Rinda::Template.new({"name"=>String})
  155. assert_equal(1, tmpl.size)
  156. assert(tmpl.match({"name"=>"Foo"}))
  157. assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
  158. assert(!tmpl.match({"message"=>:symbol, "name"=>"Foo", "1"=>2}))
  159. assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
  160. assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
  161. tmpl = Rinda::Template.new({"message"=>String, "name"=>String})
  162. assert_equal(2, tmpl.size)
  163. assert(tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
  164. assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
  165. assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
  166. assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
  167. tmpl = Rinda::Template.new({"message"=>String})
  168. assert_equal(1, tmpl.size)
  169. assert(tmpl.match({"message"=>"Hello"}))
  170. assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
  171. assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
  172. assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
  173. assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
  174. tmpl = Rinda::Template.new({"message"=>String, "name"=>nil})
  175. assert_equal(2, tmpl.size)
  176. assert(tmpl.match({"message"=>"Hello", "name"=>"Foo"}))
  177. assert(!tmpl.match({"message"=>"Hello", "name"=>"Foo", "1"=>2}))
  178. assert(!tmpl.match({"message"=>"Hi", "name"=>"Foo", "age"=>1}))
  179. assert(!tmpl.match({"message"=>"Hello", "no_name"=>"Foo"}))
  180. assert_raise(Rinda::InvalidHashTupleKey) do
  181. @ts.write({:message=>String, "name"=>String})
  182. end
  183. @ts.write([1, 2, 3])
  184. assert_equal([1, 2, 3], @ts.take([1, 2, 3]))
  185. @ts.write({'1'=>1, '2'=>2, '3'=>3})
  186. assert_equal({'1'=>1, '2'=>2, '3'=>3}, @ts.take({'1'=>1, '2'=>2, '3'=>3}))
  187. entry = @ts.write(['1'=>1, '2'=>2, '3'=>3])
  188. assert_raise(Rinda::RequestExpiredError) do
  189. assert_equal({'1'=>1, '2'=>2, '3'=>3}, @ts.read({'1'=>1}, 0))
  190. end
  191. entry.cancel
  192. end
  193. def test_00_DRbObject
  194. ro = DRbObject.new(nil, "druby://host:1234")
  195. tmpl = Rinda::DRbObjectTemplate.new
  196. assert(tmpl === ro)
  197. tmpl = Rinda::DRbObjectTemplate.new("druby://host:1234")
  198. assert(tmpl === ro)
  199. tmpl = Rinda::DRbObjectTemplate.new("druby://host:12345")
  200. assert(!(tmpl === ro))
  201. tmpl = Rinda::DRbObjectTemplate.new(/^druby:\/\/host:/)
  202. assert(tmpl === ro)
  203. ro = DRbObject.new_with(12345, 1234)
  204. assert(!(tmpl === ro))
  205. ro = DRbObject.new_with("druby://foo:12345", 1234)
  206. assert(!(tmpl === ro))
  207. tmpl = Rinda::DRbObjectTemplate.new(/^druby:\/\/(foo|bar):/)
  208. assert(tmpl === ro)
  209. ro = DRbObject.new_with("druby://bar:12345", 1234)
  210. assert(tmpl === ro)
  211. ro = DRbObject.new_with("druby://baz:12345", 1234)
  212. assert(!(tmpl === ro))
  213. end
  214. def test_inp_rdp
  215. assert_raise(Rinda::RequestExpiredError) do
  216. @ts.take([:empty], 0)
  217. end
  218. assert_raise(Rinda::RequestExpiredError) do
  219. @ts.read([:empty], 0)
  220. end
  221. end
  222. def test_ruby_talk_264062
  223. th = Thread.new {
  224. assert_raise(Rinda::RequestExpiredError) do
  225. @ts.take([:empty], 1)
  226. end
  227. }
  228. sleep(10)
  229. thread_join(th)
  230. th = Thread.new {
  231. assert_raise(Rinda::RequestExpiredError) do
  232. @ts.read([:empty], 1)
  233. end
  234. }
  235. sleep(10)
  236. thread_join(th)
  237. end
  238. def test_symbol_tuple
  239. @ts.write([:symbol, :symbol])
  240. @ts.write(['string', :string])
  241. assert_equal([[:symbol, :symbol]], @ts.read_all([:symbol, nil]))
  242. assert_equal([[:symbol, :symbol]], @ts.read_all([Symbol, nil]))
  243. assert_equal([], @ts.read_all([:nil, nil]))
  244. end
  245. def test_core_01
  246. 5.times do
  247. @ts.write([:req, 2])
  248. end
  249. assert_equal([[:req, 2], [:req, 2], [:req, 2], [:req, 2], [:req, 2]],
  250. @ts.read_all([nil, nil]))
  251. taker = Thread.new(5) do |count|
  252. s = 0
  253. count.times do
  254. tuple = @ts.take([:req, Integer])
  255. assert_equal(2, tuple[1])
  256. s += tuple[1]
  257. end
  258. @ts.write([:ans, s])
  259. s
  260. end
  261. assert_equal(10, thread_join(taker))
  262. assert_equal([:ans, 10], @ts.take([:ans, 10]))
  263. assert_equal([], @ts.read_all([nil, nil]))
  264. end
  265. def test_core_02
  266. taker = Thread.new(5) do |count|
  267. s = 0
  268. count.times do
  269. tuple = @ts.take([:req, Integer])
  270. assert_equal(2, tuple[1])
  271. s += tuple[1]
  272. end
  273. @ts.write([:ans, s])
  274. s
  275. end
  276. 5.times do
  277. @ts.write([:req, 2])
  278. end
  279. assert_equal(10, thread_join(taker))
  280. assert_equal([:ans, 10], @ts.take([:ans, 10]))
  281. assert_equal([], @ts.read_all([nil, nil]))
  282. end
  283. def test_core_03_notify
  284. notify1 = @ts.notify(nil, [:req, Integer])
  285. notify2 = @ts.notify(nil, {"message"=>String, "name"=>String})
  286. 5.times do
  287. @ts.write([:req, 2])
  288. end
  289. 5.times do
  290. tuple = @ts.take([:req, Integer])
  291. assert_equal(2, tuple[1])
  292. end
  293. 5.times do
  294. assert_equal(['write', [:req, 2]], notify1.pop)
  295. end
  296. 5.times do
  297. assert_equal(['take', [:req, 2]], notify1.pop)
  298. end
  299. @ts.write({"message"=>"first", "name"=>"3"})
  300. @ts.write({"message"=>"second", "name"=>"1"})
  301. @ts.write({"message"=>"third", "name"=>"0"})
  302. @ts.take({"message"=>"third", "name"=>"0"})
  303. @ts.take({"message"=>"first", "name"=>"3"})
  304. assert_equal(["write", {"message"=>"first", "name"=>"3"}], notify2.pop)
  305. assert_equal(["write", {"message"=>"second", "name"=>"1"}], notify2.pop)
  306. assert_equal(["write", {"message"=>"third", "name"=>"0"}], notify2.pop)
  307. assert_equal(["take", {"message"=>"third", "name"=>"0"}], notify2.pop)
  308. assert_equal(["take", {"message"=>"first", "name"=>"3"}], notify2.pop)
  309. end
  310. def test_cancel_01
  311. entry = @ts.write([:removeme, 1])
  312. assert_equal([[:removeme, 1]], @ts.read_all([nil, nil]))
  313. entry.cancel
  314. assert_equal([], @ts.read_all([nil, nil]))
  315. template = nil
  316. taker = Thread.new do
  317. assert_raise(Rinda::RequestCanceledError) do
  318. @ts.take([:take, nil], 10) do |t|
  319. template = t
  320. Thread.new do
  321. template.cancel
  322. end
  323. end
  324. end
  325. end
  326. sleep(2)
  327. thread_join(taker)
  328. assert(template.canceled?)
  329. @ts.write([:take, 1])
  330. assert_equal([[:take, 1]], @ts.read_all([nil, nil]))
  331. end
  332. def test_cancel_02
  333. skip 'this test is unstable with --jit-wait' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
  334. entry = @ts.write([:removeme, 1])
  335. assert_equal([[:removeme, 1]], @ts.read_all([nil, nil]))
  336. entry.cancel
  337. assert_equal([], @ts.read_all([nil, nil]))
  338. template = nil
  339. reader = Thread.new do
  340. assert_raise(Rinda::RequestCanceledError) do
  341. @ts.read([:take, nil], 10) do |t|
  342. template = t
  343. Thread.new do
  344. template.cancel
  345. end
  346. end
  347. end
  348. end
  349. sleep(2)
  350. thread_join(reader)
  351. assert(template.canceled?)
  352. @ts.write([:take, 1])
  353. assert_equal([[:take, 1]], @ts.read_all([nil, nil]))
  354. end
  355. class SimpleRenewer
  356. def initialize(sec, n = 1)
  357. @sec = sec
  358. @n = n
  359. end
  360. def renew
  361. return -1 if @n <= 0
  362. @n -= 1
  363. return @sec
  364. end
  365. end
  366. def test_00_renewer
  367. tuple = Rinda::TupleEntry.new([1,2,3], true)
  368. assert(!tuple.canceled?)
  369. assert(tuple.expired?)
  370. assert(!tuple.alive?)
  371. tuple = Rinda::TupleEntry.new([1,2,3], 1)
  372. assert(!tuple.canceled?)
  373. assert(!tuple.expired?)
  374. assert(tuple.alive?)
  375. sleep(2)
  376. assert(tuple.expired?)
  377. assert(!tuple.alive?)
  378. @renewer = SimpleRenewer.new(1,2)
  379. tuple = Rinda::TupleEntry.new([1,2,3], @renewer)
  380. assert(!tuple.canceled?)
  381. assert(!tuple.expired?)
  382. assert(tuple.alive?)
  383. sleep(1)
  384. assert(!tuple.canceled?)
  385. assert(!tuple.expired?)
  386. assert(tuple.alive?)
  387. sleep(2)
  388. assert(tuple.expired?)
  389. assert(!tuple.alive?)
  390. end
  391. end
  392. class TupleSpaceTest < Test::Unit::TestCase
  393. include TupleSpaceTestModule
  394. def setup
  395. super
  396. ThreadGroup.new.add(Thread.current)
  397. @ts = Rinda::TupleSpace.new(1)
  398. end
  399. def teardown
  400. # implementation-dependent
  401. @ts.instance_eval{
  402. if th = @keeper
  403. th.kill
  404. th.join
  405. end
  406. }
  407. super
  408. end
  409. end
  410. class TupleSpaceProxyTest < Test::Unit::TestCase
  411. include TupleSpaceTestModule
  412. def setup
  413. super
  414. ThreadGroup.new.add(Thread.current)
  415. @ts_base = Rinda::TupleSpace.new(1)
  416. @ts = Rinda::TupleSpaceProxy.new(@ts_base)
  417. @server = DRb.start_service("druby://localhost:0")
  418. end
  419. def teardown
  420. # implementation-dependent
  421. @ts_base.instance_eval{
  422. if th = @keeper
  423. th.kill
  424. th.join
  425. end
  426. }
  427. @server.stop_service
  428. DRb::DRbConn.stop_pool
  429. super
  430. end
  431. def test_remote_array_and_hash
  432. # Don't remove ary/hsh local variables.
  433. # These are necessary to protect objects from GC.
  434. ary = [1, 2, 3]
  435. @ts.write(DRbObject.new(ary))
  436. assert_equal([1, 2, 3], @ts.take([1, 2, 3], 0))
  437. hsh = {'head' => 1, 'tail' => 2}
  438. @ts.write(DRbObject.new(hsh))
  439. assert_equal({'head' => 1, 'tail' => 2},
  440. @ts.take({'head' => 1, 'tail' => 2}, 0))
  441. end
  442. def test_take_bug_8215
  443. skip "this test randomly fails on mswin" if /mswin/ =~ RUBY_PLATFORM
  444. service = DRb.start_service("druby://localhost:0", @ts_base)
  445. uri = service.uri
  446. args = [EnvUtil.rubybin, *%W[-rdrb/drb -rdrb/eq -rrinda/ring -rrinda/tuplespace -e]]
  447. take = spawn(*args, <<-'end;', uri)
  448. uri = ARGV[0]
  449. DRb.start_service("druby://localhost:0")
  450. ro = DRbObject.new_with_uri(uri)
  451. ts = Rinda::TupleSpaceProxy.new(ro)
  452. th = Thread.new do
  453. ts.take([:test_take, nil])
  454. rescue Interrupt
  455. # Expected
  456. end
  457. Kernel.sleep(0.1)
  458. th.raise(Interrupt) # causes loss of the taken tuple
  459. ts.write([:barrier, :continue])
  460. Kernel.sleep
  461. end;
  462. @ts_base.take([:barrier, :continue])
  463. write = spawn(*args, <<-'end;', uri)
  464. uri = ARGV[0]
  465. DRb.start_service("druby://localhost:0")
  466. ro = DRbObject.new_with_uri(uri)
  467. ts = Rinda::TupleSpaceProxy.new(ro)
  468. ts.write([:test_take, 42])
  469. end;
  470. status = Process.wait(write)
  471. assert_equal([[:test_take, 42]], @ts_base.read_all([:test_take, nil]),
  472. '[bug:8215] tuple lost')
  473. ensure
  474. service.stop_service if service
  475. DRb::DRbConn.stop_pool
  476. signal = /mswin|mingw/ =~ RUBY_PLATFORM ? "KILL" : "TERM"
  477. Process.kill(signal, write) if write && status.nil?
  478. Process.kill(signal, take) if take
  479. Process.wait(write) if write && status.nil?
  480. Process.wait(take) if take
  481. end
  482. end
  483. module RingIPv6
  484. def prepare_ipv6(r)
  485. begin
  486. Socket.getifaddrs.each do |ifaddr|
  487. next unless ifaddr.addr
  488. next unless ifaddr.addr.ipv6_linklocal?
  489. next if ifaddr.name[0, 2] == "lo"
  490. r.multicast_interface = ifaddr.ifindex
  491. return ifaddr
  492. end
  493. rescue NotImplementedError
  494. # ifindex() function may not be implemented on Windows.
  495. return if
  496. Socket.ip_address_list.any? { |addrinfo| addrinfo.ipv6? && !addrinfo.ipv6_loopback? }
  497. end
  498. skip 'IPv6 not available'
  499. end
  500. def ipv6_mc(rf, hops = nil)
  501. ifaddr = prepare_ipv6(rf)
  502. rf.multicast_hops = hops if hops
  503. begin
  504. v6mc = rf.make_socket("ff02::1")
  505. rescue Errno::EINVAL
  506. # somehow Debian 6.0.7 needs ifname
  507. v6mc = rf.make_socket("ff02::1%#{ifaddr.name}")
  508. rescue Errno::EADDRNOTAVAIL
  509. return # IPv6 address for multicast not available
  510. rescue Errno::ENETDOWN
  511. return # Network is down
  512. rescue Errno::EHOSTUNREACH
  513. return # Unreachable for some reason
  514. end
  515. begin
  516. yield v6mc
  517. ensure
  518. v6mc.close
  519. end
  520. end
  521. end
  522. class TestRingServer < Test::Unit::TestCase
  523. def setup
  524. @port = Rinda::Ring_PORT
  525. @ts = Rinda::TupleSpace.new
  526. @rs = Rinda::RingServer.new(@ts, [], @port)
  527. @server = DRb.start_service("druby://localhost:0")
  528. end
  529. def teardown
  530. @rs.shutdown
  531. # implementation-dependent
  532. @ts.instance_eval{
  533. if th = @keeper
  534. th.kill
  535. th.join
  536. end
  537. }
  538. @server.stop_service
  539. DRb::DRbConn.stop_pool
  540. end
  541. def test_do_reply
  542. with_timeout(30) {_test_do_reply}
  543. end
  544. def _test_do_reply
  545. called = nil
  546. callback_orig = proc { |ts|
  547. called = ts
  548. }
  549. callback = DRb::DRbObject.new callback_orig
  550. @ts.write [:lookup_ring, callback]
  551. @rs.do_reply
  552. wait_for(30) {called}
  553. assert_same @ts, called
  554. end
  555. def test_do_reply_local
  556. skip 'timeout-based test becomes unstable with --jit-wait' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
  557. with_timeout(30) {_test_do_reply_local}
  558. end
  559. def _test_do_reply_local
  560. called = nil
  561. callback = proc { |ts|
  562. called = ts
  563. }
  564. @ts.write [:lookup_ring, callback]
  565. @rs.do_reply
  566. wait_for(30) {called}
  567. assert_same @ts, called
  568. end
  569. def test_make_socket_unicast
  570. v4 = @rs.make_socket('127.0.0.1')
  571. assert_equal('127.0.0.1', v4.local_address.ip_address)
  572. assert_equal(@port, v4.local_address.ip_port)
  573. end
  574. def test_make_socket_ipv4_multicast
  575. begin
  576. v4mc = @rs.make_socket('239.0.0.1')
  577. rescue Errno::ENOBUFS => e
  578. skip "Missing multicast support in OS: #{e.message}"
  579. end
  580. begin
  581. if Socket.const_defined?(:SO_REUSEPORT) then
  582. assert(v4mc.getsockopt(:SOCKET, :SO_REUSEPORT).bool)
  583. else
  584. assert(v4mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool)
  585. end
  586. rescue TypeError
  587. if /aix/ =~ RUBY_PLATFORM
  588. skip "Known bug in getsockopt(2) on AIX"
  589. end
  590. raise $!
  591. end
  592. assert_equal('0.0.0.0', v4mc.local_address.ip_address)
  593. assert_equal(@port, v4mc.local_address.ip_port)
  594. end
  595. def test_make_socket_ipv6_multicast
  596. skip 'IPv6 not available' unless
  597. Socket.ip_address_list.any? { |addrinfo| addrinfo.ipv6? && !addrinfo.ipv6_loopback? }
  598. begin
  599. v6mc = @rs.make_socket('ff02::1')
  600. rescue Errno::EADDRNOTAVAIL
  601. return # IPv6 address for multicast not available
  602. rescue Errno::ENOBUFS => e
  603. skip "Missing multicast support in OS: #{e.message}"
  604. end
  605. if Socket.const_defined?(:SO_REUSEPORT) then
  606. assert v6mc.getsockopt(:SOCKET, :SO_REUSEPORT).bool
  607. else
  608. assert v6mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool
  609. end
  610. assert_equal('::1', v6mc.local_address.ip_address)
  611. assert_equal(@port, v6mc.local_address.ip_port)
  612. end
  613. def test_ring_server_ipv4_multicast
  614. @rs.shutdown
  615. begin
  616. @rs = Rinda::RingServer.new(@ts, [['239.0.0.1', '0.0.0.0']], @port)
  617. rescue Errno::ENOBUFS => e
  618. skip "Missing multicast support in OS: #{e.message}"
  619. end
  620. v4mc = @rs.instance_variable_get('@sockets').first
  621. begin
  622. if Socket.const_defined?(:SO_REUSEPORT) then
  623. assert(v4mc.getsockopt(:SOCKET, :SO_REUSEPORT).bool)
  624. else
  625. assert(v4mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool)
  626. end
  627. rescue TypeError
  628. if /aix/ =~ RUBY_PLATFORM
  629. skip "Known bug in getsockopt(2) on AIX"
  630. end
  631. raise $!
  632. end
  633. assert_equal('0.0.0.0', v4mc.local_address.ip_address)
  634. assert_equal(@port, v4mc.local_address.ip_port)
  635. end
  636. def test_ring_server_ipv6_multicast
  637. skip 'IPv6 not available' unless
  638. Socket.ip_address_list.any? { |addrinfo| addrinfo.ipv6? && !addrinfo.ipv6_loopback? }
  639. @rs.shutdown
  640. begin
  641. @rs = Rinda::RingServer.new(@ts, [['ff02::1', '::1', 0]], @port)
  642. rescue Errno::EADDRNOTAVAIL
  643. return # IPv6 address for multicast not available
  644. end
  645. v6mc = @rs.instance_variable_get('@sockets').first
  646. if Socket.const_defined?(:SO_REUSEPORT) then
  647. assert v6mc.getsockopt(:SOCKET, :SO_REUSEPORT).bool
  648. else
  649. assert v6mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool
  650. end
  651. assert_equal('::1', v6mc.local_address.ip_address)
  652. assert_equal(@port, v6mc.local_address.ip_port)
  653. end
  654. def test_shutdown
  655. @rs.shutdown
  656. assert_nil(@rs.do_reply, 'otherwise should hang forever')
  657. end
  658. private
  659. def with_timeout(n)
  660. aoe = Thread.abort_on_exception
  661. Thread.abort_on_exception = true
  662. tl0 = Thread.list
  663. tl = nil
  664. th = Thread.new(Thread.current) do |mth|
  665. sleep n
  666. (tl = Thread.list - tl0).each {|t|t.raise(Timeout::Error)}
  667. mth.raise(Timeout::Error)
  668. end
  669. tl0 << th
  670. yield
  671. rescue Timeout::Error => e
  672. $stderr.puts "TestRingServer#with_timeout: timeout in #{n}s:"
  673. $stderr.puts caller
  674. if tl
  675. bt = e.backtrace
  676. tl.each do |t|
  677. begin
  678. t.value
  679. rescue Timeout::Error => e
  680. bt.unshift("")
  681. bt[0, 0] = e.backtrace
  682. end
  683. end
  684. end
  685. raise Timeout::Error, "timeout", bt
  686. ensure
  687. if th
  688. th.kill
  689. th.join
  690. end
  691. Thread.abort_on_exception = aoe
  692. end
  693. def wait_for(n)
  694. t = n + Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
  695. until yield
  696. if t < Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
  697. flunk "timeout during waiting call"
  698. end
  699. sleep 0.1
  700. end
  701. end
  702. end
  703. class TestRingFinger < Test::Unit::TestCase
  704. include RingIPv6
  705. def setup
  706. @rf = Rinda::RingFinger.new
  707. end
  708. def test_make_socket_unicast
  709. v4 = @rf.make_socket('127.0.0.1')
  710. assert(v4.getsockopt(:SOL_SOCKET, :SO_BROADCAST).bool)
  711. rescue TypeError
  712. if /aix/ =~ RUBY_PLATFORM
  713. skip "Known bug in getsockopt(2) on AIX"
  714. end
  715. raise $!
  716. ensure
  717. v4.close if v4
  718. end
  719. def test_make_socket_ipv4_multicast
  720. v4mc = @rf.make_socket('239.0.0.1')
  721. assert_equal(1, v4mc.getsockopt(:IPPROTO_IP, :IP_MULTICAST_LOOP).ipv4_multicast_loop)
  722. assert_equal(1, v4mc.getsockopt(:IPPROTO_IP, :IP_MULTICAST_TTL).ipv4_multicast_ttl)
  723. ensure
  724. v4mc.close if v4mc
  725. end
  726. def test_make_socket_ipv6_multicast
  727. ipv6_mc(@rf) do |v6mc|
  728. assert_equal(1, v6mc.getsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_LOOP).int)
  729. assert_equal(1, v6mc.getsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS).int)
  730. end
  731. end
  732. def test_make_socket_ipv4_multicast_hops
  733. @rf.multicast_hops = 2
  734. v4mc = @rf.make_socket('239.0.0.1')
  735. assert_equal(2, v4mc.getsockopt(:IPPROTO_IP, :IP_MULTICAST_TTL).ipv4_multicast_ttl)
  736. ensure
  737. v4mc.close if v4mc
  738. end
  739. def test_make_socket_ipv6_multicast_hops
  740. ipv6_mc(@rf, 2) do |v6mc|
  741. assert_equal(2, v6mc.getsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS).int)
  742. end
  743. end
  744. end
  745. end