| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| #include "tracing/test_tracing.h" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <thread> // NOLINT(build/c++11) | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/time/clock.h" | |
| #include "absl/time/time.h" | |
| #include "gtest/gtest.h" | |
| #include "tracing/scoped_span.h" | |
| #include "tracing/span.h" | |
| #include "tracing/tracer.h" | |
| #include "tracing/tracer_factory.h" | |
| namespace credentio_tracing { | |
| namespace { | |
| TEST(TestRecordedSpanTest, GetAttributeAndGetAttributeAs) { | |
| TestRecordedSpan span; | |
| span.SetAttribute("string_key", std::string("val")); | |
| span.SetAttribute("int_key", int64_t{42}); | |
| span.SetAttribute("double_key", 3.14); | |
| span.SetAttribute("bool_key", true); | |
| EXPECT_NE(span.GetAttribute("string_key"), std::nullopt); | |
| EXPECT_EQ(span.GetAttribute("nonexistent"), std::nullopt); | |
| EXPECT_EQ(span.GetAttributeAs<std::string>("string_key"), "val"); | |
| EXPECT_EQ(span.GetAttributeAs<int64_t>("int_key"), 42); | |
| EXPECT_EQ(span.GetAttributeAs<double>("double_key"), 3.14); | |
| EXPECT_EQ(span.GetAttributeAs<bool>("bool_key"), true); | |
| // Type mismatch returns std::nullopt | |
| EXPECT_EQ(span.GetAttributeAs<int64_t>("string_key"), std::nullopt); | |
| EXPECT_EQ(span.GetAttributeAs<std::string>("nonexistent"), std::nullopt); | |
| } | |
| TEST(TestSpanTest, RecordsAttributesEventsStatusAndEnded) { | |
| auto recorded = std::make_shared<TestRecordedSpan>(); | |
| TestSpan span(recorded); | |
| span.SetAttribute("attr1", std::string("val1")); | |
| span.SetAttribute("attr2", int64_t{100}); | |
| // Overwrite existing attribute | |
| span.SetAttribute("attr1", std::string("new_val1")); | |
| span.AddEvent("event1"); | |
| span.AddEvent("event2"); | |
| span.SetStatus(absl::InvalidArgumentError("bad input")); | |
| span.End(); | |
| EXPECT_TRUE(recorded->ended()); | |
| EXPECT_EQ(recorded->status(), absl::InvalidArgumentError("bad input")); | |
| ASSERT_EQ(recorded->events().size(), 2); | |
| EXPECT_EQ(recorded->events()[0].name, "event1"); | |
| EXPECT_EQ(recorded->events()[1].name, "event2"); | |
| EXPECT_EQ(recorded->GetAttributeAs<std::string>("attr1"), "new_val1"); | |
| EXPECT_EQ(recorded->GetAttributeAs<int64_t>("attr2"), 100); | |
| } | |
| TEST(TestTracerTest, StartSpanRecordsOptionsAndParent) { | |
| TestTracer tracer; | |
| TestSpan parent_span(std::make_shared<TestRecordedSpan>()); | |
| StartSpanOptions options; | |
| options.parent = &parent_span; | |
| options.attributes.push_back({"init_key", std::string("init_val")}); | |
| std::unique_ptr<Span> span = tracer.StartSpan("child_span", options); | |
| ASSERT_NE(span, nullptr); | |
| auto spans = tracer.GetSpans(); | |
| ASSERT_EQ(spans.size(), 1); | |
| EXPECT_EQ(spans[0]->name_, "child_span"); | |
| EXPECT_EQ(spans[0]->parent_, &parent_span); | |
| EXPECT_EQ(spans[0]->GetAttributeAs<std::string>("init_key"), "init_val"); | |
| span->End(); | |
| EXPECT_TRUE(spans[0]->ended()); | |
| } | |
| TEST(TestTracerTest, QueryingSpansByName) { | |
| TestTracer tracer; | |
| tracer.StartSpan("op_a", {}); | |
| tracer.StartSpan("op_b", {}); | |
| tracer.StartSpan("op_a", {}); | |
| EXPECT_EQ(tracer.GetSpans().size(), 3); | |
| auto span_a = tracer.GetSpan("op_a"); | |
| ASSERT_NE(span_a, nullptr); | |
| EXPECT_EQ(span_a->name_, "op_a"); | |
| EXPECT_EQ(tracer.GetSpan("nonexistent"), nullptr); | |
| auto spans_a = tracer.GetSpansByName("op_a"); | |
| EXPECT_EQ(spans_a.size(), 2); | |
| tracer.Clear(); | |
| EXPECT_TRUE(tracer.GetSpans().empty()); | |
| } | |
| TEST(TestTracerTest, IntegrationWithScopedSpan) { | |
| TestTracer tracer; | |
| { | |
| ScopedSpan scoped_span(tracer.StartSpan("scoped_op", {})); | |
| ASSERT_NE(scoped_span.get(), nullptr); | |
| scoped_span->SetAttribute("key", std::string("value")); | |
| scoped_span.SetStatus(absl::OkStatus()); | |
| } | |
| auto spans = tracer.GetSpans(); | |
| ASSERT_EQ(spans.size(), 1); | |
| EXPECT_EQ(spans[0]->name_, "scoped_op"); | |
| EXPECT_TRUE(spans[0]->ended()); | |
| EXPECT_EQ(spans[0]->GetAttributeAs<std::string>("key"), "value"); | |
| } | |
| TEST(TestTracerFactoryTest, GetTracerAndRegisterGlobalFactory) { | |
| auto factory = std::make_shared<TestTracerFactory>(); | |
| TestTracerFactory::Register(factory); | |
| auto current_factory = TracerFactory::GetGlobalFactory(); | |
| EXPECT_EQ(current_factory, factory); | |
| auto tracer1 = factory->GetTracer("component_1"); | |
| auto tracer2 = factory->GetTracer("component_1"); | |
| EXPECT_EQ(tracer1, tracer2); | |
| tracer1->StartSpan("span_1", {}); | |
| auto test_tracer = factory->GetTestTracer("component_1"); | |
| ASSERT_NE(test_tracer, nullptr); | |
| EXPECT_EQ(test_tracer->GetSpans().size(), 1); | |
| EXPECT_EQ(factory->GetAllSpans().size(), 1); | |
| factory->Clear(); | |
| EXPECT_TRUE(factory->GetAllSpans().empty()); | |
| TestTracerFactory::Unregister(); | |
| EXPECT_NE(TracerFactory::GetGlobalFactory(), factory); | |
| } | |
| TEST(TestTracerFactoryTest, RegisterStaticHelper) { | |
| auto factory = TestTracerFactory::Register(); | |
| EXPECT_EQ(TracerFactory::GetGlobalFactory(), factory); | |
| auto tracer = TracerFactory::GetGlobalFactory()->GetTracer("test_comp"); | |
| tracer->StartSpan("span", {}); | |
| EXPECT_EQ(factory->GetAllSpans().size(), 1); | |
| TestTracerFactory::Unregister(); | |
| EXPECT_NE(TracerFactory::GetGlobalFactory(), factory); | |
| } | |
| TEST(TestTracerFactoryTest, RegisterThisMemberMethod) { | |
| auto factory = std::make_shared<TestTracerFactory>(); | |
| factory->RegisterThis(); | |
| EXPECT_EQ(TracerFactory::GetGlobalFactory(), factory); | |
| factory->Unregister(); | |
| EXPECT_NE(TracerFactory::GetGlobalFactory(), factory); | |
| } | |
| TEST(TestTracerTest, MultithreadedSpanCreation) { | |
| TestTracer tracer; | |
| constexpr int kNumThreads = 4; | |
| constexpr int kSpansPerThread = 50; | |
| std::vector<std::thread> threads; | |
| threads.reserve(kNumThreads); | |
| for (int t = 0; t < kNumThreads; ++t) { | |
| threads.emplace_back([&tracer, t]() { | |
| for (int i = 0; i < kSpansPerThread; ++i) { | |
| auto span = tracer.StartSpan("mt_span", {}); | |
| span->SetAttribute("thread", int64_t{t}); | |
| span->SetAttribute("iter", int64_t{i}); | |
| span->AddEvent("tick"); | |
| span->SetStatus(absl::OkStatus()); | |
| span->End(); | |
| } | |
| }); | |
| } | |
| for (auto& thread : threads) { | |
| thread.join(); | |
| } | |
| EXPECT_EQ(tracer.GetSpans().size(), kNumThreads * kSpansPerThread); | |
| } | |
| TEST(TestTracerTest, ImplicitParentLinkingWithScopedSpan) { | |
| TestTracer tracer; | |
| { | |
| ScopedSpan outer_span(tracer.StartSpan("outer", {})); | |
| { | |
| ScopedSpan inner_span(tracer.StartSpan("inner", {})); | |
| } | |
| } | |
| auto spans = tracer.GetSpans(); | |
| ASSERT_EQ(spans.size(), 2); | |
| EXPECT_EQ(spans[0]->name_, "outer"); | |
| EXPECT_EQ(spans[1]->name_, "inner"); | |
| EXPECT_FALSE(spans[0]->trace_id_.empty()); | |
| EXPECT_EQ(spans[0]->trace_id_, spans[1]->trace_id_); | |
| EXPECT_EQ(spans[1]->parent_span_id_, spans[0]->span_id_); | |
| } | |
| TEST(TestRecordedEventTest, EqualityOperator) { | |
| TestRecordedEvent event1{"event_name"}; | |
| TestRecordedEvent event2{"event_name"}; | |
| TestRecordedEvent event3{"different_name"}; | |
| EXPECT_EQ(event1, event2); | |
| EXPECT_NE(event1.name, event3.name); | |
| } | |
| TEST(TestRecordedSpanTest, EndTimeAndAttributesAccessors) { | |
| TestRecordedSpan span; | |
| EXPECT_FALSE(span.ended()); | |
| absl::Time start = absl::Now(); | |
| span.SetAttribute("key1", std::string("val1")); | |
| span.SetAttribute("key2", int64_t{50}); | |
| span.End(); | |
| EXPECT_TRUE(span.ended()); | |
| EXPECT_GE(span.end_time(), start); | |
| auto attrs = span.attributes(); | |
| ASSERT_EQ(attrs.size(), 2); | |
| EXPECT_EQ(attrs[0].first, "key1"); | |
| EXPECT_EQ(std::get<std::string>(attrs[0].second), "val1"); | |
| EXPECT_EQ(attrs[1].first, "key2"); | |
| EXPECT_EQ(std::get<int64_t>(attrs[1].second), 50); | |
| } | |
| TEST(TestSpanTest, NullTestRecordedSpanHandledSafely) { | |
| TestSpan span(nullptr); | |
| EXPECT_EQ(span.recorded_span(), nullptr); | |
| // Calling methods on TestSpan with null recorded_span should not crash | |
| span.SetAttribute("key", std::string("val")); | |
| span.AddEvent("event"); | |
| span.SetStatus(absl::OkStatus()); | |
| span.End(); | |
| } | |
| TEST(TestTracerTest, NonTestSpanParentHandling) { | |
| TestTracer tracer; | |
| class NonTestSpan : public Span { | |
| public: | |
| void SetAttribute(absl::string_view, AttributeValue) override {} | |
| void AddEvent(absl::string_view) override {} | |
| void SetStatus(const absl::Status&) override {} | |
| void End() override {} | |
| }; | |
| NonTestSpan custom_parent; | |
| StartSpanOptions options; | |
| options.parent = &custom_parent; | |
| auto span = tracer.StartSpan("span_with_custom_parent", options); | |
| ASSERT_NE(span, nullptr); | |
| auto spans = tracer.GetSpans(); | |
| ASSERT_EQ(spans.size(), 1); | |
| EXPECT_EQ(spans[0]->parent_, &custom_parent); | |
| EXPECT_TRUE(spans[0]->parent_span_id_.empty()); | |
| EXPECT_FALSE(spans[0]->trace_id_.empty()); | |
| } | |
| TEST(TestTracerTest, ParentTestSpanWithoutTestRecordedSpanStartsNewTrace) { | |
| TestTracer tracer; | |
| TestSpan parent_without_recording(nullptr); | |
| StartSpanOptions options; | |
| options.parent = &parent_without_recording; | |
| tracer.StartSpan("orphan_op", options); | |
| auto spans = tracer.GetSpans(); | |
| ASSERT_EQ(spans.size(), 1); | |
| EXPECT_EQ(spans[0]->parent_, &parent_without_recording); | |
| EXPECT_TRUE(spans[0]->parent_span_id_.empty()); | |
| EXPECT_FALSE(spans[0]->trace_id_.empty()); | |
| } | |
| TEST(TestTracerFactoryTest, GetTestTracerReturnsNullForNonexistent) { | |
| TestTracerFactory factory; | |
| EXPECT_EQ(factory.GetTestTracer("nonexistent_tracer"), nullptr); | |
| } | |
| } // namespace | |
| } // namespace credentio_tracing |