Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_battery_collector.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#include <gtest/gtest.h>
7
8#include <thread>
9
10namespace kcenon {
11namespace monitoring {
12namespace {
13
14// Test fixture for battery_collector tests
15class BatteryCollectorTest : public ::testing::Test {
16 protected:
17 void SetUp() override {
18 collector_ = std::make_unique<battery_collector>();
19 std::unordered_map<std::string, std::string> config;
20 collector_->initialize(config);
21 }
22
23 std::unique_ptr<battery_collector> collector_;
24};
25
26// Test basic initialization
27TEST_F(BatteryCollectorTest, InitializesSuccessfully) {
28 EXPECT_NE(collector_, nullptr);
29 EXPECT_EQ(collector_->name(), "battery");
30}
31
32// Test metric types returned
33TEST_F(BatteryCollectorTest, ReturnsCorrectMetricTypes) {
34 auto types = collector_->get_metric_types();
35 EXPECT_FALSE(types.empty());
36
37 // Check for expected metric types
38 std::vector<std::string> expected = {
39 "battery_level_percent",
40 "battery_charging",
41 "battery_time_to_empty_seconds",
42 "battery_time_to_full_seconds",
43 "battery_health_percent",
44 "battery_voltage_volts",
45 "battery_power_watts",
46 "battery_cycle_count",
47 "battery_temperature_celsius"
48 };
49
50 for (const auto& expected_type : expected) {
51 EXPECT_NE(std::find(types.begin(), types.end(), expected_type), types.end())
52 << "Missing metric type: " << expected_type;
53 }
54}
55
56// Test configuration options
57TEST_F(BatteryCollectorTest, ConfigurationOptions) {
58 auto collector = std::make_unique<battery_collector>();
59 std::unordered_map<std::string, std::string> config;
60 config["collect_health"] = "false";
61 config["collect_thermal"] = "false";
62 EXPECT_TRUE(collector->initialize(config));
63
64 auto stats = collector->get_statistics();
65 EXPECT_DOUBLE_EQ(stats["collect_health"], 0.0);
66 EXPECT_DOUBLE_EQ(stats["collect_thermal"], 0.0);
67}
68
69// Test disable collector
70TEST_F(BatteryCollectorTest, CanBeDisabled) {
71 auto collector = std::make_unique<battery_collector>();
72 std::unordered_map<std::string, std::string> config;
73 config["enabled"] = "false";
74 collector->initialize(config);
75
76 auto metrics = collector->collect();
77 EXPECT_TRUE(metrics.empty());
78
79 auto stats = collector->get_statistics();
80 EXPECT_DOUBLE_EQ(stats["enabled"], 0.0);
81}
82
83// Test statistics
84TEST_F(BatteryCollectorTest, TracksStatistics) {
85 // Collect some metrics
86 collector_->collect();
87 collector_->collect();
88
89 auto stats = collector_->get_statistics();
90 EXPECT_GE(stats["collection_count"], 2.0);
91 EXPECT_GE(stats["collection_errors"], 0.0);
92}
93
94// Test collect returns metrics (graceful degradation when unavailable)
95TEST_F(BatteryCollectorTest, CollectDoesNotThrow) {
96 // Should not throw even if no battery is present
97 EXPECT_NO_THROW(collector_->collect());
98}
99
100// Test get_last_readings
101TEST_F(BatteryCollectorTest, GetLastReadings) {
102 collector_->collect();
103 auto readings = collector_->get_last_readings();
104
105 // If no battery, readings will be empty - that's OK
106 // If battery present, readings should have data
107 for (const auto& reading : readings) {
108 // Timestamp should be set
109 auto now = std::chrono::system_clock::now();
110 auto diff = std::chrono::duration_cast<std::chrono::seconds>(now - reading.timestamp);
111 EXPECT_LT(diff.count(), 10); // Within 10 seconds
112 }
113}
114
115// Test is_battery_available
116TEST_F(BatteryCollectorTest, BatteryAvailabilityCheck) {
117 // This should return true or false depending on hardware
118 // Either result is valid - we just want to ensure it doesn't crash
119 EXPECT_NO_THROW(collector_->is_battery_available());
120}
121
122// Test battery_info structure
123TEST(BatteryInfoTest, DefaultInitialization) {
124 battery_info info;
125 EXPECT_TRUE(info.id.empty());
126 EXPECT_TRUE(info.name.empty());
127 EXPECT_TRUE(info.path.empty());
128 EXPECT_TRUE(info.manufacturer.empty());
129 EXPECT_TRUE(info.model.empty());
130 EXPECT_TRUE(info.serial.empty());
131 EXPECT_TRUE(info.technology.empty());
132}
133
134// Test battery_reading structure
135TEST(BatteryReadingTest, DefaultInitialization) {
136 battery_reading reading;
137 EXPECT_DOUBLE_EQ(reading.level_percent, 0.0);
138 EXPECT_EQ(reading.status, battery_status::unknown);
139 EXPECT_FALSE(reading.is_charging);
140 EXPECT_FALSE(reading.ac_connected);
141 EXPECT_EQ(reading.time_to_empty_seconds, -1);
142 EXPECT_EQ(reading.time_to_full_seconds, -1);
143 EXPECT_DOUBLE_EQ(reading.design_capacity_wh, 0.0);
144 EXPECT_DOUBLE_EQ(reading.full_charge_capacity_wh, 0.0);
145 EXPECT_DOUBLE_EQ(reading.current_capacity_wh, 0.0);
146 EXPECT_DOUBLE_EQ(reading.health_percent, 0.0);
147 EXPECT_DOUBLE_EQ(reading.voltage_volts, 0.0);
148 EXPECT_DOUBLE_EQ(reading.current_amps, 0.0);
149 EXPECT_DOUBLE_EQ(reading.power_watts, 0.0);
150 EXPECT_DOUBLE_EQ(reading.temperature_celsius, 0.0);
151 EXPECT_FALSE(reading.temperature_available);
152 EXPECT_EQ(reading.cycle_count, -1);
153 EXPECT_FALSE(reading.battery_present);
154 EXPECT_FALSE(reading.metrics_available);
155}
156
157// Test battery_status enum conversion
158TEST(BatteryStatusTest, ToStringConversion) {
161 EXPECT_EQ(battery_status_to_string(battery_status::discharging), "discharging");
162 EXPECT_EQ(battery_status_to_string(battery_status::not_charging), "not_charging");
164}
165
166// Test battery_info_collector basic functionality
167TEST(BatteryInfoCollectorTest, BasicFunctionality) {
168 battery_info_collector collector;
169
170 // Test availability check
171 EXPECT_NO_THROW(collector.is_battery_available());
172
173 // Test enumeration
174 EXPECT_NO_THROW(collector.enumerate_batteries());
175
176 // Test read all
177 EXPECT_NO_THROW(collector.read_all_batteries());
178}
179
180// Test multiple collections are stable
181TEST_F(BatteryCollectorTest, MultipleCollectionsAreStable) {
182 for (int i = 0; i < 10; ++i) {
183 auto metrics = collector_->collect();
184 // Should not crash or throw
185 EXPECT_NO_THROW(collector_->get_statistics());
186 }
187
188 auto stats = collector_->get_statistics();
189 EXPECT_GE(stats["collection_count"], 10.0);
190}
191
192// Test that metrics have correct tags when collected
193TEST_F(BatteryCollectorTest, MetricsHaveCorrectTags) {
194 auto metrics = collector_->collect();
195
196 for (const auto& m : metrics) {
197 // All metrics should have the collector tag
198 auto it = m.tags.find("collector");
199 if (it != m.tags.end()) {
200 EXPECT_EQ(it->second, "battery_collector");
201 }
202 }
203}
204
205// Test is_available reflects actual state
206TEST_F(BatteryCollectorTest, IsAvailableReflectsState) {
207 // When enabled, availability depends on battery presence
208 EXPECT_NO_THROW(collector_->is_available());
209
210 // When disabled, collector should still report availability status
211 auto disabled_collector = std::make_unique<battery_collector>();
212 std::unordered_map<std::string, std::string> config;
213 config["enabled"] = "false";
214 disabled_collector->initialize(config);
215 EXPECT_NO_THROW(disabled_collector->is_available());
216}
217
218// Test that battery metrics have battery_id tag
219TEST_F(BatteryCollectorTest, MetricsHaveBatteryIdTag) {
220 auto metrics = collector_->collect();
221
222 // If we have metrics, they should have battery_id tag
223 for (const auto& m : metrics) {
224 auto it = m.tags.find("battery_id");
225 // Battery metrics should have battery_id tag
226 if (m.name.find("battery_") == 0) {
227 EXPECT_NE(it, m.tags.end()) << "Missing battery_id tag for: " << m.name;
228 }
229 }
230}
231
232// Test reading all batteries when battery is present
233TEST(BatteryInfoCollectorTest, ReadAllBatteriesWhenPresent) {
234 battery_info_collector collector;
235
236 if (collector.is_battery_available()) {
237 auto readings = collector.read_all_batteries();
238 EXPECT_FALSE(readings.empty());
239
240 for (const auto& reading : readings) {
241 EXPECT_TRUE(reading.battery_present);
242 EXPECT_TRUE(reading.metrics_available);
243 // Level should be in valid range
244 EXPECT_GE(reading.level_percent, 0.0);
245 EXPECT_LE(reading.level_percent, 100.0);
246 }
247 }
248}
249
250// Test battery level is in valid range when present
251TEST_F(BatteryCollectorTest, BatteryLevelInValidRange) {
252 auto readings = collector_->get_last_readings();
253 collector_->collect();
254 readings = collector_->get_last_readings();
255
256 for (const auto& reading : readings) {
257 if (reading.metrics_available) {
258 EXPECT_GE(reading.level_percent, 0.0);
259 EXPECT_LE(reading.level_percent, 100.0);
260 }
261 }
262}
263
264// Test health percentage is valid when available
265TEST_F(BatteryCollectorTest, HealthPercentageIsValid) {
266 collector_->collect();
267 auto readings = collector_->get_last_readings();
268
269 for (const auto& reading : readings) {
270 if (reading.metrics_available && reading.health_percent > 0.0) {
271 // Health should be between 0 and ~150% (batteries can sometimes exceed design capacity)
272 EXPECT_GE(reading.health_percent, 0.0);
273 EXPECT_LE(reading.health_percent, 150.0);
274 }
275 }
276}
277
278// Test voltage is positive when available
279TEST_F(BatteryCollectorTest, VoltageIsPositive) {
280 collector_->collect();
281 auto readings = collector_->get_last_readings();
282
283 for (const auto& reading : readings) {
284 if (reading.metrics_available && reading.voltage_volts > 0.0) {
285 // Voltage should be reasonable (0-50V covers most battery types)
286 EXPECT_GT(reading.voltage_volts, 0.0);
287 EXPECT_LT(reading.voltage_volts, 50.0);
288 }
289 }
290}
291
292// Test status consistency
293TEST_F(BatteryCollectorTest, StatusConsistency) {
294 collector_->collect();
295 auto readings = collector_->get_last_readings();
296
297 for (const auto& reading : readings) {
298 if (reading.metrics_available) {
299 // If is_charging is true, status should be charging
300 if (reading.is_charging) {
301 EXPECT_EQ(reading.status, battery_status::charging);
302 }
303 // If status is full, level should be high
304 if (reading.status == battery_status::full) {
305 EXPECT_GE(reading.level_percent, 90.0);
306 }
307 }
308 }
309}
310
311// Test time estimates are reasonable
312TEST_F(BatteryCollectorTest, TimeEstimatesAreReasonable) {
313 collector_->collect();
314 auto readings = collector_->get_last_readings();
315
316 for (const auto& reading : readings) {
317 if (reading.metrics_available) {
318 // Time to empty should be reasonable if present (max 72 hours)
319 if (reading.time_to_empty_seconds > 0) {
320 EXPECT_LT(reading.time_to_empty_seconds, 72 * 3600);
321 }
322 // Time to full should be reasonable if present (max 24 hours)
323 if (reading.time_to_full_seconds > 0) {
324 EXPECT_LT(reading.time_to_full_seconds, 24 * 3600);
325 }
326 }
327 }
328}
329
330// Test cycle count is non-negative when available
331TEST_F(BatteryCollectorTest, CycleCountIsNonNegative) {
332 collector_->collect();
333 auto readings = collector_->get_last_readings();
334
335 for (const auto& reading : readings) {
336 if (reading.metrics_available && reading.cycle_count >= 0) {
337 // Cycle count should be reasonable (max 10000)
338 EXPECT_LT(reading.cycle_count, 10000);
339 }
340 }
341}
342
343// Test temperature is reasonable when available
344TEST_F(BatteryCollectorTest, TemperatureIsReasonable) {
345 collector_->collect();
346 auto readings = collector_->get_last_readings();
347
348 for (const auto& reading : readings) {
349 if (reading.metrics_available && reading.temperature_available) {
350 // Temperature should be reasonable (-40 to 100 Celsius)
351 EXPECT_GT(reading.temperature_celsius, -40.0);
352 EXPECT_LT(reading.temperature_celsius, 100.0);
353 }
354 }
355}
356
357} // namespace
358} // namespace monitoring
359} // namespace kcenon
Battery status monitoring collector.
@ charging
Battery is charging.
@ not_charging
Battery is not charging (plugged in but not charging)
@ discharging
Battery is discharging.
@ full
Battery is fully charged.
@ info
Informational, no action required.
std::string battery_status_to_string(battery_status status)
Convert battery_status to string representation.
TEST(AdapterFunctionalityTest, WorksWithoutLogger)
Test Scenario 1: Adapter with NULL logger.
TEST_F(AdaptiveMonitoringTest, AdaptiveConfigDefaults)
std::unique_ptr< battery_collector > collector_