/
mock.go
168 lines (150 loc) · 4.17 KB
/
mock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package log
import (
"fmt"
"log/syslog"
"regexp"
"strings"
"time"
)
// UseMock sets a mock logger as the default logger, and returns it.
func UseMock() *Mock {
m := NewMock()
_ = Set(m)
return m
}
// NewMock creates a mock logger.
func NewMock() *Mock {
return &Mock{impl{newMockWriter()}}
}
// NewWaitingMock creates a mock logger implementing the writer interface.
// It stores all logged messages in a buffer for inspection by test
// functions.
func NewWaitingMock() *WaitingMock {
return &WaitingMock{impl{newWaitingMockWriter()}}
}
// Mock is a logger that stores all log messages in memory to be examined by a
// test.
type Mock struct {
impl
}
// WaitingMock is a logger that stores all messages in memory to be examined by a test with methods
type WaitingMock struct {
impl
}
// Mock implements the writer interface. It
// stores all logged messages in a buffer for inspection by test
// functions (via GetAll()) instead of sending them to syslog.
type mockWriter struct {
logged []string
msgChan chan<- string
getChan <-chan []string
clearChan chan<- struct{}
closeChan chan<- struct{}
}
var levelName = map[syslog.Priority]string{
syslog.LOG_ERR: "ERR",
syslog.LOG_WARNING: "WARNING",
syslog.LOG_INFO: "INFO",
syslog.LOG_DEBUG: "DEBUG",
}
func (w *mockWriter) logAtLevel(p syslog.Priority, msg string, a ...interface{}) {
w.msgChan <- fmt.Sprintf("%s: %s", levelName[p&7], fmt.Sprintf(msg, a...))
}
// newMockWriter returns a new mockWriter
func newMockWriter() *mockWriter {
msgChan := make(chan string)
getChan := make(chan []string)
clearChan := make(chan struct{})
closeChan := make(chan struct{})
w := &mockWriter{
logged: []string{},
msgChan: msgChan,
getChan: getChan,
clearChan: clearChan,
closeChan: closeChan,
}
go func() {
for {
select {
case logMsg := <-msgChan:
w.logged = append(w.logged, logMsg)
case getChan <- w.logged:
case <-clearChan:
w.logged = []string{}
case <-closeChan:
close(getChan)
return
}
}
}()
return w
}
// GetAll returns all messages logged since instantiation or the last call to
// Clear().
//
// The caller must not modify the returned slice or its elements.
func (m *Mock) GetAll() []string {
w := m.w.(*mockWriter)
return <-w.getChan
}
// GetAllMatching returns all messages logged since instantiation or the last
// Clear() whose text matches the given regexp. The regexp is
// accepted as a string and compiled on the fly, because convenience
// is more important than performance.
//
// The caller must not modify the elements of the returned slice.
func (m *Mock) GetAllMatching(reString string) []string {
var matches []string
w := m.w.(*mockWriter)
re := regexp.MustCompile(reString)
for _, logMsg := range <-w.getChan {
if re.MatchString(logMsg) {
matches = append(matches, logMsg)
}
}
return matches
}
func (m *Mock) ExpectMatch(reString string) error {
results := m.GetAllMatching(reString)
if len(results) == 0 {
return fmt.Errorf("expected log line %q, got %q", reString, strings.Join(m.GetAll(), "\n"))
}
return nil
}
// Clear resets the log buffer.
func (m *Mock) Clear() {
w := m.w.(*mockWriter)
w.clearChan <- struct{}{}
}
type waitingMockWriter struct {
logChan chan string
}
// newWaitingMockWriter returns a new waitingMockWriter
func newWaitingMockWriter() *waitingMockWriter {
logChan := make(chan string, 1000)
return &waitingMockWriter{
logChan,
}
}
func (m *waitingMockWriter) logAtLevel(p syslog.Priority, msg string, a ...interface{}) {
m.logChan <- fmt.Sprintf("%s: %s", levelName[p&7], fmt.Sprintf(msg, a...))
}
// WaitForMatch returns the first log line matching a regex. It accepts a
// regexp string and timeout. If the timeout value is met before the
// matching pattern is read from the channel, an error is returned.
func (m *WaitingMock) WaitForMatch(reString string, timeout time.Duration) (string, error) {
w := m.w.(*waitingMockWriter)
deadline := time.After(timeout)
re := regexp.MustCompile(reString)
for {
select {
case logLine := <-w.logChan:
if re.MatchString(logLine) {
close(w.logChan)
return logLine, nil
}
case <-deadline:
return "", fmt.Errorf("timeout waiting for match: %q", reString)
}
}
}