blob: c129291747ba963fe773ec9ca9a5184526dcad9c [file] [log] [blame] [edit]
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package events_test
import (
"sync"
"sync/atomic"
"github.com/30x/apid-core"
"github.com/30x/apid-core/events"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"strconv"
)
var _ = Describe("Events Service", func() {
Context("local", func() {
var em apid.EventsService
BeforeEach(func() {
em = events.CreateService()
})
AfterEach(func() {
if em != nil {
em.Close()
em = nil
}
})
It("should ignore event with no listeners", func() {
em.Emit("no listeners", &test_event{"test"})
})
It("should publish an event to a listener", func(done Done) {
h := test_handler{
"handler",
func(event apid.Event) {
close(done)
},
}
em.Listen("selector", &h)
em.Emit("selector", &test_event{"test"})
})
It("should publish an event to a listener func", func(done Done) {
h := func(event apid.Event) {
close(done)
}
em.ListenFunc("selector", h)
em.Emit("selector", &test_event{"test"})
})
It("should publish multiple events to a listener", func(done Done) {
count := int32(0)
h := test_handler{
"handler",
func(event apid.Event) {
defer GinkgoRecover()
c := atomic.AddInt32(&count, 1)
if c > 1 {
close(done)
}
},
}
em.Listen("selector", &h)
em.Emit("selector", &test_event{"test1"})
em.Emit("selector", &test_event{"test2"})
})
It("EmitWithCallback should call the callback when done with delivery", func(done Done) {
delivered := func(event apid.Event) {
close(done)
}
em.EmitWithCallback("selector", &test_event{"test1"}, delivered)
})
It("should publish only one event to a listenOnce", func(done Done) {
count := 0
h := func(event apid.Event) {
defer GinkgoRecover()
count++
}
delivered := func(event apid.Event) {
defer GinkgoRecover()
Expect(count).To(Equal(1))
close(done)
}
em.ListenOnceFunc("selector", h)
em.Emit("selector", &test_event{"test1"})
em.EmitWithCallback("selector", &test_event{"test2"}, delivered)
})
It("should publish an event to multiple listeners", func(done Done) {
mut := sync.Mutex{}
hitH1 := false
hitH2 := false
h1 := test_handler{
"handler 1",
func(event apid.Event) {
defer GinkgoRecover()
mut.Lock()
defer mut.Unlock()
hitH1 = true
if hitH1 && hitH2 {
close(done)
}
},
}
h2 := test_handler{
"handler 2",
func(event apid.Event) {
defer GinkgoRecover()
mut.Lock()
defer mut.Unlock()
hitH2 = true
if hitH1 && hitH2 {
close(done)
}
},
}
em.Listen("selector", &h1)
em.Listen("selector", &h2)
em.Emit("selector", &test_event{"test"})
})
It("should publish an event delivered event", func(done Done) {
testEvent := &test_event{"test"}
var testSelector apid.EventSelector = "selector"
dummy := func(event apid.Event) {}
em.ListenFunc(testSelector, dummy)
h := test_handler{
"event delivered handler",
func(event apid.Event) {
defer GinkgoRecover()
e, ok := event.(apid.EventDeliveryEvent)
Expect(ok).To(BeTrue())
Expect(e.Event).To(Equal(testEvent))
Expect(e.Selector).To(Equal(testSelector))
close(done)
},
}
em.Listen(apid.EventDeliveredSelector, &h)
em.Emit(testSelector, testEvent)
})
It("should be able to remove a listener", func(done Done) {
event1 := &test_event{"test1"}
event2 := &test_event{"test2"}
event3 := &test_event{"test3"}
dummy := func(event apid.Event) {}
em.ListenFunc("selector", dummy)
h := test_handler{
"handler",
func(event apid.Event) {
defer GinkgoRecover()
Expect(event).NotTo(Equal(event2))
if event == event3 {
close(done)
}
},
}
em.Listen("selector", &h)
// need to drive test like this because of async delivery
var td apid.EventHandler
td = &test_handler{
"test driver",
func(event apid.Event) {
defer GinkgoRecover()
e := event.(apid.EventDeliveryEvent)
if e.Event == event1 {
em.StopListening("selector", &h)
em.Emit("selector", event2)
} else if e.Event == event2 {
em.StopListening(apid.EventDeliveredSelector, td)
em.Listen("selector", &h)
em.Emit("selector", event3)
}
},
}
em.Listen(apid.EventDeliveredSelector, td)
em.Emit("selector", event1)
})
It("should deliver events according selector", func(done Done) {
e1 := &test_event{"test1"}
e2 := &test_event{"test2"}
count := int32(0)
h1 := test_handler{
"handler1",
func(event apid.Event) {
defer GinkgoRecover()
c := atomic.AddInt32(&count, 1)
Expect(event).Should(Equal(e1))
if c == 2 {
close(done)
}
},
}
h2 := test_handler{
"handler2",
func(event apid.Event) {
defer GinkgoRecover()
c := atomic.AddInt32(&count, 1)
Expect(event).Should(Equal(e2))
if c == 2 {
close(done)
}
},
}
em.Listen("selector1", &h1)
em.Listen("selector2", &h2)
em.Emit("selector2", e2)
em.Emit("selector1", e1)
})
})
Context("plugins", func() {
BeforeEach(func() {
})
AfterEach(func() {
apid.Events().Close()
})
It("should publish PluginsInitialized event", func(done Done) {
xData := make(map[string]interface{})
xData["schemaVersion"] = "1.2.3"
p := func(s apid.Services) (pd apid.PluginData, err error) {
pd = apid.PluginData{
Name: "test plugin",
Version: "1.0.0",
ExtraData: xData,
}
return
}
apid.RegisterPlugin(p)
h := func(event apid.Event) {
defer GinkgoRecover()
if pie, ok := event.(apid.PluginsInitializedEvent); ok {
Expect(len(pie.Plugins)).Should(Equal(1))
p := pie.Plugins[0]
Expect(p.Name).To(Equal("test plugin"))
Expect(p.Version).To(Equal("1.0.0"))
Expect(p.ExtraData["schemaVersion"]).To(Equal("1.2.3"))
close(done)
}
}
apid.Events().ListenFunc(apid.SystemEventsSelector, h)
apid.InitializePlugins("")
})
It("shutdown event should be emitted and listened successfully", func(done Done) {
h := func(event apid.Event) {
defer GinkgoRecover()
if pie, ok := event.(apid.ShutdownEvent); ok {
Expect(pie.Description).Should(Equal("apid is going to shutdown"))
} else {
Fail("Received wrong event")
}
}
apid.Events().ListenFunc(apid.ShutdownEventSelector, h)
<- apid.Events().Emit(apid.ShutdownEventSelector, apid.ShutdownEvent{"apid is going to shutdown"})
close(done)
})
It("handlers registered by plugins should execute before apid shutdown", func(done Done) {
pluginNum := 10
count := int32(0)
countP := &count
// create and register plugins, listen to shutdown event
for i:=0; i<pluginNum; i++ {
apid.RegisterPlugin(createDummyPlugin(i))
h := func(event apid.Event) {
if pie, ok := event.(apid.ShutdownEvent); ok {
Expect(pie.Description).Should(Equal("apid is going to shutdown"))
atomic.AddInt32(countP, 1)
} else {
Fail("Received wrong event")
}
}
apid.Events().ListenFunc(apid.ShutdownEventSelector, h)
}
apid.InitializePlugins("")
apid.ShutdownPluginsAndWait()
// handlers of all registered plugins have executed
Expect(count).Should(Equal(int32(pluginNum)))
close(done)
})
It("should be able to read apid version from PluginsInitialized event", func(done Done) {
xData := make(map[string]interface{})
xData["schemaVersion"] = "1.2.3"
p := func(s apid.Services) (pd apid.PluginData, err error) {
pd = apid.PluginData{
Name: "test plugin",
Version: "1.0.0",
ExtraData: xData,
}
return
}
apid.RegisterPlugin(p)
apidVersion := "dummy_version"
h := func(event apid.Event) {
defer GinkgoRecover()
if pie, ok := event.(apid.PluginsInitializedEvent); ok {
Expect(pie.ApidVersion).To(Equal(apidVersion))
close(done)
}
}
apid.Events().ListenFunc(apid.SystemEventsSelector, h)
apid.InitializePlugins(apidVersion)
})
})
})
func createDummyPlugin(id int) apid.PluginInitFunc{
xData := make(map[string]interface{})
xData["schemaVersion"] = "1.2.3"
p := func(s apid.Services) (pd apid.PluginData, err error) {
pd = apid.PluginData{
Name: "test plugin " + strconv.Itoa(id),
Version: "1.0.0",
ExtraData: xData,
}
return
}
return p
}
type test_handler struct {
description string
f func(event apid.Event)
}
func (t *test_handler) String() string {
return t.description
}
func (t *test_handler) Handle(event apid.Event) {
t.f(event)
}
type test_event struct {
description string
}