diff --git a/RELEASES.markdown b/RELEASES.markdown index 6a6192e..f221c45 100644 --- a/RELEASES.markdown +++ b/RELEASES.markdown @@ -2,10 +2,26 @@ This file notes feature differences and bugfixes contained between releases. ### v1.0.0 ### +* Support for 1.8.7 WILL BE *DROPPED* in v1.1. You've been warned. + +* Threaded client (the default one) will now automatically reconnect (i.e. `reopen()`) if a `SESSION_EXPIRED` or `AUTH_FAILED` event is received. Thanks to @eric for pointing out the _nose-on-your-face obviousness_ and importance of this. If users want to handle these events themselves, and not automatically reopen, you can pass `:reconnect => false` to the constructor. + * allow for both :sequence and :sequential arguments to create, because I always forget which one is the "right one" * add zk.register(:all) to recevie node updates for all nodes (i.e. not filtered on path) +* add 'interest' feature to zk.register, now you can indicate what kind of events should be delivered to the given block (previously you had to do that filtering inside the block). The default behavior is still the same, if no 'interest' is given, then all event types for the given path will be delivered to that block. + + zk.register('/path', :created) do |event| + # event.node_created? will always be true + end + + # or multiple kinds of events + + zk.register('/path', [:created, :changed]) do |event| + # (event.node_created? or event.node_changed?) will always be true + end + * create now allows you to pass a path and options, instead of requiring the blank string zk.create('/path', '', :sequential => true) @@ -16,6 +32,20 @@ This file notes feature differences and bugfixes contained between releases. * fix for shutdown: close! called from threadpool will do the right thing +* Chroot users rejoice! By default, ZK.new will create a chrooted path for you. + + ZK.new('localhost:2181/path', :chroot => :create) # the default, create the path before returning connection + + ZK.new('localhost:2181/path', :chroot => :check) # make sure the chroot exists, raise if not + + ZK.new('localhost:2181/path', :chroot => :do_nothing) # old default behavior + + # and, just for kicks + + ZK.new('localhost:2181', :chroot => '/path') # equivalent to 'localhost:2181/path', :chroot => :create + +* Most of the event functionality used is now in a ZK::Event module. This is still mixed into the underlying slyphon-zookeeper class, but now all of the important and relevant methods are documented, and Event appears as a first-class citizen. + ### v0.9.1 ### The "Don't forget to update the RELEASES file before pushing a new release" release diff --git a/lib/zk.rb b/lib/zk.rb index c7146d0..3c158ef 100644 --- a/lib/zk.rb +++ b/lib/zk.rb @@ -13,6 +13,7 @@ require 'zk/logging' require 'zk/exceptions' require 'zk/extensions' +require 'zk/event' require 'zk/stat' require 'zk/threadpool' require 'zk/event_handler_subscription' @@ -27,11 +28,19 @@ require 'zk/group' module ZK - ZK_ROOT = File.expand_path('../..', __FILE__) unless defined?(ZK_ROOT) + silence_warnings do + # @private + ZK_ROOT = File.expand_path('../..', __FILE__).freeze + + # @private + DEFAULT_SERVER = 'localhost:2181'.freeze + end - KILL_TOKEN = Object.new unless defined?(KILL_TOKEN) + unless defined?(KILL_TOKEN) + # @private + KILL_TOKEN = Object.new + end - DEFAULT_SERVER = 'localhost:2181'.freeze unless defined?(DEFAULT_SERVER) unless @logger @logger = Logger.new($stderr).tap { |n| n.level = Logger::ERROR } @@ -80,24 +89,36 @@ def self.logger=(logger) # # zk = ZK.new('localhost:2181') # - # @example Connection to a single server with a chroot + # @example Connection to a single server with a chroot (automatically created) # - # zk = ZK.new('localhost:2181/you/are/over/here') + # zk = ZK.new('localhost:2181/look/around/you') # # @example Connection to multiple servers (a cluster) # # zk = ZK.new('server1:2181,server2:2181,server3:2181') # - # @example Connection to multiple servers with a chroot + # @example Connection to multiple servers with a chroot (chroot will automatically be creatd) + # + # zk = ZK.new('server1:2181,server2:2181,server3:2181/look/around/you') + # + # @example Connection to a single server, assert that chroot path exists, but do not create it + # + # zk = ZK.new('localhost:2181/look/around/you', :chroot => :check) + # + # @example Connection to a single server, use a chrooted connection, do not check for validity, do not create + # + # zk = ZK.new('localhost:2181/look/around/you', :chroot => :do_nothing) + # + # @example Connection to a single server, chroot path specified as an option # - # zk = ZK.new('server1:2181,server2:2181,server3:2181/you/are/over/here') + # zk = ZK.new('localhost:2181', :chroot => '/look/around/you') # # @overload new(connection_str, opts={}, &block) # @param [String] connection_str A zookeeper host connection string, which # is a comma-separated list of zookeeper servers and an optional chroot # path. # - # @option opts [:create,:check,:nothing,String] :chroot (:create) if a chrooted + # @option opts [:create,:check,:do_nothing,String] :chroot (:create) if a chrooted # `connection_str`, `:chroot` can have the following values: # # * `:create` (the default), then we will use a secondary (short-lived) @@ -108,15 +129,15 @@ def self.logger=(logger) # will raise a {Exceptions::ChrootPathDoesNotExistError # ChrootPathDoesNotExistError} if the path doesn't exist. # - # * `:ignore`, we do not create the path and furthermore we do not - # perform the check + # * `:do_nothing`, we do not create the path and furthermore we do not + # perform the check (the `<= 0.9` behavior). # # * if a `String` is given, it is used as the chroot path, and we will follow # the same rules as if `:create` was given if `connection_str` also # contains a chroot path, we raise an `ArgumentError` # # * if you don't like this for some reason, you can always use - # {ZK::Client::Threaded.initialize Threaded.new} directly. You probably + # {ZK::Client::Threaded#initialize Threaded.new} directly. You probably # also hate happiness and laughter. # # @raise [ChrootPathDoesNotExistError] if a chroot path is specified, @@ -178,7 +199,7 @@ def self.do_chroot_setup(cnx_str, chroot_opt=:create) host, chroot_path = Client.split_chroot(cnx_str) case chroot_opt - when :ignore + when :do_nothing return when String if chroot_path @@ -195,7 +216,7 @@ def self.do_chroot_setup(cnx_str, chroot_opt=:create) when :create, :check # no-op, valid options for later else - raise ArgumentError, ":chroot must be one of :create, :check, :ignore, or a String, not: #{chroot_opt.inspect}" + raise ArgumentError, ":chroot must be one of :create, :check, :do_nothing, or a String, not: #{chroot_opt.inspect}" end return cnx_str unless chroot_path # if by this point, we don't have a chroot_path, then there isn't one to be had diff --git a/lib/zk/client/base.rb b/lib/zk/client/base.rb index 73c0139..1841505 100644 --- a/lib/zk/client/base.rb +++ b/lib/zk/client/base.rb @@ -737,6 +737,10 @@ def session_passwd # This method will return an {EventHandlerSubscription} instance that can be used # to remove the block from further updates by calling its `.unsubscribe` method. # + # You can specify a list of event types after the path that you wish to + # receive in your block. This allows you to register different blocks for + # different types of events. + # # @note All node watchers are one-shot handlers. After an event is delivered to # your handler, you *must* re-watch the node to receive more events. This # leads to a pattern you will find throughout ZK code that avoids races, @@ -764,11 +768,31 @@ def session_passwd # do_something_when_node_deleted # call the callback # end # + # @example only creation events + # + # sub = zk.register('/path/to/znode', :created) do |event| + # # do something when the node is created + # end + # + # @example only changed or children events + # + # sub = zk.register('/path/to/znode', [:changed, :child]) do |event| + # if event.node_changed? + # # do something on change + # else + # # we know it's a child event + # end + # end # # @param [String,:all] path the znode path you want to listen to, or the # special value :all, that will cause the block to be delivered events # for all znode paths # + # @param [Array,Symbol,nil] interests a symbol or array-of-symbols indicating + # which events you would like the block to be called for. Valid events + # are :created, :deleted, :changed, and :child. If nil, the block will + # receive all events + # # @param [Block] block the block to execute when a watch event happpens # # @yield [event] We will call your block with the watch event object (which @@ -781,8 +805,8 @@ def session_passwd # @see ZK::EventHandlerSubscription # @see https://github.com/slyphon/zk/wiki/Events the wiki page on using events effectively # - def register(path, &block) - event_handler.register(path, &block) + def register(path, interests=nil, &block) + event_handler.register(path, interests, &block) end # returns true if the caller is calling from the event dispatch thread diff --git a/lib/zk/client/threaded.rb b/lib/zk/client/threaded.rb index d755e40..2d747ab 100644 --- a/lib/zk/client/threaded.rb +++ b/lib/zk/client/threaded.rb @@ -187,7 +187,6 @@ def raw_event_handler(event) end protected - # allows for the Mutliplexed client to wrap the connection in its ContinuationProxy # @private def create_connection(*args) ::Zookeeper.new(*args) diff --git a/lib/zk/core_ext.rb b/lib/zk/core_ext.rb index b91413a..cf66645 100644 --- a/lib/zk/core_ext.rb +++ b/lib/zk/core_ext.rb @@ -41,6 +41,18 @@ def extract_options! end end end + + # backport this from 1.9.x to 1.8.7 + # + # this obviously cannot replicate the copy-on-write semantics of the + # 1.9.3 version, and only provides a naieve filtering functionality. + # + # also, does not handle the "returning an enumerator" case + unless method_defined?(:select!) + def select!(&b) + replace(select(&b)) + end + end end # @private diff --git a/lib/zk/event.rb b/lib/zk/event.rb new file mode 100644 index 0000000..baa9a6b --- /dev/null +++ b/lib/zk/event.rb @@ -0,0 +1,168 @@ +module ZK + # Provides most of the functionality ZK uses around events. Base class is actually + # [ZookeeperCallbacks::WatcherCallback](http://rubydoc.info/gems/slyphon-zookeeper/ZookeeperCallbacks/WatcherCallback), + # but this module is mixed in and provides a lot of useful syntactic sugar. + # + module Event + include ZookeeperConstants + + # unless defined? apparently messes up yard's ability to see the @private + silence_warnings do + # XXX: this is not uesd it seems + # @private + EVENT_NAME_MAP = { + 1 => 'created', + 2 => 'deleted', + 3 => 'changed', + 4 => 'child', + -1 => 'session', + -2 => 'notwatching', + }.freeze + + # @private + STATES = %w[connecting associating connected auth_failed expired_session].freeze + + # @private + EVENT_TYPES = %w[created deleted changed child session notwatching].freeze + end + + # The numeric constant (one of `ZOO_*_EVENT`) that ZooKeeper sets to + # indicate the type of event this is. Users are advised to use the '?' + # methods below instead of using this value. + # + # @return [Fixnum] + def type + # no-op, the functionality is provided by the class this is mixed into. + # here only for documentation purposes + end + + # The numeric constant (one of `ZOO_*_STATE`) that ZooKeeper sets to + # indicate the session state this event is notifying us of. Users are + # encouraged to use the '?' methods below, instead of this value. + # + # @return [Fixnum] + def state + # no-op, the functionality is provided by the class this is mixed into. + # here only for documentation purposes + end + + # The path this event is in reference to. + # + # @return [String,nil] This value will be nil if `session_event?` is false, otherwise + # a String containing the path this event was triggered in reference to + def path + # no-op, the functionality is provided by the class this is mixed into. + # here only for documentation purposes + end + + # Is this event notifying us we're in the connecting state? + def connecting? + @state == ZOO_CONNECTING_STATE + end + alias state_connecting? connecting? + + # Is this event notifying us we're in the associating state? + def associating? + @state == ZOO_ASSOCIATING_STATE + end + alias state_associating? associating? + + # Is this event notifying us we're in the connected state? + def connected? + @state == ZOO_CONNECTED_STATE + end + alias state_connected? connected? + + # Is this event notifying us we're in the auth_failed state? + def auth_failed? + @state == ZOO_AUTH_FAILED_STATE + end + alias state_auth_failed? auth_failed? + + # Is this event notifying us we're in the expired_session state? + def expired_session? + @state == ZOO_EXPIRED_SESSION_STATE + end + alias state_expired_session? expired_session? + + # return this event's state name as a string "ZOO_*_STATE", used for debugging + def state_name + (name = STATE_NAMES[@state]) ? "ZOO_#{name.to_s.upcase}_STATE" : '' + end + + # Has a node been created? + def node_created? + @type == ZOO_CREATED_EVENT + end + + # Has a node been deleted? + def node_deleted? + @type == ZOO_DELETED_EVENT + end + + # Has a node changed? + def node_changed? + @type == ZOO_CHANGED_EVENT + end + + # Has a node's list of children changed? + def node_child? + @type == ZOO_CHILD_EVENT + end + + # Is this a session-related event? + # + # @deprecated This was an artifact of the way these methods were created + # originally, will be removed because it's kinda dumb. use {#session_event?} + def node_session? + @type == ZOO_SESSION_EVENT + end + + # I have never seen this event delivered. here for completeness. + def node_notwatching? + @type == ZOO_NOTWATCHING_EVENT + end + alias node_not_watching? node_notwatching? + + # return this event's type name as a string "ZOO_*_EVENT", used for debugging + def event_name + (name = EVENT_TYPE_NAMES[@type]) ? "ZOO_#{name.to_s.upcase}_EVENT" : '' + end + + # used by the EventHandler + # @private + def interest_key + EVENT_TYPE_NAMES.fetch(@type).to_sym + end + + # has this watcher been called because of a change in connection state? + def session_event? + @type == ZOO_SESSION_EVENT + end + alias state_event? session_event? + + # has this watcher been called because of a change to a zookeeper node? + # `node_event?` and `session_event?` are mutually exclusive. + def node_event? + path and not path.empty? + end + + # according to [the programmer's guide](http://zookeeper.apache.org/doc/r3.3.4/zookeeperProgrammers.html#Java+Binding) + # + # > once a ZooKeeper object is closed or receives a fatal event + # > (SESSION_EXPIRED and AUTH_FAILED), the ZooKeeper object becomes + # > invalid. + # + # this will return true for either of those cases + # + def client_invalid? + (@state == ZOO_EXPIRED_SESSION_STATE) || (@state == ZOO_AUTH_FAILED_STATE) + end + end +end + +# @private +class ZookeeperCallbacks::WatcherCallback + include ::ZK::Event +end + diff --git a/lib/zk/event_handler.rb b/lib/zk/event_handler.rb index 4702cb3..9b8ce17 100644 --- a/lib/zk/event_handler.rb +++ b/lib/zk/event_handler.rb @@ -36,10 +36,10 @@ def initialize(zookeeper_client) end # @see ZK::Client::Base#register - def register(path, &block) + def register(path, interests=nil, &block) path = ALL_NODE_EVENTS_KEY if path == :all - EventHandlerSubscription.new(self, path, block).tap do |subscription| + EventHandlerSubscription.new(self, path, block, interests).tap do |subscription| synchronize { @callbacks[path] << subscription } end end @@ -127,6 +127,12 @@ def process(event) cb_ary.flatten! # takes care of not modifying original arrays cb_ary.compact! + # we only filter for node events + if event.node_event? + interest_key = event.interest_key + cb_ary.select! { |sub| sub.interests.include?(interest_key) } + end + safe_call(cb_ary, event) end @@ -145,7 +151,6 @@ def clear_watch_restrictions(event) end end - public # used during shutdown to clear registered listeners @@ -155,7 +160,7 @@ def clear! #:nodoc: @callbacks.clear nil end - end + end # @private def synchronize @@ -189,8 +194,9 @@ def restricting_new_watches_for?(watch_type, path) # fired. This prevents one event delivery to *every* callback per :watch => true # argument. # - # due to somewhat poor design, we destructively modify opts before we yield - # and the client implictly knows this + # due to arguably poor design, we destructively modify opts before we yield + # and the client implictly knows this (this method constitutes some of the option + # parsing for the base class methods) # # @private def setup_watcher!(watch_type, opts) diff --git a/lib/zk/event_handler_subscription.rb b/lib/zk/event_handler_subscription.rb index e6a0220..453f111 100644 --- a/lib/zk/event_handler_subscription.rb +++ b/lib/zk/event_handler_subscription.rb @@ -15,9 +15,21 @@ class EventHandlerSubscription # @return [Proc] attr_accessor :callback + # an array of what kinds of events this handler is interested in receiving + # + # @return [Set] containing any combination of :create, :change, :delete, + # or :children + # # @private - def initialize(event_handler, path, callback) + attr_accessor :interests + + ALL_EVENTS = [:created, :deleted, :changed, :child].freeze unless defined?(ALL_EVENTS) + ALL_EVENT_SET = Set.new(ALL_EVENTS).freeze unless defined?(ALL_EVENT_SET) + + # @private + def initialize(event_handler, path, callback, interests) @event_handler, @path, @callback = event_handler, path, callback + @interests = prep_interests(interests) end # unsubscribe from the path or state you were watching @@ -31,6 +43,26 @@ def unsubscribe def call(event) callback.call(event) end + + private + def prep_interests(a) + return ALL_EVENT_SET if a.nil? + + rval = + case a + when Array + Set.new(a) + when Symbol + Set.new([a]) + else + raise ArgumentError, "Don't know how to handle interests: #{a.inspect}" + end + + rval.tap do |rv| + invalid = (rv - ALL_EVENT_SET) + raise ArgumentError, "Invalid event name(s) #{invalid.to_a.inspect} given" unless invalid.empty? + end + end end end diff --git a/lib/zk/extensions.rb b/lib/zk/extensions.rb index 7ee77ec..ded6ed7 100644 --- a/lib/zk/extensions.rb +++ b/lib/zk/extensions.rb @@ -57,93 +57,16 @@ module ClassMethods # # def create(*args, &block) - # honestly, i have no idea how this could *possibly* work, but it does... cb_inst = new { block.call(cb_inst) } end end end # Callback - - module WatcherCallbackExt - include ZookeeperConstants - - # XXX: this is not uesd it seems - EVENT_NAME_MAP = { - 1 => 'created', - 2 => 'deleted', - 3 => 'changed', - 4 => 'child', - -1 => 'session', - -2 => 'notwatching', - }.freeze unless defined?(EVENT_NAME_MAP) - - # XXX: remove this duplication here since this is available in ZookeeperConstants - # @private - STATES = %w[connecting associating connected auth_failed expired_session].freeze unless defined?(STATES) - - # XXX: ditto above - # @private - EVENT_TYPES = %w[created deleted changed child session notwatching].freeze unless defined?(EVENT_TYPES) - - # argh, event.state_expired_session? is really dumb, should be event.expired_session? - - STATES.each do |state| - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{state}? - @state == ZOO_#{state.upcase}_STATE - end - - alias state_#{state}? #{state}? # alias for backwards compatibility - RUBY - end - - def state_name - (name = STATE_NAMES[@state]) ? "ZOO_#{name.to_s.upcase}_STATE" : '' - end - - EVENT_TYPES.each do |ev| - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def node_#{ev}? - @type == ZOO_#{ev.upcase}_EVENT - end - RUBY - end - - def event_name - (name = EVENT_TYPE_NAMES[@type]) ? "ZOO_#{name.to_s.upcase}_EVENT" : '' - end - - alias :node_not_watching? :node_notwatching? - - # has this watcher been called because of a change in connection state? - def state_event? - @type == ZOO_SESSION_EVENT - end - alias session_event? state_event? - - # according to [the programmer's guide](http://zookeeper.apache.org/doc/r3.3.4/zookeeperProgrammers.html#Java+Binding) - # - # > once a ZooKeeper object is closed or receives a fatal event - # > (SESSION_EXPIRED and AUTH_FAILED), the ZooKeeper object becomes - # > invalid. - # - # this will return true for either of those cases - # - def client_invalid? - (@state == ZOO_EXPIRED_SESSION_STATE) || (@state == ZOO_AUTH_FAILED_STATE) - end - - # has this watcher been called because of a change to a zookeeper node? - def node_event? - path and not path.empty? - end - end end # Callbacks end # Extensions end # ZK # ZookeeperCallbacks::Callback.extend(ZK::Extensions::Callbacks::Callback) ZookeeperCallbacks::Callback.send(:include, ZK::Extensions::Callbacks::Callback) -ZookeeperCallbacks::WatcherCallback.send(:include, ZK::Extensions::Callbacks::WatcherCallbackExt) # Include the InterruptedSession module in key ZookeeperExceptions to allow # clients to catch a single error type when waiting on a node (for example) diff --git a/spec/support/event_catcher.rb b/spec/support/event_catcher.rb new file mode 100644 index 0000000..1433abb --- /dev/null +++ b/spec/support/event_catcher.rb @@ -0,0 +1,11 @@ +class EventCatcher < Struct.new(:created, :changed, :deleted, :child, :all) + + def initialize(*args) + super + + [:created, :changed, :deleted, :child, :all].each do |k| + self.__send__(:"#{k}=", []) if self.__send__(:"#{k}").nil? + end + end +end + diff --git a/spec/support/exist_matcher.rb b/spec/support/exist_matcher.rb new file mode 100644 index 0000000..2dcad06 --- /dev/null +++ b/spec/support/exist_matcher.rb @@ -0,0 +1,6 @@ +RSpec::Matchers.define :exist do + match do |actual| + actual.exists? + end +end + diff --git a/spec/watch_spec.rb b/spec/watch_spec.rb index e721fd7..21f1191 100644 --- a/spec/watch_spec.rb +++ b/spec/watch_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ZK do - describe do + describe 'watchers' do before do mute_logger do @cnx_str = "localhost:#{ZK_TEST_PORT}" @@ -180,8 +180,160 @@ def wait_for_events_to_not_be_delivered(events) wait_until { events.length == 2 }.should be_true end end - end + describe %[event interest] do + context do # event catcher scope + before do + @events = EventCatcher.new + + @zk.register(@path, :created) do |event| + @events.created << event + end + + @zk.register(@path, :changed) do |event| + @events.changed << event + end + + @zk.register(@path, :child) do |event| + @events.child << event + end + + @zk.register(@path, :deleted) do |event| + @events.deleted << event + end + + # this will catch all events, that way we don't have to wait for an + # event to *not* be delivered to one of the other callbacks (which is + # kinda stupid) + @zk.register(@path) do |event| + @events.all << event + end + end + + it %[should deliver only the created event to the created block] do + @zk.stat(@path, :watch => true).should_not exist + + @zk.create(@path) + wait_while { @events.created.empty? }.should be_false + @events.created.first.should be_node_created + + @zk.stat(@path, :watch => true).should exist + + @events.all.length.should == 1 + + @zk.delete(@path) + + wait_until { @events.all.length > 1 } + + # :deleted event was delivered, make sure it didn't get delivered to the :created block + @events.created.length.should == 1 + end + + it %[should deliver only the changed event to the changed block] do + @zk.create(@path) + + @zk.stat(@path, :watch => true).should exist + + @zk.set(@path, 'data') + + wait_while { @events.changed.empty? } + + @events.changed.first.should be_node_changed + + @zk.stat(@path, :watch => true).should exist + + @events.all.length.should == 1 + + @zk.delete(@path) + + wait_until { @events.all.length > 1 } + + # :deleted event was delivered, make sure it didn't get delivered to the :changed block + @events.changed.length.should == 1 + end + + it %[should deliver only the child event to the child block] do + @zk.create(@path) + + @zk.children(@path, :watch => true).should be_empty + + child_path = @zk.create("#{@path}/m", '', :sequence => true) + + wait_while { @events.child.empty? } + + @events.child.first.should be_node_child + + @zk.stat(@path, :watch => true).should exist + + @events.all.length.should == 1 + + @zk.set(@path, '') # equivalent to a 'touch' + + wait_until { @events.all.length > 1 } + + # :changed event was delivered, make sure it didn't get delivered to the :child block + @events.child.length.should == 1 + end + + it %[should deliver only the deleted event to the deleted block] do + @zk.create(@path) + + @zk.stat(@path, :watch => true).should exist + + @zk.delete(@path) + + wait_while { @events.deleted.empty? } + + @events.deleted.first.should be_node_deleted + + @zk.stat(@path, :watch => true).should_not exist + + @events.all.length.should == 1 + + @zk.create(@path) + + wait_until { @events.all.length > 1 } + + # :deleted event was delivered, make sure it didn't get delivered to the :created block + @events.deleted.length.should == 1 + end + end # event catcher scope + + it %[should deliver interested events to a block registered for multiple deliveries] do + @events = [] + + @zk.register(@path, [:created, :changed]) do |event| + @events << event + end + + @zk.stat(@path, :watch => true).should_not exist + + @zk.create(@path) + + wait_while { @events.empty? } + + @events.length.should == 1 + + @events.first.should be_node_created + + @zk.stat(@path, :watch => true).should exist + + @zk.set(@path, 'blah') + + wait_until { @events.length > 1 } + + @events.length.should == 2 + + @events.last.should be_node_changed + end + + it %[should barf if an invalid event name is given] do + lambda do + @zk.register(@path, :tripping) { } + end.should raise_error(ArgumentError) + end + end # event interest + end # watchers describe 'state watcher' do describe 'live-fire test' do @@ -210,7 +362,7 @@ def wait_for_events_to_not_be_delivered(events) m.should_receive(:state).and_return(ZookeeperConstants::ZOO_CONNECTED_STATE) end end - end + end # registered listeners end end diff --git a/spec/zk/module_spec.rb b/spec/zk/module_spec.rb index 1f63f08..43f143d 100644 --- a/spec/zk/module_spec.rb +++ b/spec/zk/module_spec.rb @@ -90,9 +90,9 @@ end end - describe %[and :chroot => :ignore] do + describe %[and :chroot => :do_nothing] do it %[should return a connection in a weird state] do - @zk = ZK.new("localhost:2181#{chroot_path}", :chroot => :ignore) + @zk = ZK.new("localhost:2181#{chroot_path}", :chroot => :do_nothing) lambda { @zk.get('/') }.should raise_error(ZK::Exceptions::NoNode) end end @@ -160,9 +160,9 @@ end end - describe %[and :chroot => :ignore] do + describe %[and :chroot => :do_nothing] do it %[should totally work] do - @zk = ZK.new("localhost:2181#{chroot_path}", :chroot => :ignore) + @zk = ZK.new("localhost:2181#{chroot_path}", :chroot => :do_nothing) lambda { @zk.get('/') }.should_not raise_error end end