1package monitor
2
3import (
4	"fmt"
5	"testing"
6	"time"
7
8	log "github.com/hashicorp/go-hclog"
9	"github.com/stretchr/testify/require"
10)
11
12func TestMonitor_Start(t *testing.T) {
13	require := require.New(t)
14
15	logger := log.NewInterceptLogger(&log.LoggerOptions{
16		Level: log.Error,
17	})
18
19	m := New(Config{
20		BufferSize: 512,
21		Logger:     logger,
22		LoggerOptions: &log.LoggerOptions{
23			Level: log.Debug},
24	})
25
26	logCh := m.Start()
27	defer m.Stop()
28
29	logger.Debug("test log")
30
31	for {
32		select {
33		case log := <-logCh:
34			require.Contains(string(log), "[DEBUG] test log")
35			return
36		case <-time.After(3 * time.Second):
37			t.Fatal("Expected to receive from log channel")
38		}
39	}
40}
41
42func TestMonitor_Stop(t *testing.T) {
43	if testing.Short() {
44		t.Skip("too slow for testing.Short")
45	}
46
47	require := require.New(t)
48
49	logger := log.NewInterceptLogger(&log.LoggerOptions{
50		Level: log.Error,
51	})
52
53	m := New(Config{
54		BufferSize: 512,
55		Logger:     logger,
56		LoggerOptions: &log.LoggerOptions{
57			Level: log.Debug,
58		},
59	})
60	logCh := m.Start()
61	logger.Debug("test log")
62
63	require.Eventually(func() bool {
64		return len(logCh) == 1
65	}, 3*time.Second, 100*time.Millisecond, "expected logCh to have 1 log in it")
66
67	m.Stop()
68
69	// This log line should not be output to the log channel
70	logger.Debug("After Stop")
71
72	for {
73		select {
74		case log := <-logCh:
75			if string(log) != "" {
76				require.Contains(string(log), "[DEBUG] test log")
77			} else {
78				return
79			}
80		case <-time.After(3 * time.Second):
81			t.Fatal("Expected to receive from log channel")
82		}
83	}
84}
85
86func TestMonitor_DroppedMessages(t *testing.T) {
87	if testing.Short() {
88		t.Skip("too slow for testing.Short")
89	}
90
91	require := require.New(t)
92
93	logger := log.NewInterceptLogger(&log.LoggerOptions{
94		Level: log.Warn,
95	})
96
97	mcfg := Config{
98		BufferSize: 5,
99		Logger:     logger,
100		LoggerOptions: &log.LoggerOptions{
101			Level: log.Debug,
102		},
103	}
104	m := New(mcfg)
105
106	doneCh := make(chan struct{})
107	defer close(doneCh)
108
109	logCh := m.Start()
110
111	// Overflow the configured buffer size before we attempt to receive from the
112	// logCh. We choose (2*bufSize+1) to account for:
113	// - buffer size of internal write channel
114	// - buffer size of channel returned from Start()
115	// - A message guaranteed to be dropped because all buffers are full
116	for i := 0; i <= 2*mcfg.BufferSize+1; i++ {
117		logger.Debug(fmt.Sprintf("test message %d", i))
118	}
119
120	// Make sure we do not stop before the goroutines have time to process.
121	require.Eventually(func() bool {
122		return len(logCh) == mcfg.BufferSize
123	}, 3*time.Second, 100*time.Millisecond, "expected logCh to have a full log buffer")
124
125	dropped := m.Stop()
126
127	// The number of dropped messages is non-deterministic, so we only assert
128	// that we dropped at least 1.
129	require.GreaterOrEqual(dropped, 1)
130}
131
132func TestMonitor_ZeroBufSizeDefault(t *testing.T) {
133	if testing.Short() {
134		t.Skip("too slow for testing.Short")
135	}
136
137	require := require.New(t)
138
139	logger := log.NewInterceptLogger(&log.LoggerOptions{
140		Level: log.Error,
141	})
142
143	m := New(Config{
144		BufferSize: 0,
145		Logger:     logger,
146		LoggerOptions: &log.LoggerOptions{
147			Level: log.Debug,
148		},
149	})
150	logCh := m.Start()
151	defer m.Stop()
152
153	logger.Debug("test log")
154
155	// If we do not default the buffer size, the monitor will be unable to buffer
156	// a log line.
157	require.Eventually(func() bool {
158		return len(logCh) == 1
159	}, 3*time.Second, 100*time.Millisecond, "expected logCh to have 1 log buffered")
160
161	for {
162		select {
163		case log := <-logCh:
164			require.Contains(string(log), "[DEBUG] test log")
165			return
166		case <-time.After(3 * time.Second):
167			t.Fatal("Expected to receive from log channel")
168		}
169	}
170}
171
172func TestMonitor_WriteStopped(t *testing.T) {
173	require := require.New(t)
174
175	logger := log.NewInterceptLogger(&log.LoggerOptions{
176		Level: log.Error,
177	})
178
179	mwriter := &monitor{
180		logger: logger,
181		doneCh: make(chan struct{}, 1),
182	}
183
184	mwriter.Stop()
185	n, err := mwriter.Write([]byte("write after close"))
186	require.Equal(n, 0)
187	require.EqualError(err, "monitor stopped")
188}
189