Concurrent-safe notification mock

This patch provides additional attribute to the notification mock in
order to protect an access to the internal maps from multiple
go-routines. This is required to prevent panic errors caused by
inconsistent map state.
This commit is contained in:
Yakau Bubnou 2016-12-07 19:26:43 +03:00
parent 3ff0271e68
commit 5c210fb25d
1 changed files with 52 additions and 22 deletions

View File

@ -11,6 +11,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
@ -24,15 +25,44 @@ type MockNotify struct {
state map[types.CheckID]string state map[types.CheckID]string
updates map[types.CheckID]int updates map[types.CheckID]int
output map[types.CheckID]string output map[types.CheckID]string
// A guard to protect an access to the internal attributes
// of the notification mock in order to prevent panics
// raised by the race conditions detector.
mu sync.RWMutex
} }
func (m *MockNotify) UpdateCheck(id types.CheckID, status, output string) { func (m *MockNotify) UpdateCheck(id types.CheckID, status, output string) {
m.mu.Lock()
defer m.mu.Unlock()
m.state[id] = status m.state[id] = status
old := m.updates[id] old := m.updates[id]
m.updates[id] = old + 1 m.updates[id] = old + 1
m.output[id] = output m.output[id] = output
} }
// State returns the state of the specified health-check.
func (m *MockNotify) State(id types.CheckID) string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.state[id]
}
// Updates returns the count of updates of the specified health-check.
func (m *MockNotify) Updates(id types.CheckID) int {
m.mu.RLock()
defer m.mu.RUnlock()
return m.updates[id]
}
// Output returns an output string of the specified health-check.
func (m *MockNotify) Output(id types.CheckID) string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.output[id]
}
func expectStatus(t *testing.T, script, status string) { func expectStatus(t *testing.T, script, status string) {
mock := &MockNotify{ mock := &MockNotify{
state: make(map[types.CheckID]string), state: make(map[types.CheckID]string),
@ -51,11 +81,11 @@ func expectStatus(t *testing.T, script, status string) {
testutil.WaitForResult(func() (bool, error) { testutil.WaitForResult(func() (bool, error) {
// Should have at least 2 updates // Should have at least 2 updates
if mock.updates["foo"] < 2 { if mock.Updates("foo") < 2 {
return false, fmt.Errorf("should have 2 updates %v", mock.updates) return false, fmt.Errorf("should have 2 updates %v", mock.updates)
} }
if mock.state["foo"] != status { if mock.State("foo") != status {
return false, fmt.Errorf("should be %v %v", status, mock.state) return false, fmt.Errorf("should be %v %v", status, mock.state)
} }
@ -101,11 +131,11 @@ func TestCheckMonitor_Timeout(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
// Should have at least 2 updates // Should have at least 2 updates
if mock.updates["foo"] < 2 { if mock.Updates("foo") < 2 {
t.Fatalf("should have at least 2 updates %v", mock.updates) t.Fatalf("should have at least 2 updates %v", mock.updates)
} }
if mock.state["foo"] != "critical" { if mock.State("foo") != "critical" {
t.Fatalf("should be critical %v", mock.state) t.Fatalf("should be critical %v", mock.state)
} }
} }
@ -129,11 +159,11 @@ func TestCheckMonitor_RandomStagger(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
// Should have at least 1 update // Should have at least 1 update
if mock.updates["foo"] < 1 { if mock.Updates("foo") < 1 {
t.Fatalf("should have 1 or more updates %v", mock.updates) t.Fatalf("should have 1 or more updates %v", mock.updates)
} }
if mock.state["foo"] != structs.HealthPassing { if mock.State("foo") != structs.HealthPassing {
t.Fatalf("should be %v %v", structs.HealthPassing, mock.state) t.Fatalf("should be %v %v", structs.HealthPassing, mock.state)
} }
} }
@ -157,7 +187,7 @@ func TestCheckMonitor_LimitOutput(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
// Allow for extra bytes for the truncation message // Allow for extra bytes for the truncation message
if len(mock.output["foo"]) > CheckBufSize+100 { if len(mock.Output("foo")) > CheckBufSize+100 {
t.Fatalf("output size is too long") t.Fatalf("output size is too long")
} }
} }
@ -180,32 +210,32 @@ func TestCheckTTL(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
check.SetStatus(structs.HealthPassing, "test-output") check.SetStatus(structs.HealthPassing, "test-output")
if mock.updates["foo"] != 1 { if mock.Updates("foo") != 1 {
t.Fatalf("should have 1 updates %v", mock.updates) t.Fatalf("should have 1 updates %v", mock.updates)
} }
if mock.state["foo"] != structs.HealthPassing { if mock.State("foo") != structs.HealthPassing {
t.Fatalf("should be passing %v", mock.state) t.Fatalf("should be passing %v", mock.state)
} }
// Ensure we don't fail early // Ensure we don't fail early
time.Sleep(75 * time.Millisecond) time.Sleep(75 * time.Millisecond)
if mock.updates["foo"] != 1 { if mock.Updates("foo") != 1 {
t.Fatalf("should have 1 updates %v", mock.updates) t.Fatalf("should have 1 updates %v", mock.updates)
} }
// Wait for the TTL to expire // Wait for the TTL to expire
time.Sleep(75 * time.Millisecond) time.Sleep(75 * time.Millisecond)
if mock.updates["foo"] != 2 { if mock.Updates("foo") != 2 {
t.Fatalf("should have 2 updates %v", mock.updates) t.Fatalf("should have 2 updates %v", mock.updates)
} }
if mock.state["foo"] != structs.HealthCritical { if mock.State("foo") != structs.HealthCritical {
t.Fatalf("should be critical %v", mock.state) t.Fatalf("should be critical %v", mock.state)
} }
if !strings.Contains(mock.output["foo"], "test-output") { if !strings.Contains(mock.Output("foo"), "test-output") {
t.Fatalf("should have retained output %v", mock.output) t.Fatalf("should have retained output %v", mock.output)
} }
} }
@ -254,16 +284,16 @@ func expectHTTPStatus(t *testing.T, url string, status string) {
testutil.WaitForResult(func() (bool, error) { testutil.WaitForResult(func() (bool, error) {
// Should have at least 2 updates // Should have at least 2 updates
if mock.updates["foo"] < 2 { if mock.Updates("foo") < 2 {
return false, fmt.Errorf("should have 2 updates %v", mock.updates) return false, fmt.Errorf("should have 2 updates %v", mock.updates)
} }
if mock.state["foo"] != status { if mock.State("foo") != status {
return false, fmt.Errorf("should be %v %v", status, mock.state) return false, fmt.Errorf("should be %v %v", status, mock.state)
} }
// Allow slightly more data than CheckBufSize, for the header // Allow slightly more data than CheckBufSize, for the header
if n := len(mock.output["foo"]); n > (CheckBufSize + 256) { if n := len(mock.Output("foo")); n > (CheckBufSize + 256) {
return false, fmt.Errorf("output too long: %d (%d-byte limit)", n, CheckBufSize) return false, fmt.Errorf("output too long: %d (%d-byte limit)", n, CheckBufSize)
} }
return true, nil return true, nil
@ -554,11 +584,11 @@ func expectTCPStatus(t *testing.T, tcp string, status string) {
testutil.WaitForResult(func() (bool, error) { testutil.WaitForResult(func() (bool, error) {
// Should have at least 2 updates // Should have at least 2 updates
if mock.updates["foo"] < 2 { if mock.Updates("foo") < 2 {
return false, fmt.Errorf("should have 2 updates %v", mock.updates) return false, fmt.Errorf("should have 2 updates %v", mock.updates)
} }
if mock.state["foo"] != status { if mock.State("foo") != status {
return false, fmt.Errorf("should be %v %v", status, mock.state) return false, fmt.Errorf("should be %v %v", status, mock.state)
} }
return true, nil return true, nil
@ -741,15 +771,15 @@ func expectDockerCheckStatus(t *testing.T, dockerClient DockerClient, status str
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
// Should have at least 2 updates // Should have at least 2 updates
if mock.updates["foo"] < 2 { if mock.Updates("foo") < 2 {
t.Fatalf("should have 2 updates %v", mock.updates) t.Fatalf("should have 2 updates %v", mock.updates)
} }
if mock.state["foo"] != status { if mock.State("foo") != status {
t.Fatalf("should be %v %v", status, mock.state) t.Fatalf("should be %v %v", status, mock.state)
} }
if mock.output["foo"] != output { if mock.Output("foo") != output {
t.Fatalf("should be %v %v", output, mock.output) t.Fatalf("should be %v %v", output, mock.output)
} }
} }
@ -851,7 +881,7 @@ func TestDockerCheckTruncateOutput(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
// Allow for extra bytes for the truncation message // Allow for extra bytes for the truncation message
if len(mock.output["foo"]) > CheckBufSize+100 { if len(mock.Output("foo")) > CheckBufSize+100 {
t.Fatalf("output size is too long") t.Fatalf("output size is too long")
} }