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