spec/z_k/client_spec.rb in zk-0.8.8 vs spec/z_k/client_spec.rb in zk-0.8.9

- old
+ new

@@ -1,10 +1,11 @@ require 'spec_helper' describe ZK::Client do before do - @zk = ZK.new("localhost:#{ZK_TEST_PORT}") + @connection_string = "localhost:#{ZK_TEST_PORT}" + @zk = ZK.new(@connection_string) wait_until{ @zk.connected? } @zk.rm_rf('/test') end after do @@ -80,10 +81,11 @@ it %[should block until the node is deleted] do @a = false th = Thread.new do + @zk.block_until_node_deleted(@path) @a = true end Thread.pass @@ -92,19 +94,140 @@ @zk.delete(@path) wait_until(2) { @a } @a.should be_true end + + shared_examples_for 'session death' do + def deliver_session_event_to(event_num, zk) + # jeez, Zookeeper callbacks are so frustratingly stupid + bogus_event = ZookeeperCallbacks::WatcherCallback.new + bogus_event.initialize_context(:type => -1, :state => event_num, :path => '', :context => 'bogustestevent') + # XXX: this is bad because we're in the wrong thread, but we'll fix this after the next Zookeeper release + zk.event_handler.process(bogus_event) + end + + before do + @other_zk = ZK.new(@connection_string) + end + + after do + @other_zk.close! unless @other_zk.closed? + end + + it %[should wake up in the case of an expired session and throw an exception] do + @a = false + + @other_zk.event_handler.register_state_handler(zoo_state) do |event| + @a = event + end + + th = Thread.new do + @other_zk.block_until_node_deleted(@path) + end + + wait_until(2) { th.status == 'sleep' } + + # not on the other thread, this may be bad + deliver_session_event_to(zoo_state, @other_zk) + + # ditto, this is probably happening synchrnously + wait_until(2) { @a } + + lambda { th.join(2) }.should raise_error(zoo_error_class) + end + end + + describe 'exceptional conditions' do + describe 'ZOO_EXPIRED_SESSION_STATE' do + let(:zoo_state) { ZookeeperConstants::ZOO_EXPIRED_SESSION_STATE } + let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::SessionExpired } + + it_behaves_like 'session death' + end + + describe 'ZOO_CONNECTING_STATE' do + let(:zoo_state) { ZookeeperConstants::ZOO_CONNECTING_STATE } + let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::NotConnected } + + it_behaves_like 'session death' + end + + describe 'ZOO_CLOSED_STATE' do + let(:zoo_state) { ZookeeperConstants::ZOO_CLOSED_STATE } + let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::ConnectionClosed } + + it_behaves_like 'session death' + end + end end end describe 'session_id and session_passwd' do it %[should expose the underlying session_id] do @zk.session_id.should be_kind_of(Fixnum) end it %[should expose the underlying session_passwd] do @zk.session_passwd.should be_kind_of(String) + end + end + + describe 'reopen' do + describe 'watchers' do + before do + @path = '/testwatchers' + @queue = Queue.new + end + + after do + @zk.delete(@path) + end + + def ensure_event_delivery! + @sub ||= @zk.event_handler.register(@path) do |event| + logger.debug { "got event: #{event.inspect}" } + @queue << event + end + + @zk.exists?(@path, :watch => true).should be_false + @zk.create(@path, '') + + logger.debug { "waiting for event delivery" } + + wait_until(2) do + begin + @events << @queue.pop(true) + true + rescue ThreadError + false + end + end + + # first watch delivered correctly + @events.length.should > 0 + end + + it %[should fire re-registered watchers after reopen (#9)] do + @events = [] + + logger.debug { "ensure event delivery" } + ensure_event_delivery! + + logger.debug { "reopening connection" } + @zk.reopen + + wait_until(2) { @zk.connected? } + + logger.debug { "deleting path" } + @zk.delete(@path) + + logger.debug { "clearing events" } + @events.clear + + logger.debug { "taunt them a second time" } + ensure_event_delivery! + end end end end