testlib/mock.go (232 lines of code) (raw):
// Copyright 2018 Google LLC
//
// 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
//
// https://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 testlib
import (
"sync"
"sync/atomic"
"testing"
"time"
"github.com/GoogleCloudPlatform/ubbagent/metrics"
"github.com/GoogleCloudPlatform/ubbagent/pipeline"
)
// Type waitForCalls is a base type that provides a doAndWait function.
type waitForCalls struct {
calls int32
waitChan chan bool
}
// DoAndWait executes the given function and then waits until the total number of calls reaches the
// given value.
func (wfc *waitForCalls) DoAndWait(t *testing.T, calls int32, f func()) {
f()
for atomic.LoadInt32(&wfc.calls) < calls {
select {
case <-wfc.waitChan:
case <-time.After(5 * time.Second):
t.Fatal("DoAndWait: nothing happened after 5 seconds")
}
}
}
func (wfc *waitForCalls) called() {
atomic.AddInt32(&wfc.calls, 1)
wfc.waitChan <- true
}
func (wfc *waitForCalls) Calls() int32 {
return atomic.LoadInt32(&wfc.calls)
}
func (wfc *waitForCalls) wfcInit() {
wfc.waitChan = make(chan bool, 100)
}
// Type MockInput is a mock pipeline.Input.
type MockInput struct {
waitForCalls
Used bool
Released bool
reports []metrics.MetricReport // must hold mu to read/write
addErr error
mu sync.Mutex
}
func (i *MockInput) AddReport(report metrics.MetricReport) error {
i.mu.Lock()
err := i.addErr
if err == nil {
i.reports = append(i.reports, report)
}
i.mu.Unlock()
i.called()
return err
}
func (i *MockInput) Use() {
i.Used = true
}
func (i *MockInput) Release() error {
i.Released = true
return nil
}
func (i *MockInput) Reports() (reports []metrics.MetricReport) {
i.mu.Lock()
reports = i.reports
i.reports = []metrics.MetricReport{}
i.mu.Unlock()
return
}
func (i *MockInput) SetAddError(err error) {
i.addErr = err
}
// NewMockSender creates a new MockInput.
func NewMockInput() *MockInput {
mi := &MockInput{}
mi.Reports()
mi.wfcInit()
return mi
}
// Type MockSender is a mock sender.Sender.
type MockSender struct {
waitForCalls
Used bool
Released bool
reports []metrics.MetricReport // must hold mu to read/write
sendErr error
mu sync.Mutex
endpoints []string
}
func (s *MockSender) Send(report metrics.StampedMetricReport) error {
s.mu.Lock()
err := s.sendErr
if err == nil {
s.reports = append(s.reports, report.MetricReport)
}
s.mu.Unlock()
s.called()
return err
}
func (s *MockSender) Endpoints() []string {
return s.endpoints
}
func (s *MockSender) Use() {
s.Used = true
}
func (s *MockSender) Release() error {
s.Released = true
return nil
}
func (s *MockSender) Reports() (reports []metrics.MetricReport) {
s.mu.Lock()
reports = s.reports
s.reports = []metrics.MetricReport{}
s.mu.Unlock()
return
}
func (s *MockSender) SetSendError(err error) {
s.sendErr = err
}
// NewMockSender creates a new MockSender with the given endpoint IDs.
func NewMockSender(endpoints ...string) *MockSender {
ms := &MockSender{endpoints: endpoints}
ms.Reports()
ms.wfcInit()
return ms
}
// Type MockEndpoint is a mock endpoint.Endpoint.
type MockEndpoint struct {
waitForCalls
Used bool
Released bool
reports []pipeline.EndpointReport // must hold mu to read/write
name string
sendErr error
buildErr error
mu sync.Mutex
}
func (ep *MockEndpoint) Name() string {
return ep.name
}
func (ep *MockEndpoint) Send(report pipeline.EndpointReport) error {
ep.mu.Lock()
err := ep.sendErr
if err == nil {
ep.reports = append(ep.reports, report)
}
ep.mu.Unlock()
ep.called()
return err
}
func (ep *MockEndpoint) BuildReport(report metrics.StampedMetricReport) (pipeline.EndpointReport, error) {
if ep.buildErr != nil {
return pipeline.EndpointReport{}, ep.buildErr
}
return pipeline.NewEndpointReport(report, nil)
}
func (ep *MockEndpoint) Use() {
ep.Used = true
}
func (ep *MockEndpoint) Release() error {
ep.Released = true
return nil
}
func (ep *MockEndpoint) IsTransient(err error) bool {
return err != nil && err.Error() != "FATAL"
}
func (ep *MockEndpoint) Reports() (reports []pipeline.EndpointReport) {
ep.mu.Lock()
reports = ep.reports
ep.reports = []pipeline.EndpointReport{}
ep.mu.Unlock()
return
}
func (ep *MockEndpoint) SetSendErr(err error) {
ep.mu.Lock()
ep.sendErr = err
ep.mu.Unlock()
}
func (ep *MockEndpoint) SetBuildErr(err error) {
ep.mu.Lock()
ep.buildErr = err
ep.mu.Unlock()
}
// NewMockEndpoint creates a new MockEndpoint with the given name.
func NewMockEndpoint(name string) *MockEndpoint {
ep := &MockEndpoint{name: name}
ep.Reports()
ep.wfcInit()
return ep
}
// Type MockStatsRecorder is a mock stats.StatsRecorder.
type MockStatsRecorder struct {
waitForCalls
mu sync.RWMutex
registered map[string][]string
succeeded []RecordedEntry
failed []RecordedEntry
}
type RecordedEntry struct {
Id string
Handler string
}
func (sr *MockStatsRecorder) Register(id string, handlers []string) {
sr.mu.Lock()
if sr.registered == nil {
sr.registered = make(map[string][]string)
}
sr.registered[id] = handlers
sr.mu.Unlock()
}
func (sr *MockStatsRecorder) SendSucceeded(id string, handler string) {
sr.mu.Lock()
sr.succeeded = append(sr.succeeded, RecordedEntry{id, handler})
sr.mu.Unlock()
sr.called()
}
func (sr *MockStatsRecorder) SendFailed(id string, handler string) {
sr.mu.Lock()
sr.failed = append(sr.failed, RecordedEntry{id, handler})
sr.mu.Unlock()
sr.called()
}
func (sr *MockStatsRecorder) Registered() map[string][]string {
sr.mu.RLock()
defer sr.mu.RUnlock()
return sr.registered
}
func (sr *MockStatsRecorder) Succeeded() []RecordedEntry {
sr.mu.RLock()
defer sr.mu.RUnlock()
return sr.succeeded
}
func (sr *MockStatsRecorder) Failed() []RecordedEntry {
sr.mu.RLock()
defer sr.mu.RUnlock()
return sr.failed
}
func NewMockStatsRecorder() *MockStatsRecorder {
sr := &MockStatsRecorder{}
sr.wfcInit()
return sr
}