280 lines
5.9 KiB
Go
280 lines
5.9 KiB
Go
package state
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// verifyWatch will set up a watch channel, call the given function, and then
|
|
// make sure the watch fires.
|
|
func verifyWatch(t *testing.T, watch Watch, fn func()) {
|
|
ch := make(chan struct{}, 1)
|
|
watch.Wait(ch)
|
|
|
|
fn()
|
|
|
|
select {
|
|
case <-ch:
|
|
default:
|
|
t.Fatalf("watch should have been notified")
|
|
}
|
|
}
|
|
|
|
// verifyNoWatch will set up a watch channel, call the given function, and then
|
|
// make sure the watch never fires.
|
|
func verifyNoWatch(t *testing.T, watch Watch, fn func()) {
|
|
ch := make(chan struct{}, 1)
|
|
watch.Wait(ch)
|
|
|
|
fn()
|
|
|
|
select {
|
|
case <-ch:
|
|
t.Fatalf("watch should not been notified")
|
|
default:
|
|
}
|
|
}
|
|
|
|
func TestWatch_FullTableWatch(t *testing.T) {
|
|
w := NewFullTableWatch()
|
|
|
|
// Test the basic trigger with a single watcher.
|
|
verifyWatch(t, w, func() {
|
|
w.Notify()
|
|
})
|
|
|
|
// Run multiple watchers and make sure they both fire.
|
|
verifyWatch(t, w, func() {
|
|
verifyWatch(t, w, func() {
|
|
w.Notify()
|
|
})
|
|
})
|
|
|
|
// Make sure clear works.
|
|
ch := make(chan struct{}, 1)
|
|
w.Wait(ch)
|
|
w.Clear(ch)
|
|
w.Notify()
|
|
select {
|
|
case <-ch:
|
|
t.Fatalf("watch should not have been notified")
|
|
default:
|
|
}
|
|
|
|
// Make sure notify is a one shot.
|
|
w.Wait(ch)
|
|
w.Notify()
|
|
select {
|
|
case <-ch:
|
|
default:
|
|
t.Fatalf("watch should have been notified")
|
|
}
|
|
w.Notify()
|
|
select {
|
|
case <-ch:
|
|
t.Fatalf("watch should not have been notified")
|
|
default:
|
|
}
|
|
}
|
|
|
|
func TestWatch_DumbWatchManager(t *testing.T) {
|
|
watches := map[string]*FullTableWatch{
|
|
"alice": NewFullTableWatch(),
|
|
"bob": NewFullTableWatch(),
|
|
"carol": NewFullTableWatch(),
|
|
}
|
|
|
|
// Notify with nothing armed and make sure nothing triggers.
|
|
func() {
|
|
w := NewDumbWatchManager(watches)
|
|
verifyNoWatch(t, watches["alice"], func() {
|
|
verifyNoWatch(t, watches["bob"], func() {
|
|
verifyNoWatch(t, watches["carol"], func() {
|
|
w.Notify()
|
|
})
|
|
})
|
|
})
|
|
}()
|
|
|
|
// Trigger one watch.
|
|
func() {
|
|
w := NewDumbWatchManager(watches)
|
|
verifyWatch(t, watches["alice"], func() {
|
|
verifyNoWatch(t, watches["bob"], func() {
|
|
verifyNoWatch(t, watches["carol"], func() {
|
|
w.Arm("alice")
|
|
w.Notify()
|
|
})
|
|
})
|
|
})
|
|
}()
|
|
|
|
// Trigger two watches.
|
|
func() {
|
|
w := NewDumbWatchManager(watches)
|
|
verifyWatch(t, watches["alice"], func() {
|
|
verifyNoWatch(t, watches["bob"], func() {
|
|
verifyWatch(t, watches["carol"], func() {
|
|
w.Arm("alice")
|
|
w.Arm("carol")
|
|
w.Notify()
|
|
})
|
|
})
|
|
})
|
|
}()
|
|
|
|
// Trigger all three watches.
|
|
func() {
|
|
w := NewDumbWatchManager(watches)
|
|
verifyWatch(t, watches["alice"], func() {
|
|
verifyWatch(t, watches["bob"], func() {
|
|
verifyWatch(t, watches["carol"], func() {
|
|
w.Arm("alice")
|
|
w.Arm("bob")
|
|
w.Arm("carol")
|
|
w.Notify()
|
|
})
|
|
})
|
|
})
|
|
}()
|
|
|
|
// Trigger multiple times.
|
|
func() {
|
|
w := NewDumbWatchManager(watches)
|
|
verifyWatch(t, watches["alice"], func() {
|
|
verifyNoWatch(t, watches["bob"], func() {
|
|
verifyNoWatch(t, watches["carol"], func() {
|
|
w.Arm("alice")
|
|
w.Arm("alice")
|
|
w.Notify()
|
|
})
|
|
})
|
|
})
|
|
}()
|
|
|
|
// Make sure it panics when asked to arm an unknown table.
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Fatalf("didn't get expected panic")
|
|
}
|
|
}()
|
|
w := NewDumbWatchManager(watches)
|
|
w.Arm("nope")
|
|
}()
|
|
}
|
|
|
|
func TestWatch_PrefixWatch(t *testing.T) {
|
|
w := NewPrefixWatch()
|
|
|
|
// Hit a specific key.
|
|
verifyWatch(t, w.GetSubwatch(""), func() {
|
|
verifyWatch(t, w.GetSubwatch("foo/bar/baz"), func() {
|
|
verifyNoWatch(t, w.GetSubwatch("foo/bar/zoo"), func() {
|
|
verifyNoWatch(t, w.GetSubwatch("nope"), func() {
|
|
w.Notify("foo/bar/baz", false)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
// Make sure cleanup is happening. All that should be left is the
|
|
// full-table watch and the un-fired watches.
|
|
fn := func(k string, v interface{}) bool {
|
|
if k != "" && k != "foo/bar/zoo" && k != "nope" {
|
|
t.Fatalf("unexpected watch: %s", k)
|
|
}
|
|
return false
|
|
}
|
|
w.watches.WalkPrefix("", fn)
|
|
|
|
// Delete a subtree.
|
|
verifyWatch(t, w.GetSubwatch(""), func() {
|
|
verifyWatch(t, w.GetSubwatch("foo/bar/baz"), func() {
|
|
verifyWatch(t, w.GetSubwatch("foo/bar/zoo"), func() {
|
|
verifyNoWatch(t, w.GetSubwatch("nope"), func() {
|
|
w.Notify("foo/", true)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
// Hit an unknown key.
|
|
verifyWatch(t, w.GetSubwatch(""), func() {
|
|
verifyNoWatch(t, w.GetSubwatch("foo/bar/baz"), func() {
|
|
verifyNoWatch(t, w.GetSubwatch("foo/bar/zoo"), func() {
|
|
verifyNoWatch(t, w.GetSubwatch("nope"), func() {
|
|
w.Notify("not/in/there", false)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
type MockWatch struct {
|
|
Waits map[chan struct{}] int
|
|
Clears map[chan struct{}] int
|
|
}
|
|
|
|
func NewMockWatch() *MockWatch {
|
|
return &MockWatch{
|
|
Waits: make(map[chan struct{}] int),
|
|
Clears: make(map[chan struct{}] int),
|
|
}
|
|
}
|
|
|
|
func (m *MockWatch) Wait(notifyCh chan struct{}) {
|
|
if _, ok := m.Waits[notifyCh]; ok {
|
|
m.Waits[notifyCh]++
|
|
} else {
|
|
m.Waits[notifyCh] = 1
|
|
}
|
|
}
|
|
|
|
func (m *MockWatch) Clear(notifyCh chan struct{}) {
|
|
if _, ok := m.Clears[notifyCh]; ok {
|
|
m.Clears[notifyCh]++
|
|
} else {
|
|
m.Clears[notifyCh] = 1
|
|
}
|
|
}
|
|
|
|
func TestWatch_MultiWatch(t *testing.T) {
|
|
w1, w2 := NewMockWatch(), NewMockWatch()
|
|
w := NewMultiWatch(w1, w2)
|
|
|
|
// Do some activity.
|
|
c1, c2 := make(chan struct{}), make(chan struct{})
|
|
w.Wait(c1)
|
|
w.Clear(c1)
|
|
w.Wait(c1)
|
|
w.Wait(c2)
|
|
w.Clear(c1)
|
|
w.Clear(c2)
|
|
|
|
// Make sure all the events were forwarded.
|
|
if cnt, ok := w1.Waits[c1]; !ok || cnt != 2 {
|
|
t.Fatalf("bad: %d", w1.Waits[c1])
|
|
}
|
|
if cnt, ok := w1.Clears[c1]; !ok || cnt != 2 {
|
|
t.Fatalf("bad: %d", w1.Clears[c1])
|
|
}
|
|
if cnt, ok := w1.Waits[c2]; !ok || cnt != 1 {
|
|
t.Fatalf("bad: %d", w1.Waits[c2])
|
|
}
|
|
if cnt, ok := w1.Clears[c2]; !ok || cnt != 1 {
|
|
t.Fatalf("bad: %d", w1.Clears[c2])
|
|
}
|
|
if cnt, ok := w2.Waits[c1]; !ok || cnt != 2 {
|
|
t.Fatalf("bad: %d", w2.Waits[c1])
|
|
}
|
|
if cnt, ok := w2.Clears[c1]; !ok || cnt != 2 {
|
|
t.Fatalf("bad: %d", w2.Clears[c1])
|
|
}
|
|
if cnt, ok := w2.Waits[c2]; !ok || cnt != 1 {
|
|
t.Fatalf("bad: %d", w2.Waits[c2])
|
|
}
|
|
if cnt, ok := w2.Clears[c2]; !ok || cnt != 1 {
|
|
t.Fatalf("bad: %d", w2.Clears[c2])
|
|
}
|
|
}
|