Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_error_boundaries.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#include <algorithm>
7#include <thread>
8#include <chrono>
9#include <atomic>
12
13using namespace kcenon::monitoring;
14
15class ErrorBoundariesTest : public ::testing::Test {
16protected:
17 void SetUp() override {
18 call_count = 0;
20 }
21
22 void TearDown() override {
23 // Clean up registries
25 }
26
27 std::atomic<int> call_count{0};
28 std::atomic<int> success_after_attempts{0};
29
30 // Helper function that fails for the first N attempts
31 kcenon::common::Result<int> failing_operation() {
32 int current_call = ++call_count;
33 if (success_after_attempts > 0 && current_call <= success_after_attempts) {
34 return kcenon::common::make_error<int>(static_cast<int>(monitoring_error_code::operation_failed),
35 "Simulated failure on attempt " + std::to_string(current_call));
36 }
37 return kcenon::common::ok(42);
38 }
39
40 // Helper function that always fails
41 kcenon::common::Result<int> always_failing_operation() {
42 ++call_count;
43 return kcenon::common::make_error<int>(static_cast<int>(monitoring_error_code::operation_failed), "Always fails");
44 }
45
46 // Helper function that always succeeds
47 kcenon::common::Result<int> always_succeeding_operation() {
48 ++call_count;
49 return kcenon::common::ok(100);
50 }
51
52 // Throwing operation for exception handling
53 kcenon::common::Result<int> throwing_operation() {
54 ++call_count;
55 throw std::runtime_error("Simulated exception");
56 }
57};
58
59// Error Boundary Tests
60TEST_F(ErrorBoundariesTest, ErrorBoundaryNormalOperation) {
62 error_boundary<int> boundary("test_boundary", config);
63
64 auto result = boundary.execute([this]() { return always_succeeding_operation(); });
65
66 EXPECT_TRUE(result.is_ok());
67 EXPECT_EQ(result.value(), 100);
68 EXPECT_EQ(boundary.get_degradation_level(), degradation_level::normal);
69 EXPECT_EQ(call_count.load(), 1);
70}
71
72TEST_F(ErrorBoundariesTest, ErrorBoundaryFailFastPolicy) {
74 config.policy = error_boundary_policy::fail_fast;
75
76 error_boundary<int> boundary("test_boundary", config);
77
78 auto result = boundary.execute([this]() { return always_failing_operation(); });
79
80 EXPECT_TRUE(result.is_err());
81 EXPECT_EQ(result.error().code, static_cast<int>(monitoring_error_code::operation_failed));
82 EXPECT_EQ(boundary.get_degradation_level(), degradation_level::normal);
83}
84
85TEST_F(ErrorBoundariesTest, ErrorBoundaryIsolatePolicy) {
87 config.policy = error_boundary_policy::isolate;
88
89 error_boundary<int> boundary("test_boundary", config);
90
91 auto result = boundary.execute([this]() { return always_failing_operation(); });
92
93 EXPECT_TRUE(result.is_err());
94 EXPECT_EQ(result.error().code, static_cast<int>(monitoring_error_code::service_degraded));
95 EXPECT_EQ(boundary.get_degradation_level(), degradation_level::normal);
96}
97
98TEST_F(ErrorBoundariesTest, ErrorBoundaryDegradePolicy) {
100 config.policy = error_boundary_policy::degrade;
101 config.error_threshold = 2;
102
103 error_boundary<int> boundary("test_boundary", config);
104
105 // First failure
106 auto result1 = boundary.execute([this]() { return always_failing_operation(); });
107 EXPECT_TRUE(result1.is_err());
108
109 // Second failure should trigger degradation
110 auto result2 = boundary.execute([this]() { return always_failing_operation(); });
111 EXPECT_TRUE(result2.is_err());
112
113 // Check that degradation occurred
114 EXPECT_GT(boundary.get_degradation_level(), degradation_level::normal);
115}
116
117TEST_F(ErrorBoundariesTest, ErrorBoundaryWithFallback) {
119 config.policy = error_boundary_policy::fallback;
120
121 error_boundary<int> boundary("test_boundary", config);
122
123 auto fallback = [](const error_info&, degradation_level) {
124 return kcenon::common::ok(999);
125 };
126
127 auto result = boundary.execute([this]() { return always_failing_operation(); }, fallback);
128
129 EXPECT_TRUE(result.is_ok());
130 EXPECT_EQ(result.value(), 999);
131}
132
133TEST_F(ErrorBoundariesTest, ErrorBoundaryExceptionHandling) {
134 error_boundary<int> boundary("test_boundary");
135
136 auto result = boundary.execute([this]() { return throwing_operation(); });
137
138 EXPECT_TRUE(result.is_err());
139 EXPECT_EQ(result.error().code, static_cast<int>(monitoring_error_code::operation_failed));
140 EXPECT_EQ(call_count.load(), 1);
141}
142
143TEST_F(ErrorBoundariesTest, ErrorBoundaryMetrics) {
144 error_boundary<int> boundary("test_boundary");
145
146 // Execute some operations
147 boundary.execute([this]() { return always_succeeding_operation(); });
148 boundary.execute([this]() { return always_failing_operation(); });
149 boundary.execute([this]() { return always_succeeding_operation(); });
150
151 auto metrics = boundary.get_metrics();
152 EXPECT_EQ(metrics.total_operations.load(), 3);
153 EXPECT_EQ(metrics.successful_operations.load(), 2);
154 EXPECT_EQ(metrics.failed_operations.load(), 1);
155 EXPECT_NEAR(metrics.get_success_rate(), 2.0/3.0, 0.01);
156}
157
158TEST_F(ErrorBoundariesTest, ErrorBoundaryRecovery) {
160 config.policy = error_boundary_policy::degrade;
161 config.error_threshold = 1;
162 config.enable_automatic_recovery = true;
163 config.recovery_timeout = std::chrono::milliseconds(100);
164
165 error_boundary<int> boundary("test_boundary", config);
166
167 // Trigger degradation
168 boundary.execute([this]() { return always_failing_operation(); });
169 EXPECT_GT(boundary.get_degradation_level(), degradation_level::normal);
170
171 // Wait for recovery timeout
172 std::this_thread::sleep_for(std::chrono::milliseconds(150));
173
174 // Execute successful operation to trigger recovery
175 auto result = boundary.execute([this]() { return always_succeeding_operation(); });
176 EXPECT_TRUE(result.is_ok());
177
178 // Check if recovery occurred (might need multiple successful operations)
179 for (int i = 0; i < 5; ++i) {
180 boundary.execute([this]() { return always_succeeding_operation(); });
181 }
182
183 auto metrics = boundary.get_metrics();
184 EXPECT_GT(metrics.recovery_attempts.load(), 0);
185}
186
187// Fallback Strategy Tests
188TEST_F(ErrorBoundariesTest, DefaultValueStrategy) {
189 error_boundary<int> boundary("test_boundary");
190
191 auto strategy = std::make_shared<default_value_strategy<int>>(777);
192 boundary.set_fallback_strategy(strategy);
193
195 config.policy = error_boundary_policy::fallback;
196 error_boundary<int> fallback_boundary("fallback_test", config);
197 fallback_boundary.set_fallback_strategy(strategy);
198
199 auto result = fallback_boundary.execute([this]() { return always_failing_operation(); });
200
201 EXPECT_TRUE(result.is_ok());
202 EXPECT_EQ(result.value(), 777);
203}
204
205TEST_F(ErrorBoundariesTest, CachedValueStrategy) {
206 auto strategy = std::make_shared<cached_value_strategy<int>>(std::chrono::seconds(1));
207
208 // Update cache with a value
209 strategy->update_cache(555);
210
212 config.policy = error_boundary_policy::fallback;
213 error_boundary<int> boundary("cached_test", config);
214 boundary.set_fallback_strategy(strategy);
215
216 auto result = boundary.execute([this]() { return always_failing_operation(); });
217
218 EXPECT_TRUE(result.is_ok());
219 EXPECT_EQ(result.value(), 555);
220}
221
222TEST_F(ErrorBoundariesTest, AlternativeServiceStrategy) {
223 auto alternative_op = []() { return kcenon::common::ok(888); };
224 auto strategy = std::make_shared<alternative_service_strategy<int>>(alternative_op);
225
227 config.policy = error_boundary_policy::fallback;
228 error_boundary<int> boundary("alternative_test", config);
229 boundary.set_fallback_strategy(strategy);
230
231 auto result = boundary.execute([this]() { return always_failing_operation(); });
232
233 EXPECT_TRUE(result.is_ok());
234 EXPECT_EQ(result.value(), 888);
235}
236
237// Graceful Degradation Tests
238TEST_F(ErrorBoundariesTest, GracefulDegradationManagerBasic) {
239 auto manager = create_degradation_manager("test_manager");
240
241 auto config = create_service_config("test_service", service_priority::normal);
242 auto result = manager->register_service(config);
243
244 EXPECT_TRUE(result.is_ok());
245 EXPECT_EQ(manager->get_service_level("test_service"), degradation_level::normal);
246}
247
248TEST_F(ErrorBoundariesTest, GracefulDegradationServiceDegrade) {
249 auto manager = create_degradation_manager("test_manager");
250
251 auto config = create_service_config("test_service", service_priority::normal);
252 manager->register_service(config);
253
254 auto result = manager->degrade_service("test_service", degradation_level::limited, "Test degradation");
255
256 EXPECT_TRUE(result.is_ok());
257 EXPECT_EQ(manager->get_service_level("test_service"), degradation_level::limited);
258}
259
260TEST_F(ErrorBoundariesTest, GracefulDegradationPlanExecution) {
261 auto manager = create_degradation_manager("test_manager");
262
263 // Register services
264 manager->register_service(create_service_config("service1", service_priority::normal));
265 manager->register_service(create_service_config("service2", service_priority::optional));
266
267 // Create degradation plan
268 auto plan = create_degradation_plan("emergency_plan",
269 {"service1"},
270 {"service2"},
271 degradation_level::minimal);
272 manager->add_degradation_plan(plan);
273
274 // Execute plan
275 auto result = manager->execute_plan("emergency_plan", "Test emergency");
276
277 EXPECT_TRUE(result.is_ok());
278 EXPECT_EQ(manager->get_service_level("service1"), degradation_level::minimal);
279 EXPECT_EQ(manager->get_service_level("service2"), degradation_level::emergency);
280}
281
282TEST_F(ErrorBoundariesTest, GracefulDegradationServiceRecovery) {
283 auto manager = create_degradation_manager("test_manager");
284
285 auto config = create_service_config("test_service", service_priority::normal);
286 manager->register_service(config);
287
288 // Degrade service
289 manager->degrade_service("test_service", degradation_level::minimal, "Test degradation");
290 EXPECT_EQ(manager->get_service_level("test_service"), degradation_level::minimal);
291
292 // Recover service
293 auto result = manager->recover_service("test_service");
294
295 EXPECT_TRUE(result.is_ok());
296 EXPECT_EQ(manager->get_service_level("test_service"), degradation_level::normal);
297}
298
299TEST_F(ErrorBoundariesTest, GracefulDegradationRecoverAll) {
300 auto manager = create_degradation_manager("test_manager");
301
302 // Register and degrade multiple services
303 manager->register_service(create_service_config("service1", service_priority::normal));
304 manager->register_service(create_service_config("service2", service_priority::important));
305
306 manager->degrade_service("service1", degradation_level::limited, "Test");
307 manager->degrade_service("service2", degradation_level::minimal, "Test");
308
309 // Recover all
310 auto result = manager->recover_all_services();
311
312 EXPECT_TRUE(result.is_ok());
313 EXPECT_EQ(manager->get_service_level("service1"), degradation_level::normal);
314 EXPECT_EQ(manager->get_service_level("service2"), degradation_level::normal);
315}
316
317TEST_F(ErrorBoundariesTest, GracefulDegradationMetrics) {
318 auto manager = create_degradation_manager("test_manager");
319
320 manager->register_service(create_service_config("service1", service_priority::normal));
321
322 // Perform degradation and recovery operations
323 manager->degrade_service("service1", degradation_level::limited, "Test");
324 manager->recover_service("service1");
325
326 auto metrics = manager->get_metrics();
327 EXPECT_GT(metrics.total_degradations.load(), 0);
328 EXPECT_GT(metrics.successful_degradations.load(), 0);
329 EXPECT_GT(metrics.recovery_attempts.load(), 0);
330}
331
332TEST_F(ErrorBoundariesTest, DegradableServiceWrapper) {
333 auto manager = std::make_shared<graceful_degradation_manager>("test_manager");
334
335 auto config = create_service_config("wrapper_service", service_priority::normal);
336 manager->register_service(config);
337
338 auto normal_op = [this]() { return always_succeeding_operation(); };
339 auto degraded_op = [](degradation_level level) {
340 return kcenon::common::ok(static_cast<int>(level) * 100);
341 };
342
343 auto service = create_degradable_service<int>("wrapper_service", manager, normal_op, degraded_op);
344
345 // Test normal operation
346 auto result1 = service->execute();
347 EXPECT_TRUE(result1.is_ok());
348 EXPECT_EQ(result1.value(), 100);
349
350 // Degrade service and test degraded operation
351 manager->degrade_service("wrapper_service", degradation_level::limited, "Test");
352 auto result2 = service->execute();
353 EXPECT_TRUE(result2.is_ok());
354 EXPECT_EQ(result2.value(), static_cast<int>(degradation_level::limited) * 100);
355}
356
357// Error Boundary Registry Tests
358TEST_F(ErrorBoundariesTest, ErrorBoundaryRegistry) {
359 auto& registry = global_error_boundary_registry();
360
361 auto boundary = std::make_shared<error_boundary<int>>("registry_test");
362 registry.register_boundary<int>("test", boundary);
363
364 auto retrieved = registry.get_boundary<int>("test");
365 EXPECT_EQ(retrieved, boundary);
366
367 auto names = registry.get_all_names();
368 EXPECT_EQ(names.size(), 1);
369 EXPECT_EQ(names[0], "test");
370
371 registry.remove_boundary("test");
372 retrieved = registry.get_boundary<int>("test");
373 EXPECT_EQ(retrieved, nullptr);
374}
375
376// Configuration Validation Tests
377TEST_F(ErrorBoundariesTest, ErrorBoundaryConfigValidation) {
379
380 // Valid config
381 config.name = "test";
382 EXPECT_TRUE(config.validate());
383
384 // Invalid config - empty name
385 config.name = "";
386 EXPECT_FALSE(config.validate());
387
388 // Reset to valid
389 config.name = "test";
390 EXPECT_TRUE(config.validate());
391
392 // Invalid error threshold
393 config.error_threshold = 0;
394 EXPECT_FALSE(config.validate());
395}
396
397TEST_F(ErrorBoundariesTest, ServiceConfigValidation) {
398 service_config config;
399
400 // Valid config
401 config.name = "test_service";
402 EXPECT_TRUE(config.validate());
403
404 // Invalid config - empty name
405 config.name = "";
406 EXPECT_FALSE(config.validate());
407
408 // Reset and test error rate threshold
409 config.name = "test_service";
410 config.error_rate_threshold = -0.1; // Invalid
411 EXPECT_FALSE(config.validate());
412
413 config.error_rate_threshold = 1.1; // Invalid
414 EXPECT_FALSE(config.validate());
415
416 config.error_rate_threshold = 0.5; // Valid
417 EXPECT_TRUE(config.validate());
418}
419
420TEST_F(ErrorBoundariesTest, DegradationPlanValidation) {
421 degradation_plan plan;
422
423 // Valid plan
424 plan.name = "test_plan";
425 EXPECT_TRUE(plan.validate());
426
427 // Invalid plan - empty name
428 plan.name = "";
429 EXPECT_FALSE(plan.validate());
430}
431
432// Health Check Tests
433TEST_F(ErrorBoundariesTest, ErrorBoundaryHealthCheck) {
435 config.max_degradation = degradation_level::emergency; // Allow emergency degradation
436 error_boundary<int> boundary("health_test", config);
437
438 // Initially healthy
439 auto health = boundary.is_healthy();
440 EXPECT_TRUE(health.is_ok());
441 EXPECT_TRUE(health.value());
442 EXPECT_EQ(boundary.get_degradation_level(), degradation_level::normal);
443
444 // Force degradation to emergency
445 boundary.force_degradation(degradation_level::emergency);
446
447 // Verify degradation was applied
448 EXPECT_EQ(boundary.get_degradation_level(), degradation_level::emergency);
449
450 // Test that health check method works (even if logic needs refinement)
451 health = boundary.is_healthy();
452 EXPECT_TRUE(health.is_ok()); // Health check method returns a valid result
453 // Note: Health check logic may need refinement for emergency degradation
454}
455
456TEST_F(ErrorBoundariesTest, DegradationManagerHealthCheck) {
457 auto manager = create_degradation_manager("health_test");
458
459 // Register multiple services
460 manager->register_service(create_service_config("service1", service_priority::normal));
461 manager->register_service(create_service_config("service2", service_priority::normal));
462
463 // Initially healthy
464 auto health = manager->is_healthy();
465 EXPECT_TRUE(health.is_ok());
466 EXPECT_TRUE(health.value());
467
468 // Degrade more than 50% of services
469 manager->degrade_service("service1", degradation_level::minimal, "Test");
470 manager->degrade_service("service2", degradation_level::minimal, "Test");
471
472 // Should now be unhealthy
473 health = manager->is_healthy();
474 EXPECT_TRUE(health.is_ok());
475 EXPECT_FALSE(health.value());
476}
477
478// ============================================================================
479// Graceful Degradation Error Paths
480// ============================================================================
481
482TEST_F(ErrorBoundariesTest, UnregisterNonexistentService) {
483 auto manager = create_degradation_manager("test_manager");
484
485 auto result = manager->unregister_service("nonexistent");
486 EXPECT_TRUE(result.is_err());
487}
488
489TEST_F(ErrorBoundariesTest, DegradeNonexistentService) {
490 auto manager = create_degradation_manager("test_manager");
491
492 auto result = manager->degrade_service("nonexistent", degradation_level::limited, "test");
493 EXPECT_TRUE(result.is_err());
494
495 auto metrics = manager->get_metrics();
496 EXPECT_EQ(metrics.failed_degradations.load(), 1);
497}
498
499TEST_F(ErrorBoundariesTest, RecoverNonexistentService) {
500 auto manager = create_degradation_manager("test_manager");
501
502 auto result = manager->recover_service("nonexistent");
503 EXPECT_TRUE(result.is_err());
504}
505
506TEST_F(ErrorBoundariesTest, ExecuteNonexistentPlan) {
507 auto manager = create_degradation_manager("test_manager");
508
509 auto result = manager->execute_plan("nonexistent_plan", "reason");
510 EXPECT_TRUE(result.is_err());
511}
512
513TEST_F(ErrorBoundariesTest, AddDegradationPlanEmptyName) {
514 auto manager = create_degradation_manager("test_manager");
515
516 degradation_plan plan;
517 plan.name = "";
518 auto result = manager->add_degradation_plan(plan);
519 EXPECT_TRUE(result.is_err());
520}
521
522TEST_F(ErrorBoundariesTest, RegisterDuplicateService) {
523 auto manager = create_degradation_manager("test_manager");
524
525 auto config = create_service_config("dup_service", service_priority::normal);
526 auto result1 = manager->register_service(config);
527 EXPECT_TRUE(result1.is_ok());
528
529 auto result2 = manager->register_service(config);
530 EXPECT_TRUE(result2.is_err());
531}
532
533// ============================================================================
534// Accessor Coverage
535// ============================================================================
536
537TEST_F(ErrorBoundariesTest, DegradationManagerGetName) {
538 auto manager = create_degradation_manager("my_manager");
539 EXPECT_EQ(manager->get_name(), "my_manager");
540
542 EXPECT_EQ(default_mgr.get_name(), "default");
543}
544
545TEST_F(ErrorBoundariesTest, DegradationManagerGetServiceNames) {
546 auto manager = create_degradation_manager("test_manager");
547
548 // Empty initially
549 auto names = manager->get_service_names();
550 EXPECT_TRUE(names.empty());
551
552 // Register services
553 manager->register_service(create_service_config("alpha", service_priority::normal));
554 manager->register_service(create_service_config("beta", service_priority::important));
555 manager->register_service(create_service_config("gamma", service_priority::critical));
556
557 names = manager->get_service_names();
558 EXPECT_EQ(names.size(), 3);
559
560 // Verify all names present (order may vary due to unordered_map)
561 std::sort(names.begin(), names.end());
562 EXPECT_EQ(names[0], "alpha");
563 EXPECT_EQ(names[1], "beta");
564 EXPECT_EQ(names[2], "gamma");
565}
566
567// ============================================================================
568// Config Validation Boundary Values
569// ============================================================================
570
571TEST_F(ErrorBoundariesTest, ServiceConfigValidationBoundaryValues) {
572 service_config config;
573 config.name = "test_service";
574
575 // error_rate_threshold = 0.0 should be valid
576 config.error_rate_threshold = 0.0;
577 EXPECT_TRUE(config.validate());
578
579 // error_rate_threshold = 1.0 should be valid
580 config.error_rate_threshold = 1.0;
581 EXPECT_TRUE(config.validate());
582}
583
584// ============================================================================
585// degradable_service Edge Cases
586// ============================================================================
587
588TEST_F(ErrorBoundariesTest, DegradableServiceNullptrManager) {
589 auto normal_op = []() { return kcenon::common::ok(42); };
590 auto degraded_op = [](degradation_level) { return kcenon::common::ok(0); };
591
592 // Create with nullptr manager
593 degradable_service<int> service("null_manager_service", nullptr, normal_op, degraded_op);
594
595 // Should use normal_op directly when manager is nullptr
596 auto result = service.execute();
597 EXPECT_TRUE(result.is_ok());
598 EXPECT_EQ(result.value(), 42);
599 EXPECT_EQ(service.get_name(), "null_manager_service");
600}
std::atomic< int > call_count
kcenon::common::Result< int > always_succeeding_operation()
std::atomic< int > success_after_attempts
kcenon::common::Result< int > failing_operation()
kcenon::common::Result< int > throwing_operation()
kcenon::common::Result< int > always_failing_operation()
Error boundary implementation for resilient operations.
void force_degradation(degradation_level level)
Force degradation to a specific level.
auto execute(Func &&func) -> common::Result< T >
Execute a function within the error boundary.
degradation_level get_degradation_level() const
Get current degradation level.
error_boundary_metrics get_metrics() const
Get metrics.
void set_fallback_strategy(std::shared_ptr< fallback_strategy_interface< T > > strategy)
Set fallback strategy.
common::Result< bool > is_healthy() const
Check if the boundary is healthy.
const std::string & get_name() const
Get manager name.
Error boundary with degradation levels for fault isolation.
Service priority levels and graceful degradation strategies.
std::shared_ptr< degradable_service< T > > create_degradable_service(const std::string &name, std::shared_ptr< graceful_degradation_manager > manager, typename degradable_service< T >::normal_operation normal_op, typename degradable_service< T >::degraded_operation degraded_op)
Create a degradable service.
service_config create_service_config(const std::string &name, service_priority priority)
Create a service configuration.
degradation_plan create_degradation_plan(const std::string &name, const std::vector< std::string > &maintain, const std::vector< std::string > &disable, degradation_level level)
Create a degradation plan.
degradation_level
Degradation levels for error boundary.
std::shared_ptr< graceful_degradation_manager > create_degradation_manager(const std::string &name)
Create a graceful degradation manager.
error_boundary_registry & global_error_boundary_registry()
Get global error boundary registry.
Degradation plan for coordinated service degradation.
Error boundary configuration.
std::chrono::milliseconds recovery_timeout
Extended error information with context.
Service configuration for graceful degradation.
TEST_F(ErrorBoundariesTest, ErrorBoundaryNormalOperation)