master
go 365 lines 11.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package pgbouncer
4
5 import (
6 "bufio"
7 "bytes"
8 "context"
9 "database/sql/driver"
10 "errors"
11 "fmt"
12 "os"
13 "strings"
14 "testing"
15
16 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
17
18 "github.com/DATA-DOG/go-sqlmock"
19 "github.com/stretchr/testify/assert"
20 "github.com/stretchr/testify/require"
21 )
22
23 var (
24 dataConfigJSON, _ = os.ReadFile("testdata/config.json")
25 dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
26
27 dataVer170Version, _ = os.ReadFile("testdata/v1.7.0/version.txt")
28 dataVer1170Version, _ = os.ReadFile("testdata/v1.17.0/version.txt")
29 dataVer1170Config, _ = os.ReadFile("testdata/v1.17.0/config.txt")
30 dataVer1170Databases, _ = os.ReadFile("testdata/v1.17.0/databases.txt")
31 dataVer1170Pools, _ = os.ReadFile("testdata/v1.17.0/pools.txt")
32 dataVer1170Stats, _ = os.ReadFile("testdata/v1.17.0/stats.txt")
33 )
34
35 func Test_testDataIsValid(t *testing.T) {
36 for name, data := range map[string][]byte{
37 "dataConfigJSON": dataConfigJSON,
38 "dataConfigYAML": dataConfigYAML,
39 "dataVer170Version": dataVer170Version,
40 "dataVer1170Version": dataVer1170Version,
41 "dataVer1170Config": dataVer1170Config,
42 "dataVer1170Databases": dataVer1170Databases,
43 "dataVer1170Pools": dataVer1170Pools,
44 "dataVer1170Stats": dataVer1170Stats,
45 } {
46 require.NotNil(t, data, name)
47 }
48 }
49
50 func TestCollector_ConfigurationSerialize(t *testing.T) {
51 collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML)
52 }
53
54 func TestCollector_Init(t *testing.T) {
55 tests := map[string]struct {
56 wantFail bool
57 config Config
58 }{
59 "Success with default": {
60 wantFail: false,
61 config: New().Config,
62 },
63 "Fail when DSN not set": {
64 wantFail: true,
65 config: Config{DSN: ""},
66 },
67 }
68
69 for name, test := range tests {
70 t.Run(name, func(t *testing.T) {
71 collr := New()
72 collr.Config = test.config
73
74 if test.wantFail {
75 assert.Error(t, collr.Init(context.Background()))
76 } else {
77 assert.NoError(t, collr.Init(context.Background()))
78 }
79 })
80 }
81 }
82
83 func TestCollector_Charts(t *testing.T) {
84 assert.NotNil(t, New().Charts())
85 }
86
87 func TestCollector_Check(t *testing.T) {
88 tests := map[string]struct {
89 prepareMock func(t *testing.T, m sqlmock.Sqlmock)
90 wantFail bool
91 }{
92 "Success when all queries are successful (v1.17.0)": {
93 wantFail: false,
94 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
95 mockExpect(t, m, queryShowVersion, dataVer1170Version)
96 mockExpect(t, m, queryShowConfig, dataVer1170Config)
97 mockExpect(t, m, queryShowDatabases, dataVer1170Databases)
98 mockExpect(t, m, queryShowStats, dataVer1170Stats)
99 mockExpect(t, m, queryShowPools, dataVer1170Pools)
100 },
101 },
102 "Fail when querying version returns an error": {
103 wantFail: true,
104 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
105 mockExpectErr(m, queryShowVersion)
106 },
107 },
108 "Fail when querying version returns unsupported version": {
109 wantFail: true,
110 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
111 mockExpect(t, m, queryShowVersion, dataVer170Version)
112 },
113 },
114 "Fail when querying config returns an error": {
115 wantFail: true,
116 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
117 mockExpect(t, m, queryShowVersion, dataVer1170Version)
118 mockExpectErr(m, queryShowConfig)
119 },
120 },
121 }
122
123 for name, test := range tests {
124 t.Run(name, func(t *testing.T) {
125 db, mock, err := sqlmock.New(
126 sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual),
127 )
128 require.NoError(t, err)
129 collr := New()
130 collr.db = db
131 defer func() { _ = db.Close() }()
132
133 require.NoError(t, collr.Init(context.Background()))
134
135 test.prepareMock(t, mock)
136
137 if test.wantFail {
138 assert.Error(t, collr.Check(context.Background()))
139 } else {
140 assert.NoError(t, collr.Check(context.Background()))
141 }
142 assert.NoError(t, mock.ExpectationsWereMet())
143 })
144 }
145 }
146
147 func TestCollector_Collect(t *testing.T) {
148 type testCaseStep struct {
149 prepareMock func(*testing.T, sqlmock.Sqlmock)
150 check func(*testing.T, *Collector)
151 }
152 tests := map[string][]testCaseStep{
153 "Success on all queries (v1.17.0)": {
154 {
155 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
156 mockExpect(t, m, queryShowVersion, dataVer1170Version)
157 mockExpect(t, m, queryShowConfig, dataVer1170Config)
158 mockExpect(t, m, queryShowDatabases, dataVer1170Databases)
159 mockExpect(t, m, queryShowStats, dataVer1170Stats)
160 mockExpect(t, m, queryShowPools, dataVer1170Pools)
161 },
162 check: func(t *testing.T, collr *Collector) {
163 mx := collr.Collect(context.Background())
164
165 expected := map[string]int64{
166 "cl_conns_utilization": 47,
167 "db_myprod1_avg_query_time": 575,
168 "db_myprod1_avg_xact_time": 575,
169 "db_myprod1_cl_active": 15,
170 "db_myprod1_cl_cancel_req": 0,
171 "db_myprod1_cl_waiting": 0,
172 "db_myprod1_maxwait": 0,
173 "db_myprod1_sv_active": 15,
174 "db_myprod1_sv_conns_utilization": 0,
175 "db_myprod1_sv_idle": 5,
176 "db_myprod1_sv_login": 0,
177 "db_myprod1_sv_tested": 0,
178 "db_myprod1_sv_used": 0,
179 "db_myprod1_total_query_count": 12683170,
180 "db_myprod1_total_query_time": 7223566620,
181 "db_myprod1_total_received": 809093651,
182 "db_myprod1_total_sent": 1990971542,
183 "db_myprod1_total_wait_time": 1029555,
184 "db_myprod1_total_xact_count": 12683170,
185 "db_myprod1_total_xact_time": 7223566620,
186 "db_myprod2_avg_query_time": 581,
187 "db_myprod2_avg_xact_time": 581,
188 "db_myprod2_cl_active": 12,
189 "db_myprod2_cl_cancel_req": 0,
190 "db_myprod2_cl_waiting": 0,
191 "db_myprod2_maxwait": 0,
192 "db_myprod2_sv_active": 11,
193 "db_myprod2_sv_conns_utilization": 0,
194 "db_myprod2_sv_idle": 9,
195 "db_myprod2_sv_login": 0,
196 "db_myprod2_sv_tested": 0,
197 "db_myprod2_sv_used": 0,
198 "db_myprod2_total_query_count": 12538544,
199 "db_myprod2_total_query_time": 7144226450,
200 "db_myprod2_total_received": 799867464,
201 "db_myprod2_total_sent": 1968267687,
202 "db_myprod2_total_wait_time": 993313,
203 "db_myprod2_total_xact_count": 12538544,
204 "db_myprod2_total_xact_time": 7144226450,
205 "db_pgbouncer_avg_query_time": 0,
206 "db_pgbouncer_avg_xact_time": 0,
207 "db_pgbouncer_cl_active": 2,
208 "db_pgbouncer_cl_cancel_req": 0,
209 "db_pgbouncer_cl_waiting": 0,
210 "db_pgbouncer_maxwait": 0,
211 "db_pgbouncer_sv_active": 0,
212 "db_pgbouncer_sv_conns_utilization": 0,
213 "db_pgbouncer_sv_idle": 0,
214 "db_pgbouncer_sv_login": 0,
215 "db_pgbouncer_sv_tested": 0,
216 "db_pgbouncer_sv_used": 0,
217 "db_pgbouncer_total_query_count": 45,
218 "db_pgbouncer_total_query_time": 0,
219 "db_pgbouncer_total_received": 0,
220 "db_pgbouncer_total_sent": 0,
221 "db_pgbouncer_total_wait_time": 0,
222 "db_pgbouncer_total_xact_count": 45,
223 "db_pgbouncer_total_xact_time": 0,
224 "db_postgres_avg_query_time": 2790,
225 "db_postgres_avg_xact_time": 2790,
226 "db_postgres_cl_active": 18,
227 "db_postgres_cl_cancel_req": 0,
228 "db_postgres_cl_waiting": 0,
229 "db_postgres_maxwait": 0,
230 "db_postgres_sv_active": 18,
231 "db_postgres_sv_conns_utilization": 0,
232 "db_postgres_sv_idle": 2,
233 "db_postgres_sv_login": 0,
234 "db_postgres_sv_tested": 0,
235 "db_postgres_sv_used": 0,
236 "db_postgres_total_query_count": 25328823,
237 "db_postgres_total_query_time": 72471882827,
238 "db_postgres_total_received": 1615791619,
239 "db_postgres_total_sent": 3976053858,
240 "db_postgres_total_wait_time": 50439622253,
241 "db_postgres_total_xact_count": 25328823,
242 "db_postgres_total_xact_time": 72471882827,
243 }
244
245 assert.Equal(t, expected, mx)
246 },
247 },
248 },
249 "Fail when querying version returns an error": {
250 {
251 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
252 mockExpectErr(m, queryShowVersion)
253 },
254 check: func(t *testing.T, collr *Collector) {
255 mx := collr.Collect(context.Background())
256 var expected map[string]int64
257 assert.Equal(t, expected, mx)
258 },
259 },
260 },
261 "Fail when querying version returns unsupported version": {
262 {
263 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
264 mockExpect(t, m, queryShowVersion, dataVer170Version)
265 },
266 check: func(t *testing.T, collr *Collector) {
267 mx := collr.Collect(context.Background())
268 var expected map[string]int64
269 assert.Equal(t, expected, mx)
270 },
271 },
272 },
273 "Fail when querying config returns an error": {
274 {
275 prepareMock: func(t *testing.T, m sqlmock.Sqlmock) {
276 mockExpect(t, m, queryShowVersion, dataVer1170Version)
277 mockExpectErr(m, queryShowConfig)
278 },
279 check: func(t *testing.T, collr *Collector) {
280 mx := collr.Collect(context.Background())
281 var expected map[string]int64
282 assert.Equal(t, expected, mx)
283 },
284 },
285 },
286 }
287
288 for name, test := range tests {
289 t.Run(name, func(t *testing.T) {
290 db, mock, err := sqlmock.New(
291 sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual),
292 )
293 require.NoError(t, err)
294 collr := New()
295 collr.db = db
296 defer func() { _ = db.Close() }()
297
298 require.NoError(t, collr.Init(context.Background()))
299
300 for i, step := range test {
301 t.Run(fmt.Sprintf("step[%d]", i), func(t *testing.T) {
302 step.prepareMock(t, mock)
303 step.check(t, collr)
304 })
305 }
306 assert.NoError(t, mock.ExpectationsWereMet())
307 })
308 }
309 }
310
311 func mockExpect(t *testing.T, mock sqlmock.Sqlmock, query string, rows []byte) {
312 mock.ExpectQuery(query).WillReturnRows(mustMockRows(t, rows)).RowsWillBeClosed()
313 }
314
315 func mockExpectErr(mock sqlmock.Sqlmock, query string) {
316 mock.ExpectQuery(query).WillReturnError(fmt.Errorf("mock error (%s)", query))
317 }
318
319 func mustMockRows(t *testing.T, data []byte) *sqlmock.Rows {
320 rows, err := prepareMockRows(data)
321 require.NoError(t, err)
322 return rows
323 }
324
325 func prepareMockRows(data []byte) (*sqlmock.Rows, error) {
326 r := bytes.NewReader(data)
327 sc := bufio.NewScanner(r)
328
329 var numColumns int
330 var rows *sqlmock.Rows
331
332 for sc.Scan() {
333 s := strings.TrimSpace(sc.Text())
334 if s == "" || strings.HasPrefix(s, "---") {
335 continue
336 }
337
338 parts := strings.Split(s, "|")
339 for i, v := range parts {
340 parts[i] = strings.TrimSpace(v)
341 }
342
343 if rows == nil {
344 numColumns = len(parts)
345 rows = sqlmock.NewRows(parts)
346 continue
347 }
348
349 if len(parts) != numColumns {
350 return nil, fmt.Errorf("prepareMockRows(): columns != values (%d/%d)", numColumns, len(parts))
351 }
352
353 values := make([]driver.Value, len(parts))
354 for i, v := range parts {
355 values[i] = v
356 }
357 rows.AddRow(values...)
358 }
359
360 if rows == nil {
361 return nil, errors.New("prepareMockRows(): nil rows result")
362 }
363
364 return rows, nil
365 }