github.com/netdata/go.d.plugin@v0.58.1/modules/upsd/upsd_test.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package upsd 4 5 import ( 6 "errors" 7 "fmt" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 func TestUpsd_Cleanup(t *testing.T) { 15 upsd := New() 16 17 require.NotPanics(t, upsd.Cleanup) 18 19 mock := prepareMockConnOK() 20 upsd.newUpsdConn = func(Config) upsdConn { return mock } 21 22 require.True(t, upsd.Init()) 23 _ = upsd.Collect() 24 require.NotPanics(t, upsd.Cleanup) 25 assert.True(t, mock.calledDisconnect) 26 } 27 28 func TestUpsd_Init(t *testing.T) { 29 tests := map[string]struct { 30 config Config 31 wantFail bool 32 }{ 33 "success on default config": { 34 wantFail: false, 35 config: New().Config, 36 }, 37 "fails when 'address' option not set": { 38 wantFail: true, 39 config: Config{Address: ""}, 40 }, 41 } 42 43 for name, test := range tests { 44 t.Run(name, func(t *testing.T) { 45 upsd := New() 46 upsd.Config = test.config 47 48 if test.wantFail { 49 assert.False(t, upsd.Init()) 50 } else { 51 assert.True(t, upsd.Init()) 52 } 53 }) 54 } 55 } 56 57 func TestUpsd_Check(t *testing.T) { 58 tests := map[string]struct { 59 prepareUpsd func() *Upsd 60 prepareMock func() *mockUpsdConn 61 wantFail bool 62 }{ 63 "successful data collection": { 64 wantFail: false, 65 prepareUpsd: New, 66 prepareMock: prepareMockConnOK, 67 }, 68 "error on connect()": { 69 wantFail: true, 70 prepareUpsd: New, 71 prepareMock: prepareMockConnErrOnConnect, 72 }, 73 "error on authenticate()": { 74 wantFail: true, 75 prepareUpsd: func() *Upsd { 76 upsd := New() 77 upsd.Username = "user" 78 upsd.Password = "pass" 79 return upsd 80 }, 81 prepareMock: prepareMockConnErrOnAuthenticate, 82 }, 83 "error on upsList()": { 84 wantFail: true, 85 prepareUpsd: New, 86 prepareMock: prepareMockConnErrOnUpsUnits, 87 }, 88 } 89 90 for name, test := range tests { 91 t.Run(name, func(t *testing.T) { 92 upsd := test.prepareUpsd() 93 upsd.newUpsdConn = func(Config) upsdConn { return test.prepareMock() } 94 95 require.True(t, upsd.Init()) 96 97 if test.wantFail { 98 assert.False(t, upsd.Check()) 99 } else { 100 assert.True(t, upsd.Check()) 101 } 102 }) 103 } 104 } 105 106 func TestUpsd_Charts(t *testing.T) { 107 upsd := New() 108 require.True(t, upsd.Init()) 109 assert.NotNil(t, upsd.Charts()) 110 } 111 112 func TestUpsd_Collect(t *testing.T) { 113 tests := map[string]struct { 114 prepareUpsd func() *Upsd 115 prepareMock func() *mockUpsdConn 116 wantCollected map[string]int64 117 wantCharts int 118 wantConnConnect bool 119 wantConnDisconnect bool 120 wantConnAuthenticate bool 121 }{ 122 "successful data collection": { 123 prepareUpsd: New, 124 prepareMock: prepareMockConnOK, 125 wantCollected: map[string]int64{ 126 "ups_cp1500_battery.charge": 10000, 127 "ups_cp1500_battery.runtime": 489000, 128 "ups_cp1500_battery.voltage": 2400, 129 "ups_cp1500_battery.voltage.nominal": 2400, 130 "ups_cp1500_input.voltage": 22700, 131 "ups_cp1500_input.voltage.nominal": 23000, 132 "ups_cp1500_output.voltage": 26000, 133 "ups_cp1500_ups.load": 800, 134 "ups_cp1500_ups.load.usage": 4300, 135 "ups_cp1500_ups.realpower.nominal": 90000, 136 "ups_cp1500_ups.status.BOOST": 0, 137 "ups_cp1500_ups.status.BYPASS": 0, 138 "ups_cp1500_ups.status.CAL": 0, 139 "ups_cp1500_ups.status.CHRG": 0, 140 "ups_cp1500_ups.status.DISCHRG": 0, 141 "ups_cp1500_ups.status.FSD": 0, 142 "ups_cp1500_ups.status.HB": 0, 143 "ups_cp1500_ups.status.LB": 0, 144 "ups_cp1500_ups.status.OB": 0, 145 "ups_cp1500_ups.status.OFF": 0, 146 "ups_cp1500_ups.status.OL": 1, 147 "ups_cp1500_ups.status.OVER": 0, 148 "ups_cp1500_ups.status.RB": 0, 149 "ups_cp1500_ups.status.TRIM": 0, 150 "ups_cp1500_ups.status.other": 0, 151 "ups_pr3000_battery.charge": 10000, 152 "ups_pr3000_battery.runtime": 110800, 153 "ups_pr3000_battery.voltage": 5990, 154 "ups_pr3000_battery.voltage.nominal": 4800, 155 "ups_pr3000_input.voltage": 22500, 156 "ups_pr3000_input.voltage.nominal": 23000, 157 "ups_pr3000_output.voltage": 22500, 158 "ups_pr3000_ups.load": 2800, 159 "ups_pr3000_ups.load.usage": 84000, 160 "ups_pr3000_ups.realpower.nominal": 300000, 161 "ups_pr3000_ups.status.BOOST": 0, 162 "ups_pr3000_ups.status.BYPASS": 0, 163 "ups_pr3000_ups.status.CAL": 0, 164 "ups_pr3000_ups.status.CHRG": 0, 165 "ups_pr3000_ups.status.DISCHRG": 0, 166 "ups_pr3000_ups.status.FSD": 0, 167 "ups_pr3000_ups.status.HB": 0, 168 "ups_pr3000_ups.status.LB": 0, 169 "ups_pr3000_ups.status.OB": 0, 170 "ups_pr3000_ups.status.OFF": 0, 171 "ups_pr3000_ups.status.OL": 1, 172 "ups_pr3000_ups.status.OVER": 0, 173 "ups_pr3000_ups.status.RB": 0, 174 "ups_pr3000_ups.status.TRIM": 0, 175 "ups_pr3000_ups.status.other": 0, 176 }, 177 wantCharts: 20, 178 wantConnConnect: true, 179 wantConnDisconnect: false, 180 wantConnAuthenticate: false, 181 }, 182 "error on connect()": { 183 prepareUpsd: New, 184 prepareMock: prepareMockConnErrOnConnect, 185 wantCollected: nil, 186 wantCharts: 0, 187 wantConnConnect: true, 188 wantConnDisconnect: false, 189 wantConnAuthenticate: false, 190 }, 191 "error on authenticate()": { 192 prepareUpsd: func() *Upsd { 193 upsd := New() 194 upsd.Username = "user" 195 upsd.Password = "pass" 196 return upsd 197 }, 198 prepareMock: prepareMockConnErrOnAuthenticate, 199 wantCollected: nil, 200 wantCharts: 0, 201 wantConnConnect: true, 202 wantConnDisconnect: true, 203 wantConnAuthenticate: true, 204 }, 205 "err on upsList()": { 206 prepareUpsd: New, 207 prepareMock: prepareMockConnErrOnUpsUnits, 208 wantCollected: nil, 209 wantCharts: 0, 210 wantConnConnect: true, 211 wantConnDisconnect: true, 212 wantConnAuthenticate: false, 213 }, 214 "command err on upsList() (unknown ups)": { 215 prepareUpsd: New, 216 prepareMock: prepareMockConnCommandErrOnUpsUnits, 217 wantCollected: nil, 218 wantCharts: 0, 219 wantConnConnect: true, 220 wantConnDisconnect: false, 221 wantConnAuthenticate: false, 222 }, 223 } 224 225 for name, test := range tests { 226 t.Run(name, func(t *testing.T) { 227 upsd := test.prepareUpsd() 228 require.True(t, upsd.Init()) 229 230 mock := test.prepareMock() 231 upsd.newUpsdConn = func(Config) upsdConn { return mock } 232 233 mx := upsd.Collect() 234 235 assert.Equal(t, test.wantCollected, mx) 236 assert.Equalf(t, test.wantCharts, len(*upsd.Charts()), "number of charts") 237 if len(test.wantCollected) > 0 { 238 ensureCollectedHasAllChartsDims(t, upsd, mx) 239 } 240 assert.Equalf(t, test.wantConnConnect, mock.calledConnect, "calledConnect") 241 assert.Equalf(t, test.wantConnDisconnect, mock.calledDisconnect, "calledDisconnect") 242 assert.Equal(t, test.wantConnAuthenticate, mock.calledAuthenticate, "calledAuthenticate") 243 }) 244 } 245 } 246 247 func ensureCollectedHasAllChartsDims(t *testing.T, upsd *Upsd, mx map[string]int64) { 248 for _, chart := range *upsd.Charts() { 249 if chart.Obsolete { 250 continue 251 } 252 for _, dim := range chart.Dims { 253 _, ok := mx[dim.ID] 254 assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID) 255 } 256 for _, v := range chart.Vars { 257 _, ok := mx[v.ID] 258 assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID) 259 } 260 } 261 } 262 263 func prepareMockConnOK() *mockUpsdConn { 264 return &mockUpsdConn{} 265 } 266 267 func prepareMockConnErrOnConnect() *mockUpsdConn { 268 return &mockUpsdConn{errOnConnect: true} 269 } 270 271 func prepareMockConnErrOnAuthenticate() *mockUpsdConn { 272 return &mockUpsdConn{errOnAuthenticate: true} 273 } 274 275 func prepareMockConnErrOnUpsUnits() *mockUpsdConn { 276 return &mockUpsdConn{errOnUpsUnits: true} 277 } 278 279 func prepareMockConnCommandErrOnUpsUnits() *mockUpsdConn { 280 return &mockUpsdConn{commandErrOnUpsUnits: true} 281 } 282 283 type mockUpsdConn struct { 284 errOnConnect bool 285 errOnDisconnect bool 286 errOnAuthenticate bool 287 errOnUpsUnits bool 288 commandErrOnUpsUnits bool 289 290 calledConnect bool 291 calledDisconnect bool 292 calledAuthenticate bool 293 } 294 295 func (m *mockUpsdConn) connect() error { 296 m.calledConnect = true 297 if m.errOnConnect { 298 return errors.New("mock error on connect()") 299 } 300 return nil 301 } 302 303 func (m *mockUpsdConn) disconnect() error { 304 m.calledDisconnect = true 305 if m.errOnDisconnect { 306 return errors.New("mock error on disconnect()") 307 } 308 return nil 309 } 310 311 func (m *mockUpsdConn) authenticate(_, _ string) error { 312 m.calledAuthenticate = true 313 if m.errOnAuthenticate { 314 return errors.New("mock error on authenticate()") 315 } 316 return nil 317 } 318 319 func (m *mockUpsdConn) upsUnits() ([]upsUnit, error) { 320 if m.errOnUpsUnits { 321 return nil, errors.New("mock error on upsUnits()") 322 } 323 if m.commandErrOnUpsUnits { 324 return nil, fmt.Errorf("%w: mock command error on upsUnits()", errUpsdCommand) 325 } 326 327 upsUnits := []upsUnit{ 328 { 329 name: "pr3000", 330 vars: map[string]string{ 331 "battery.charge": "100", 332 "battery.charge.warning": "35", 333 "battery.mfr.date": "CPS", 334 "battery.runtime": "1108", 335 "battery.runtime.low": "300", 336 "battery.type": "PbAcid", 337 "battery.voltage": "59.9", 338 "battery.voltage.nominal": "48", 339 "device.mfr": "CPS", 340 "device.model": "PR3000ERT2U", 341 "device.serial": "P11MQ2000041", 342 "device.type": "ups", 343 "driver.name": "usbhid-ups", 344 "driver.parameter.pollfreq": "30", 345 "driver.parameter.pollinterval": "2", 346 "driver.parameter.port": "auto", 347 "driver.parameter.synchronous": "no", 348 "driver.version": "2.7.4", 349 "driver.version.data": "CyberPower HID 0.4", 350 "driver.version.internal": "0.41", 351 "input.voltage": "225.0", 352 "input.voltage.nominal": "230", 353 "output.voltage": "225.0", 354 "ups.beeper.status": "enabled", 355 "ups.delay.shutdown": "20", 356 "ups.delay.start": "30", 357 "ups.load": "28", 358 "ups.mfr": "CPS", 359 "ups.model": "PR3000ERT2U", 360 "ups.productid": "0601", 361 "ups.realpower.nominal": "3000", 362 "ups.serial": "P11MQ2000041", 363 "ups.status": "OL", 364 "ups.test.result": "No test initiated", 365 "ups.timer.shutdown": "0", 366 "ups.timer.start": "0", 367 "ups.vendorid": "0764", 368 }, 369 }, 370 { 371 name: "cp1500", 372 vars: map[string]string{ 373 "battery.charge": "100", 374 "battery.charge.low": "10", 375 "battery.charge.warning": "20", 376 "battery.mfr.date": "CPS", 377 "battery.runtime": "4890", 378 "battery.runtime.low": "300", 379 "battery.type": "PbAcid", 380 "battery.voltage": "24.0", 381 "battery.voltage.nominal": "24", 382 "device.mfr": "CPS", 383 "device.model": "CP1500EPFCLCD", 384 "device.serial": "CRMNO2000312", 385 "device.type": "ups", 386 "driver.name": "usbhid-ups", 387 "driver.parameter.bus": "001", 388 "driver.parameter.pollfreq": "30", 389 "driver.parameter.pollinterval": "2", 390 "driver.parameter.port": "auto", 391 "driver.parameter.product": "CP1500EPFCLCD", 392 "driver.parameter.productid": "0501", 393 "driver.parameter.serial": "CRMNO2000312", 394 "driver.parameter.synchronous": "no", 395 "driver.parameter.vendor": "CPS", 396 "driver.parameter.vendorid": "0764", 397 "driver.version": "2.7.4", 398 "driver.version.data": "CyberPower HID 0.4", 399 "driver.version.internal": "0.41", 400 "input.transfer.high": "260", 401 "input.transfer.low": "170", 402 "input.voltage": "227.0", 403 "input.voltage.nominal": "230", 404 "output.voltage": "260.0", 405 "ups.beeper.status": "enabled", 406 "ups.delay.shutdown": "20", 407 "ups.delay.start": "30", 408 "ups.load": "8", 409 "ups.mfr": "CPS", 410 "ups.model": "CP1500EPFCLCD", 411 "ups.productid": "0501", 412 "ups.realpower": "43", 413 "ups.realpower.nominal": "900", 414 "ups.serial": "CRMNO2000312", 415 "ups.status": "OL", 416 "ups.test.result": "No test initiated", 417 "ups.timer.shutdown": "-60", 418 "ups.timer.start": "-60", 419 "ups.vendorid": "0764", 420 }, 421 }, 422 } 423 424 return upsUnits, nil 425 }