Skip to content

Commit

Permalink
Adds new testing infrastructure for filling a cache with objects from…
Browse files Browse the repository at this point in the history
… state directly.

Recruits new test controller in logging config API server tests.
  • Loading branch information
manadart committed Jun 21, 2019
1 parent ee944e0 commit b78e03e
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 83 deletions.
71 changes: 19 additions & 52 deletions apiserver/facades/agent/logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
package logger_test

import (
"time"

jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"gopkg.in/juju/names.v2"
Expand All @@ -21,7 +19,6 @@ import (
"github.com/juju/juju/core/cache/cachetest"
"github.com/juju/juju/state"
statetesting "github.com/juju/juju/state/testing"
"github.com/juju/juju/testing"
)

type loggerSuite struct {
Expand All @@ -34,11 +31,10 @@ type loggerSuite struct {
resources *common.Resources
authorizer apiservertesting.FakeAuthorizer

change cache.ModelChange
changes chan interface{}
controller *cache.Controller
events chan interface{}
capture func(change interface{})
change cache.ModelChange
ctrl *cachetest.TestController
events <-chan interface{}
capture func(change interface{})
}

var _ = gc.Suite(&loggerSuite{})
Expand All @@ -58,52 +54,26 @@ func (s *loggerSuite) SetUpTest(c *gc.C) {
Tag: s.rawMachine.Tag(),
}

s.events = make(chan interface{})
notify := func(change interface{}) {
send := false
switch change.(type) {
case cache.ModelChange:
send = true
case cache.RemoveModel:
send = true
default:
// no-op
}
if send {
c.Logf("sending %#v", change)
select {
case s.events <- change:
case <-time.After(testing.LongWait):
c.Fatalf("change not processed by test")
}
}
}
s.ctrl = cachetest.NewTestController(cachetest.ModelEvents)
s.events = s.ctrl.Init(c)

s.changes = make(chan interface{})
controller, err := cache.NewController(cache.ControllerConfig{
Changes: s.changes,
Notify: notify,
})
c.Assert(err, jc.ErrorIsNil)
s.controller = controller
s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, s.controller) })
// Add the current model to the controller.
s.change = cachetest.ModelChangeFromState(c, s.State)
s.changes <- s.change
// Ensure it is processed before we create the logger api.
select {
case <-s.events:
case <-time.After(testing.LongWait):
c.Fatalf("change not processed by test")
}
m := cachetest.ModelChangeFromState(c, s.State)
s.ctrl.SendChange(m)

// Ensure it is processed before we create the logger API.
s.ctrl.NextChange(c, s.events)

s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, s.ctrl.Controller) })

s.logger, err = s.makeLoggerAPI(s.authorizer)
c.Assert(err, jc.ErrorIsNil)
}

func (s *loggerSuite) makeLoggerAPI(auth facade.Authorizer) (*logger.LoggerAPI, error) {
ctx := facadetest.Context{
Auth_: auth,
Controller_: s.controller,
Controller_: s.ctrl.Controller,
Resources_: s.resources,
State_: s.State,
}
Expand Down Expand Up @@ -143,13 +113,10 @@ func (s *loggerSuite) TestWatchLoggingConfigNothing(c *gc.C) {
}

func (s *loggerSuite) setLoggingConfig(c *gc.C, loggingConfig string) {
s.change.Config["logging-config"] = loggingConfig
s.changes <- s.change
select {
case <-s.events:
case <-time.After(testing.LongWait):
c.Fatalf("change not processed by test")
}
m := cachetest.ModelChangeFromState(c, s.State)
m.Config["logging-config"] = loggingConfig
s.ctrl.SendChange(m)
s.ctrl.NextChange(c, s.events)
}

func (s *loggerSuite) TestWatchLoggingConfig(c *gc.C) {
Expand Down
114 changes: 114 additions & 0 deletions core/cache/cachetest/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2019 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package cachetest

import (
"time"

jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"

"github.com/juju/juju/core/cache"
"github.com/juju/juju/state"
"github.com/juju/juju/testing"
)

// TestController wraps a cache controller for testing.
// It allows synchronisation of state objects with the cache
// without the need for a multi-watcher and cache worker.
type TestController struct {
*cache.Controller

matchers []func(interface{}) bool
changes chan interface{}
}

// NewTestController returns creates and returns a new test controller
// with an initial set of matchers for receiving cache event notifications.
// The controller can be instantiated like this in suite/test setups in order
// to retain a common set of matchers, but `Init` should be called in each
// test (see below).
func NewTestController(matchers ...func(interface{}) bool) *TestController {
return &TestController{
matchers: matchers,
}
}

// Init instantiates the inner cache controller and returns a channel for
// synchronising tests. Based on the input matchers, cache events for those
// types will be sent on the channel when the cache processes them.
//
// NOTE: It is recommended to perform this initialisation in the actual test
// method rather than `SetupSuite` or `SetupTest` as different gc.C references
// are supplied to each of those methods.
func (tc *TestController) Init(c *gc.C, matchers ...func(interface{}) bool) <-chan interface{} {
events := make(chan interface{})
matchers = append(tc.matchers, matchers...)

notify := func(change interface{}) {
send := false
for _, m := range matchers {
if m(change) {
send = true
break
}
}

if send {
c.Logf("sending %#v", change)
select {
case events <- change:
case <-time.After(testing.LongWait):
c.Fatalf("change not processed by test")
}
}
}

tc.changes = make(chan interface{})
cc, err := cache.NewController(cache.ControllerConfig{
Changes: tc.changes,
Notify: notify,
})
c.Assert(err, jc.ErrorIsNil)
tc.Controller = cc

return events
}

// UpdateModel updates the current model for the input state in the cache.
func (tc *TestController) UpdateModel(c *gc.C, m *state.Model) {
tc.SendChange(ModelChange(c, m))
}

// UpdateCharm updates the input state charm in the cache.
func (tc *TestController) UpdateCharm(modelUUID string, ch *state.Charm) {
tc.SendChange(CharmChange(modelUUID, ch))
}

// UpdateApplication updates the input state application in the cache.
func (tc *TestController) UpdateApplication(c *gc.C, modelUUID string, app *state.Application) {
tc.SendChange(ApplicationChange(c, modelUUID, app))
}

// UpdateMachine updates the input state machine in the cache.
func (tc *TestController) UpdateMachine(c *gc.C, modelUUID string, machine *state.Machine) {
tc.SendChange(MachineChange(c, modelUUID, machine))
}

func (tc *TestController) SendChange(change interface{}) {
tc.changes <- change
}

// NextChange returns the next change processed by the cache that satisfies a
// matcher, or fails the test with a time-out.
// This method should receive the channel returned by a call to `Init`.
func (tc *TestController) NextChange(c *gc.C, changes <-chan interface{}) interface{} {
var obtained interface{}
select {
case obtained = <-changes:
case <-time.After(testing.LongWait):
c.Fatalf("change not processed by test")
}
return obtained
}
Loading

0 comments on commit b78e03e

Please sign in to comment.