Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_metric_factory.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>
6
7#include <algorithm>
8#include <chrono>
9#include <unordered_set>
10
14
15namespace kcenon::monitoring {
16namespace {
17
18// Test fixture for metric_factory tests
19class MetricFactoryTest : public ::testing::Test {
20 protected:
21 void SetUp() override {
22 // Clear and re-register collectors for each test
25 }
26
27 void TearDown() override { metric_factory::instance().clear(); }
28};
29
30// Test singleton instance
31TEST_F(MetricFactoryTest, SingletonInstance) {
32 auto& instance1 = metric_factory::instance();
33 auto& instance2 = metric_factory::instance();
34 EXPECT_EQ(&instance1, &instance2);
35}
36
37// Test builtin collectors registration
38TEST_F(MetricFactoryTest, BuiltinCollectorsRegistered) {
39 auto& factory = metric_factory::instance();
40
41 auto names = get_builtin_collector_names();
42 for (const auto& name : names) {
43 EXPECT_TRUE(factory.is_registered(name)) << "Collector not registered: " << name;
44 }
45}
46
47// Test create system_resource_collector
48TEST_F(MetricFactoryTest, CreateSystemResourceCollector) {
49 auto& factory = metric_factory::instance();
50
51 auto result = factory.create("system_resource_collector", {});
52 EXPECT_TRUE(result) << result.error_message;
53 EXPECT_NE(result.collector, nullptr);
54 EXPECT_EQ(result.collector->get_name(), "system_resource_collector");
55}
56
57// Test create vm_collector
58TEST_F(MetricFactoryTest, CreateVmCollector) {
59 auto& factory = metric_factory::instance();
60
61 auto result = factory.create("vm_collector", {});
62 EXPECT_TRUE(result) << result.error_message;
63 EXPECT_NE(result.collector, nullptr);
64 EXPECT_EQ(result.collector->get_name(), "vm_collector");
65}
66
67// Test create with configuration
68TEST_F(MetricFactoryTest, CreateWithConfiguration) {
69 auto& factory = metric_factory::instance();
70
71 config_map config = {{"enabled", "true"}};
72 auto result = factory.create("uptime_collector", config);
73 EXPECT_TRUE(result) << result.error_message;
74 EXPECT_TRUE(result.collector->is_healthy());
75}
76
77// Test create unknown collector
78TEST_F(MetricFactoryTest, CreateUnknownCollectorFails) {
79 auto& factory = metric_factory::instance();
80
81 auto result = factory.create("nonexistent_collector", {});
82 EXPECT_FALSE(result);
83 EXPECT_EQ(result.collector, nullptr);
84 EXPECT_FALSE(result.error_message.empty());
85}
86
87// Test create_or_null
88TEST_F(MetricFactoryTest, CreateOrNull) {
89 auto& factory = metric_factory::instance();
90
91 auto collector = factory.create_or_null("system_resource_collector", {});
92 EXPECT_NE(collector, nullptr);
93
94 auto null_collector = factory.create_or_null("nonexistent", {});
95 EXPECT_EQ(null_collector, nullptr);
96}
97
98// Test get_registered_collectors
99TEST_F(MetricFactoryTest, GetRegisteredCollectors) {
100 auto& factory = metric_factory::instance();
101
102 auto registered = factory.get_registered_collectors();
103 EXPECT_FALSE(registered.empty());
104
105 // Check that all builtin collectors are in the list
106 auto builtin = get_builtin_collector_names();
107 for (const auto& name : builtin) {
108 bool found = std::find(registered.begin(), registered.end(), name) != registered.end();
109 EXPECT_TRUE(found) << "Builtin collector not in registered list: " << name;
110 }
111}
112
113// Test unregister_collector
114TEST_F(MetricFactoryTest, UnregisterCollector) {
115 auto& factory = metric_factory::instance();
116
117 EXPECT_TRUE(factory.is_registered("vm_collector"));
118 EXPECT_TRUE(factory.unregister_collector("vm_collector"));
119 EXPECT_FALSE(factory.is_registered("vm_collector"));
120
121 // Unregistering again should fail
122 EXPECT_FALSE(factory.unregister_collector("vm_collector"));
123}
124
125// Test custom collector registration
126class MockCollector : public collector_interface {
127 public:
128 bool initialize(const config_map& /* config */) override { return true; }
129 [[nodiscard]] std::string get_name() const override { return "mock_collector"; }
130 [[nodiscard]] bool is_healthy() const override { return true; }
131 [[nodiscard]] std::vector<std::string> get_metric_types() const override { return {"mock.metric"}; }
132};
133
134TEST_F(MetricFactoryTest, RegisterCustomCollector) {
135 auto& factory = metric_factory::instance();
136
137 bool registered = factory.register_collector("mock_collector", []() {
138 return std::make_unique<MockCollector>();
139 });
140 EXPECT_TRUE(registered);
141 EXPECT_TRUE(factory.is_registered("mock_collector"));
142
143 auto result = factory.create("mock_collector", {});
144 EXPECT_TRUE(result);
145 EXPECT_EQ(result.collector->get_name(), "mock_collector");
146}
147
148// Test duplicate registration fails
149TEST_F(MetricFactoryTest, DuplicateRegistrationFails) {
150 auto& factory = metric_factory::instance();
151
152 EXPECT_TRUE(factory.is_registered("vm_collector"));
153 bool registered = factory.register_collector("vm_collector", []() {
154 return std::make_unique<MockCollector>();
155 });
156 EXPECT_FALSE(registered);
157}
158
159// Test create_multiple
160TEST_F(MetricFactoryTest, CreateMultiple) {
161 auto& factory = metric_factory::instance();
162
163 std::unordered_map<std::string, config_map> configs = {
164 {"system_resource_collector", {}},
165 {"vm_collector", {}},
166 {"uptime_collector", {{"enabled", "true"}}}};
167
168 auto collectors = factory.create_multiple(configs);
169 EXPECT_EQ(collectors.size(), 3);
170}
171
172// Test fixture for config_parser tests
173class ConfigParserTest : public ::testing::Test {};
174
175// Test boolean parsing
176TEST_F(ConfigParserTest, ParseBool) {
177 config_map config = {{"enabled", "true"},
178 {"disabled", "false"},
179 {"one", "1"},
180 {"zero", "0"},
181 {"yes", "yes"},
182 {"no", "no"},
183 {"on", "on"},
184 {"off", "off"},
185 {"TRUE", "TRUE"},
186 {"FALSE", "FALSE"}};
187
188 EXPECT_TRUE(config_parser::get<bool>(config, "enabled", false));
189 EXPECT_FALSE(config_parser::get<bool>(config, "disabled", true));
190 EXPECT_TRUE(config_parser::get<bool>(config, "one", false));
191 EXPECT_FALSE(config_parser::get<bool>(config, "zero", true));
192 EXPECT_TRUE(config_parser::get<bool>(config, "yes", false));
193 EXPECT_FALSE(config_parser::get<bool>(config, "no", true));
194 EXPECT_TRUE(config_parser::get<bool>(config, "on", false));
195 EXPECT_FALSE(config_parser::get<bool>(config, "off", true));
196 EXPECT_TRUE(config_parser::get<bool>(config, "TRUE", false));
197 EXPECT_FALSE(config_parser::get<bool>(config, "FALSE", true));
198}
199
200// Test integer parsing
201TEST_F(ConfigParserTest, ParseInt) {
202 config_map config = {{"positive", "42"},
203 {"negative", "-10"},
204 {"zero", "0"},
205 {"large", "1000000"}};
206
207 EXPECT_EQ(config_parser::get<int>(config, "positive", 0), 42);
208 EXPECT_EQ(config_parser::get<int>(config, "negative", 0), -10);
209 EXPECT_EQ(config_parser::get<int>(config, "zero", 1), 0);
210 EXPECT_EQ(config_parser::get<int>(config, "large", 0), 1000000);
211}
212
213// Test size_t parsing
214TEST_F(ConfigParserTest, ParseSizeT) {
215 config_map config = {{"samples", "1000"}, {"max", "9999999999"}};
216
217 EXPECT_EQ(config_parser::get<size_t>(config, "samples", 0), 1000u);
218 EXPECT_EQ(config_parser::get<size_t>(config, "max", 0), 9999999999u);
219}
220
221// Test double parsing
222TEST_F(ConfigParserTest, ParseDouble) {
223 config_map config = {{"threshold", "0.75"}, {"negative", "-1.5"}, {"integer", "10"}};
224
225 EXPECT_DOUBLE_EQ(config_parser::get<double>(config, "threshold", 0.0), 0.75);
226 EXPECT_DOUBLE_EQ(config_parser::get<double>(config, "negative", 0.0), -1.5);
227 EXPECT_DOUBLE_EQ(config_parser::get<double>(config, "integer", 0.0), 10.0);
228}
229
230// Test string parsing
231TEST_F(ConfigParserTest, ParseString) {
232 config_map config = {{"name", "test_collector"}, {"empty", ""}};
233
234 EXPECT_EQ(config_parser::get<std::string>(config, "name", ""), "test_collector");
235 EXPECT_EQ(config_parser::get<std::string>(config, "empty", "default"), "");
236}
237
238// Test default value for missing key
239TEST_F(ConfigParserTest, DefaultValueForMissingKey) {
240 config_map config = {};
241
242 EXPECT_TRUE(config_parser::get<bool>(config, "missing", true));
243 EXPECT_EQ(config_parser::get<int>(config, "missing", 42), 42);
244 EXPECT_EQ(config_parser::get<std::string>(config, "missing", "default"), "default");
245}
246
247// Test default value for invalid parsing
248TEST_F(ConfigParserTest, DefaultValueForInvalidParsing) {
249 config_map config = {{"invalid_int", "not_a_number"}, {"invalid_double", "abc"}};
250
251 EXPECT_EQ(config_parser::get<int>(config, "invalid_int", 100), 100);
252 EXPECT_DOUBLE_EQ(config_parser::get<double>(config, "invalid_double", 1.5), 1.5);
253}
254
255// Test has_key
256TEST_F(ConfigParserTest, HasKey) {
257 config_map config = {{"exists", "value"}};
258
259 EXPECT_TRUE(config_parser::has_key(config, "exists"));
260 EXPECT_FALSE(config_parser::has_key(config, "missing"));
261}
262
263// Test get_optional
264TEST_F(ConfigParserTest, GetOptional) {
265 config_map config = {{"exists", "42"}};
266
267 auto value = config_parser::get_optional<int>(config, "exists");
268 EXPECT_TRUE(value.has_value());
269 EXPECT_EQ(*value, 42);
270
271 auto missing = config_parser::get_optional<int>(config, "missing");
272 EXPECT_FALSE(missing.has_value());
273}
274
275// Test get_clamped
276TEST_F(ConfigParserTest, GetClamped) {
277 config_map config = {{"low", "5"}, {"high", "150"}, {"normal", "50"}};
278
279 EXPECT_EQ(config_parser::get_clamped<int>(config, "low", 50, 10, 100), 10);
280 EXPECT_EQ(config_parser::get_clamped<int>(config, "high", 50, 10, 100), 100);
281 EXPECT_EQ(config_parser::get_clamped<int>(config, "normal", 0, 10, 100), 50);
282}
283
284// Test get_enum
285TEST_F(ConfigParserTest, GetEnum) {
286 config_map config = {{"level", "debug"}, {"invalid_level", "unknown"}};
287
288 std::unordered_set<std::string> allowed = {"debug", "info", "warning", "error"};
289
290 EXPECT_EQ(config_parser::get_enum<std::string>(config, "level", "info", allowed), "debug");
291 EXPECT_EQ(config_parser::get_enum<std::string>(config, "invalid_level", "info", allowed), "info");
292 EXPECT_EQ(config_parser::get_enum<std::string>(config, "missing", "info", allowed), "info");
293}
294
295// Test get_enum with integer
296TEST_F(ConfigParserTest, GetEnumInteger) {
297 config_map config = {{"priority", "1"}, {"invalid_priority", "5"}};
298
299 std::unordered_set<int> allowed = {0, 1, 2, 3};
300
301 EXPECT_EQ(config_parser::get_enum<int>(config, "priority", 0, allowed), 1);
302 EXPECT_EQ(config_parser::get_enum<int>(config, "invalid_priority", 0, allowed), 0);
303}
304
305// Test get_matching (regex validation)
306TEST_F(ConfigParserTest, GetMatching) {
307 config_map config = {
308 {"valid_email", "test@example.com"},
309 {"invalid_email", "not-an-email"},
310 {"ipv4", "192.168.1.1"}
311 };
312
313 // Simple email pattern
314 std::string email_pattern = R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})";
315 EXPECT_EQ(config_parser::get_matching(config, "valid_email", "", email_pattern), "test@example.com");
316 EXPECT_EQ(config_parser::get_matching(config, "invalid_email", "default@test.com", email_pattern), "default@test.com");
317
318 // IPv4 pattern
319 std::string ipv4_pattern = R"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})";
320 EXPECT_EQ(config_parser::get_matching(config, "ipv4", "0.0.0.0", ipv4_pattern), "192.168.1.1");
321}
322
323// Test get_validated with custom validator
324TEST_F(ConfigParserTest, GetValidated) {
325 config_map config = {{"port", "8080"}, {"invalid_port", "70000"}};
326
327 auto port_validator = [](const int& value) { return value > 0 && value < 65536; };
328
329 EXPECT_EQ(config_parser::get_validated<int>(config, "port", 80, port_validator), 8080);
330 EXPECT_EQ(config_parser::get_validated<int>(config, "invalid_port", 80, port_validator), 80);
331 EXPECT_EQ(config_parser::get_validated<int>(config, "missing", 80, port_validator), 80);
332}
333
334// Test get_validated with size validation
335TEST_F(ConfigParserTest, GetValidatedSizeConstraint) {
336 config_map config = {{"buffer_size", "1024"}, {"too_small", "10"}};
337
338 auto min_size_validator = [](const size_t& value) { return value >= 100; };
339
340 EXPECT_EQ(config_parser::get_validated<size_t>(config, "buffer_size", 512, min_size_validator), 1024u);
341 EXPECT_EQ(config_parser::get_validated<size_t>(config, "too_small", 512, min_size_validator), 512u);
342}
343
344// Test get_duration with various suffixes
345TEST_F(ConfigParserTest, GetDurationMilliseconds) {
346 config_map config = {
347 {"plain", "1000"},
348 {"ms", "500ms"},
349 {"seconds", "2s"},
350 {"minutes", "1m"},
351 {"hours", "1h"}
352 };
353
354 using ms = std::chrono::milliseconds;
355
356 EXPECT_EQ(config_parser::get_duration<ms>(config, "plain", ms(0)).count(), 1000);
357 EXPECT_EQ(config_parser::get_duration<ms>(config, "ms", ms(0)).count(), 500);
358 EXPECT_EQ(config_parser::get_duration<ms>(config, "seconds", ms(0)).count(), 2000);
359 EXPECT_EQ(config_parser::get_duration<ms>(config, "minutes", ms(0)).count(), 60000);
360 EXPECT_EQ(config_parser::get_duration<ms>(config, "hours", ms(0)).count(), 3600000);
361}
362
363// Test get_duration with seconds as target type
364TEST_F(ConfigParserTest, GetDurationSeconds) {
365 config_map config = {
366 {"ms", "5000ms"},
367 {"sec", "30sec"},
368 {"min", "2min"}
369 };
370
371 using sec = std::chrono::seconds;
372
373 EXPECT_EQ(config_parser::get_duration<sec>(config, "ms", sec(0)).count(), 5);
374 EXPECT_EQ(config_parser::get_duration<sec>(config, "sec", sec(0)).count(), 30);
375 EXPECT_EQ(config_parser::get_duration<sec>(config, "min", sec(0)).count(), 120);
376}
377
378// Test get_duration with default value
379TEST_F(ConfigParserTest, GetDurationDefault) {
380 config_map config = {{"invalid", "not_a_duration"}};
381
382 using ms = std::chrono::milliseconds;
383
384 EXPECT_EQ(config_parser::get_duration<ms>(config, "missing", ms(100)).count(), 100);
385 EXPECT_EQ(config_parser::get_duration<ms>(config, "invalid", ms(100)).count(), 100);
386}
387
388// Test get_list with integers
389TEST_F(ConfigParserTest, GetListInt) {
390 config_map config = {
391 {"ports", "80, 443, 8080"},
392 {"single", "9000"},
393 {"empty", ""}
394 };
395
396 auto ports = config_parser::get_list<int>(config, "ports", {});
397 EXPECT_EQ(ports.size(), 3u);
398 EXPECT_EQ(ports[0], 80);
399 EXPECT_EQ(ports[1], 443);
400 EXPECT_EQ(ports[2], 8080);
401
402 auto single = config_parser::get_list<int>(config, "single", {});
403 EXPECT_EQ(single.size(), 1u);
404 EXPECT_EQ(single[0], 9000);
405
406 auto empty = config_parser::get_list<int>(config, "empty", {100});
407 EXPECT_EQ(empty.size(), 1u); // Uses default
408 EXPECT_EQ(empty[0], 100);
409}
410
411// Test get_list with strings
412TEST_F(ConfigParserTest, GetListString) {
413 config_map config = {{"tags", "cpu, memory, disk"}};
414
415 auto tags = config_parser::get_list<std::string>(config, "tags", {});
416 EXPECT_EQ(tags.size(), 3u);
417 EXPECT_EQ(tags[0], "cpu");
418 EXPECT_EQ(tags[1], "memory");
419 EXPECT_EQ(tags[2], "disk");
420}
421
422// Test get_list with default values
423TEST_F(ConfigParserTest, GetListDefault) {
424 config_map config = {};
425
426 std::vector<int> defaults = {1, 2, 3};
427 auto result = config_parser::get_list<int>(config, "missing", defaults);
428 EXPECT_EQ(result.size(), 3u);
429 EXPECT_EQ(result[0], 1);
430 EXPECT_EQ(result[1], 2);
431 EXPECT_EQ(result[2], 3);
432}
433
434} // namespace
435} // namespace kcenon::monitoring
Registration of built-in metric collectors with the registry.
static T get_validated(const config_map &config, const std::string &key, const T &default_value, std::function< bool(const T &)> validator)
Get a configuration value with custom validation.
static Duration get_duration(const config_map &config, const std::string &key, const Duration &default_value)
Get a duration value from configuration.
static bool has_key(const config_map &config, const std::string &key)
Check if a configuration key exists.
static std::optional< T > get_optional(const config_map &config, const std::string &key)
Get a configuration value as optional.
static std::vector< T > get_list(const config_map &config, const std::string &key, const std::vector< T > &default_values)
Get a list of values from a comma-separated string.
static T get_enum(const config_map &config, const std::string &key, const T &default_value, const std::unordered_set< T > &allowed_values)
Get a configuration value from a set of allowed values.
static T get(const config_map &config, const std::string &key, const T &default_value)
Get a configuration value with type conversion.
static std::string get_matching(const config_map &config, const std::string &key, const std::string &default_value, const std::string &pattern)
Get a string configuration value matching a regex pattern.
static T get_clamped(const config_map &config, const std::string &key, const T &default_value, const T &min_value, const T &max_value)
Get a configuration value with validation.
static metric_factory & instance()
Get the singleton instance.
void clear()
Clear all registered collectors.
Unified configuration parsing utility.
Unified factory for metric collector instantiation.
std::vector< std::string > get_builtin_collector_names()
Get list of all built-in collector names.
std::unordered_map< std::string, std::string > config_map
Type alias for configuration map.
bool register_builtin_collectors()
Register all built-in collectors with the collector_registry.
TEST_F(AdaptiveMonitoringTest, AdaptiveConfigDefaults)