From 139f59f7d1ebe83130dce18e56c18c301e14cdec Mon Sep 17 00:00:00 2001 From: tobz Date: Tue, 21 Jan 2014 20:26:56 -0500 Subject: [PATCH 1/5] fix(store): properly hide hidden keys from watchers, not just gets --- store/store.go | 19 +++++++--- store/store_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++ store/watcher_hub.go | 19 +++++++--- 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/store/store.go b/store/store.go index 8593c5178..eb8c62123 100644 --- a/store/store.go +++ b/store/store.go @@ -289,7 +289,10 @@ func (s *store) Delete(nodePath string, dir, recursive bool) (*Event, error) { // update etcd index s.CurrentIndex++ - s.WatcherHub.notify(e) + if !n.IsHidden() { + s.WatcherHub.notify(e) + } + s.Stats.Inc(DeleteSuccess) return e, nil @@ -429,7 +432,9 @@ func (s *store) Update(nodePath string, newValue string, expireTime time.Time) ( eNode.Expiration, eNode.TTL = n.ExpirationAndTTL() - s.WatcherHub.notify(e) + if !n.IsHidden() { + s.WatcherHub.notify(e) + } s.Stats.Inc(UpdateSuccess) @@ -513,7 +518,10 @@ func (s *store) internalCreate(nodePath string, dir bool, value string, unique, s.CurrentIndex = nextIndex - s.WatcherHub.notify(e) + if !n.IsHidden() { + s.WatcherHub.notify(e) + } + return e, nil } @@ -568,7 +576,10 @@ func (s *store) DeleteExpiredKeys(cutoff time.Time) { node.Remove(true, true, callback) s.Stats.Inc(ExpireCount) - s.WatcherHub.notify(e) + + if !node.IsHidden() { + s.WatcherHub.notify(e) + } } } diff --git a/store/store_test.go b/store/store_test.go index 5eae1b46d..c08314250 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -499,6 +499,15 @@ func TestStoreWatchCreate(t *testing.T) { assert.Nil(t, e, "") } +// Ensure that the store doesn't see hidden key creations. +func TestStoreWatchCreateWithHiddenKey(t *testing.T) { + s := newStore() + w, _ := s.Watch("/_foo", false, false, 0) + s.Create("/_foo", false, "bar", false, Permanent) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + // Ensure that the store can watch for recursive key creation. func TestStoreWatchRecursiveCreate(t *testing.T) { s := newStore() @@ -509,6 +518,15 @@ func TestStoreWatchRecursiveCreate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } +// Ensure that the store can watch for recursive key creation. +func TestStoreWatchRecursiveCreateWithHiddenKey(t *testing.T) { + s := newStore() + w, _ := s.Watch("/foo", true, false, 0) + s.Create("/foo/_bar", false, "baz", false, Permanent) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + // Ensure that the store can watch for key updates. func TestStoreWatchUpdate(t *testing.T) { s := newStore() @@ -520,6 +538,16 @@ func TestStoreWatchUpdate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo", "") } +// Ensure that the store doesn't see hidden key updates. +func TestStoreWatchUpdateWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/_foo", false, "bar", false, Permanent) + w, _ := s.Watch("/_foo", false, false, 0) + s.Update("/_foo", "baz", Permanent) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + // Ensure that the store can watch for recursive key updates. func TestStoreWatchRecursiveUpdate(t *testing.T) { s := newStore() @@ -531,6 +559,16 @@ func TestStoreWatchRecursiveUpdate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } +// Ensure that the store doesn't get recursive key updates for hidden keys. +func TestStoreWatchRecursiveUpdateWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/foo/_bar", false, "baz", false, Permanent) + w, _ := s.Watch("/foo", true, false, 0) + s.Update("/foo/_bar", "baz", Permanent) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + // Ensure that the store can watch for key deletions. func TestStoreWatchDelete(t *testing.T) { s := newStore() @@ -542,6 +580,16 @@ func TestStoreWatchDelete(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo", "") } +// Ensure that the store doesn't see hidden key deletions. +func TestStoreWatchDeleteWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/_foo", false, "bar", false, Permanent) + w, _ := s.Watch("/foo", false, false, 0) + s.Delete("/_foo", false, false) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + // Ensure that the store can watch for recursive key deletions. func TestStoreWatchRecursiveDelete(t *testing.T) { s := newStore() @@ -553,6 +601,16 @@ func TestStoreWatchRecursiveDelete(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } +// Ensure that the store can watch for recursive key deletions. +func TestStoreWatchRecursiveDeleteWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/foo/_bar", false, "baz", false, Permanent) + w, _ := s.Watch("/foo", true, false, 0) + s.Delete("/foo/_bar", false, false) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + // Ensure that the store can watch for CAS updates. func TestStoreWatchCompareAndSwap(t *testing.T) { s := newStore() @@ -602,6 +660,32 @@ func TestStoreWatchExpire(t *testing.T) { assert.Equal(t, e.Node.Key, "/foofoo", "") } +// Ensure that the store doesn't see expirations of hidden keys. +func TestStoreWatchExpireWithHiddenKey(t *testing.T) { + s := newStore() + + stopChan := make(chan bool) + defer func() { + stopChan <- true + }() + go mockSyncService(s.DeleteExpiredKeys, stopChan) + + s.Create("/_foo", false, "bar", false, time.Now().Add(500*time.Millisecond)) + s.Create("/foofoo", false, "barbarbar", false, time.Now().Add(1000*time.Millisecond)) + + w, _ := s.Watch("/", true, false, 0) + c := w.EventChan + e := nbselect(c) + assert.Nil(t, e, "") + time.Sleep(600 * time.Millisecond) + e = nbselect(c) + assert.Nil(t, e, "") + time.Sleep(600 * time.Millisecond) + e = nbselect(c) + assert.Equal(t, e.Action, "expire", "") + assert.Equal(t, e.Node.Key, "/foofoo", "") +} + // Ensure that the store can watch in streaming mode. func TestStoreWatchStream(t *testing.T) { s := newStore() diff --git a/store/watcher_hub.go b/store/watcher_hub.go index aeda171a9..8c2487ab6 100644 --- a/store/watcher_hub.go +++ b/store/watcher_hub.go @@ -113,11 +113,11 @@ func (wh *watcherHub) notify(e *Event) { } } -func (wh *watcherHub) notifyWatchers(e *Event, path string, deleted bool) { +func (wh *watcherHub) notifyWatchers(e *Event, nodePath string, deleted bool) { wh.mutex.Lock() defer wh.mutex.Unlock() - l, ok := wh.watchers[path] + l, ok := wh.watchers[nodePath] if ok { curr := l.Front() @@ -126,7 +126,7 @@ func (wh *watcherHub) notifyWatchers(e *Event, path string, deleted bool) { w, _ := curr.Value.(*Watcher) - if w.notify(e, e.Node.Key == path, deleted) { + if !isHidden(nodePath) && w.notify(e, e.Node.Key == nodePath, deleted) { if !w.stream { // do not remove the stream watcher // if we successfully notify a watcher // we need to remove the watcher from the list @@ -142,7 +142,7 @@ func (wh *watcherHub) notifyWatchers(e *Event, path string, deleted bool) { if l.Len() == 0 { // if we have notified all watcher in the list // we can delete the list - delete(wh.watchers, path) + delete(wh.watchers, nodePath) } } } @@ -156,3 +156,14 @@ func (wh *watcherHub) clone() *watcherHub { EventHistory: clonedHistory, } } + +// isHidden checks if a path has a hidden key. since we don't get the Node +// object for notifyWatchers, we have to duplicate it here. consolidate me? +func isHidden(nodePath string) bool { + _, name := path.Split(nodePath) + if name == "" { + return false + } + + return name[0] == '_' +} From 7a948746a80fc7c117376537b823ddf3f8195224 Mon Sep 17 00:00:00 2001 From: tobz Date: Wed, 22 Jan 2014 09:02:42 -0500 Subject: [PATCH 2/5] fix(store): move logic to handle whether or not to notify (re: hidden keys) entirely into watcher hub --- store/store.go | 16 ++++------------ store/store_test.go | 21 +++++++++++++++------ store/watcher_hub.go | 3 ++- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/store/store.go b/store/store.go index eb8c62123..05f6626cc 100644 --- a/store/store.go +++ b/store/store.go @@ -289,9 +289,7 @@ func (s *store) Delete(nodePath string, dir, recursive bool) (*Event, error) { // update etcd index s.CurrentIndex++ - if !n.IsHidden() { - s.WatcherHub.notify(e) - } + s.WatcherHub.notify(e) s.Stats.Inc(DeleteSuccess) @@ -432,9 +430,7 @@ func (s *store) Update(nodePath string, newValue string, expireTime time.Time) ( eNode.Expiration, eNode.TTL = n.ExpirationAndTTL() - if !n.IsHidden() { - s.WatcherHub.notify(e) - } + s.WatcherHub.notify(e) s.Stats.Inc(UpdateSuccess) @@ -518,9 +514,7 @@ func (s *store) internalCreate(nodePath string, dir bool, value string, unique, s.CurrentIndex = nextIndex - if !n.IsHidden() { - s.WatcherHub.notify(e) - } + s.WatcherHub.notify(e) return e, nil } @@ -577,9 +571,7 @@ func (s *store) DeleteExpiredKeys(cutoff time.Time) { s.Stats.Inc(ExpireCount) - if !node.IsHidden() { - s.WatcherHub.notify(e) - } + s.WatcherHub.notify(e) } } diff --git a/store/store_test.go b/store/store_test.go index c08314250..33ecffac2 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -499,12 +499,15 @@ func TestStoreWatchCreate(t *testing.T) { assert.Nil(t, e, "") } -// Ensure that the store doesn't see hidden key creations. +// Ensure that the store can watch for hidden keys as long as it's an exact path match. func TestStoreWatchCreateWithHiddenKey(t *testing.T) { s := newStore() w, _ := s.Watch("/_foo", false, false, 0) s.Create("/_foo", false, "bar", false, Permanent) e := nbselect(w.EventChan) + assert.Equal(t, e.Action, "create", "") + assert.Equal(t, e.Node.Key, "/_foo", "") + e = nbselect(w.EventChan) assert.Nil(t, e, "") } @@ -518,7 +521,7 @@ func TestStoreWatchRecursiveCreate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } -// Ensure that the store can watch for recursive key creation. +// Ensure that the store doesn't see hidden key creates without an exact path match in recursive mode. func TestStoreWatchRecursiveCreateWithHiddenKey(t *testing.T) { s := newStore() w, _ := s.Watch("/foo", true, false, 0) @@ -545,6 +548,9 @@ func TestStoreWatchUpdateWithHiddenKey(t *testing.T) { w, _ := s.Watch("/_foo", false, false, 0) s.Update("/_foo", "baz", Permanent) e := nbselect(w.EventChan) + assert.Equal(t, e.Action, "update", "") + assert.Equal(t, e.Node.Key, "/_foo", "") + e = nbselect(w.EventChan) assert.Nil(t, e, "") } @@ -559,7 +565,7 @@ func TestStoreWatchRecursiveUpdate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } -// Ensure that the store doesn't get recursive key updates for hidden keys. +// Ensure that the store doesn't see hidden key updates without an exact path match in recursive mode. func TestStoreWatchRecursiveUpdateWithHiddenKey(t *testing.T) { s := newStore() s.Create("/foo/_bar", false, "baz", false, Permanent) @@ -580,13 +586,16 @@ func TestStoreWatchDelete(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo", "") } -// Ensure that the store doesn't see hidden key deletions. +// Ensure that the store can watch for key deletions. func TestStoreWatchDeleteWithHiddenKey(t *testing.T) { s := newStore() s.Create("/_foo", false, "bar", false, Permanent) - w, _ := s.Watch("/foo", false, false, 0) + w, _ := s.Watch("/_foo", false, false, 0) s.Delete("/_foo", false, false) e := nbselect(w.EventChan) + assert.Equal(t, e.Action, "delete", "") + assert.Equal(t, e.Node.Key, "/_foo", "") + e = nbselect(w.EventChan) assert.Nil(t, e, "") } @@ -601,7 +610,7 @@ func TestStoreWatchRecursiveDelete(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } -// Ensure that the store can watch for recursive key deletions. +// Ensure that the store doesn't see hidden key deletes without an exact path match in recursive mode. func TestStoreWatchRecursiveDeleteWithHiddenKey(t *testing.T) { s := newStore() s.Create("/foo/_bar", false, "baz", false, Permanent) diff --git a/store/watcher_hub.go b/store/watcher_hub.go index 8c2487ab6..db90447a5 100644 --- a/store/watcher_hub.go +++ b/store/watcher_hub.go @@ -126,7 +126,8 @@ func (wh *watcherHub) notifyWatchers(e *Event, nodePath string, deleted bool) { w, _ := curr.Value.(*Watcher) - if !isHidden(nodePath) && w.notify(e, e.Node.Key == nodePath, deleted) { + originalPath := (e.Node.Key == nodePath) + if (originalPath || !isHidden(e.Node.Key)) && w.notify(e, originalPath, deleted) { if !w.stream { // do not remove the stream watcher // if we successfully notify a watcher // we need to remove the watcher from the list From 0cacb6cba480efaa8b4c5e9a54e369059e1840d6 Mon Sep 17 00:00:00 2001 From: tobz Date: Wed, 22 Jan 2014 09:20:57 -0500 Subject: [PATCH 3/5] test(store): exercise watchers receiving notifications of non-hidden keys within hidden directories --- store/store_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/store/store_test.go b/store/store_test.go index 33ecffac2..b90a6f411 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -528,6 +528,13 @@ func TestStoreWatchRecursiveCreateWithHiddenKey(t *testing.T) { s.Create("/foo/_bar", false, "baz", false, Permanent) e := nbselect(w.EventChan) assert.Nil(t, e, "") + w, _ = s.Watch("/foo", true, false, 0) + s.Create("/foo/_baz", true, "", false, Permanent) + e = nbselect(w.EventChan) + assert.Nil(t, e, "") + s.Create("/foo/_baz/quux", false, "quux", false, Permanent) + e = nbselect(w.EventChan) + assert.Nil(t, e, "") } // Ensure that the store can watch for key updates. From 823fdfab1292f1920c3f817d102c91d78bdeb21a Mon Sep 17 00:00:00 2001 From: tobz Date: Wed, 22 Jan 2014 09:29:33 -0500 Subject: [PATCH 4/5] fix(store): make isHidden see if any portion of the path is hidden, not just the last element --- store/watcher_hub.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/store/watcher_hub.go b/store/watcher_hub.go index db90447a5..3ed8d2697 100644 --- a/store/watcher_hub.go +++ b/store/watcher_hub.go @@ -158,13 +158,8 @@ func (wh *watcherHub) clone() *watcherHub { } } -// isHidden checks if a path has a hidden key. since we don't get the Node -// object for notifyWatchers, we have to duplicate it here. consolidate me? +// isHidden checks to see if this path is considered hidden i.e. the +// last element is hidden or it's within a hidden directory func isHidden(nodePath string) bool { - _, name := path.Split(nodePath) - if name == "" { - return false - } - - return name[0] == '_' + return strings.Contains(nodePath, "/_") } From 641edd4e6e5836e61e6d5500ea23d879129b886f Mon Sep 17 00:00:00 2001 From: tobz Date: Wed, 22 Jan 2014 09:29:53 -0500 Subject: [PATCH 5/5] test(store): group together all store tests that deal with hidden keys --- store/store_test.go | 200 ++++++++++++++++++++++---------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/store/store_test.go b/store/store_test.go index b90a6f411..0a7e49e4f 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -499,18 +499,6 @@ func TestStoreWatchCreate(t *testing.T) { assert.Nil(t, e, "") } -// Ensure that the store can watch for hidden keys as long as it's an exact path match. -func TestStoreWatchCreateWithHiddenKey(t *testing.T) { - s := newStore() - w, _ := s.Watch("/_foo", false, false, 0) - s.Create("/_foo", false, "bar", false, Permanent) - e := nbselect(w.EventChan) - assert.Equal(t, e.Action, "create", "") - assert.Equal(t, e.Node.Key, "/_foo", "") - e = nbselect(w.EventChan) - assert.Nil(t, e, "") -} - // Ensure that the store can watch for recursive key creation. func TestStoreWatchRecursiveCreate(t *testing.T) { s := newStore() @@ -521,22 +509,6 @@ func TestStoreWatchRecursiveCreate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } -// Ensure that the store doesn't see hidden key creates without an exact path match in recursive mode. -func TestStoreWatchRecursiveCreateWithHiddenKey(t *testing.T) { - s := newStore() - w, _ := s.Watch("/foo", true, false, 0) - s.Create("/foo/_bar", false, "baz", false, Permanent) - e := nbselect(w.EventChan) - assert.Nil(t, e, "") - w, _ = s.Watch("/foo", true, false, 0) - s.Create("/foo/_baz", true, "", false, Permanent) - e = nbselect(w.EventChan) - assert.Nil(t, e, "") - s.Create("/foo/_baz/quux", false, "quux", false, Permanent) - e = nbselect(w.EventChan) - assert.Nil(t, e, "") -} - // Ensure that the store can watch for key updates. func TestStoreWatchUpdate(t *testing.T) { s := newStore() @@ -548,19 +520,6 @@ func TestStoreWatchUpdate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo", "") } -// Ensure that the store doesn't see hidden key updates. -func TestStoreWatchUpdateWithHiddenKey(t *testing.T) { - s := newStore() - s.Create("/_foo", false, "bar", false, Permanent) - w, _ := s.Watch("/_foo", false, false, 0) - s.Update("/_foo", "baz", Permanent) - e := nbselect(w.EventChan) - assert.Equal(t, e.Action, "update", "") - assert.Equal(t, e.Node.Key, "/_foo", "") - e = nbselect(w.EventChan) - assert.Nil(t, e, "") -} - // Ensure that the store can watch for recursive key updates. func TestStoreWatchRecursiveUpdate(t *testing.T) { s := newStore() @@ -572,16 +531,6 @@ func TestStoreWatchRecursiveUpdate(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } -// Ensure that the store doesn't see hidden key updates without an exact path match in recursive mode. -func TestStoreWatchRecursiveUpdateWithHiddenKey(t *testing.T) { - s := newStore() - s.Create("/foo/_bar", false, "baz", false, Permanent) - w, _ := s.Watch("/foo", true, false, 0) - s.Update("/foo/_bar", "baz", Permanent) - e := nbselect(w.EventChan) - assert.Nil(t, e, "") -} - // Ensure that the store can watch for key deletions. func TestStoreWatchDelete(t *testing.T) { s := newStore() @@ -593,19 +542,6 @@ func TestStoreWatchDelete(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo", "") } -// Ensure that the store can watch for key deletions. -func TestStoreWatchDeleteWithHiddenKey(t *testing.T) { - s := newStore() - s.Create("/_foo", false, "bar", false, Permanent) - w, _ := s.Watch("/_foo", false, false, 0) - s.Delete("/_foo", false, false) - e := nbselect(w.EventChan) - assert.Equal(t, e.Action, "delete", "") - assert.Equal(t, e.Node.Key, "/_foo", "") - e = nbselect(w.EventChan) - assert.Nil(t, e, "") -} - // Ensure that the store can watch for recursive key deletions. func TestStoreWatchRecursiveDelete(t *testing.T) { s := newStore() @@ -617,16 +553,6 @@ func TestStoreWatchRecursiveDelete(t *testing.T) { assert.Equal(t, e.Node.Key, "/foo/bar", "") } -// Ensure that the store doesn't see hidden key deletes without an exact path match in recursive mode. -func TestStoreWatchRecursiveDeleteWithHiddenKey(t *testing.T) { - s := newStore() - s.Create("/foo/_bar", false, "baz", false, Permanent) - w, _ := s.Watch("/foo", true, false, 0) - s.Delete("/foo/_bar", false, false) - e := nbselect(w.EventChan) - assert.Nil(t, e, "") -} - // Ensure that the store can watch for CAS updates. func TestStoreWatchCompareAndSwap(t *testing.T) { s := newStore() @@ -676,32 +602,6 @@ func TestStoreWatchExpire(t *testing.T) { assert.Equal(t, e.Node.Key, "/foofoo", "") } -// Ensure that the store doesn't see expirations of hidden keys. -func TestStoreWatchExpireWithHiddenKey(t *testing.T) { - s := newStore() - - stopChan := make(chan bool) - defer func() { - stopChan <- true - }() - go mockSyncService(s.DeleteExpiredKeys, stopChan) - - s.Create("/_foo", false, "bar", false, time.Now().Add(500*time.Millisecond)) - s.Create("/foofoo", false, "barbarbar", false, time.Now().Add(1000*time.Millisecond)) - - w, _ := s.Watch("/", true, false, 0) - c := w.EventChan - e := nbselect(c) - assert.Nil(t, e, "") - time.Sleep(600 * time.Millisecond) - e = nbselect(c) - assert.Nil(t, e, "") - time.Sleep(600 * time.Millisecond) - e = nbselect(c) - assert.Equal(t, e.Action, "expire", "") - assert.Equal(t, e.Node.Key, "/foofoo", "") -} - // Ensure that the store can watch in streaming mode. func TestStoreWatchStream(t *testing.T) { s := newStore() @@ -782,6 +682,106 @@ func TestStoreRecoverWithExpiration(t *testing.T) { assert.Nil(t, e, "") } +// Ensure that the store can watch for hidden keys as long as it's an exact path match. +func TestStoreWatchCreateWithHiddenKey(t *testing.T) { + s := newStore() + w, _ := s.Watch("/_foo", false, false, 0) + s.Create("/_foo", false, "bar", false, Permanent) + e := nbselect(w.EventChan) + assert.Equal(t, e.Action, "create", "") + assert.Equal(t, e.Node.Key, "/_foo", "") + e = nbselect(w.EventChan) + assert.Nil(t, e, "") +} + +// Ensure that the store doesn't see hidden key creates without an exact path match in recursive mode. +func TestStoreWatchRecursiveCreateWithHiddenKey(t *testing.T) { + s := newStore() + w, _ := s.Watch("/foo", true, false, 0) + s.Create("/foo/_bar", false, "baz", false, Permanent) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") + w, _ = s.Watch("/foo", true, false, 0) + s.Create("/foo/_baz", true, "", false, Permanent) + e = nbselect(w.EventChan) + assert.Nil(t, e, "") + s.Create("/foo/_baz/quux", false, "quux", false, Permanent) + e = nbselect(w.EventChan) + assert.Nil(t, e, "") +} + +// Ensure that the store doesn't see hidden key updates. +func TestStoreWatchUpdateWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/_foo", false, "bar", false, Permanent) + w, _ := s.Watch("/_foo", false, false, 0) + s.Update("/_foo", "baz", Permanent) + e := nbselect(w.EventChan) + assert.Equal(t, e.Action, "update", "") + assert.Equal(t, e.Node.Key, "/_foo", "") + e = nbselect(w.EventChan) + assert.Nil(t, e, "") +} + +// Ensure that the store doesn't see hidden key updates without an exact path match in recursive mode. +func TestStoreWatchRecursiveUpdateWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/foo/_bar", false, "baz", false, Permanent) + w, _ := s.Watch("/foo", true, false, 0) + s.Update("/foo/_bar", "baz", Permanent) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + +// Ensure that the store can watch for key deletions. +func TestStoreWatchDeleteWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/_foo", false, "bar", false, Permanent) + w, _ := s.Watch("/_foo", false, false, 0) + s.Delete("/_foo", false, false) + e := nbselect(w.EventChan) + assert.Equal(t, e.Action, "delete", "") + assert.Equal(t, e.Node.Key, "/_foo", "") + e = nbselect(w.EventChan) + assert.Nil(t, e, "") +} + +// Ensure that the store doesn't see hidden key deletes without an exact path match in recursive mode. +func TestStoreWatchRecursiveDeleteWithHiddenKey(t *testing.T) { + s := newStore() + s.Create("/foo/_bar", false, "baz", false, Permanent) + w, _ := s.Watch("/foo", true, false, 0) + s.Delete("/foo/_bar", false, false) + e := nbselect(w.EventChan) + assert.Nil(t, e, "") +} + +// Ensure that the store doesn't see expirations of hidden keys. +func TestStoreWatchExpireWithHiddenKey(t *testing.T) { + s := newStore() + + stopChan := make(chan bool) + defer func() { + stopChan <- true + }() + go mockSyncService(s.DeleteExpiredKeys, stopChan) + + s.Create("/_foo", false, "bar", false, time.Now().Add(500*time.Millisecond)) + s.Create("/foofoo", false, "barbarbar", false, time.Now().Add(1000*time.Millisecond)) + + w, _ := s.Watch("/", true, false, 0) + c := w.EventChan + e := nbselect(c) + assert.Nil(t, e, "") + time.Sleep(600 * time.Millisecond) + e = nbselect(c) + assert.Nil(t, e, "") + time.Sleep(600 * time.Millisecond) + e = nbselect(c) + assert.Equal(t, e.Action, "expire", "") + assert.Equal(t, e.Node.Key, "/foofoo", "") +} + // Performs a non-blocking select on an event channel. func nbselect(c <-chan *Event) *Event { select {