Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_thread_context.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5
11#include <gtest/gtest.h>
13#include <thread>
14#include <vector>
15#include <set>
16#include <future>
17
18using namespace kcenon::monitoring;
19
23class ThreadContextTest : public ::testing::Test {
24protected:
25 void SetUp() override {
26 // Ensure clean state
28 }
29
30 void TearDown() override {
31 // Clean up after each test
33 }
34};
35
39TEST_F(ThreadContextTest, ContextMetadataBasicOperations) {
40 context_metadata metadata("req-123");
41
42 // Check initial state
43 EXPECT_EQ(metadata.request_id, "req-123");
44 EXPECT_TRUE(metadata.correlation_id.empty());
45 EXPECT_TRUE(metadata.user_id.empty());
46 EXPECT_FALSE(metadata.empty());
47
48 // Set fields
49 metadata.correlation_id = "corr-456";
50 metadata.user_id = "user-789";
51
52 // Add custom tags using set_tag method
53 metadata.set_tag("environment", "production");
54 metadata.set_tag("version", "1.2.3");
55
56 // Verify tags using get_tag method (returns string, not optional)
57 auto env = metadata.get_tag("environment");
58 EXPECT_EQ(env, "production");
59
60 auto ver = metadata.get_tag("version");
61 EXPECT_EQ(ver, "1.2.3");
62
63 auto missing = metadata.get_tag("nonexistent");
64 EXPECT_EQ(missing, ""); // Returns empty string for missing tags
65
66 // Verify tags map directly
67 EXPECT_TRUE(metadata.tags.find("environment") != metadata.tags.end());
68 EXPECT_TRUE(metadata.tags.find("version") != metadata.tags.end());
69
70 // Verify empty check
71 EXPECT_FALSE(metadata.empty()); // Should not be empty with data
72}
73
77TEST_F(ThreadContextTest, DISABLED_ContextMetadataMerge) {
78 // Disabled until merge functionality is implemented
79 context_metadata metadata1("req-1");
80 metadata1.user_id = "user-1";
81 metadata1.set_tag("tag1", "value1");
82
83 context_metadata metadata2("req-2");
84 metadata2.correlation_id = "corr-2";
85 metadata2.user_id = "user-2";
86 metadata2.set_tag("tag2", "value2");
87
88 // Basic copy test instead of merge
89 context_metadata copy = metadata1;
90 EXPECT_EQ(copy.request_id, metadata1.request_id);
91 EXPECT_EQ(copy.user_id, metadata1.user_id);
92
93 // Verify tags are copied
94 EXPECT_EQ(copy.get_tag("tag1"), "value1");
95}
96
100TEST_F(ThreadContextTest, ThreadContextBasicOperations) {
101 // Initially no context
102 EXPECT_FALSE(thread_context::has_context());
103 EXPECT_EQ(thread_context::current(), nullptr);
104
105 // Create context
106 auto& ctx = thread_context::create("test-request");
107 EXPECT_TRUE(thread_context::has_context());
108 EXPECT_NE(thread_context::current(), nullptr);
109 EXPECT_EQ(ctx.request_id, "test-request");
110
111 // Modify current context
112 auto* current = thread_context::current();
113 ASSERT_NE(current, nullptr);
114 current->user_id = "test-user";
115 current->add_tag("test", "value");
116
117 // Verify modifications
118 EXPECT_EQ(thread_context::current()->user_id, "test-user");
119 auto tag = thread_context::current()->get_tag("test");
120 ASSERT_TRUE(tag.has_value());
121 EXPECT_EQ(tag.value(), "value");
122
123 // Clear context
125 EXPECT_FALSE(thread_context::has_context());
126 EXPECT_EQ(thread_context::current(), nullptr);
127}
128
132TEST_F(ThreadContextTest, RequestIdGeneration) {
133 // Generate multiple IDs
134 std::set<std::string> ids;
135 for (int i = 0; i < 100; ++i) {
137 }
138
139 // All should be unique
140 EXPECT_EQ(ids.size(), 100);
141
142 // Create context without request ID
143 auto& ctx = thread_context::create();
144 EXPECT_FALSE(ctx.request_id.empty());
145
146 // Generated ID should be unique
147 auto generated_id = ctx.request_id;
148 EXPECT_EQ(ids.find(generated_id), ids.end());
149}
150
154TEST_F(ThreadContextTest, CorrelationIdGeneration) {
157
158 EXPECT_FALSE(corr_id1.empty());
159 EXPECT_FALSE(corr_id2.empty());
160 EXPECT_NE(corr_id1, corr_id2);
161
162 // Should have "corr-" prefix
163 EXPECT_EQ(corr_id1.substr(0, 5), "corr-");
164 EXPECT_EQ(corr_id2.substr(0, 5), "corr-");
165}
166
170TEST_F(ThreadContextTest, ContextScope) {
171 // No initial context
172 EXPECT_FALSE(thread_context::has_context());
173
174 {
175 // Create scope with new context
176 context_scope scope("scoped-request");
177
178 EXPECT_TRUE(thread_context::has_context());
179 EXPECT_EQ(thread_context::current()->request_id, "scoped-request");
180
181 // Modify context within scope
182 thread_context::current()->user_id = "scoped-user";
183 }
184
185 // Context should be cleared after scope
186 EXPECT_FALSE(thread_context::has_context());
187}
188
192TEST_F(ThreadContextTest, ContextScopeWithPreservation) {
193 // Set initial context
194 thread_context::create("original-request");
195 thread_context::current()->user_id = "original-user";
196
197 {
198 // Create scope that preserves previous context
199 auto new_metadata = std::make_unique<context_metadata>("scoped-request");
200 new_metadata->user_id = "scoped-user";
201 context_scope scope(std::move(new_metadata), true);
202
203 // Should have new context
204 EXPECT_EQ(thread_context::current()->request_id, "scoped-request");
205 EXPECT_EQ(thread_context::current()->user_id, "scoped-user");
206 }
207
208 // Should restore original context
209 EXPECT_TRUE(thread_context::has_context());
210 EXPECT_EQ(thread_context::current()->request_id, "original-request");
211 EXPECT_EQ(thread_context::current()->user_id, "original-user");
212}
213
217TEST_F(ThreadContextTest, ContextPropagator) {
218 // Create context in main thread
219 thread_context::create("main-request");
220 thread_context::current()->user_id = "main-user";
221 thread_context::current()->add_tag("source", "main");
222
223 // Capture context
224 context_propagator propagator;
225 auto capture_result = propagator.capture();
226 ASSERT_TRUE(capture_result);
227 EXPECT_TRUE(propagator.has_captured());
228
229 // Clear main thread context
231 EXPECT_FALSE(thread_context::has_context());
232
233 // Apply in same thread
234 auto apply_result = propagator.apply();
235 ASSERT_TRUE(apply_result);
236
237 // Should have restored context
238 EXPECT_TRUE(thread_context::has_context());
239 EXPECT_EQ(thread_context::current()->request_id, "main-request");
240 EXPECT_EQ(thread_context::current()->user_id, "main-user");
241 auto tag = thread_context::current()->get_tag("source");
242 ASSERT_TRUE(tag.has_value());
243 EXPECT_EQ(tag.value(), "main");
244}
245
249TEST_F(ThreadContextTest, CrossThreadPropagation) {
250 // Create context in main thread
251 thread_context::create("main-thread-request");
252 thread_context::current()->correlation_id = "main-correlation";
253 thread_context::current()->add_tag("thread", "main");
254
255 // Capture for propagation
256 auto propagator = context_propagator::from_current();
257
258 // Verify in another thread
259 std::thread worker([propagator]() {
260 // Initially no context in new thread
261 EXPECT_FALSE(thread_context::has_context());
262
263 // Apply captured context
264 auto result = propagator.apply();
265 EXPECT_TRUE(result);
266
267 // Should have context from main thread
268 EXPECT_TRUE(thread_context::has_context());
269 EXPECT_EQ(thread_context::current()->request_id, "main-thread-request");
270 EXPECT_EQ(thread_context::current()->correlation_id, "main-correlation");
271
272 auto tag = thread_context::current()->get_tag("thread");
273 ASSERT_TRUE(tag.has_value());
274 EXPECT_EQ(tag.value(), "main");
275
276 // Modify in worker thread
277 thread_context::current()->add_tag("thread", "worker");
278 });
279
280 worker.join();
281
282 // Main thread context should be unchanged
283 auto main_tag = thread_context::current()->get_tag("thread");
284 ASSERT_TRUE(main_tag.has_value());
285 EXPECT_EQ(main_tag.value(), "main");
286}
287
291TEST_F(ThreadContextTest, ContextAwareEnrichment) {
292 // Create test implementation
293 class test_context_aware : public context_aware_monitoring {
294 public:
295 // Use default implementation
296 };
297
298 test_context_aware aware;
299
300 // Create context
301 thread_context::create("enrich-request");
302 thread_context::current()->correlation_id = "enrich-corr";
303 thread_context::current()->user_id = "enrich-user";
304 thread_context::current()->add_tag("custom1", "value1");
305 thread_context::current()->add_tag("custom2", "value2");
306
307 // Create monitoring data
308 monitoring_data data("test-component");
309 data.add_metric("metric1", 100.0);
310
311 // Enrich with context
312 auto result = aware.enrich_with_context(data);
313 ASSERT_TRUE(result);
314
315 // Verify context was added as tags
316 auto req_id = data.get_tag("request_id");
317 ASSERT_TRUE(req_id.has_value());
318 EXPECT_EQ(req_id.value(), "enrich-request");
319
320 auto corr_id = data.get_tag("correlation_id");
321 ASSERT_TRUE(corr_id.has_value());
322 EXPECT_EQ(corr_id.value(), "enrich-corr");
323
324 auto user_id = data.get_tag("user_id");
325 ASSERT_TRUE(user_id.has_value());
326 EXPECT_EQ(user_id.value(), "enrich-user");
327
328 // Custom tags should have "ctx." prefix
329 auto custom1 = data.get_tag("ctx.custom1");
330 ASSERT_TRUE(custom1.has_value());
331 EXPECT_EQ(custom1.value(), "value1");
332
333 auto custom2 = data.get_tag("ctx.custom2");
334 ASSERT_TRUE(custom2.has_value());
335 EXPECT_EQ(custom2.value(), "value2");
336
337 // Original metric should remain
338 auto metric = data.get_metric("metric1");
339 ASSERT_TRUE(metric.has_value());
340 EXPECT_DOUBLE_EQ(metric.value(), 100.0);
341}
342
346TEST_F(ThreadContextTest, ContextMetricsCollector) {
347 // Create test collector
348 class test_collector : public context_metrics_collector {
349 public:
350 explicit test_collector(const std::string& name)
351 : context_metrics_collector(name) {}
352
353 kcenon::common::Result<metrics_snapshot> collect() override {
354 auto snapshot = create_snapshot_with_context();
355 snapshot.add_metric("test_metric", 42.0);
356 return kcenon::common::ok(std::move(snapshot));
357 }
358 };
359
360 test_collector collector("test-collector");
361
362 // Set thread context
363 thread_context::create("collector-request");
364 thread_context::current()->user_id = "collector-user";
365
366 // Collect with context awareness enabled
367 collector.set_context_aware(true);
368 auto result1 = collector.collect();
369 ASSERT_TRUE(result1);
370
371 auto snapshot1 = result1.value();
372 EXPECT_EQ(snapshot1.source_id, "test-collector");
373
374 // Verify metric
375 auto metric = snapshot1.get_metric("test_metric");
376 ASSERT_TRUE(metric.has_value());
377 EXPECT_DOUBLE_EQ(metric.value(), 42.0);
378
379 // Collect with context awareness disabled
380 collector.set_context_aware(false);
381 auto result2 = collector.collect();
382 ASSERT_TRUE(result2);
383
384 // Should still work but without context
385 auto snapshot2 = result2.value();
386 EXPECT_EQ(snapshot2.source_id, "test-collector");
387}
388
392TEST_F(ThreadContextTest, ThreadIsolation) {
393 const int thread_count = 10;
394 std::vector<std::thread> threads;
395 std::vector<std::future<std::string>> futures;
396
397 // Create threads with their own contexts
398 for (int i = 0; i < thread_count; ++i) {
399 std::promise<std::string> promise;
400 futures.push_back(promise.get_future());
401
402 threads.emplace_back([i, p = std::move(promise)]() mutable {
403 // Each thread creates its own context
404 auto req_id = "thread-" + std::to_string(i);
406 thread_context::current()->user_id = "user-" + std::to_string(i);
407
408 // Do some work
409 std::this_thread::sleep_for(std::chrono::milliseconds(10));
410
411 // Verify context is still correct
412 auto* ctx = thread_context::current();
413 if (ctx && ctx->request_id == req_id &&
414 ctx->user_id == "user-" + std::to_string(i)) {
415 p.set_value(req_id);
416 } else {
417 p.set_value("error");
418 }
419 });
420 }
421
422 // Collect results
423 std::set<std::string> results;
424 for (auto& future : futures) {
425 results.insert(future.get());
426 }
427
428 // Wait for threads
429 for (auto& t : threads) {
430 t.join();
431 }
432
433 // Each thread should have maintained its own context
434 EXPECT_EQ(results.size(), thread_count);
435 for (int i = 0; i < thread_count; ++i) {
436 auto expected = "thread-" + std::to_string(i);
437 EXPECT_NE(results.find(expected), results.end());
438 }
439}
440
444TEST_F(ThreadContextTest, CopyFromContext) {
445 // Create source context
446 context_metadata source("source-request");
447 source.correlation_id = "source-corr";
448 source.user_id = "source-user";
449 source.add_tag("tag1", "value1");
450
451 // Copy to current thread
452 auto result = thread_context::copy_from(source);
453 ASSERT_TRUE(result);
454
455 // Verify copy
456 EXPECT_TRUE(thread_context::has_context());
457 auto* current = thread_context::current();
458 ASSERT_NE(current, nullptr);
459
460 EXPECT_EQ(current->request_id, "source-request");
461 EXPECT_EQ(current->correlation_id, "source-corr");
462 EXPECT_EQ(current->user_id, "source-user");
463
464 auto tag = current->get_tag("tag1");
465 ASSERT_TRUE(tag.has_value());
466 EXPECT_EQ(tag.value(), "value1");
467
468 // Modifications shouldn't affect source
469 current->user_id = "modified-user";
470 EXPECT_EQ(source.user_id, "source-user");
471}
472
473// Main function provided by gtest_main
static std::string generate_correlation_id()
Generate a unique correlation ID.
static bool copy_from(const thread_context_data &source)
Copy context data from another source into the current thread.
static void clear()
Clear and destroy the current thread-local context.
static thread_context_data * current()
Get the current thread-local context.
static thread_context_data & create(const std::string &request_id="")
Create a new thread-local context, replacing any existing one.
static std::string generate_request_id()
Generate a unique request ID.
static bool has_context()
Check whether a context exists on the current thread.
Context metadata for thread-specific information.
std::string correlation_id
Correlation ID for tracing across services.
std::unordered_map< std::string, std::string > tags
Arbitrary key-value tags.
std::string request_id
Unique identifier for the current request.
std::string user_id
User identifier associated with the request.
std::string get_tag(const std::string &key) const
Retrieve a tag value by key.
void set_tag(const std::string &key, const std::string &value)
Set a custom tag on this context.
bool empty() const
Check if all metadata fields are empty.
Basic metric structure for interface compatibility.
std::variant< double, int64_t, std::string > value
Container for monitoring metrics from a component.
std::optional< std::string > get_tag(const std::string &key) const
Get a tag value.
std::optional< double > get_metric(const std::string &key) const
Get a metric value.
void add_metric(const std::string &key, double value)
Add a numeric metric.
void add_tag(const std::string &key, const std::string &value)
Add or update a custom tag.
std::string correlation_id
Correlation ID for cross-service tracing.
std::string get_tag(const std::string &key) const
Retrieve a tag value by key.
std::string user_id
User identifier associated with the request.
TEST_F(ThreadContextTest, ContextMetadataBasicOperations)
Thread-local context management for request tracking and distributed tracing.