master
go 297 lines 7.01 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package memcached
4
5 import (
6 "context"
7 "errors"
8 "os"
9 "testing"
10
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
12
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 )
16
17 var (
18 dataConfigJSON, _ = os.ReadFile("testdata/config.json")
19 dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
20
21 dataMemcachedStats, _ = os.ReadFile("testdata/stats.txt")
22 )
23
24 func Test_testDataIsValid(t *testing.T) {
25 for name, data := range map[string][]byte{
26 "dataConfigJSON": dataConfigJSON,
27 "dataConfigYAML": dataConfigYAML,
28
29 "dataMemcachedStats": dataMemcachedStats,
30 } {
31 require.NotNil(t, data, name)
32 }
33 }
34
35 func TestCollector_ConfigurationSerialize(t *testing.T) {
36 collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML)
37 }
38
39 func TestCollector_Init(t *testing.T) {
40 tests := map[string]struct {
41 config Config
42 wantFail bool
43 }{
44 "success with default config": {
45 wantFail: false,
46 config: New().Config,
47 },
48 "fails if address not set": {
49 wantFail: true,
50 config: func() Config {
51 conf := New().Config
52 conf.Address = ""
53 return conf
54 }(),
55 },
56 }
57
58 for name, test := range tests {
59 t.Run(name, func(t *testing.T) {
60 collr := New()
61 collr.Config = test.config
62
63 if test.wantFail {
64 assert.Error(t, collr.Init(context.Background()))
65 } else {
66 assert.NoError(t, collr.Init(context.Background()))
67 }
68 })
69 }
70 }
71
72 func TestCollector_Cleanup(t *testing.T) {
73 tests := map[string]struct {
74 prepare func() *Collector
75 }{
76 "not initialized": {
77 prepare: func() *Collector {
78 return New()
79 },
80 },
81 "after check": {
82 prepare: func() *Collector {
83 collr := New()
84 collr.newMemcachedConn = func(config Config) memcachedConn { return prepareMockOk() }
85 _ = collr.Check(context.Background())
86 return collr
87 },
88 },
89 "after collect": {
90 prepare: func() *Collector {
91 collr := New()
92 collr.newMemcachedConn = func(config Config) memcachedConn { return prepareMockOk() }
93 _ = collr.Collect(context.Background())
94 return collr
95 },
96 },
97 }
98
99 for name, test := range tests {
100 t.Run(name, func(t *testing.T) {
101 collr := test.prepare()
102
103 assert.NotPanics(t, func() { collr.Cleanup(context.Background()) })
104 })
105 }
106 }
107
108 func TestCollector_Charts(t *testing.T) {
109 assert.NotNil(t, New().Charts())
110 }
111
112 func TestCollector_Check(t *testing.T) {
113 tests := map[string]struct {
114 prepareMock func() *mockMemcachedConn
115 wantFail bool
116 }{
117 "success case": {
118 wantFail: false,
119 prepareMock: prepareMockOk,
120 },
121 "err on connect": {
122 wantFail: true,
123 prepareMock: prepareMockErrOnConnect,
124 },
125 "unexpected response": {
126 wantFail: true,
127 prepareMock: prepareMockUnexpectedResponse,
128 },
129 "empty response": {
130 wantFail: true,
131 prepareMock: prepareMockEmptyResponse,
132 },
133 }
134
135 for name, test := range tests {
136 t.Run(name, func(t *testing.T) {
137 collr := New()
138 mock := test.prepareMock()
139 collr.newMemcachedConn = func(config Config) memcachedConn { return mock }
140
141 if test.wantFail {
142 assert.Error(t, collr.Check(context.Background()))
143 } else {
144 assert.NoError(t, collr.Check(context.Background()))
145 }
146 })
147 }
148 }
149
150 func TestCollector_Collect(t *testing.T) {
151 tests := map[string]struct {
152 prepareMock func() *mockMemcachedConn
153 wantMetrics map[string]int64
154 disconnectBeforeCleanup bool
155 disconnectAfterCleanup bool
156 }{
157 "success case": {
158 prepareMock: prepareMockOk,
159 disconnectBeforeCleanup: false,
160 disconnectAfterCleanup: true,
161 wantMetrics: map[string]int64{
162 "avail": 67108831,
163 "bytes": 33,
164 "bytes_read": 108662,
165 "bytes_written": 9761348,
166 "cas_badval": 0,
167 "cas_hits": 0,
168 "cas_misses": 0,
169 "cmd_get": 1,
170 "cmd_set": 1,
171 "cmd_touch": 0,
172 "curr_connections": 3,
173 "curr_items": 0,
174 "decr_hits": 0,
175 "decr_misses": 0,
176 "delete_hits": 0,
177 "delete_misses": 0,
178 "evictions": 0,
179 "get_hits": 0,
180 "get_misses": 1,
181 "incr_hits": 0,
182 "incr_misses": 0,
183 "limit_maxbytes": 67108864,
184 "reclaimed": 1,
185 "rejected_connections": 0,
186 "total_connections": 39,
187 "total_items": 1,
188 "touch_hits": 0,
189 "touch_misses": 0,
190 },
191 },
192 "error response": {
193 prepareMock: prepareMockErrorResponse,
194 disconnectBeforeCleanup: false,
195 disconnectAfterCleanup: true,
196 },
197 "unexpected response": {
198 prepareMock: prepareMockUnexpectedResponse,
199 disconnectBeforeCleanup: false,
200 disconnectAfterCleanup: true,
201 },
202 "empty response": {
203 prepareMock: prepareMockEmptyResponse,
204 disconnectBeforeCleanup: false,
205 disconnectAfterCleanup: true,
206 },
207 "err on connect": {
208 prepareMock: prepareMockErrOnConnect,
209 disconnectBeforeCleanup: false,
210 disconnectAfterCleanup: false,
211 },
212 "err on query stats": {
213 prepareMock: prepareMockErrOnQueryStats,
214 disconnectBeforeCleanup: true,
215 disconnectAfterCleanup: true,
216 },
217 }
218
219 for name, test := range tests {
220 t.Run(name, func(t *testing.T) {
221 collr := New()
222 mock := test.prepareMock()
223 collr.newMemcachedConn = func(config Config) memcachedConn { return mock }
224
225 mx := collr.Collect(context.Background())
226
227 require.Equal(t, test.wantMetrics, mx)
228
229 if len(test.wantMetrics) > 0 {
230 collecttest.TestMetricsHasAllChartsDims(t, collr.Charts(), mx)
231 }
232
233 assert.Equal(t, test.disconnectBeforeCleanup, mock.disconnectCalled, "disconnect before cleanup")
234 collr.Cleanup(context.Background())
235 assert.Equal(t, test.disconnectAfterCleanup, mock.disconnectCalled, "disconnect after cleanup")
236 })
237 }
238 }
239
240 func prepareMockOk() *mockMemcachedConn {
241 return &mockMemcachedConn{
242 statsResponse: dataMemcachedStats,
243 }
244 }
245
246 func prepareMockErrorResponse() *mockMemcachedConn {
247 return &mockMemcachedConn{
248 statsResponse: []byte("ERROR"),
249 }
250 }
251
252 func prepareMockErrOnConnect() *mockMemcachedConn {
253 return &mockMemcachedConn{
254 errOnConnect: true,
255 }
256 }
257
258 func prepareMockErrOnQueryStats() *mockMemcachedConn {
259 return &mockMemcachedConn{
260 errOnQueryStats: true,
261 }
262 }
263
264 func prepareMockUnexpectedResponse() *mockMemcachedConn {
265 return &mockMemcachedConn{
266 statsResponse: []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
267 }
268 }
269
270 func prepareMockEmptyResponse() *mockMemcachedConn {
271 return &mockMemcachedConn{}
272 }
273
274 type mockMemcachedConn struct {
275 errOnConnect bool
276 errOnQueryStats bool
277 statsResponse []byte
278 disconnectCalled bool
279 }
280
281 func (m *mockMemcachedConn) connect() error {
282 if m.errOnConnect {
283 return errors.New("mock.connect() error")
284 }
285 return nil
286 }
287
288 func (m *mockMemcachedConn) disconnect() {
289 m.disconnectCalled = true
290 }
291
292 func (m *mockMemcachedConn) queryStats() ([]byte, error) {
293 if m.errOnQueryStats {
294 return nil, errors.New("mock.queryStats() error")
295 }
296 return m.statsResponse, nil
297 }